一些关键字
volatile关键字
使用volatile关键字声明变量,目的是为了告诉编译器不要对其进行‘优化’。那什么是‘优化’呢?,举个例子如下:1
2
3
4
5int a = 1,b,c; //为a b c申请内存并初始化
b = a; //内存(&a) -> CPU
//CPU -> 内存(&b)
c = a; //内存(&a) -> CPU
//CPU -> 内存(&c)以上代码可以帮助我们更好理解寄存器(CPU)读取规则:在初始化完成,寄存器会从a的内存中读取值,然后再将这存到b的内存中;同理,接下来寄存器会从a的内存中读取值,然后再将这存到c的内存中。其中
内存(&a) -> CPU
这一步进行了俩次,但是第二次明显多多余了,于是编译器便对其进行了‘优化’:直接将上一次寄存器(CPU)中存储的a的值存入C的内存中,这样便减少了一次指令的执行。
但是在这个过程中,如果a的值在其他地方发生变化,那么寄存器(CPU)中存储的值就不能直接存入c的内存,这个时候就需要使用volatile声明变量a,告诉编译器,这个变量会经常改变,必须从它的内存中取读取数据。
在以下三种情况应该使用volatile关键字:- 并行设备的硬件寄存器。它们的寄存器状态随时会因为自身硬件而改变,因此,对存储器映射的硬件寄存器应该使用vlatile关键字。
- 中断服务程序(ISR)与主程序的共享变量
- 多线程中的共享变量
static 关键字
static是被声明的静态类型的变量,存储在静态区(全局区)中,它的生命周期是整个程序的生命周期。static声明的变量如果没有初始化,那么默认初始化为0。static声明的全局变量,作用域为当前文件,声明的局部变量则是当前{}
。
static 声明的变量初始化后,都会保存在内存区域中,由于它的生命周期相当于整个程序的生命周期,因此它不会被销毁。const 关键字
定义变量为常量(即不可被改变)。
当const声明指针时,有以下几种情况:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const int *p;
int const *p;
int* const p;
const int* const p;
```
第1、2种情况完全相同,但是常量指针:p都是指向一个不可变的常量,但是p本身的值可以改变;
第3种是指针常量:可以通过p更改p指向的值(即*p=4),但不能更改p的值;
第4种指向常量的常指针:即指针是常量,指针所指向的值也是常量,即不能更改指针的值,也不能通过该指针更改其指向的值。
const如果声明函数传参时,即函数内不能改变该参数的值;
const如果声明函数返回值时:分两种情况,如果返回值是指针,那么指针指向的内容是不可改变的,并且获取该函数的返回值时必须也用同样有const修饰的对应类型的变量;如果返回值是其他,那么由于函数执行的return时,它的生命周期结束了,用const修饰的变量也被销毁了,即使用const毫无意义。
4. typedef 和 define
define 是c语言中定义的语法,是预处理指令。它的作用是在预处理的时候,进行简单的机械字符串替换,无论正确与否,即不做正确性检查,只有当编译已被展开的源码时才会发现错误。而typedef是关键字,在编译时处理,因此它具有类型检查的功能。它的作用是,在自己的作用域内,为已经存在的类型增加一个别名,以便与理解,但他不能再函数内使用。
define没有作用域的限制,只有之前预定义过的宏,在之后的程序中都可以使用,而typedef有作用域的限制
对指针操作也不同,例如在以下代码中:
```c
typedef INTP2 int*
INTP1 p1,p2;
INTP2 p3,p4;INTP1 p1,p2;
经过字符串替换后为int* p1,p2
,即声明一个int指针和int类型的变量,而INTP2 p3,p4;
由于 INTP2是由含义的:指向整型的指针。因此后面的p3,p4都是指向整型的指针(如果不能理解你可以把 int* 想成 char,那p3,p4就都是char类型)。
变量
- 定义常量谁更好?# define还是 const?
- 全局变量和局部变量的区别是什么?
- 全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
- 局部变量能否和全局变量重名?
指针
数组与指针
- 数组指针,是一个指向数组的指针,例如
int (*p)[4];
声明了一个指针,该指针指向一个有4个int类型元素的数组。(我看的能个链接好像有问题,没怎么看懂,后面补一个新的例题) - 指针数组,是一个数组,其内容都是由指针构成。如这样是合理的数组p里面存储的都是int类型的指针。
1
2
3
4
5int *p[3];
int a[3];
p[0] = &a[0];
p[1] = &a[1];
p[2] = &a[2]; - 数组所占存储空间的大小:sizeof(数组名)
数组大小:sizeof(数组名)/sizeof(数据类型)
sizeof(指针名),无论该指针是什么类型,在32位系统下结果位4,64位系统下结果为8。 - 指针进行强制类型转换后与地址进行加法运算,结果是什么?
函数与指针
函数指针。在程序中声明了一个函数后,系统会为这个函数分配空间,这段存储空间的首地址便是这个函数的地址,同时,函数名表示的就是这个地址。因此,可以用一个指针来存储该地址,这样的指针变量就叫做函数指针,其形式类似于int (*p)(int,int);
因为它是一个指针变量,所以是*p,后面两个int说明该指针指向的函数需要两个int类型的参数。指向函数的指针没有++或者–等操作。
另外由于*
的优先级比括号低,如果不加括号int *p(int,int);
,这样则是一个返回类型为int*,传入参数为两个int的函数声明。
函数指针的使用案例:
1 |
|
内存
C语言函数参数压栈顺序是怎样的?
从右向左,主要原因是为了方便支持可变长参数。例如printf(const char* format,…)
,采用从右向左压栈后,固定参数 format会位于栈顶附件,可以用指针直接访问,然后通过指针偏移访问其他参数。示例:可变长参数函数
1 |
|
预处理
- #error。当编译程序时,当遇到一个#error时,就会生成一个编译错误的提示信息。例如
1
2
3
- 使用
#intclude<filename.h>
与#intclude"filename.h"
的区别?
区别是当使用第一个是,系统编译器会先从标准库的路径搜索filename.h
,使得系统文件调用较快。而第二个则先从用户的工作路径去查找文件,使得自定义文件较快。 - 在头文件中定义静态变量是否可行,为什么?
不可行,在头文件中定义静态变量,会造成资源浪费的问题。因为按照编译的步骤,如果头文件中存在一个静态变量,那么每一个使用该头文件的文件都会创建一个新的静态变量,从而引起空间浪费或者程序错误。 - # 和## 的使用
#可以把宏参数转换成其对于的字符串,例如:##则是连接操作符,把多个形参转换成一个实际的参数名,例如:1
2
3
4
5
6
7
8
9
void main(void)
{
int num = 0;
printf(CONVERT(num)); //其输出结果为num
}1
2
3
4
5
6
7
8
9
void main(void)
{
int lastnum = 5;
printf("%d\n",CONNECT(last,num)); //其输出结果为5
}
其他
- strlen(“\0”) =? sizeof(“\0”)=? 两者结果与区别
strlen(“\0”) =0 sizeof(“\0”)=2。strlen是从字符串开始计数直到’\0’结束;而sizof则是计算括号内所占的空间大小。 - C语言中 struct与 union的区别是什么?
结构体是所有成员所占空间是它们之和,所有成员都是存在的;而联合体是所有成员共用同一块空间,联合体内只存放了其中一个成员,其变量长度为最长的成员长度。(两者的长度都要考虑字节对齐)。 - 什么是大端和小端?
大端:高地址存低字节,低地址存高字节。
小端:低地址存低字节,高地址存高字节