当前位置:   article > 正文

1 宏的使用大全_产品宏的处理

产品宏的处理

概述

        宏,也叫宏定义,并不是C/C++语言本身的一部分,不能直接对宏进行编译。在编译程序前,必须先对宏进行预先的处理(专业术语叫作“预处理”)。预处理器会分析所有的源文件,当遇到宏名称严格匹配时,就会展开宏,也就是用定义好的文本来代替宏名称。宏的用途主要有三点:一是可以为程序员的开发提供一定的便利性;二是可以有效减少源文件中大量重复的、相似的代码;三是可以在一定程度上减少系统的开销,提高程序的运行效率。

不带参数的宏

        在C/C++中,可以使用#define命令来定义宏,用于将宏名称或标识符指定为一个文本,比如:常量、语句等。具体的格式如下:

                #define 宏名称 文本

        宏名称一般采用全大写或者首字母大写的形式,以与普通的变量相区别。宏定义的末尾不能像普通语句一样加分号,如果加了分号,则分号也是上述文本的一部分。定义宏之后,如果不希望该宏定义继续生效,可以使用#undef命令来取消。不带参数的宏的定义和使用都非常简单,这里举几个例子:

  1. #define MATH_PI 3.1415926
  2. #define STR_HTTP "http"
  3. #define MAX_NUMBER 666 + 888
  4. #define EXPORT_CLASS
  5. double dbRadius = 2.0;
  6. double dbCircumference = 2 * MATH_PI * dbRadius;
  7. printf("string is %s, number is %d\n", STR_HTTP, MAX_NUMBER);
  8. class EXPORT_CLASS CTest
  9. {
  10. };
  11. #undef EXPORT_CLASS

        可以看到,宏定义指定的文本可以是整型、浮点型、字符串、常量表达式等各种数据。当然,也可以不指定文本的值,比如上面的EXPORT_CLASS,只是定义了这个宏。预处理进行宏的替换后,class EXPORT_CLASS CTest实际上等价于class CTest。不指定文本值的宏定义,常用于条件编译,可参考下面的代码。

  1. #ifdef EXPORT_CLASS
  2. // 代码段1
  3. #else
  4. // 代码段2
  5. #endif

        上面举例的宏定义都是一行,那能不能是多行呢?当然可以。如果宏定义指定的文本过长,一行写不下时,可以写成多行,并在每行的末尾(最后一行除外)加上"\"字符,用于表明后面的行仍是宏定义的内容。

  1. #define STR_POST \
  2. "POST /article/details HTTP/1.1\r\n\
  3. Host: hope-wisdom.blog.csdn.net\r\n\
  4. name1=value1&name2=value2"
  5. #define ADD_NUMBER \
  6. int nNumber = 66; \
  7. nNumber += 88;
  8. ADD_NUMBER
  9. ADD_NUMBER // 会报变量重复声明的错误

        当宏定义中有多条或多行语句,且声明了变量时,连续多次使用宏,很容易导致变量重复声明的问题。此时,将多条或多行语句放到do {} while(0)代码块中,就可以起到作用域隔离的效果。因此,上例中的宏定义ADD_NUMBER最好修改成下面这样:

  1. #define ADD_NUMBER do { \
  2. int nNumber = 66; \
  3. nNumber += 88; \
  4. } while (0);

        有两点需要注意的是:

        1、在预处理进行宏的替换时,字符串中的内容不会被替换,即使其与某个宏名称相同。

printf("MATH_PI is %.7f\n", MATH_PI);    // 输出为:MATH_PI is 3.1415926

        可以看到,第一个MATH_PI由于出现在字符串中,不会进行宏替换,而是原样输出。

        2、当要替换的文本是一个表达式时,宏替换后,可能会存在运算优先级错配导致计算结果不正确的问题。

  1. #define CIRCLE_RADIUS 2 + 3
  2. int nNumber = CIRCLE_RADIUS * CIRCLE_RADIUS;

        宏替换后,CIRCLE_RADIUS * CIRCLE_RADIUS变成了:2 + 3 * 2 + 3。为了解决该问题,通常将整个表达式加上括号即可。

