前言
预处理完整系列推荐阅读顺序:
预处理详解(上卷)——宏(上卷)——宏(下卷)——预处理详解(中卷)——预处理详解(下卷)
本文接着讲预处理相关的内容。
#和##
#运算符
#可以将宏的一个参数转换成字符串字面量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执行的操作可以理解为“字符串化”。
我们先来想这个问题:
#include<stdio.h>
int main()
{
int a = 10;
printf("the value of a is %d\n", a);
int b = 20;
printf("the calue of b is %d\n", b);
return 0;
}
这个代码,如果我们想封装为一个函数,也就是说只写一份,让a和b都能使用,这是做不到的:
#include<stdio.h>
void print(int n)
{
printf("the value of n is %d\n", n);
}
int main()
{
int a = 10;
printf("the value of a is %d\n", a);
int b = 20;
printf("the calue of b is %d\n", b);
return 0;
}
这样是不能解决这个问题的。因为n是改不了的。
但是宏就可以解决这个问题:
在将宏怎么解决这个问题之前我们先来看,C语言中两个字符串是天然可以合成一个字符串的:
回到正题,利用这一点我们可以写一个这样的宏:
但是此时还有一个问题没能解决,我们希望打印出来的是a is 10而不是n is 10,那么要怎么改呢?我们利用#可以改为这样:
我们来分析一下:
替换是这样替换的:
PRINT("%d\n", a);
printf("the value of " "a" " is " "%d""\n", a);
这两句代码是等价的。 后面一句有5个字符串,效果相当于一整个字符串。
这5个字符串分别是:
"the value of "
"a"
" is "
"%d"
"\n"
加上它们之间的间隔,相当于:"the value of a is %d\n"。
而对于我们的宏体:
printf("the value of " #n " is " format"\n",n)
是有3个字符串加#n再加format。
#n的作用是,传来n为a时,会替换为"a",让其变为对应的字符串。
利用这个宏,我们甚至可以打印不同类型:
#可以将宏的一个参数转换成字符串字面量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执行的操作可以理解为“字符串化”。
有关#的内容确实较为抽象。
我们现在再接着了解另一个运算符。
##运算符
##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。##被称为记号粘合。
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
这里我们想一想,写一个函数求2个数的较大值时,不同的数据类型就得写不同的函数:
int int_max(int x, int y)
{
return x > y ? x : y;
}
float float_max(float x, float y)
{
return x > y ? x : y;
}
如果针对每一个不同的数据类型我们都得写一个新的函数,将十分繁琐。
所以我们能否造出一个函数的模具呢?可以的,只要我们使用宏。调用一次宏就定义一个函数:
//生成函数的模版
#define GENERIC_MAX(type) \
type type##_max(type x,type y)\
{\
return x > y ? x : y;\
}
//使用上面的模版定义函数
GENERIC_MAX(int)//相当于定义了一个int_max函数
GENERIC_MAX(float)//相当于定义了一个float_max函数
int main()
{
printf("%d\n", int_max(3, 5));
printf("%f\n", float_max(3.0f, 5.0f));
return 0;
}
打印结果:
分析:
在程序执行起来后:
int main()
{
printf("%d\n", int_max(3, 5));
printf("%f\n", float_max(3.0f, 5.0f));
return 0;
}
调用了函数int_max()和float_max(),也就是说我们已经定义了,而我们不是完整写了两遍函数定义,我们是这样定义的:
GENERIC_MAX(int)//相当于定义了一个int_max函数
GENERIC_MAX(float)//相当于定义了一个float_max函数
这么简洁是因为我们使用了“模具”:
#define GENERIC_MAX(type) \
type type##_max(type x,type y)\
{\
return x > y ? x : y;\
}
也就是说我们只需要给这个宏传一个参数type,告诉它我们想要什么数据类型的函数定义,它就能制造出一个对应类型的函数定义:
GENERIC_MAX(int)会替换为:
int int_max(int x,int y)
{
return x > y ? x : y;
}
在这个过程中,##发挥的作用就是将int_max合并为一个符号,使其成为这个函数的标识符。
总之,我们利用了宏可以直接替换这一特性,实现了“模具”的功能。
在实际的开发过程中,#和##的使用很少。
命名约定
一般来讲函数和宏的使用语法很相似。所以语言本身无法帮我们区分二者。
比如:
print(a);
你一看是个函数调用,其实我说它也可以是个宏:
#define print(n) printf("%d\n",n)
所以我们习惯把宏名全部大写,函数名不要全部大写,以此来区分。
当然,也有特例,offsetof是个宏,但是是全小写的。
#undef
这条指令用于移除一个宏定义。
使用方式:
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
举例:
到此,预处理详解(中卷)结束,祝阅读愉快^_^