一、面试中常见关键字
1 :static:
定义:改变生命周期,限制作用域,
对于:在函数内部声明为内部静态变量只需要初始化一次。变量类型由auto变成STATIC,使变量存储在静态区延长生命周期至整个程序结束,扩大其作用域为全局可见。
修饰全局变量加STATIC,限制其作用范围,使嘚外部程序不可访问
修饰函数时,也是和全局变量一样,使得外部程序不可访问。
存放空间:静态存储区
生命周期:从定义开始,直到程序运行结束。
作用域:当前文件可见,其他文件不可访问。
2、extern
外部变量声明
修饰变量时,声明这个变量为外部全局变量。可用于变量在本程序外定义或在声明下面被定义声明。
修饰函数时也是一样的。
3、register
寄存器变量
当你的某个数据频繁的被cpu访问时,你可以使用regiater修饰,这样整个变量就会被放在cpu的寄存器里面用以提高访问速率,要注意的是,寄存器变量不可以使用取地址的操作。cpu内部寄存器,没有地址。而且cpu内部寄存器是有限的比如我们的EXYNOS4421卡发班只有好像是40个吧——R0~R15,还有各个模式下的专用寄存器。以及cpsr。所以很多寄存器都有专门的用途。只有少数几个能被用来存放变量,尽量少用,但是改用还是的用。
4、const:
限定修饰符,常用于变量不可被改变的时候。(我们采用const后面的不可以被改变的方法判别)
用法: //在判别过程中不用看数据类型的位置,当它不存在就完事了
指向常量的指针——const int *p; 简化后 const(p)所以p不可以被改变
_______________ int const *p: *p就是指针指向的地址存放的东西,那这个东西不可以变所以它是都是指向常量的指针
2) 常指针——char *const p;简化后 *const§ p不可以被改变说明了,指针存放的地址不可以被改变。而地址里面存放的内容可以被改变。
3) 指向常量的常指针——const char *const pc='a’ :简化后const(*const(p)) p不可以被改变 *p也不可以被改变。地址内容不可以被改变,指向地址的指针也不能被改变
5、volatile:
编译器警告提示字:防止编译器优化:编译器一般都数据都是直接从缓存中读取,但是对于易变的变量,这种做法就比较危险,对数据加上volatile:修饰让编译器从数据的内存地址中去读取数据。
被誉为区分嵌入式程序员和c程序员的最基本方法
在嵌入式的开发中常用于:
1、中断服务程序中的修改的,可被其它程序使用的变量。比如我中断会操作一个全局资源,而其它程序会检测这个资源并使用,如果不加volatile,那么一般编译器会把这个共享资源放在缓存中方便读取,但是我一个中断来了,这个值已经变了,而编译器还是从缓存中读取整个数据。这肯定是不得行的。
2、多任务环境中各任务间共享的资源
3、存储器映射的硬件寄存器通常也要加volatile
——————————————————————————————————————————
二、构造数据类型
1、结构体:
struct 声明结构体变量。
常见声明有:注意区分类型和定义
设计结构体类型的时候,同时取别名
1、typedef struct A{
int a;
int b;
}st_t;
st_t 是一个结构体类型,它实际上是struct A的别名,此时除了struct A可以定义变量外,还可以通过st_t来定义(常用)
2、struct A{
int a;
int b;
}st;
这个st是一个结构体变量,可以通过struct A结构体类型来定义变量
设计结构体类型的时候,同时定义结构体变量
3、struct{
int a;
int b;
}st;
这个st是一个结构体变量,且以后无法在使用结构体来定义变量 (内核使用)
4、typedef struct{
int a;
int b;
}st_t;
st_t 是一个结构体类型,此时只能通过st_t类型来定义变量。(不常用)
结构体内存在字节对齐:后一个数据会根据前一个数据来对齐:
结构体对齐规则:
1、 结构体变量的首地址能够被其最宽基本类型成员的大小所整除
2、结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍——即第n个成员的首地址为,前面成员大小%成员大小的值补齐成员大小
3、结构体的总大小为结构体最宽基本类型成员大小的整数倍。就是最后整个结构体大小要为最大的成员的倍数。
一个很有意思的现象:同一个结构体在用不同环境下有不同的结果。
#includeint main(){ struct A{ int a; char b; double C; short d; }B; printf("%dn",sizeof(B)); return 0; }
我们来算一遍:int a :(4) ——>char b (1) 5( 4为1的整数倍不补) ——>double(8) 16(5不为8的倍数 5%8= 3 补三个)
——>short(2) 18 (16为2的整数倍,不补) 这样在规则二下大小为18,
但是还要遵循规则三:结构体的总大小为结构体最宽基本类型成员大小的整数倍,就是18要补齐之后为double的整数倍即:
24.
but:
其实24是对的,20也是对的。20是由于我们gcc编译器是4字节对齐的方式:
char(1byte) short(2byte) int(4byte) float(4byte) double(4byte)。在4字节对齐下,强如double也要变成4字节对齐。那么我如果里面加了结构体呢?
struct A{
int a;
char b;
struct B{
int a;
char b;
double c;
short d;
}b;
double C;
short d;
}B;
那么这个结构体大小为40.并不会像我们像的一样,struct b 为20 前面又补15个,后面又加double 然后又凑20的倍数。即便里面的结构体,它也是按数据算的。算的时候可以把它的结构体的一分给扒了。
1.1:
取消结构体对齐:attribute
struct D{
int a; //4
char b; //1
double c; //8
short d; //2
}__attribute__((__packed__)); //取消内存对齐 = 15
2、联合体:union
共用体 --- 成员共享内存,修改成员就是覆盖原成员
union 共用体名{
成员
};
问:它与结构体区别在于什么地方?
答:
<1>结构体使用struct关键字来定义,共用体使用union关键字来定义
<2>结构体的每个成员都有单独的内存空间,而共用体的所有成员共用同一块内存(最大的成员对应的内存)
<3>共用体变量与结构体变量访问成员的方法是一样
问:什么时候使用共用体?
答:如果需要使用的数据类型有很多,但是同一时刻只需要使用其中的一种数据类型
学生:小学生 中学生 大学生 研究生
联合体经常在内存中倍用作:ioctl的操作数。
因为同一时刻我们只要会传一个cmd。
3、枚举 enum
枚举更像是#denfine 把定义变量,默认第一个变量值为1后面的值加一。
enum 名字{
罗列的成员1,
罗列的成员2,
};
问:宏定义与枚举的区别
答:枚举是一中类型,可以用来定义变量,可以包含多个枚举成员,而宏是替换,没有类型,也没有多个成员
强调:enum;
1、枚举成员的每一个成员都是整数
2、分隔符:为逗号‘,’
3、可以直接赋值:struct和union都不行。
4、枚举的成员直接访问
————————————————————————————————————————
三、优先级:单算移关与,异或逻条赋,逗号来结尾
括号:优先级最高
单:单目运算符 ++ – & ~ !sizeof
算:算数运算符 * / % + -
移:移位运算符:<< >>
关:关系运算符:< > >= <= == !=
与:位与 &
异: 异或 ^
或: 位或 |
逻: 逻辑运算符 && ||
条: 条件运算符 ?:
赋: 赋值运算符 = += -= *= /= ^=
————————————————————————————–
四、数组、typeof 、#define
#define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。
宏定义在很多地方有很多巧妙的应用,可以用来替换简单函数,在内核中也应用比较多
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名,但是You cannot use the typedef specifier inside a function definition。
Typedef 在 C 语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。
例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s* tPS;
以上两种情况的意图都是要定义 dPS 和 tPS 作为一个指向结构 s 指针。哪种方法更好呢?(如果有的话)
为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是: typedef 更好。
思考下面的例子:
dPS p1, p2;
tPS p3, p4;
第一个扩展为
struct s * p1, p2;
上面的代码定义 p1 为一个指向结构的指针, p2 为一个实际的结构体,这也许不是你想要的。 第二个例子正 确地定义了 p3 和 p4 两个指针。
数组:
数组的赋值:
int a[5] = {1,2,3,4,5};
char arr[10] = "hello world"; //字符串 字符串都是以/n结尾,所以定义10个只能存九个
char str[10] = {'h','e','l'......};
字符的数组名是字符的首地址。
字符数组名和指针的区别 指针可以++等运算,数组名不可以。
二维数组:
二维数组本质上就是一维数组。按行先排列的数组。
数组的运算:
void mian(){
int a[10] = {9,8,7,6,5,4,3,2,1,0};
int b[2][3] = {9,7,33,11,22,35};
int *p = *b;
printf("%d, %d ,%d n",*(a+3), *(*int *)((int *) b[1] + 2)),*(P++));
printf("%d,%d,%d", *a+6, *(*b+2), *(++P));
答案:7, 35, 9
15,33, 7
(b+2) = 33 是因为b 是一微数组即b【3】={9,7,33} + 2就是 指向33再加 就是33了。
———————————————————————————————————————————————————
五、数组指针、指针数组、函数指针、指针函数。
数组指针:int * arr[10];
本质上是指针,代表数组的首地址。其实arr就已经代表数组的首地址了。如果int * arr[10],就可以进行++ 等操作。
指针数组:int (* p)[10];
本质上是数组:数组里面存放着指针。指针指向int型数据。
指针函数 int * function (paramete);
表示返回值是一个指针的一个函数,他的返回值一定要是一个指针,不可以是数组名
函数指针:int (*function) (paramete);
表示一个函数的指针,就是函数的一个地址。这样通过一个函数的地址就可以找到这个函数。



