eefas 发表于 2025-6-17 11:32

C语言预处理

1、#define的深度认识1.1 数值宏常量宏定义数值常量相信大家都不陌生,相信很多小伙伴用过,这里我们就简单的提一下,我们前面也讲过,#define 本质上是替换,它可以出现在代码的任何地方,也可以把任何东西都定义成宏,编译器会在预编译的时候进行替换掉,举例:#dfeine PI 3.1415926这样在以后的代码中你就可以用PI 来代替3.1415926 那么这样做的好处是什么呢?假设在未来的某一天,你要提升这个精度,如果你代码中出现3.1415926 过多的话,你提升精度还得一个个修改, 如果使用宏定义的话,你只需要改一次即可。
1.2 字符串宏常量除了宏定义常量之外,还经常用来定义字符串,特别是路径: ①#define PATH_1 D:\code\lesson1\test②#define PATH_1 "D:\code\lesson1\test"

以上哪个是正确的呢?如果觉得太长还可以用续行符:③ #define PATH_1 "D:\code\lesson1\\
test"很显然第一个肯定是不对的,字符串需要用"" 引起来,第三个也不对,第二个呢?我们去实践证明下(以上写法都不推荐!):在Linux平台环境下: https://edit.wpgdadawant.com/uploads/news_file/blog/2022/7015/tinymce/1.png
在Windows环境下: https://edit.wpgdadawant.com/uploads/news_file/blog/2022/7015/tinymce/2-1.png
很显然他们都有同样的警告,都是未知转义序列,也无法正确打印出我们的路径,在前面我们讲到,' \ ' 是转义字符,当我们要打印路径的时候需要用转义字符' \ ' 去还原' \ ' 的字面意思,所以这里打印路径要用\\ !

注意:Windows路径分隔是用' \ ',而Linux路径分隔是用' / ',所以如上测试用例改成' / ' 的话是不会报警告的。所以要正确的打印如上用例应该这样写:
//不使用续行符

#define PATH_1 "D:\\code\\lesson1\\test"



//使用续行符

#define PATH_1 "D:\\code\\lesson1\\\

test"
1.3 用宏充当注释符号因为Linux 环境能直接查看预处理过程,便于我们验证,所以我们下边会在Linux 环境下测试。我们先简单了解下程序的翻译过程:1.预处理-E:头文件展开,去注释,宏替换,条件编译...2.编译-S:将预处理后的C语言翻译成汇编语言3.汇编-c:将汇编语言转化为可目标二进制文件( 可被链接 )4.链接:将目标二进制文件与相关库链接,形成可执行程序

这里我们来看一段用宏充当注释符号的代码:
#include <stdio.h>

#define BSC //

int main()

{

       BSC printf("hello world\n");

       printf("you can see me!\n");

       return 0;

}
这里我们要探讨一个什么问题呢?如果替换成功,则不会执行第一个函数,如果替换失败,则我们会看到两行打印: https://edit.wpgdadawant.com/uploads/news_file/blog/2022/7015/tinymce/3.png
这究竟是为什么呢?我们可以执行:@localhost code]$ gcc -E test.c -o test.i把预处理后的结果保留下来为test.i 文件,接着我们可以去用vim 编辑器查看一下它与源文件的区别在哪,究竟是如何替换的: https://edit.wpgdadawant.com/uploads/news_file/blog/2022/7015/tinymce/4-1.png通过上图我们可以发现,在预处理之后的文件中,并没有去成功通过宏替换注释掉第一个printf 函数,由此可见,在预处理阶段,是先执行去掉注释,然后在进行宏替换,如上代码,本质是直接定义了一个空宏,我们特别不推荐这样写代码!(C语言注释风格也一样不行,感兴趣可以下去尝试下)
1.4 用宏替换多条语句先看一段代码:
#include <stdio.h>


#define INIT_VALUE(a, b) a = 0; b = 0;

int main()

{

int flag = 0;

scanf("%d", &flag);

int a = 100;

int b = 200;

if (flag)

INIT_VALUE(a, b);

else

printf("%d, %d\n", a, b);



return 0;

}
我想请问,这段代码有问题吗?应该如何改进呢?这段代码明显是编译不会通过的,但是可以通过执行预处理指令,发现预处理并没有出问题,那么,我们可以看一下预处理之后的结果与源文件的区别在哪: https://edit.wpgdadawant.com/uploads/news_file/blog/2022/7015/tinymce/5-1.png通过预处理之后的结果我们可以看到,宏替换多了一个分号。于是有小伙伴就讨论出来如下三种解决方法:1.去掉宏定义的最后一个分号2.规范代码风格,给if 和else 加上大括号3.给宏定义要替换的部分用大括号括起来

第一种解决方法肯定是不行的,去掉最后一个分号并不能解决问题,if else 在没有大括号的情况下后面只能跟一条语句,所以第一条行不通。第二种解决方案看似不错,但是我们有没有想过,并不是所有人都会有良好的代码风格,我们作为程序员,写出的宏应该具有健壮性,所以第二条不可取。第三种解决方案我们看着好像靠谱,但是我们通常写完一条语句中后面都会带上分号,那可想而知会出现这种情况:{a = 0, b = 0;}; 大括号外是不能跟分号的,所以这个方法也不可取!

