[C语言]2021/11/12 C预处理器和C库-#define

本文详细探讨了C语言预处理器的作用,特别是#define指令在创建宏中的应用。预处理器通过文本替换进行工作,不执行计算。举例说明了宏定义的使用,包括无参数和带参数的宏,以及宏嵌套。同时,指出了类函数宏可能导致的错误和解决方法,强调了适当使用括号的重要性。文章还提及了预处理器中的其他指令,如#include和条件编译指令。最后,讨论了const关键字在定义常量时的替代作用。
摘要由CSDN通过智能技术生成

C预处理器,即在程序执行之前查看程序。根据程序中的预处理器指令,预处理器将符号替换为他内容进行表示。预处理器可以包含程序执行时所需要的其他文件中的内容。可以将其看作是一种文本的替换,但这种替换并不能诠释其功用。

预处理指令:#define、#include、#ifdef、#else、#endif、#ifndef、#If、 #elif、#line、#erro、#pragma。

涉及关键字:_Generic,_Noreturn,_Static——assert

函数/宏:sqrt(),atan(),atan2(),exit(),atexit(),assert(),memcpy(),memove(),va_start(),va_arg(),va_copy(),va_end(). 

        在程序被编译之前 ,编译器会将代码分成三个部分,分别是:预处理序列,空白序列和注释序列。这篇主要讨论的就是预处理序列。

        1·#define——明示常量

        预处理指令都为#开头,#define也不例外,#符号就像是一个标记,用来提示预处理器 ,我接下来要对你发出具体的指令。#define指令可以出现在源文件的任何一个地方,它的作用域就是他出现的位置到该文件的末尾。#define常被用于的常量的替换,但它的功能不止这些。

        #define的出现,必然伴随着‘宏’这个名词的出现。举个例子:

        #define        TX        printf("想变强?充八万!")

     ↑预处理指令 ↑宏     ↑替换体

        这里‘TX’即被称之为宏,在后续的大代码中如果出现TX,那么会被自动替换为后面的替换体,其直接执行printf函数。

        当然你也可以在替换体中写入一个带有参数算法的函数。例如:

        #define        SAMPLE01        printf("x is %d.\n",x++) /*预处理指令后不必加;,同时宏的明明习惯为全大写*/

#define SAMPLE01 printf("x is %d.\n",x++)
 
int main()
{
    int x = 1;
    SAMPLE01;
    int x = 2;
    SAMPLE01;
return 0;
}

        

 以上程序执行的结果是;x is 2.

                                      x is 3.

对于这种在宏中使用了参数,并执行了类似于函数的功能,我们称之为类函数宏。

由此可见,宏不仅可以表示明示常量。从该例中也可以看出,宏可以表示任何字符串,或者整个C的表达式,但表达式中的运算过程并不在预处理器中完成,这一过程在编译时执行。预处理器不做运算不做求值,它只进行替换。

       C语言中最为出名的嵌套,自然也可用于宏定义中,也就是说,宏定义还可以包含其他宏。这种功能大部分的编译器是支持的。 

#define TEST1 "x is %d.\n"
#define SAMPLE02 printf(TEST1, x)

以上的效果等同于:#define SAMPLE02  printf("x is %d.\n",x),这两种写法你的用途,各有各的好处,不过要注意的是,第一种写法TEST1和SAMPLE02的顺序不能写反,原因之前提到过,宏定义的作用域只在他出现的位置到文件的结束。

        一般而言,预处理器发现程序中的宏后,会用宏等价的替换文本进行替换。如果替换的字符串中还包含宏,则继续替换这些宏。当然,在这种无脑替换下,也存在唯一的例外——双引号中的宏。一般双引号内会被识别为字符串,因此:

#define TEST2 "hello world!"
printf("TEST2");

打印的是TEST2,而不是打印:hello world!

要打印这一行,应该这样写:

printf("%s\n",TEST2);//这行代码中,宏并不在双引号内。

其实在C++中,更推荐开发者使用const关键字代替这种宏定义,我会在effective C++ 的解读中详细讨论。这种方式在C语言中同样被允许使用,但具体要根据你的编译器是否支持。总体来说,C语言更推荐你使用#define。

        但const在C语言的宏定义方面自由它的妙用——重定义常量。具体例如下:

         

#define EIGHT 2 * 4
#define EIGHT 2 * 4

当代码中出现这样的情况,即为重定义。这两条定义都有3个记号,此处要引入一个概念——记号。

从技术角度看,宏的替换体可以看作是记号(token)型字符串,而不是字符型字符串。C预处理器记号是宏定义的替换提中单独的数字或字符或称之为‘词’,用空格将这些词分开。如上面的定义。#define EIGHT 2 * 4 中有3个记号 分别是2,*,4,因为他们三个之间都用空格分隔开来。而#define EIGHT 2*4  中,就只有一个记号:2*4序列。这条概念在更复杂的情况下会显示出其中的差别意义。就目前看来倒是没什么差别。

        书接上文,在重定义时,如果你想在上面两条定义下重定义一个

#define EIGHT 1*8

其中,替换体的内容和记号都发生了变化。此时我们有两种选择,第一使用#undef(undefine),这是一个不错的方法,非常简洁明了,但是还是那句话,在不同的情况下,开发者应该选择最适合自己的方式和规约编写,所以还有const这种更容易的关键字可供使用。这两种方法后续都会进行诠释,在define为主的题目下暂且不过多讨论,且const在C++的妙用也将会与此紧密联系 。

        在演示SAMPLE01中我使用了类函数宏,以我的经验来讲并不推荐这种用法,因为宏向来是无脑替换,其中括号的使用,是一个类函数宏的难点,也是很大的一个陷阱。

#define    SQUARE(X)    X*X

X=5
Evaluating SQUARE(X):The result is 25;
Evaluating SQUARE(2):The result is 4;
Evaluating SQUARE(x+2):The result is 17;
Evaluating 100/SQUARE(2):The result is 100;
x is 5.
Evaluating SQUARE(++x):The result is 42.
After incrementing, x is 7.

 这是一个很经典的例子。前两行与预期相符合,但是接下来奇怪的结果就出现了。但是别忘了我之前反复提到过的:宏只是完成了无脑替换。所以真正的过程是这样的:

#define SQUARE(X) X*X
int x=5;
SQUARE(x+2) == 5+2*5+2;
100/SQUARE(2) == 100/2*2;
x is 5;
SQUARE(++x) == ++x*++x = 6*7

这样看是不是就清晰了奇怪的结果是如何出现的。

所以我们如果要使用类函数宏,我们要善用括号。要处理前面两种情况要这样定义:

#define SQUARE(X) ((X)*(X))

这样修改之后再尝试带入计算,就会得到并不奇怪的结果了。因此,必要时要使用足够多的圆括号来确保运算和结合的正切顺序。 

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值