一.预处理将宏展开
编译C语言程序第一步就是预处理阶段,这一阶段就是宏展开但不检查语法错误的阶段。C预处理器在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释、插入被#include进来的文件内容、定义和替换由#define 定义的符号以及确定代码部分内容是否根据条件编译(#if)来进行编译。”文本性质”的操作,就是指一段文本替换成另外一段文本,而不考虑其中任何的语义内容。宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见。
宏定义只有在定义所在文件,或引用所在文件的其它文件中使用。 而全局变量可以在工程所有文件中使用,只要再使用前加一个声明就可以了。换句话说,宏定义不需要extern。
宏定义作用:提高效率、可读性、易改性。核心是替换
宏的要点:
通常命名为大写
通常用做为静态配置参数,可以让编译好的程序有不同的行为
宏与条件编译配合,可以静态调整程序模块、逻辑流程等
参数宏是代码替换,可能引起二级制代码的体积增大
配合预定义宏,可以实现很灵活的功能
_DATE_,_TIME_,_FILE_,LINE_,等
用法1:宏常量
#define的用法就是用#define来定义一个符号常量,而要修改时,只需修改#define这条语句就行了,不必每处代码都修改。
//不带参数
#include"stdio.h"
#define PI 3.14 //注意:宏定义后面没有分号
#define STR "圆周率约等于"
int main()
{
printf("%s %f",STR,PI); //预处理时会被替换为 printf("%s %f","圆周率约等于",3.14);
return 0;
}
结果:
huislee@huislee:~/workspace/git/huislee/study/language/C/cBasic/define$ gcc defineConstant.c -o a
huislee@huislee:~/workspace/git/huislee/study/language/C/cBasic/define$ ./a
圆周率约等于 3.140000
//带参数
#include"stdio.h"
#define PI 3.14
#define AREA(r) ((PI)*(r)*(r)) //注意:每个参数必须带括号防止传参失误出错(如传参为2+3)
int main()
{
printf("AREA=%lf\n",AREA(4));
return 0;
}
结果:
AREA=50.240000
用法2:宏语句
//用宏定义一条或多条语句
//注意:第1条打印后面有分号第二条没有;因为第一条作为单独语句,第二条属于宏结尾
#include"stdio.h"
#define Print printf("hello");\
printf("world\n")
int main()
{
Print; //预处理时会被替换为 printf
return 0;
}
结果:
helloworld
//带参数
#define Show(str1,str2,str3)\
{\
printf("%s\n",str1);\
printf("%s\n",str2);\
printf("%s\n",str3);\
}
int main()
{
Show("hello","world","go");
return 0;
}
结果:
hello
world
go
用法3 宏函数
'注1:分号吞噬问题'
> 有如下宏定义
> #define fun(x) fun1(x); fun2(x)
> 调用: if (!var)fun(wolf);
> 这将被宏扩展为: if (!var) bar(wolf); baz(wolf); baz(wolf);不在判断条件中,显而易见,这是错误。
如下:
void fun1(int x)
{
printf("%d\n",x);
}
void fun2(int x)
{
printf("%d\n",x);
}
#define fun(x) fun1(x);fun2(x)
int main()
{
int i=0;
if(!i)
fun(i);
return 0;
}
huislee@huislee:~$:gcc -E defineConstant.c
结果:
int i=0;
if(!i)
fun1(i);fun2(i);
如果用大括号将其包起来依然会有问题,例如:
如下:
void fun1(int x)
{
printf("%d\n",x);
}
void fun2(int x)
{
printf("%d\n",x);
}
#define fun(x) { fun1(x);fun2(x); }
int main()
{
int i=0;
if(!i)
fun(i);
else
fun(10);
return 0;
}
结果:
gcc -E defineConstant.c
int i=0;
if(!i)
{ fun1(i);fun2(i); };
else
{ fun1(10);fun2(10); };
return 0;
}
huislee@huislee:~$ gcc defineConstant.c -o a
defineConstant.c: In function ‘main’:
defineConstant.c:32:2: error: ‘else’ without a previous ‘if’
else
上面中的else将不会被执行。
通过==do{…}while(0) 能够解决上述问题
#define fun(x) do{fun(x);fun2(x);}while(0
if (!var)
foo(wolf);
else
bin(wolf);
被扩展成:
#define fun(x) do{ fun(x); fun(x); }while(0)
if (!var)
do{ fun1(x); fun2(x); }while(0);
else
bin(wolf);
结果:
gcc -E defineConstant.c
int i=0;
if(!i)
do{fun1(i);fun2(i);}while(0);
else
do{fun1(10);fun2(10);}while(0);
'使用do{…}while(0)构造后的宏定义不会受到大括号、分号等的影响,总是会按你期望的方式调用运行。'
例子:
#define TCN_MACRO_START do {
#define TCN_MACRO_END } while (0)
#define TCN_EMPTY_STATEMENT TCN_MACRO_START TCN_MACRO_END
#define TCN_UNUSED_PARAMETER(_type_, _name_) \
TCN_MACRO_START \
_type_ __tmp1 = (_name_); \
_type_ __tmp2 = __tmp1; \
__tmp1 = __tmp2; \
TCN_MACRO_END
'注2:利用宏参数创建字符串:”#运算符”'
在宏体中,如果宏参数前加个#,那么在宏体扩展的时候,宏参数会被扩展成字符串的形式。如:
#include <stdio.h>
#define PSQR(x) printf("the square of "#x" is %d.\n",((x)*(x)))
#define PSQR2(x) printf("the square of %s is %d.\n",#x,((x)*(x)))
int main() {
int R=5;
PSQR(R); //the square of R is 25.
PSQR2(R); // the square of R is 25.
return 0;
}
结果:
$ gcc -E defineConstant.c
printf("the square of ""R"" is %d.\n",((R)*(R)));
printf("the square of %s is %d.\n","R",((R)*(R)));
$ gcc defineConstant.c -o a
$./a
the square of R is 25.
the square of R is 25.
这种用法可以用在一些出错处理中
#include <stdio.h>
#define WARN_IF(EXPR)\
do {\
if (EXPR)\
fprintf(stderr, "Warning: EXPR \n");\
} while(0)
int main() {
int R=5;
WARN_IF(R>0);
return 0;
}
' 预处理器的粘合剂:”##运算符”'
“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。在普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些##来替代空格。
#include"stdio.h"
#define Add(n,value)\
{\
num##n+=value;\
}
int main()
{
int num1=1;
int num2=10;
Add(2,10); //等价于num2+=10; 这里把num和2连接成了num2
printf(" num1=%d\n num2=%d",num1,num2);
return 0;
}
'可变宏:… 和_VA_ARGS'
有些函数如prinft()可以接受可变数量的参数。
int __cdecl printf(const char * __restrict__ _Format,...);
实现思想就是在宏定义中参数列表的最后一个参数作为省略号(三个句号)。这样,预定义宏_VA_ARGS就可以被用在替换部分中,以表明省略号代表什么
输出
#define PR(...) printf(__VA_ARGS_)
PR("Howdy");
PR("weight=%d,shipping=$%.2f.\n",wt,sp)
参数初始化
可以通过参数初始化完成对多个参数的初始化,就像int数组的初始化那样
例如动态数组的添加
动态数组multi append
darray(int) arr=darray_new();
int *i;
darray_appends(arr, 0,1,2,3,4);
darray_foreach(i, arr)
{
printf("%d ", *i);
}
#ifdef和#ifdefined()之间的区别
实际上,两者并无本质的差别,但后者的应用范围更广,能支持多个预编译变量的检查。
#if defined(MACRO_A) && !defined(MACRO_B)
#endif
如果用前者的方式来表达,只能使用嵌套的条件判断
#ifdef(MACRO_A)
#ifndef(MACRO_B)
...;
...;
#endif
#endif
#if和#ifdef之间的区别
对于#if后面需要是一个表达式,如果表达式为1则调用#if下面的代码。
对于#ifdef后面需要的只是这个值有没有用#define定义,并不关心define的这个值是0还是1。
要使得判别条件#ifdef结果为假,也就是不执行判别条件后面所跟的语句,需要对#ifdef后面所跟的宏使用#undef语句进行声明。
————————————————
原文链接:https://blog.csdn.net/weixin_45787652/article/details/109779398