register:这个关键字请求编译器尽可能的将变量存在 CPU 内部寄存器中而不是通过内存寻址访问以提高效率。注意是尽可能,不是绝对,因为一个CPU的寄存器的数量是有限的,不可能把这些变量都放入寄存器中。
寄存器:数据从内存里拿出来先放到寄存器,然后 CPU 再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU 不直接和内存打交道。
为啥要这么麻烦啊?因为速度。寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快多。近水楼台先得月嘛,它离 CPU 很近,CPU 一伸手就拿到数据了,比在那么大的一块内存里去寻找某个地址上的数据快多了,同时寄存器的价格也比较高昂。
使用 register 修饰符的注意点
虽然寄存器的速度非常快,但是使用register 修饰符也有些限制的:register变量必须是能被 CPU 寄存器所接受的类型。意味着register 变量必须是一个单个的值,并且其长度应小于或等于整型的长度。 而且 register变量可能不存放在内存中,所以不能用取址运算符“&”来获取register 变量的地址。
2.static2.1修饰变量
变量又分为局部和全局变量,但它们都存在内存的静态区。
静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用extern 声明也没法使用他。准确地说作用域是从定义之处开始,到文件结尾处结束,在定义之处前面的那些代码行也不能使用它。想要使用就得在前面再加extern XXX。静态局部变量,在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他函数也用不了。由于被 static 修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。
static int j;void fun1(void){static int i = 0; i ++;}void fun2(void){j = 0;j++;}int main(){for(k=0; k<10; k++){fun1();fun2();}return 0;} i 和 j 的值分别是什么,为什么?
2.2修饰函数
函数前加 static 使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
在一段程序代码中,从内存高地址到内存低地址,依次分布着栈区、堆区、全局区(静态区)、常量区、代码区,其中全局区(静态区)中高地址分布着.bss段,低地址分布着.data段。总的分布如图所示。
3.sizeof全局区(静态区)
全局区有.bss段和.data段组成,可读可写。.bss段
未初始化的全局变量存放在.bss段。初始化为0的全局变量和初始化为0的静态变量存放在.bss段。.bss段不占用可执行文件空间,其内容有操作系统初始化。
.data段
已经初始化的全局变量存放在.data段。静态变量存放在.data段。.data段占用可执行文件空间,其内容有程序初始化。const定义的全局变量存放在.rodata段。
经常被误解为函数,sizeof是关键字不是函数。
int i=0;(A)sizeof(int);(B)sizeof(i); (C)sizeof int; (D)sizeof i;
思考一下:假设在 32 位系统下为什么A、B、D的结果为4,而C编译器提示出错呢.
4.signed与unsigned一个 32 位的 signed int 类型整数其值表示法范围为:- 231~ 231 -1;8 位的char 类型数其值表示的范围为- 27 ~ 27 -1。一个 32 位的 unsigned int 类型整数其值表示法范围为:0~ 232 -1;8 位的 char 类型数其值表示的范围为 0~ 28 -1。
5.break 与 continue 的区别break 关键字很重要,表示终止本层循环。现在这个例子只有一层循环,当代码执行到
break 时,循环便终止。
如果把 break 换成 continue会是什么样子呢?continue表示终止**本次(本轮)**循环。当
代码执行到 continue 时,本轮循环终止,进入下一轮循环。
6.cosnt 6.1修饰只读变量定义 const 只读变量,具有不可变性。
例如:
const int Max=100;int Array[Max];
6.2节省空间编译器通常不为普通 const 只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。
#define M 3 //宏常量const int N=5; //此时并未将 N 放入内存中int i=N; //此时为 N 分配内存,以后不再分配!int I=M; //预编译期间进行宏替换,分配内存int j=N; //没有内存分配int J=M; //再进行宏替换,又一次分配内存!
6.3修饰一般变量const 定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const 定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区),而#define 定义的宏常量在内存中有若干个拷贝。#define 宏是在预编译阶段进行替换,而 const 修饰的只读变量是在编译的时候确定其值。#define 宏没有类型,而 const 修饰的只读变量具有特定的类型。
一般常量是指简单类型的只读变量。这种只读变量在定义时,修饰符 const 可以用在类型说明符前,也可以用在类型说明符后。例如:
int const i=6; const int i=6;
6.4修饰数组定义或说明一个只读数组可采用如下格式:
int const a[3]={1, 2, 3};const int a[3]={1, 2, 3};
6.5修饰指针const int *p; // p 可变,p 指向的对象不可变int const *p; // p 可变,p 指向的对象不可变int *const p; // p 不可变,p 指向的对象可变const int *const p; //指针 p 和 p 指向的对象都不可变
这里给出一个记忆和理解的方法:先忽略类型名(编译器解析的时候也是忽略类型名),我们看 const 离哪个近。“近水楼台先得月”,离谁近就修饰谁。
6.6修饰函数的参数const 修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使用。例如:
void Fun(const int i);告诉编译器 i 在函数体中的不能改变,从而防止了使用者的一些无意的或错误的修改。
6.7修饰函数的返回值const修饰符也可以修饰函数的返回值,返回值不可被改变。例如:
const int Fun (void);
7.volatilevolatile 是易变的、不稳定的意思。很多人根本就没见过这个关键字,不知道它的存在。也有很多程序员知道它的存在,但从来没用过它。
volatile 关键字和 const 一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问
volatile int i=10;int j = i;int k = i;
volatile关键字告诉编译器i是随时可能发生变化的,每次使用它的时候必须从内存中取出 i的值,因而编译器生成的汇编代码会重新从i的地址处读取数据放在k 中。
8.struct与union
相同点:二者都是常见的符合结构,都是由多个不同的数据类型成员组成;
不同点:联合体中所有的成员共用一块地址空间,即联合体只存放一个被选中的成员,内存空间是最长成员占用的空间,需要进行内存对齐。
结构体所有成员占用空间是累加的,其所有成员都存在,不同成员会存在不同的地址,内存空间等于所有成员 占用的空间之和,同样需要内存对齐。
9.枚举与#define 宏的区别#define 宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。一般在编译器里,可以调试枚举常量,但是不能调试宏常量。枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个 10.typedef
在实际项目中,为了方便,可能很多数据类型(尤其是结构体之类的自定义数据类型),需要我们重新取一个适用实际情况的别名。这时候 typedef 就可以帮助我们。例如:
typedef struct student{ //code}Stu_st,*Stu_pst;struct student stu1;和 Stu_st stu1;没有区别。struct student *stu2;和 Stu_pst stu2;和 Stu_st *stu2;没有区别。
推荐阅读:
面试中常被问到的C语言基础知识值得收藏 (qq.com)
参考书籍:
[1]C primer
[2]C 专家编程
[3]C陷阱与缺陷
[4]C和指针
感谢阅读:)
inner peace
知行合一