今天分享一点C语言的预处理还有宏相关的内容。
目录
预处理和宏定义
一、一个C程序的诞生
1、预编译阶段(.c-->.i)
2、编译阶段(.i-->.s)
3、汇编阶段(.s-->.o)
4、链接阶段(该程序的所有.o-->.exe)
二、宏相关内容
1、C语言中部分内置宏
2、宏的功能
(1)、使用宏定义常量
(2)、使用宏重命名运算符
(3)、使用宏重新定义关键字的别名
(4)、宏还能定义一段代码片段
(5)、宏还可以作为“编译开关”
(6)、使用宏完成算数表达式
三、预处理指令
1、#if 和 #elif
2、#ifdef
3、#ifndef
4、#if defined
5、#undef
当我们写好一个一段C语言的代码并且按下编译并运行这个按钮时,它共经历了这样几个过程:
该阶段主要进行这几个操作,宏替换、头文件的展开、预编译指令的处理等。
编译器将 .i 文件翻译成文本文件 .s ,每条语句都以标准的文本格式确切描述一条低级机器语言指令;也是在这一个阶段进行语法语义的分析检查。
汇编器将 .s 文件翻译成机器语言指令。把这些指令打包成可重定位目标文件,即 .o 文件。这里 .o 是一个二进制文件,前面两个阶段都还有字符。
将刚才生成的还有有头文件库中的 .o 文件以某种方式合并到一个 .o 文件中(这个文件是主函数所在的那个 .o 文件),在经过一系列处理(如:合并段表,符号表的合并和符号表的重定位等)生成一个 .exe 文件。
宏名 | 功能 |
_ _FILE_ _ | 进行编译的源文件 |
_ _LINE_ _ | 文件当前的行号 |
_ _DATE_ _ | 文件被编译的日期 |
_ _TIME_ _ | 文件被编译的时间 |
_ _STDC_ _ | 如果编译器遵循ANSI C,其值为1,否则未定义 |
注意,这里是两个下划线,中间没有空格。
//这些基本都是语言内置的宏
//预定义符号是预处理的一部分,所以显示的都是编译时候的信息而不是执行时候的信息
printf("file:%s\n", __FILE__);//进行编译的源文件
printf("line:%d\n", __LINE__);//当前编译的行号
printf("date:%s\n", __DATE__);//文件被编译的日期
printf("time:%s\n", __TIME__);//文件被编译的时间
//printf("stdc:%d\n", __STDC__);//编译器是否遵循ANSI C,如果遵循其值为1,否则不遵循,显然VS不遵循
运行结果如下:
首先得知道宏在实际的使用中是一种简单的文本替换
例如:
#define SIZE 10
int a = SIZE;
在预处理阶段,将文件中所有出现 SIZE 的地方全部换成 10 ,于是int a = SIZE; 被替换为int a = 10;
正是因为宏独特的特点,所以我们可以使用宏完成很多事情。
// 1. 使用宏定义常量
#define SIZE 10
// 2. 使用宏给运算符重命名
#define and &&
#define or ||
// 3. 使用宏重新定义关键字的别名.
#define uint unsigned int
在某些场景,我们可能会频繁的使用一些代码片段,例如:
实际开发中经常有这种需求:
如果程序执行成功,就继续往下走如果程序执行失败, 就结束程序
int main(){
//实际开发中经常有这种需求:
//如果程序执行成功, 就继续往下走
//如果程序执行失败, 就结束程序
int ret = 0;
ret = login();
if (ret == 0) {
printf("执行失败\n");
return 1;
}
ret = enterRoom();
if (ret == 0) {
printf("执行失败\n");
return 1;
}
ret = startMatch();
if (ret == 0) {
printf("执行失败\n");
return 1;
}
ret = acceptGame();
if (ret == 0) {
printf("执行失败\n");
return 1;
}
return 0;
}
我们发现这个代码中多次出现了
if (ret == 0) {
printf("执行失败\n");
return 1;
}
为了使代码更为简洁,我们可以使用宏定义:
// 5. 宏还能定义一段代码片段
#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);
return 0;
}
注意 \ 在C语言中还有换行的意思。
这里和函数很像,但是这里千万不能写成函数。因为函数执行完之后只是退出它本身,但是我们这里的需求是退出主函数。
某些宏可以根据条件让一些代码能编译或者不编译。
_CRT_SECURE_NO_WARNINGS
这个宏想必使用VS 编译器的同学们应该不陌生,没有这个宏的定义的时候, VS 就会多编译一些对于 scanf 等函数安全检查的逻辑;有这个宏定义, 相关的检查代码就不被编译了。
需要注意的是,这个宏的声明应该在 stdio.h 这个头文件的上面,因为这个头文件包含了 scanf 等函数的声明。
比如我们想要算平方,就可以直接用宏来定义:
#define SQUARE( x ) x * x
那这样的定义方法可行吗?
SQUARE( 5+1 )为多少?我们想要的结果是 6 的平方 36 ,但是宏在编译的过程中实际上是文本替换结果为 5+1*5+1,结果为11,所以我们需要给每一个可能出现问题的地方加上括号。
#define SQUARE( x ) ((x) * (x))
这样就万无一失了。
代码大概长这样:
#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#elif 整型常量表达式3
程序段3
#else
程序段4
#endif
这段代码的意思是:“表达式1”的值为真(非0),就对“程序段1”进行编译,否则就计算“表达式2”,结果为真的话就对“程序段2”进行编译,为假的话就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else。这一点和 if else 非常类似。
代码大概长这样
#ifdef 宏名
程序段1
#else
程序段2
#endif
它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
代码大概长这样
#ifndef 宏名
程序段1
#else
程序段2
#endif
与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。
需要注意的是,#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。
代码大概长这样
#if defined 宏名
代码段
#endif
判断该宏有没有被定义,如果有则执行代码段,否则跳过,可以在改变宏的值之前进行判断。
#undef 宏名
这个指令用于移除宏定义,当一个现存的宏需要被重新定义的时候,先移除它。
这个指令经常和上面的#if defined一起使用,我们可以看下面的例子:
#define B 20
#if defined B //判断宏B是否被定义
#undef B //若被定义,则撤销定义
#endif
#define B 200 //改变宏的值
今天就先分享到这里,希望大家多多评论。