C语言之宏定义用法总结

一.预处理将宏展开

编译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 ‘ifelse
上面中的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值