最好的解决方法是什么呢?使用do while 结构:#include <stdio.h>

#define INIT_VALUE(a, b) do{a = 0; b = 0;}while(0)

int main()

{

    int flag = 0;

    scanf("%d", &flag);

    int a = 100;

    int b = 200;

    if (flag)

      INIT_VALUE(a, b);

    else

      printf("%d, %d\n", a, b);



   return 0;

}
循环会被看成一条复合语句,所以if 不带大括号也没事(建议带上),这样我们的宏就会更健壮,也不会出错,同时你也可以在中间添加续行符,让他们的格式更清晰!同时我也有个小建议,宏定义的结尾最好都不要带分号。
结论: 当我们需要宏进行多条语句替换的时候,推荐使用do-while-zero结构。
1.5 宏定义的使用建议【建议1】在宏定义体的结尾省略分号。
【建议2】函数宏的调用不能省略参数。
【建议3】函数宏的定义中,每个参数都应该以小括号括起来,避免替换之后出现优先级的问题。
2、#undef 撤销宏2.1 宏的定义位置和有效范围第一个问题,宏定义的位置有限制要求吗? https://edit.wpgdadawant.com/uploads/news_file/blog/2022/7015/tinymce/6.png答案:源文件的任何地方,宏都可以定义,与是否在函数内外无关。
第二个问题,宏的有效范围有多大呢?
#include <stdio.h>                                                                                                                  

void test()

{

   printf("test: %d\n", M);

}



int main()

{

   test();

#define M 10

   printf("main: %d\n", M);

   return 0;

}
这段代码我们就发现编译不通过了,那么我们来进入预处理文件来对比下源文件: https://edit.wpgdadawant.com/uploads/news_file/blog/2022/7015/tinymce/7-1.png答案:宏的作用范围,从定义处开始,往后都是有效的!
2.2 宏的取消这里我们用一个例子就能很好的证明了:
#include <stdio.h>                                                                                                                  

2

3 #define M 10

4 int main()

5 {

6 printf("%d\n", M);

7 #undef M

8 printf("undef: %d\n", M);

9 return 0;

10 } 我们来查看如上代码的预处理之后的结果: https://edit.wpgdadawant.com/uploads/news_file/blog/2022/7015/tinymce/8.png结论:undef 是取消宏的意思,可以用来限定宏的有效范围!

macpherson 发表于 2025-7-2 11:17

通过局部定义宏、及时撤销宏定义(#undef)等方式控制宏的作用范围。

alvpeg 发表于 2025-7-3 14:54

为了避免运算符优先级的问题,建议在宏定义中使用括号。例如,#define MAX(a, b) ((a) > (b) ? (a) : (b))。

burgessmaggie 发表于 2025-7-4 12:32

C 语言预处理主要包括以下几类指令:宏定义(#define)、文件包含(#include)、条件编译(#if、#ifdef等)、错误处理(#error)和行号控制(#line)。

i1mcu 发表于 2025-7-4 13:28

以#开头的指令,如#include、#define等,用于在编译前对源代码进行处理。

ingramward 发表于 2025-7-4 16:11

根据条件选择性地编译代码块,常用于多平台开发或调试。

xiaoyaodz 发表于 2025-7-4 18:02

头文件只包含必要内容,避免冗余。

yorkbarney 发表于 2025-7-4 21:30

宏函数在参数中有副作用时,可能导致意外结果。尽量使用内联函数替代复杂的宏函数。

pentruman 发表于 2025-7-6 12:55

宏定义是预处理阶段最常用的指令,用于创建常量或函数式宏。

houjiakai 发表于 2025-7-6 13:49

合理利用C语言的预处理功能,可以编写出更加灵活、高效且易于维护的代码。

timfordlare 发表于 2025-7-7 14:45

宏名通常使用大写字母,以便与变量名区分开来。

louliana 发表于 2025-7-10 11:42

预处理指令通常放在文件的顶部或逻辑相关的位置,不要将其混杂在普通代码中,以保持代码的整洁和可读性。

bartonalfred 发表于 2025-7-10 12:25

虽然宏可以提高代码的灵活性,但是滥用可能导致代码难以阅读和调试。尽量使用内联函数代替复杂的宏。

earlmax 发表于 2025-7-10 15:23

#include <file>:从系统标准库路径查找头文件。
#include "file":优先从当前目录查找,找不到再搜索标准库路径。

usysm 发表于 2025-7-11 10:18

避免在函数内定义宏,除非明确需要局部作用

usysm 发表于 2025-7-11 14:09

#ifdef / #ifndef / #endif:用于条件编译,根据是否定义了某个宏来决定是否编译某段代码。

iyoum 发表于 2025-7-12 12:54

宏定义没有作用域的概念,它们在整个文件中有效

iyoum 发表于 2025-7-12 16:11

避免包含不必要的头文件,以减少编译时间和可能的命名冲突。

linfelix 发表于 2025-7-12 16:41

向编译器传递特殊指令,控制编译行为,如优化级别、警告设置等。

51xlf 发表于 2025-7-12 17:20

未定义的宏会导致条件编译失败,需确保宏定义与条件匹配。
页: [1] 2
查看完整版本: C语言预处理