C语言宏的几种用法

 
Category: C_C++

写在前面

简单总结一下C语言中x-macros(宏)的几种主要用法, 虽然主流的C语言程序中一般是不推荐使用宏的, 但是作为一种不错的C语言语法糖, 了解一下也不错,至少能看懂点大佬的代码. 主要参考了C语言程序设计:现代方法第十四章预处理器.

基本语法

直接展开

其实就是简单的展开而已.

#include <stdio.h>

#define n1 100
#define M (n1*n1+3*n1)

int main() {
    int sum;
    sum = 3 * M + 4 * M + 5 * M;
    printf("sum=%d\n", sum);
    return 0;
}

这段程序中就是一个预处理器(宏)的简单用法, 直接进行简单的(无脑)替换操作.

带参数的宏

#include <stdio.h>
#define printn(n) printf("n=%d\n", n)

int main() {
    int sum = 20 + 12;
    printn(sum);
    return 0;
}

井号的用途

单个井号表示变量替换, 后面跟上要替换的变量; 两个井号挨着表示连接.

#include <stdio.h>
#include <stdlib.h>
void t1() {
    // #表示变量替换
#define AREA(x,y) printf("长为"#x",宽为"#y"的长方形面积为%d.\n",(x)*(y));

    int i = 55, j = 3;
    AREA(i, j);
    AREA(3, 2);
    /*
    长为i,宽为j的长方形面积为165.
    长为3,宽为2的长方形面积为6.
    */
}


//##表示连接
#define VAR(n)v##n

int v1(int a, int b) {
    return a + b;
}
int v2(int a, int b) {
    return a - b;
}
void t2() {
    printf("%d\n", VAR(1)(2, 3));
    printf("%d\n", VAR(2)(2, 3));
    /*
    5
    -1
    */
}

int main(int argc, char const *argv[]) {
    // t1();
    t2();
    return 0;
}


预定义的宏

C语言中预定义了一些宏, 可以用于调试代码.

名称 描述
__LINE__ 被编译的文件中的行号
__FILE__ 被编译的文件名称
__DATE__ 编译日期(mm dd yyyy)
TIME 编译时间((hh:mm:ss))
__STDC__ 编译器符合C标准(89or99),值为1
__func__ 存储当前正在执行的函数的名字的字符串变量

这里直接用之前写好的C++代码了,C也一样,改成printf即可.

#include <iostream>
using namespace std;

#define LOG(x) (cout<<__FILE__<<" "<<__LINE__<<": "<<__PRETTY_FUNCTION__<<(x)<<endl)


void t1() {
    LOG("hello");
}

template<typename T>
void t2(T s) {
    LOG(s);
    cout << __TIME__ << endl;
    cout << __DATE__ << endl;
    cout << __func__ << endl;
    /*
    gcc_pre_define.cc 13: void t2(T) [with T = char]1
    00:17:03
    Aug 24 2022
    t2
    */
}

int main(int argc, char const *argv[]) {
    // t1();
    t2('1');
    return 0;
}

任意个数的参数的宏(C99)

#include <stdio.h>
#include <stdarg.h>
// 测试函数的用法是: 满足参数条件的话, 执行打印"通过测试", 否则执行自定语句
#define TEST(cond, ...) \
((cond) ? printf("Passed test: %s\n", #cond) : \
printf(__VA_ARGS__))

void t1() {
    int v = 10, maxV = 20;
    TEST(v <= maxV, "v %d exceeds %d\n", v, maxV);
    v = 100, maxV = 20;
    TEST(v <= maxV, "v %d exceeds %d\n", v, maxV);
    /*
    Passed test: v <= maxV
    v 100 exceeds 20
    */
}

int main(int argc, char const *argv[]) {
    t1();
    return 0;
}

…记号(省略号)出现在宏参数列表的最后,前面是普通参数。__VA_ARGS__是一个专用的标识符,只能出现在具有可变参数个数的宏的替换列表中,代表所有与省略号相对应的参数。

(至少有一个与省略号相对应的参数,但该参数可以为空。)宏TEST至少要有两个参数,第一个参数匹配condition,剩下的参数匹配省略号。

注意事项

  1. 在每一个宏替换的变量两边都要加爱上圆括号, 这样可以防止宏替换之后的代码意义改变.
  2. 多行的宏定义, 在断行位置需要加上反斜杠(转义);
  3. 单一的井号后接变量名, 这种用法放在printf中, 隐含了C语言字符串字面量可以直接合并.

更多细节请看上面提到的书, 对一些比较特殊的例子也有解释.