#define CIRCLE_RADIUS           (2 + 3)

        此时,宏替换后变成了:(2 + 3) * (2 + 3),符合预期。

        另外,宏替换由于在预处理阶段进行,不会占用程序的运行时间,只会占用编译时间。

带参数的宏

        宏也可以像函数一样,带有参数列表。具体的格式如下:

                #define 宏名称(参数列表) 文本

        注意:宏名称和左括号之间不能有空格,否则,空格之后的部分都会成为文本,该宏也会成为一个不带参数的宏定义。

        带参数的宏在进行文本替换之前,还需要进行参数替换。如果实参是一个表达式,宏替换时,并不会像函数一样先计算表达式的值,而是直接用表达式替换形参。

  1. #define MATH_PI 3.1415926
  2. #define MATH_CIRCUMFERENCE(r) 2 * MATH_PI * r
  3. MATH_CIRCUMFERENCE(2)
  4. MATH_CIRCUMFERENCE(2 + 3)

        在上面的例子中,MATH_CIRCUMFERENCE(2)在进行参数替换后,变为:2 * MATH_PI * 2,结果计算正常。但MATH_CIRCUMFERENCE(2 + 3)在进行参数替换后,变为:2 * MATH_PI * 2 + 3,结果计算不正常。为了解决该问题,通常会将文本中的形参和整个表达式都加上括号,以确保替换后的结果符合预期。

  1. #define MATH_CIRCUMFERENCE(r) (2 * MATH_PI * (r))
  2. #define MATH_MULTIPLY(a, b) ((a) * (b))

        注意:参数替换只是简单的实参代替形参,并不会像函数一样去检查参数的类型。因此,如果参数替换后发生类型不匹配等情况,后续的编译过程会报错。

MATH_MULTIPLY(2, "hello")            // 编译出错

带可变参数的宏

        C99标准允许定义带有省略号的宏,类似于printf中的可变参数。省略号必须放在参数列表的最后面,用于表示可变参数。具体的格式如下:

                #define 宏名称(参数列表, ...) 文本

        参数替换时,预处理器会将所有可变参数和分隔它们的逗号打包成一个参数,传递给系统预定义宏__VA_ARGS__

  1. #define LOG_DEBUG(format, ...) printf(format, __VA_ARGS__)
  2. LOG_DEBUG("no data\n");
  3. LOG_DEBUG("data is %d\n", 66);
  4. LOG_DEBUG("data is %d, %s\n", 66, "hope_wisdom");

        在较低版本的编译器中,当可变参数为空时,LOG_DEBU会发生编译错误。这是因为宏替换后,后面会有一个多余的逗号,类似于下面这样。

printf("no data\n", );            // 宏替换后,后面会多出一个逗号

        为了解决这个问题,兼容性更好的写法是:在__VA_ARGS__前面加上##字符。##字符的作用为:当可变参数为空时,预处理器将自动去除掉它前面的那个逗号。

#define LOG_DEBUG(format, ...)      printf(format, ##__VA_ARGS__)

        在GNU C标准中,还支持带可变参数的宏的另一种写法:不再使用__VA_ARGS__,而是直接使用args...来表示可变参数,文本中则可直接使用args。示例代码如下:

#define LOG_DEBUG(format, args...)      printf(format, ##args)

特殊符号#的作用

        特殊符号#相当于一个一元运算符,用于把#符号后边的参数变成一个字符串。变为字符串时,出现在#符号后边的空白符和#符号本身会一起被删除。示例代码如下:

  1. #define TO_STR(str) # str
  2. #define CALC_EXPRESSION(exp) printf(#exp" = %f\n", exp)
  3. printf("str is %s\n", TO_STR(hope_wisdom)); // 输出:str is hope_wisdom
  4. CALC_EXPRESSION(2 * sin(30)); // 输出:2 * sin(30) = -1.976063

