C语言宏的进化

以求两个数中的最大值为例

第一阶:裸奔

#define MAX(x,y) x > y ? x : y

测试程序

int main(void)
{
    printf("max=%d",MAX(1,2));
    printf("max=%d”,MAX(2,1));
    printf(“max=%d",MAX(2,2));
    printf("max=%d”,MAX(1!=1,1!=2));
    return 0
}

        在执行第四句会出现问题,实际运行结果为max=0,和预期结果不一致,这是因为宏展开后变成了:

        printf("max=%d”,1!=1>1!=2?1!=1:1!=2);

        因为比较运算符>的优先级为6,大于!=(优先级为7),所以在展开的表达式中,运算顺序发生了改变,结果就和预期不—样了.为了避免这种展开错误,我们可以给宏的参数加一个小括号(),防止展开后表达式的运算顺序发生变化。

#define MAx(x,y) (x)>(y) ?(x):(y)

第二阶:为变量加上括号

#define MAx(x,y) (x)>(y) ?(x):(y)

        测试程序

int main(void)
{
    printf("max=%d",3+MAX(1,2));
    return 0;
}

        在程序中,我们打印表达式3+MAX(1,2)的值预期结果应该是5实际运行结果却是1宏展开后,我们发现同样有问题。

3+(1)>(2)?(1):(2);

        因为运算符 +的优先级大于比较运算符 ,所以这个表达式就为 4>2?1:2,最后结果为1也就不奇怪了。此时我们继续修改宏

#define MAX(x,y) ((x)>(y)?(x):(y))

第三阶:为宏运算加上括号

#define MAX(x,y) ((x)>(y)?(x):(y))

        这个宏虽然解决了运算符优先级带来的问题,但是仍存在一定的漏洞。例如,我们使用下面的代码来测试我们定义的宏。

测试程序

int main(void)
{
   int i = 2;
   int j = 6;
   printf("max=%d",MAX(i++,j++));
   return 0; 
}

        在程序中,我们定义两个变量i和j然后比较两个变量的大小,并作自增运算。实际运行这是因为变量i和在宏展开后,做了两次自增运结果发现max=7,而不是预期结果max=6,导致打印出的值为 7。

        C语言编程规范里,使用宏时一般是不允许参数变化的。但是万一碰到这种情况,该怎么办呢?这时候,语句表达式就该上场了。我们可以使用语句表达式来定义这个宏,在语句表达式中定义两个临时变量,分别来暂时存储 和的值,然后使用临时变量进行比较,这样就避免了两次自增、自减问题。

第四阶:解决变量中存在运算的问题

#define MAX(x,y)({ \
    int _x = x;    \
    int _y = y;    \ 
    _x > _y ? _x : _y; \
 })

测试程序

int main(void)
{
    int i = 2;
    int j = 6;
    printf("max-%d",MAX(i++,j++));
    return 0;
}

        在语句表达式中,我们定义了2个局部变量_x和_y来存储宏参数x和y的值然后使用_x和_y来比较大小,这样就避免了 i 和 j 带来的2次自增运算问题。

第五阶:对变量类型进行适配

        在第五阶的宏中,我们定义的两个临时变量数据类型是int型,只能比较两个整型数据。那么对于其他类型的数据,就需要重新定义一个宏了,这样太麻烦了!我们可以基于上面的宏继续修改,让它可以支持任意类型的数据比较大小。

#define MAX(type,x,y)({  \
    type _x = x;        \
    type _y = y;        \
    _x > _y ? _x : _y; \
    })
int main(void)
{
    int i=2;
    int j=6;
    printf("max=%d\n",MAX(int,i++,j++));
    printf("max=%f\n",MAX(float,3.14,3.15));
    return 0;
}

        在这个宏中,我们添加一个参数type,用来指定临时变量x和y的类型。这样,我们在比较两个数的大小时,只要将 2个数据的类型作为参数传给宏,就可以比较任意类型的数据了。

第六阶:自动识别变量类型

        在上面的宏定义中,我们增加了一个 type 类型参数,来适配不同的数据类型。如何把这个参数也省去。可以使用 typeof 就可以了,typeof是GNUC新增的一个关键字,用来获取数据类型,我们不用传参进去,让 typeof 直接获取。

#define max(x,y)({ \
    typeof(x)_x=(x);\
    typeof(y)y=(y); \
    (void)(&x==&y); \
    _x > _y ? _x : _y;\
})

        在这个宏定义中,我们使用了 typeof 关键字来自动获取宏的两个参数类型。比较难理解的是(void)(&x= &y); 这句话,看起来很多余,仔分析一下,你会发现这条语句很有意思。它的作用有两个:

        一是用来给用户提示一个警告对于不同类型的指针比较,编译器会发出一个警告,提示两种数据的类型不同。

        二是两个数进行比较运算,运算的结果却没有用到,有些编译器可能会给出一个 warning,加一个(void)后,就可以消除这个警告。

你可能感兴趣的:(C语言,c语言)