【C/C++】宏、宏的作用/副作用

基础:
宏是预处理指令;
宏的本质是简单的字符串替换,预处理时进行宏替换;
可在定义宏时要求它接收参数,宏替换时会代入参数;
宏的名字不允许重载;
宏预处理代码没有能力处理递归调用。
作用:
看一段math.h中的宏定义:

#if defined _USE_MATH_DEFINES && !defined _MATH_DEFINES_DEFINED
    #define _MATH_DEFINES_DEFINED
    // Definitions of useful mathematical constants
    //
    // Define _USE_MATH_DEFINES before including <math.h> to expose these macro
    // definitions for common math constants.  These are placed under an #ifdef
    // since these commonly-defined names are not part of the C or C++ standards
    #define M_E        2.71828182845904523536   // e
    #define M_LOG2E    1.44269504088896340736   // log2(e)
    #define M_LOG10E   0.434294481903251827651  // log10(e)
    #define M_LN2      0.693147180559945309417  // ln(2)
    #define M_LN10     2.30258509299404568402   // ln(10)
    #define M_PI       3.14159265358979323846   // pi
    #define M_PI_2     1.57079632679489661923   // pi/2
    #define M_PI_4     0.785398163397448309616  // pi/4
    #define M_1_PI     0.318309886183790671538  // 1/pi
    #define M_2_PI     0.636619772367581343076  // 2/pi
    #define M_2_SQRTPI 1.12837916709551257390   // 2/sqrt(pi)
    #define M_SQRT2    1.41421356237309504880   // sqrt(2)
    #define M_SQRT1_2  0.707106781186547524401  // 1/sqrt(2)
#endif

要使用M_PI:

#define _USE_MATH_DEFINES
//#undef _USE_MATH_DEFINES
#include <math.h>

问题:这样的宏定义是否真的有存在的必要?答案是否。
宏定义“函数”:
一个简单的加法:

#define ADD(a,b) a+b

副作用:

int z = ADD(1,10)*ADD(1,10) //期望11*11=121;实际结果为21;

这很简单,即1+10*1+10;为了避免这种情况,宏定义时多加括号:

#define ADD(a,b) (a+b)

然而,有些副作用是无法避免的:

#include <iostream>
#define MIN(a,b) ((a)<(b))?(a):(b)

int main(int argc, char** argv) {
 int x = 1, y = 10;
 int z = MIN(x++, y++);
 std::cout << x << ", " << y << ", " << z << std::endl;
 getchar();
 return (0);
}

会输出3,11,2;即x++执行了两次,为什么?展开:

int z = ((x++)<(y++))?(x++):(y++);

判断后一次,赋值后一次。
宏只有在展开后才能被编译器看到,因此,如果在宏里面存在错误,只有当宏展开后才可能发现。编译器无法在定义宏的时候发现错误,这就导致关于宏的报错信息常常晦涩不清,让人不明所以。而不幸的是几乎每个宏都带有一些美中不足甚至是缺陷。
建议:
预处理程序本身的能力非常有限,你根本没必要也不可能处理太复杂的宏;宏在C语言中非常重要,但在C++中的作用就小多了;实际上,在现代C++语言中,auto、constexpr、const、decltype、enum、inline、lambda表达式、namespace和template机制可以完成原来的预处理机制的大多数功能,如:

const double pi = 3.14159265358979323846;

template<class T>
inline const T& min(const T& a, const T& b) {
 return (a < b) ? a : b;
}

因此,建议只有在进行条件编译尤其是执行包含文件防护的任务时再使用宏。
NOTE:
##宏运算符可以把两个字符串拼接成一个:

#define NAME(a,b) a##b 
int NAME(a, Func)(); //会展开成int aFunc();

在置换字符串中,如果参数的名字前面有一个单独的#,表示此处是包含宏参数的字符串:

#define printx(x) std::cout << #x "=" << x << std::endl
//int a = 1;
//printx(a); //std::cout << "a" << "=" << a << std::endl;

宏的参数列表可以为空,甚至可以是可变参数的,不赘述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章