特殊符号##的作用

        特殊符号##相当于一个二元运算符,用于把##符号两边的参数结合在一起。结合后,出现在##符号两边的空白符和##符号本身会一起被删除。假如结合后的文本中还包含某个宏名称,则预处理器会继续进行宏替换。可参考下面的示例代码:

  1. #define STR_CSDN "Hello, CSDN"
  2. #define OUTPUT_STR(str) puts(STR_ ## str)
  3. OUTPUT_STR(CSDN);

        在上面的例子中,OUTPUT_STR在进行参数替换后,变为:puts(STR_ ##    CSDN)。接着##符号起作用,删除空白符和##符号本身,进一步变为:puts(STR_CSDN)。最后发现STR_CSDN是一个宏名称,替换后变为:puts("Hello, CSDN")

宏的嵌套

        宏是可以嵌套的,也就是,一个宏定义的参数可以是另一个宏。示例如下:

  1. #define CONST_NUMBER 2
  2. #define CONST_SQUARE(a) (a * a)
  3. #define CONST_POWER(a) int(a##e##a)
  4. int nSquare = CONST_SQUARE(CONST_NUMBER);
  5. int nPower1 = CONST_POWER(2);
  6. int nPower2 = CONST_POWER(CONST_NUMBER); // 发生编译错误

        在上面的例子中,宏替换后,CONST_SQUARE(CONST_NUMBER)变为:(2 * 2)CONST_POWER(2)变为:int(2e2),但CONST_POWER(CONST_NUMBER)变为:CONST_NUMBEReCONST_NUMBER,会导致编译出错。CONST_NUMBER的值就是2,为什么后两条语句的结果不一样呢?更奇怪的是,为什么使用CONST_SQUARE却是正常的呢?这是因为,当宏定义中出现特殊符号#和##时,参数如果是另一个宏,则该宏不再展开,而是直接使用实参的值。

        解决这个问题的方法是:多加一个用于转换的宏定义,该宏定义中不出现特殊符号#和##。可参考下面的代码:

  1. #define _CONST_POWER(a) int(a##e##a)
  2. #define CONST_POWER(a) _CONST_POWER(a)

常用预定义宏

        C/C++语言预定义了一些宏定义,常用的宏定义如下表。

宏名称

数据类型

备注

__FILE__

字符串

表示当前文件的完整路径

__LINE__

数字

表示当前代码所在的行号

__FUNCTION__

字符串

表示当前代码所在的函数名

__DATE__

字符串

表示预处理当前代码的日期

__TIME__

字符串

表示预处理当前代码的时间

__STDC__

常数

用于判断编译器是否遵守ISO标准C

__STDC_VERSION__

常数

表示当前使用的C语言标准的版本

__cplusplus

常数

表示当前使用的C++语言标准的版本

        除此之外,各操作系统、指令架构和IDE也预定义了一些宏定义,常用的宏定义如下表。

宏名称

备注

_WIN32

Windows 32位和64位程序环境下,均会定义该宏

_WIN64

仅Windows 64位程序环境下,会定义该宏

__linux__

Linux操作系统下,会定义该宏

__ANDROID__

Android NDK环境下,会定义该宏

__APPLE__

苹果操作系统下,会定义该宏

TARGET_IPHONE_SIMULATOR

iOS模拟器环境下,会定义该宏

TARGET_OS_IPHONE

iOS环境(包括iOS模拟器环境)下,会定义该宏

TARGET_OS_MAC

Mac环境(包括iOS环境)下,会定义该宏

__i386__

X86架构32位时,会定义该宏

__x86_64__

X86架构64位时,会定义该宏

__arm__

Arm架构32位时,会定义该宏

__aarch64__

Arm架构64位时,会定义该宏

__mips__

MIPS架构时,会定义该宏

__powerpc__

RISC架构时,会定义该宏

_MSC_VER

VC的版本号

__GNUC__

GNU C的版本号

__BORLANDC__

Borland C的版本号

QT_VERSION

Qt的版本号

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/600641
推荐阅读
相关标签
  

闽ICP备14008679号