一、C语言编译过程
- 预编译:将.c文件的头文件展开、宏展开,生成.i文件。.i文件仍然是c语言代码,区别只是头文件、宏的源码替代了引用头文件或宏的代码,使程序完整。
- 编译:将预处理后的.i文件生成.s汇编文件。.s文件是汇编语言的文件。
- 汇编:将.s汇编文件生成.o目标文件。.o是二进制文件。
- 链接:将.o文件链接成目标文件
二、include
- include<>用尖括号包含头文件,在系统指定路径下找头文件。
- include"" 用双引号包含头文件,先在当前目录下找,如果找不到再到系统指定的路径下去找。
注意:
- include不仅可以包含.h还可以包含.c,但是一般不建议这么做,因为如果一个.c被包含多次,在预编译阶段就会被展开多次,会导致函数重复定义而报错。
- 预编译只是对include等预处理操作进行处理并不会进行语法检查,所以即使有错误也不会报错,只有到编译阶段才会进行语法检查。
三、定义宏-define关键字
宏:在预编译时候出现会对宏出现的地方进行替换。宏的好处是只修改宏定义,其他地方预编译就会自动重新替换了。
注意:
- 宏后边不能跟分号。
- 宏的名称尽量用大写,跟变量区分开。
- 宏定义的作用:从定义的行开始到本文件末尾(如果没有被#undef终止)。
- 宏可以在任何地方开始定义,不非要是文件头。
- 宏的名和形参都没有类型。
终止宏的定义范围:#undef PI
宏的定义:
- 不带参宏:
#define PI 3.14
- 带参宏(看起来跟函数有点类似,但本质完全不是一个东西):
- 定义:
#define S(a,b) a*b
- 使用:
printf("S(a,b) = %d\n", S(2, 3));//替换成 2 * 3
,也可以嵌套使用:printf("S(a,b) = %d\n", S(S(2, 3),3));
- 定义:
拓展:
那么问题来了,如果宏的定义是#define S(a,b) a*b
,但是使用的时候这样写:printf("S(a,b) = %d\n", S(2+3, 3));
,最后结果到底是什么呢?
可以看到运行结果是11,也就是2+3*3,这里很明确了,宏只会进行单纯的替换,并不会做其他任何操作,包括加个括号之类的,所以使用宏时要考虑清楚。
再把代码换成printf("S(a,b) = %d\n", S((2+3), 3));
,这里我们手动添加了一个括号:
这样结果就正确了。
四、选择性编译
#ifdef
:如果宏AAA被定义,就编译代码段一,否则编译代码段二,下面代码编译的是代码段一。
#include<stdio.h>
#define AAA 1.14
int main() {
#ifdef AAA
//代码段一
printf("hello world\n");
#else
//代码段二
printf("hello China\n");
#endif
return 0;
}
#ifndef
:跟上面#ifdef
的作用相反,这里不演示了。注意,不管是从来没有定义过,或者是被#undef
终止过的宏,都满足#ifndef
的条件。一般用来防止头文件重复包含。
那么,如何防止头文件重复包含呢?我们一般在被包含的文件,也就是.h文件中使用#ifndef
,例如:
#ifndef __TEST_H__
#define __TEST_H__
extern int fun();
#endif
这样写即可解决问题,如果在一个.c文件中,test.h被包含两次,那么第二次则不会满足#ifndef __TEST_H__
这个条件,因为在第一次中#define __TEST_H__
宏已经被定义了。
#if [] .... #else ....
最普通的选择性编译语句,一般通过一个宏作为开关来控制一些语句的编译:
#include<stdio.h>
#define AAA 0
int main() {
#if AAA
printf("hello world");
#endif
return 0;
}
这样通过控制宏AAA的值,来控制编译是否进行。