一:volatile关键字
volatile关键字和const一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统,硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
举个例子:
此例没有volatile,就说明是可优化的
#include<stdio.h> int pass = 1; int main() { while (pass) { //思考一下,这个代码有哪些地方,编译器是可以优化的。 } return 0; }
要具体清楚整个优化的过程,需要查看反汇编!!!
加上volatile时:说明是不可以优化
#include<stdio.h> volatile int pass = 1; //加上volatile int main() { while (pass) { } return 0; }
再看下反汇编:来观察怎么个不优化法:
结论: volatile 忽略编译器的优化,保持内存可见性。
其它问题:
const volatile int a = 10;
//在vs2013和gcc 4.8中都能编译通过
//const是在编译期间起效果
//volatile在编译期间主要影响编译器,形成不优化的代码,进而影响运行,故:编译和运行都起效果。
const要求你不要进行写入就可以。volatile意思是你读取的时候,每次都要从内存读。 两者并不冲突。
虽然volatile就叫做易变关键字,但这里仅仅是描述它修饰的变量可能会变化,要编译器注意,并不是它要求对应变量必须 变化!这点要特别注意。
二:extern关键字
作用:变量或者函数声明。
在正式讲解之前,我们需要在编译器创建多文件,在test.c中逻辑实现,在main.c中逻辑调用,在test.h中负责的是相关头文件,相关变量 / 函数的声明工作。
举例:
一:erxtern声明变量:
假如我们在test.c文件中定义了一个变量int x = 1234;
如果想在main.c文件中访问。就需要先在test.h文件中声明变量。
但是声明的时候不能给x再赋值(extern int x = 1234; // err) 因为声明并不开辟空间,那么也就意味着此时的这个声明就没有空间保存这样的数据
二:extern声明函数:
流程与上类似,都是在test.c文件中定义函数,需要在main.c文件中访问,同样需要在test.h文件中声明:如下:
声明变量的时候必须带上extern, 声明函数的时候可以不带extern。但是为了养成良好的编码习惯,最好还是带上extern。
三:struct关键字
为什么定义结构体?
我们计算的场景需要定义结构体,如果要描绘一名学生,用计算机将学生的信息存储起来,那么就需要用结构体来描绘这名学生
怎么使用结构体?
看代码:
#include<stdio.h> #define NUM 64 struct stu { char name[NUM / 2]; int age; char sex; char addr[NUM]; }; int main() { struct stu zhangsan; struct stu lisi; return 0; }
在这里,我们定义了一个结构体类型struct stu,而这种类型包含的属性通常代表的是我们描绘自然世界中的一个被管理对象,比如说你要管理学生,我们就把所有学生用这样的方式去描绘起来。甚至我们可以这样定义:struct stu cls[30];相当于定义了一个有30人的班级,每名同学身上的属性是一样的,如姓名,年龄,性别,地址,但属性的内容是不一样的。
结论:定义结构体,本质是制作类型。
struct stu { char name[NUM / 2]; int age; char sex; char addr[NUM]; }a, b, c; // 等价于 int a, b, c; int* p;
结构体本质:具有相同或不同元素的集合。
结构体的初始化:
#include<stdio.h> struct stu { char name[NUM / 2]; int age; char sex; char addr[NUM]; }; int main() { // 初始化 struct stu x = { "zhangsan",19,'w',"地球" }; return 0; }
结构体初始化的时候跟数组一致采用花括号{},{}内部放的值要和属性一一对应起来
结构体只能整体初始化,不能直接赋值!!!
struct stu x; x = { "zhangsan",19,'w',"地球" }; // err
但是当我们需要赋值时,可以这样做:
struct stu x; //x.name="张三"; //err 对于数组赋值的时候,它和结构体类似不能直接被赋值,需要用到字符串拷贝strcpy strcpy(x.name, "张三"); x.age = 18;
结构体访问:(以name和age为例)
int main() { struct stu x; strcpy(x.name, "张三"); x.age = 18; struct stu* p = &x; printf("%s\n", x.name); // 张三 printf("%s\n", (*p).name); // 张三 printf("%s\n", p->name); // 张三 printf("%d\n", p->age); // 18 return 0; }
上述访问过程中,有->这么个方法,这是指向操作符,是C语言通过指针方案去访问结构体成员的便捷写法。
为什么结构体访问会有两种访问方式?
结构体在定义的时候访问,用 . 更方便些,
如果传参的话,用指向操作符会更方便些。
就是因为一个结构体可能会贯穿多个函数,所以很有必要对结构体单独设立一种访问方式,便于代码编写。
空结构体有多大?
结论:和平台有关系。在VS编译器不能编过,而在linux中,空结构体的大小为0.
柔性数组:
柔性数组的定义:
柔性数组必须得放在结构体内!!!
#include<stdio.h> struct stu { int arr[0]; }; int main() { struct stu zhangsan; return 0; }
注意!!!
一般一个结构体内部包含柔性数组,那么第一个元素不要是这个数组,也就是第一个元素一定要是一个有效元素,可能还有其它的,不过最后一个才是我们的柔性数组。
struct stu { char name[20]; //...... int arr[0]; };
柔性数组怎么用?
柔性数组通常用来形成一个变长数组。要用到malloc(在堆空间进行生成内存的函数)
柔性数组是不占用空间大小的。
#include<stdio.h> struct data { int num; int arr[0]; }; int main() { printf("%d\n", sizeof(struct data)); // 4 return 0; }
使用过程见代码:
#include<stdio.h> struct data { int num; int arr[0]; }; int main() { struct data* p = malloc(sizeof(struct data)) + sizeof(int) * 10; p->num = 10; for (int i = 0; i < p->num; i++) { p->arr[i] = i; } free(p); return 0; }