C/C++常见宏的基本使用及语法

基本介绍

1.宏定义的基本语法

宏定义在 C 语言源程序中允许用一个标识符来表示一个字符串,称为“宏/宏体” ,被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。 宏定义是由源程序中的宏定义命令完成的,宏代换是由预处理程序自动完成的。
在 C 语言中,宏分为 有参数和无参数两种。无参宏的宏名后不带参数,其定义的一般形式为:

#define 标识符 字符串
//# 表示这是一条预处理命令(在C语言中凡是以#开头的均为预处理命令)
//define 为宏定义命令
//标识符 为所定义的宏名,
//字符串 可以是常数、表达式、格式串等。符号常量
// 不带参数的宏定义
#define PI 3.1415926
if(i>PI)//等效于if(i>3.1415926)
/*带参宏定义*/
#define abs(y) y<0?-y:y
k=M(5);//等效于k=5<0?-5:5;

2.宏定义的优点

方便程序的修改

使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时, 我们可以用较短的有意义的标识符来写程序,这样更方便一些。
相对于全局变量两者的区别如下:

宏定义参数全局变量
在编译期间即会使用并替换全局变量要到运行时才使用
只是文本替换,编译的时候被替换到引用的位置

变量在运行时要为其分配内存
不可以被赋值,即其值一旦定义不可修改 变量在运行过程中可以被修改
作用域定义所在文件,或引用所在文件的其它文件中使用 全局变量可以在工程所有文件中使用,只要再使用前加一个extern声明就可以

提高程序的运行效率

使用带参数的宏定义可完成函数调用的功能,又能减少系统开销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子 函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽 略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问 题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。

3.宏定义的缺点

由于是直接嵌入的,所以代码可能相对多一点;
嵌套定义过多可能会影响程序的可读性,而且很容易出错,不容易调试。
对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患。

4.宏or函数?

宏函数,函数比较

从时间上来看

宏只占编译时间,函数调用则占用运行时间(分配单元,保存现场,值传递,返回),每次执行都要载入,所以执行相对宏会较慢。
使用宏次数多时,宏展开后源程序很长,因为每展开一次都使程序增长,但是执行起来比较快一点(这也不是绝对的,当有很多宏展开,目标文件很大,执行的时候运行时系统换页频繁,效率就会低下)。而函数调用不使源程序变长。

从安全上来看

函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符替换。
函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
宏的定义很容易产生二义性,如:定义#define S(a) (a)(a)代码S(a++)宏展开变成(a++)(a++) 这个大家都知道,在不同编译环境下会有不同结果。
调用函数只可得到一个返回值,且有返回类型,而宏没有返回值和返回类型,但是用宏可以设法得到几个结果。
函数体内有Bug,可以在函数体内打断点调试。如果宏体内有Bug,那么在执行的时候是不能对宏调试的,即不能深入到宏内部。
C++中宏不能访问对象的私有成员,但是成员函数就可以。

内联函数

在C99中引入了内联函数(inline),联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。

宏函数的适用范围

一般来说,用宏来代表简短的表达式比较合适。
在考虑效率的时候,可以考虑使用宏,或者内联函数。
还有一些任务根本无法用函数实现,但是用宏定义却很好实现。比如参数类型没法作为参数传递给函数,但是可以把参数类型传递给带参的宏。

使用宏

#define MULTIPLY(x, y) ((x) * (y))

分号吞噬问题

do{}while(0)用法使用if防止语法错误
#define swap(a, b){a = a+b; b = a-b; a = a-b;}
int main()
{
    int a = 1, b = 2;
    if(1)
    	swap(a, b);
    else
    	a = b = 0;
    	/*展开为*/
    if(1){
        a = a+b; 
        b = a-b; //swap(a, b)
        a = a-b;
    };//多了分号;存在结构错误
    else//错误没有匹配的if
        a = b = 0;
    return 0;
}
#define swap(a, b) do{a = a+b; b = a-b; a = a-b;}while(0)
int main()
{
    int a = 1, b = 2;
    if(1)
    do{
        a = a+b; 
        b = a-b; //swap(a, b)
        a = a-b;
    }while(0);
    else
        a = b = 0;
    return 0;
}

宏参数重复调用

有如下宏定义:

#define min(X, Y)  ((X) < (Y) ? (X) : (Y))

当有如下调用时next = min (x + y, foo (z));,宏体被展开成next = ((x + y) < (foo (z)) ? (x + y) : (foo (z)));,可以看到,foo(z)有可能会被重复调用了两次,做了重复计算。更严重的是,如果foo是不可重入的(foo内修改了全局或静态变量),程序会产生逻辑错误。
对自身的递归引用
有如下宏定义:

