• 可变宏


    C99中规定宏可以像函数一样带有可变参数,实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个英文输入法下的句号)。这样预定义宏__VA_ARGS__就可以被用在替换部分中,以表明省略号代表什么。

    eg:

    #include<stdio.h>
    #define Variable_Macro(...) printf(__VA_ARGS__)
    int main(void)
    {
         Variable_Macro("This is a variable macro test... ");
         Variable_Macro("My age is %d",24);
         return 0;
    }

    这里和可变参数函数不同,可变宏可以只有可变参数“...”,但是可变参数函数必须在可变参数前有至少一个其他变量,否则会报错:

     但是可变宏也得遵守约定——可变参数只能出现在宏的最后,不能在“...”后面再加参数:

    #define Variable_Macro(...,a) 这样是错误的。

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

    eg:

    #include<stdio.h>
    #define Parameter_Macro(y)    printf("The square of y is %d
    ",((y)*(y)))
    int main(void)
    {
        Parameter_Macro(10);
        return 0;
    }

    引号中的字符串y被当做了普通文本,而不是被看做一个可以替换的语言符号,此时我们可以通过#运算符达到目的。

    #include<stdio.h>
    #define Parameter_Macro(y)    printf("The square of " #y " is %d
    ",((y)*(y)))
    
    int main(void)
    {
        Parameter_Macro(10);
        return 0;
    }

    顺便多说一句,ANSI C字符串本就具有拼接功能,在宏中使用  #y 时,可以把实参的字符串传进来。

    字符串拼接test:

    int main(void)
    {
        printf("hello"     "boy
    ");
        return 0;
    }

    就算两个双引号之外隔了很多空格,拼接之后也是紧挨着的,要想展现空格,请让空格位于双引号内部。

    预处理器的粘合剂:##运算符

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

    eg:

    #define XNAME(n) x##n
    int main(void)
    {
        int XNAME(1)=100;//等价于 int x1=100
        printf("XNAME(1) is %d",XNAME(1));
        return 0;
    }

    ##运算符把上例的x和n组合成了一个字符。

    如果你觉得你已经掌握了#和##在宏中用法,那么看看下面的呢?

    eg:

    #include<stdio.h>
    #define Print_ERROR_1(fmt,...)             printf("<<-Print-ERROR_1->> "fmt"
    ",__VA_ARGS__)
    #define Print_ERROR_2(fmt,arg...)          printf("<<-Print-ERROR_2->> "fmt"
    ",arg)
    int main(void)
    {
        Print_ERROR_1("%d",1);
        Print_ERROR_2("%s","do you konw?");
        return 0;
    }

    第一个不难理解,和前面介绍的可变参数宏一样,用__VA_ARGS__作为替换。可是第二个看起来似乎有点不一样,在可变参数三个句号前还加了一个arg,居然这样也行?

    是的,它们都是可行的,而且效果都是一样的。但是,我们把main函数中的调用改变一下呢,看看会发生什么:

    #include<stdio.h>
    #define Print_ERROR_1(fmt,...)             printf("<<-Print-ERROR_1->> "fmt"
    ",__VA_ARGS__)
    #define Print_ERROR_2(fmt,arg...)          printf("<<-Print-ERROR_2->> "fmt"
    ",arg)
    int main(void)
    {
        Print_ERROR_1("why ?");
        Print_ERROR_2("occured err?");
        return 0;
    }

    是的,我们只改变了调用形式,当可变参数为0时,发生了错误:

    那么你知道这是为什么吗?

    我们看看预编译之后的程序:

    注意,在没有使用可变参数的时候,最后宏替换在printf函数末尾出现了逗号,这是语法错误,必然导致调用出错。

    我们测试最熟悉的printf函数,用错误的方式调用:

    报错和上面宏展开一样,知道这里在可变参数为0个的时候会出错,那么怎么解决这个问题呢?

    eg:

    #include<stdio.h>
    #define Print_ERROR_1(fmt,...)             printf("<<-Print-ERROR_1->> "fmt"
    ",##__VA_ARGS__)
    #define Print_ERROR_2(fmt,arg...)          printf("<<-Print-ERROR_2->> "fmt"
    ",##arg)
    int main(void)
    {
        Print_ERROR_1("why ?");
        Print_ERROR_2("right now?");
        return 0;
    }

    是的,我们所做的更改很少,仅仅增加了上面红色部分,增加了##运算符,它就可行了:

    看看预编译之后的程序:

    是的,末尾没有逗号了,成功也是必然的。

    那么,我们再测试一下这样的宏能在有可变参数的情况下运行吗?

    #include<stdio.h>
    #define Print_ERROR_1(fmt,...)             printf("<<-Print-ERROR_1->> "fmt"
    ",##__VA_ARGS__)
    #define Print_ERROR_2(fmt,arg...)          printf("<<-Print-ERROR_2->> "fmt"
    ",##arg)
    int main(void)
    {
        Print_ERROR_1("%s","yes");
        Print_ERROR_2("%s","I can");
        return 0;
    }

    看来后者应该是我们程序中应该出现的,它可以在可变参数有或者没有的时候都能正常工作,这样的宏,一般用作打印调试信息,例如STM32 IIC,SPI中都有这样的宏:

    那么,问题来了,为什么加了##运算符就成功了,逗号是怎么消失的?

    这里,请翻上去看看我举的第一个##运算符的例子,他可以作为预处理器的粘合剂,可以把##前面和后面的字符链接成一个。

    以#define Print_ERROR_2(fmt,arg...)          printf("<<-Print-ERROR_2->> "fmt" ",##arg)为例子

    最后那里:,##arg    在没可变参数的时候,##把逗号给吸收了。在没有加上##时,调用Print_ERROR_2(fmt)   ,会被展开成

    printf("<<-Print-ERROR_2->> "fmt" ",)    最后的逗号的存在会让程序出现语法错误,为了消除这个逗号,我们使用##运算符,在有##时,调用展开成:printf("<<-Print-ERROR_2->> "fmt" ")    为什么最后的逗号不见了,因为它被预处理器粘合剂##吸收了。让逗号跟着后面的可变参数,如果有一个可变参数,例如a,这样上面展开成printf("<<-Print-ERROR_2->> "fmt" ",a)  ,如果没有可变参数,逗号和空结合,逗号会被预处理器特殊处理,达到让逗号消失的作用。如果可变参数被忽略或为空,'##'操作将使预处理器(preprocessor)去除掉它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,它也会正常工作,它会把这些可变参数放到逗号的后面。当##遇到后面是可变参数的时候,编译器的预处理器会特殊处理,例如:#define XNAME(n) ,##n这样的宏编译会出错,但是改成#define XNAME(n...) ,##n之后编译就会通过,这是预处理做了特殊处理,我们并不知道编译器设计者如何去实现的,但是我们应该知道这个特殊的应用场景。

  • 相关阅读:
    命令行标签
    ts关键还是js 因为要编译成js
    nuxt axios
    vuecli3-ssr
    v-text
    这样竟然也可以水平居中 两个属性都必须
    纯CSS实现垂直居中的几种方法
    下图片异步变同步
    [Java] 扯淡系列_找工作流程 与 注意问题
    [Java] Spring3.0 Annotation
  • 原文地址:https://www.cnblogs.com/yangguang-it/p/7113002.html
Copyright © 2020-2023  润新知