目录
1.编译的过程:
将.c文件转成二进制文件。
C语言中,每个.c文件都视为一个“编译单元”,针对每个编译单元,编译器先把.c以及所包含的.h转换成.o(.o是一个“目标文件”,也是一个二进制的文件,但是还不能执行)
2链接的过程:
把.o文件最后合并到一起。
实际开发中,经常会把一个函数的定义和调用分散在两个不同的.c中,就会被编译到不同的.o里面,所以最后需要合并这些.o文件。
链接过程除了要链接用户自己写的文件编译的.o之外,还需要链接一些库文件(标准库,第三方库),此处的库可以是动态库的方式提供,也可以是静态库的方式提供。
3.编译链接又包含四部分
①预处理:
进行的操作:宏替换,头文件展开,条件编译,去掉注释,预编译指令的处理。
但凡是#开头的东西都是预处理阶段做的事情,如:#define #include #pragma
条件编译:(可以方便我们选择性地编译,比如有的代码删除可惜,保留又碍事;条件编译也可以作为多行注释)
#if :既关注宏是否有定义,也关注宏的值是不是真
#endif
#else
#ifdef :只关注后面的宏是否有定义
#ifndef:相当于对 #ifdef进行逻辑取反
上面这些操作都是用于控制“编译”的,和程序运行无关。
预定义符号:
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
这些预定义符号都是语言内置的,例如:
printf("file:%s line:%d\n", __FILE__, __LINE__);
②编译
狭义的编译,特指把.c源代码文件变成汇编指令
③汇编
把汇编指令变成二进制的机器指令
④链接
4.关于宏
①宏的本质
在预处理阶段进行文本替换。
②宏的使用
1.使用宏定义常量
#define SIZE 10
2.使用宏给运算符重命名
#define and && #define or ||
3.使用宏重新定义类型的别名
// typedef unsigned int uint; #define uint unsigned int //给unsigned int类型取一个别名 unit //上面两种方式的区别在于使用typedef定义后面需要加封号
4.宏还可以用来给关键字定义别名
// register 表示 "寄存器" // 这个关键字已经废弃了. #define reg register
5.宏还可以用来定义一个代码片段
#define CHECK(ret) if (ret == 0) { \ printf("执行失败\n"); \ return 1; \ }
6.宏还可以作为 "编译开关"
// 可以根据条件让一些代码能编译或者不编译. // 结合条件编译来使用 // _CRT_SECURE_NO_WARNINGS 就是一个编译开关 // 没有这个宏的定义的时候, VS 就会多编译一些对于 scanf 等函数安全检查的逻辑 // 有这个宏定义, 相关的检查代码就不被编译了. // 这段检查的代码在 stdio.h 里头. 所以必须把这个宏定义到 stdio.h 的上方
③宏的优缺点(和函数比)
缺点:
Ⅰ.写正确有点难,在展开的时候可能因为运算符的优先级导致表达式的运行结果和预期不符合。
Ⅱ.宏对于参数是没有类型检查的,函数是有类型检查的,要求实参必须和形参类型匹配才能使用,但是宏没有,宏只是进行“文本替换”。
Ⅲ.宏难以调试
Ⅳ.可读性不如函数
优点:
Ⅰ.能实现一些函数做不到或者很难做到的事情.
#define CHECK(ret) if(ret==0){\ printf("执行失败\n");\ return 1;\ } int main() { //如果程序执行成功, 就继续往下走 // 如果程序执行失败, 就结束程序 int ret = 0; ret = login(); CHECK(ret); ret = enterRoom(); CHECK(ret); ret = startMatch(); CHECK(ret); ret = acceptGame(); CHECK(ret); }
Ⅱ.宏的执行效率略高于函数,函数调用需要传参(对实参拷贝一份)
Ⅲ.宏可以一定程度实现“泛型编程”.
如下面这段代码既可以支持整数相加,也能支持浮点数相加。
#define ADD(x,y) ((x)+(y))
④#和##:属于“宏的高级用法”
# :把一个宏参数变成对应的字符串
#define PRINT(FORMAT,VALUE)\
printf("the value of "#VALUE" is "FORMAT" \n",VALUE);
int main()
{
PRINT("%d",(i+2));
return 0;
}
//运行结果: the value of (i+2) is 12
//此处#VALUE相当于把(i+2)转成了字符串,变成结果的一部分了.
## :可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。
#define ADD_TO_SUM(num,value)\
sum##num +=value;
int main()
{
int sum1=0;
int sum2=0;
ADD_TO_SUM(1,10);
ADD_TO_SUM(2,20);
}
//上面1和2这个参数被拼接成了变量名,成了sum1,sum2的一个部分