#define foo (4 + foo)

按前面的理解,(4 + foo)会展开成(4 + (4 + foo)),然后一直展开下去,直至内存耗尽。但是,预处理器采取的策略是只展开一次。也就是说,foo只会展开成4 + foo,而展开之后foo的含义就要根据上下文来确定了。

宏函数的集中特定语法

#运算符利用宏参数创建字符串

在宏体中,如果宏参数前加个#,那么在宏体扩展的时候,宏参数会被扩展成字符串的形式。如:

#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;
}

多重引用#,一次使用#时宏参数的会直接变成字符串,在重复调用这个宏函数预处理器会将宏参数的值展开,如;

#include <stdio.h>
#define SAMPLE_RATE (16000) 
#define PI 3.1415926  
#define STR1(R)  #R  
#define STR2(R)  STR1(R)  
int main()
{
    printf(STR1(SAMPLE_RATE) "\n");  //打印“SAMPLE_RATE”字符
    printf(STR2(SAMPLE_RATE) "\n");  //打印“(16000)”字符
    printf(STR1(PI) "\n");//打印“PI”字符  
    printf(STR2(PI) "\n");//打印“3.1415926”字符 
   return 0;
}

##预处理器的粘合剂

和#运算符一样,##运算符可以用于类函数宏的替换部分。另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如

#define XNAME(n) x ## n
int x1=10;
XNAME(1)+=1;  //等效x1+=11

#define COMMAND(NAME)  { NAME, NAME ## _command }

多重引用#,一次使用#时宏参数的会直接变成字符串,在重复调用这个宏函数预处理器会将宏参数的值展开,如;

#define AFTERX(x) ## x
#define XAFTERX(x) AFTERX(x)
#define TABLESIZE 1024
#define BUFSIZE TABLESIZE
int main()
{
    printf(AFTERX(BUFSIZE) "\n");  //打印“X_BUFSIZE”字符
    printf(X_BUFSIZE(BUFSIZE) "\n");  //打印“X_1024”字符
   return 0;
}

VA_ARGS …可变宏

#define p(...) printf(__VA_ARGS__)
p("Howdy");//打印“Howdy”字符
与##联合起来用
#define print_bug(fmt, ...) printf("<(fun)%s (line)%d]>" fmt,__func__,__LINE__,##__VA_ARGS__)//##与__VA_ARGS_的联合使用
print_bug("Howdy%d",666);//打印“<(fun)所在函数名 (line)所在行数]>Howdy666”字符

#if … #else … #endif 条件编译

#if的后面跟的是表达式。
#if (A==10)||(B==20)
 code1...
#elif //和C类似elseif 和else可省略
 code2...
#else
 code3...
#endif

作用:判断成立条件,那么编译器就会将符合条件的代码编译进去(注意:是编译进去,不是执行!!)

#ifdef \ #if defined\#if  !defined\#ifndef条件宏编译
#if  defined  xxx  //如果对xxx进行了宏定义,则编译此处内容。
#ifdef xxx         //如果对xxx进行了宏定义,则编译此处内容。
//#if defined(x) == #if defined x == #ifdef x
#if  !defined  xxx  //如果对xxx未进行宏定义,则编译此处内容。
#ifndef xxx         //如果对xxx为进行宏定义,则编译此处内容。
//#if !defined(x) == #if !defined x == # ifndef x

不同点:#ifdef和 #if defined 的区别在于,后者可以组成复杂的预编译条件

#if  defined  xxx1  //如果对xxx1进行了宏定义,则编译此处内容。
 code1...
#elif defined  xxx2//如果xxx1未定义,对xxx2进行了宏定义,则编译此处内容。
 code2...
#else如果上述条件都不符合,则编译此处内容
 code3...
#endif
//#if  !defined 和#ifndef等效

#error \ #warning

是用来产生编译时错误\警告信息XXXX的,一般用在预处理过程中;

#if !defined(__cplusplus) 
#error C++ compiler required. 
#endif

#undef 解除宏定义

#if defined(CREDIT)
#undef CREDIT     //解除定义
#defined CREDIT 1//重新定义
#endif

常见的标准预定义宏

__FUNTION__  /__func__获取当前函数名 
__LINE__ 获取当前代码行号 
__FILE__ 获取当前文件名 
__DATE__ 获取当前日期 
__TIME__ 获取当前时间
__STDC_VERSION__
  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值