0


C语言——预处理详解

预处理

一,预定义符号

C语言中有一些预定义符号,这些符号是语言内置的,可以直接使用
例如:__FILE__   __LINE__  __TIME__  __DATE__  __STDC__(如果编译器支持ANSI C就返回1
,否则未定义)
int main(){printf("%s\n", __FILE__);printf("%d\n", __LINE__);printf("%s\n", __TIME__);printf("%s\n", __DATE__);//printf("%d\n", __STDC__);return0;}

在这里插入图片描述

可以看到我的这个编译器是不支持ANSI C的

二,#define

1,#define定义标识符

语法:#define name  stuff
#define定义的标识符,可以代表很多东西
例如:
#define M10
#define Q"hello world"
#define PRINTprintf("hello world")
int main(){printf("%d\n",M);printf("%s\n",Q);PRINT;return0;}

在这里插入图片描述

上面那些都是正常的使用情况:
下面介绍两种特殊的情况:
1,定义一个死循环
2,如果定义的stuff过长,可以分几行写,除最后一行外,每行最后加上\作为分行符
#define DO_FOEVERfor(;;)
#define DEBUG_PRINTprintf("file:%s\tline:%d\t \
                             date:%s\ttime:%s\n",\
                            __    FILE__,__LINE__ ,       \
                            __DATE__,__TIME__ )

2,#define定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,
这种实现通常称为宏(macro)或定义 宏(define macro)
语法:#define M(参数) stuff
无论在定义宏还是普通标识符的时候,最终都会在编译预处理阶段完成替换
并且在定义宏的时候,应注意带括号,防止运算符的优先级问题
下面举几个例子,自己体会一下:
#define DOUBLE(x) x*x
int main(){printf("%d\n",DOUBLE(6));printf("%d\n",DOUBLE(6+1));printf("%d\n",2*DOUBLE(6));return0;}

在这里插入图片描述

看到这个结果明显不对劲,这就是上述所说的应该给宏定义的文本中的各项加上括号,避免出错
下面看下修改过后:
#define DOUBLE(x)((x)*(x))

在这里插入图片描述

注意:无论在定义标识符还是在定义宏的时候,最好不要在结尾加上分号,
这样很容易在使用上出错,例如:
#define DOUBLE(x)((x)*(x));if(1)printf("%d\n",DOUBLE(6));else;
实质上,编译预处理完后,DOUBLE(6)被替换成下面的样子
if(1)printf("%d\n",((6)*(6)));;else;
上面这种情况是会报错的,一条if语句跟了两条语句(没有大括号前提下)。
所以,我们在定义标识符,或者宏的时候 末尾不要加分号,避免使用时的错误。

3,#

讲述这个之前,首先介绍一下C语言的一种机制:
字符串相邻在一起时,会合并成一个字符串
例如:
int main(){printf("%s\n","hello ""world");return0;}

在这里插入图片描述

而,#的作用是,当宏参数前面加上#的时候,会将这个参数转化为字符串。
例如:#define(x) printf("..."#x"...");
最终会被替换成:printf("...""x""...");
下面举一个业务场景做例子:
int main(){
    int a =10;printf("the value of a is %d\n", a);
    int b =20;printf("the value of b is %d\n", b);
    float c =30.0f;printf("the value of c is %f\n", c);return0;}

在这里插入图片描述

每一个数据后面都跟着一条输出语句,而且每条输出语句的内容极为相似,
我们能不能将它封装为一个函数呢?其实是不可以的,因为我们想让它以何种方式打印,
需要传给函数"%d"  "%f"这样的参数,但是函数会误以为这些是字符串。
所以要借助宏来实现
#define PRINT(x,format)printf("the value of "#x" is "format"\n",x)
int main(){
    int a =10;PRINT(a,"%d");
    int b =20;PRINT(b,"%d");
    float c =30.0f;PRINT(c,"%f");return0;}

在这里插入图片描述

4,##

##的作用非常奇怪,是将##两边的字符合成一个字符
例如:
#define A(x,y)printf("%d\n",x##y)
int main(){
    int ab =10;A(a, b);return0;}

在这里插入图片描述

5,带副作用的宏参数

当宏的参数在宏定义的文本中出现了不止一次的时候,就可能会出现副作用。
例如:我们定义一个宏来输出两个数中的较大值
#define MAX(x,y)((x)>(y)?(x):(y))
int main(){
    int a =2;
    int b =3;//想输出++a 与++b中的较大值printf("%d\n",MAX(++a,++b));return0;}
我们本想输出的是4,但是你看结果,这带副作用的参数

在这里插入图片描述

6,只能靠宏实现而函数实现不了的功能

(1)对malloc进行一次封装

比如,我们正常用malloc来开辟内存空间
int* a=(int*) malloc(sizeof(int)*10);
写起来较为麻烦,可以用宏来封装一层
#define MALLOC(type,num)(type*)malloc(sizeof(type)*num)
这样开辟内存空间就可以这样写了,
int*a=MALLOC(int,10);
比上面的代码就简洁了许多

(2)offsetof的实现

我们在结构体那片博客中介绍过offsetof()的功能:是结构体变量的成员地址
相对于结构体变量的地址的偏移量
今天,我们就来模拟实现一下offsetof()
struct stu
{
    int a;
    char c;
    int b;};
#define OFFSETOF(type,x)(int)&(((type*)0)->x)
int main(){printf("%d\n",OFFSETOF(struct stu, a));printf("%d\n",OFFSETOF(struct stu, c));printf("%d\n",OFFSETOF(struct stu, b));return0;}

在这里插入图片描述

所以,可以看到函数与宏各有千秋,函数不能实现的宏可以实现,宏不能实现的但是函数可以实现,
所以下面我们来总结一下,函数与宏的区别

(3)特殊场景

#define PRINT(x,format)printf("the value of "#x" is "format"\n",x)
int main(){
    int a =10;PRINT(a,"%d");
    int b =20;PRINT(b,"%d");
    float c =30.0f;PRINT(c,"%f");return0;}

在这里插入图片描述

三,函数与宏的比较

1,代码长度

宏:每次使用时,在编译预处理阶段定义的宏的代码就会插入到程序中,如果定义的宏代码量很大
,这样的话就大大增加了代码量。
函数:函数的代码只出现在一个地方,每次调用函数的时候都只会调用这一份代码

2,执行速度

宏:当实现的功能比较简单是,宏的速度是很快的
函数:当实现的功能比较简单时,调用函数和释放函数栈帧的时间,比真正实现函数功能还长
举个简单例子:输出较大值
int main(){
    int a =2;
    int b =3;max(a, b);MAX(a, b);return0;}

在这里插入图片描述
在这里插入图片描述

可以看到,如果实现简单的功能,宏要比函数运行速度快不少

3,操作符的优先级

宏:如果在定义宏的时候没有适当加上括号,可能会在周围环境的表达式中,与临近运算符出现
未曾预料的结果
函数:函数参数只在函数调用的时候计算一次,它的运算结果传递给函数。表达式的结果更容易预测。
例如:
#define DOUBLE(x) x+x
int main(){
    int a =3;printf("%d\n",2*DOUBLE(3));}
我们预料的结果应该是12,但结果:

在这里插入图片描述

4,带有副作用的参数

宏:宏参数可能被替换到宏体中的多个位置,所以带有副作用的参数会造成不可预料的结果。

函数:函数只在传参的时候求值一次,结果更容易预料。

5,参数类型

宏:参数与类型无关,只要对参数的操作是合法的,他就可以运用到各种类型。

函数:函数参数是与类型有关的,参数的类型不同那么就需要不同的函数。
例如:比较大小用宏来实现,就可以比较多种数据类型的大小
#define MAX(x,y)((x)>(y)?(x):(y))

6,调试。

宏:由于宏的实现是替换,所以宏不能够调试。

函数:函数是可以调试的。我们在调试的时候按F11进入到函数内部。
voidprint(int* arr, int len){
    int i =0;for(i =0; i < len; i++){printf("%d ", arr[i]);}}
int main(){
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int sz =sizeof(arr)/sizeof(arr[0]);print(arr, sz);return0;}

在这里插入图片描述

7,递归

宏:宏是不能够递归的。

函数:函数可以实现递归。
例如我们写一个递归函数实现求前N项和
int sumN(int n){if(n ==1){return1;}else{return n +sumN(n -1);}}
int main(){
    int n =10;printf("%d\n",sumN(n));return0;}
综上,介绍了七点函数与宏的区别。

本文转载自: https://blog.csdn.net/Djsnxbjans/article/details/127345463
版权归原作者 大理寺j 所有, 如有侵权,请联系我们删除。

“C语言&mdash;&mdash;预处理详解”的评论:

还没有评论