前言
- C参考手册中给出了C语言的32个关键字
这篇博客主要是梳理和辨析这些关键字中大家最不熟悉的几个,主要包括:
- extern
- static
- const
- register
一、extern
首先,我们要区分定义和声明的区别
- 定义在创建实体的同时,并给其分配一块内存
- 声明并没有分配存储空间
用途:
extern是一个外部声明,其作用是告诉编译器,此时所使用的的全局变量和全局函数并不是在本文件中定义,而是在同一个工程中的其他文件中进行了定义,使其通过编译产生.obj
文件,从而在链接过程中将该声明的变量名和其他文件关联起来。
因此,extern只适用于具有全局作用域的变量,从而在其他文件中使用,不适用于局部变量和块变量。
特殊说明:
若在test1.c中定义了一个全局常变量const int max
- 若不希望该工程的其他文件使用这一变量,就不需要再test1.c这个文件中加extern,则这一常变量只在该文件中中有效;
- 若希望在该工程的其他文件照片那个使用,则必须在本文件中声明extern const int max;
二、static
- 修饰对象:主要修饰全局变量、局部变量和函数
①修饰局部变量时:
当函数第一次被调用,函数中的静态局部变量被初始化,再次调用这个函数,这个static变量也不会被再次初始化,仍然保存第一次函数调用时的结果,究其原因,还要从程序编译链接的过程说起:
下面简单回忆一下一个C程序编译链接的过程:
经历了上述过程后,*.c
文件已经编译成功
程序的相关信息以及存储在以下几个区域:
- 代码区:存放程序代码 .txt;
- 栈区:.stack;存放局部变量和函数调用(这两部分分别使用不同的栈帧),同时函数调用结束后分配的栈帧就会被释放,栈的大小大约为1M左右,由系统管理,默认值为随机值;
- 堆区:.heap;动态内存,很大(2G左右),由程序员自行管理,使用该区域时要注意内存泄漏;
- 数据区:.data存放全局变量或静态变量,默认值为0,同时数据区内的数据的生存周期和该程序的生存周期一样。
通过上述说明,我们也就不难理解static修饰的变量为什么生存期会延长了,但是static修饰的变量并不会改变变量的作用域。
②修饰全局变量时:
static修饰的全局变量值可以在当前C程序文件中使用,即便在其他文件中使用extern关键字也会失效,因为static使得该变量的作用域受到限制;
③修饰函数时:
同理,static修饰的函数值可以在当前C程序文件中使用,因为static使得该函数的作用域受到限制;
记忆函数:
我们常将含有静态局部变量的函数称为记忆函数。(注意理解“记忆”这个词的含义)
特殊说明:
C语言不能通过变量给静态局部变量初始化,即以下书写方式会报错:
int x = 10;
static ina a = x;
然而,在C++中允许这种写法
三、const
- 用途:定义只读变量
- 修饰对象:const无论修饰变量和数组,无论修饰全局变量还是局部变量还是数组,都要注意,用const修饰时需要初始化,否则,未初始化的const就失去了意义。
const和#define的区别
- 在C++的编译模式 中,const修饰的常变量在编译时被替换,而#define修饰的宏是在预编译时被替换;
- const修饰的变量需要开辟空间以及类型检查,而宏不需要;
- const修饰的是变量,而#define定义的是常量,const定义的对象有数据类型,而宏定义的对象没有数据类型。
拓展:#define的括号问题
#define SUM(x,y) x*y
int main()
{
int a = 3;
int b = 4;
int c = SUM(a + 5, b + 6);
printf("%d\n", c);
return 0;
}
问:结果等于80吗?
答:不是,结果应该为29,这不同于函数的形参,作为宏参,它只是做了简单的替换,int c = a+5*b+6
如果想得到80,就必须在宏定义出添加括号。
#define SUM(x,y) (x)*(y)
int main()
{
int a = 3;
int b = 4;
int c = SUM(a + 5, b + 6);
printf("%d\n", c);
return 0;
}
四、register
- 用途:建议编译器尽可能地将变量存储在CPU内部寄存器中(因为寄存器就那么几个,如果这类变量多了,肯定是存不下的),而不是通过内存寻址的方式,从而提高效率;
- 若一个变量用register修饰,就意味着这个变量是一个寄存器变量,有可能使得该变量的访问速度达到最快
但register修饰还有以下的一些限制:
- 因为register变量必须是一个单个的值,并且长度必须是小于或等于整型的长度,因为CPU的内部寄存器也是32位(4byte);
- 因为register修饰的变量不存放在内存中,而存放在内部寄存器中,所以不能用 “&” 来获取变量的地址;
- 只有局部变量和形式参数可以作为寄存器变量,其他(全局变量)不行,因为:局部变量的使用和函数调用只会暂时占用寄存器空间,变量生存期结束或函数调用完成后会释放这一空间供其他变量和函数使用;
- 局部静态变量不能定义为寄存器变量,不能写成register static int a;
- 由于寄存器的数量有限(不同CPU寄存器的数目不一),不能定义任意多个寄存器变量,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符和数目和类型都依赖于运行程序的机器,而任何多余的register修饰符也都被编译程序所忽略(这个第一点也有所解释)。