• #define 和 typedef 中的##


    https://whu-pzhang.github.io/define-and-typedef/

    c语言中,#define 和 typedef 均是用来定义别名的符号,但又有明显的不同。 #define 定义的宏只是简单的文本替换,typedef 则是类型别名。

    C语言中宏为预处理阶段的一种文本替换工具。从使用上分为

    • 对象类的宏

    • 函数类的宏。

    可以将任何有效的标识符定义为宏,你甚至可以将c语言关键字定义为宏,你能这么做的原因是因为c预处理器 没有关键字这个概念。利用这个特性你可以将const关键字对不支持的编译器隐藏起来。

    基础用法

    对象类的宏仅仅为会被替换为定义的代码片段,被称为对象类的宏也是因为其在代码中以数据对象的形式存在。

    1. 标识符别名
    1
    2
    #define BUFFER_SIZE 1024

    这是最常用的用法,预处理阶段,BUFFER_SIZE会被替换为1024。按照惯例,宏名用大写字母表示。

    若宏体过长,可以加反斜杠换行

    1
    2
    3
    4
    #define NUMBERS 1,
    2,
    3

    预处理阶段,int x[] = { NUMBERS };会被替换为int x[] = { 1, 2, 3 };

    1. 宏函数

    带括号的宏被认为是宏函数。用法和普通函数一样,只不过其在预处理阶段就展开。

    1
    2
    #define MIN(X, Y) ((X) < (Y) ? (X) : (Y))

    需要注意的是,宏名和括号之间不能有空格!

    高级用法

    字符串化(Stringizing)

    在写调试程序的宏函数时,我们希望可以将宏函数的参数转换为字符串嵌入字符常量中,这时我们可以使用符号#将其字符串化。

    1
    2
    3
    4
    5
    #define WARN_IF(EXP)
    do { if (EXP)
    fprintf(stderr, "Warning: " #EXP " ");}
    while (0)

    这时WARN_IF(x==0);会被扩展成:

    1
    2
    3
    do { if (x == 0)
    fprintf (stderr, "Warning: " "x == 0" " "); }
    while (0);

    上面宏函数体中的 do { } while (0) 是在宏中有多个语句时用到的。为了将宏代码和其他片段分割开来。 譬如以下的程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #define M() a(); b()
     
    if (cond)
    M();
    else
    c();
     
    /* 预处理后 */
     
    if (cond)
    a(); b();
    else /* <- else 没有对应的 if */
    c();

    只用 {} 也不行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #define M() { a(); b(); }
     
    if (cond)
    M();
    else
    c();
     
    /* 预处理后 */
     
    if (cond)
    { a(); b(); }; /* 最后的分号表示 if 语句结束 */
    else /* <- else 没有对应的 if */
    c();

    用 do {} while(0) 才可以:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #define M() do { a(); b(); } while(0)
     
    if (cond)
    M();
    else
    c();
     
    /* 预处理后 */
     
    if (cond)
    do { a(); b(); } while(0);
    else
    c();

    连接字符串(Concatenation)

    当宏中出现##时,会对 token 进行连接:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #define COMMAND(NAME) { #NAME, NAME ## _command }
     
    struct command
    {
    char *name;
    void (*function) (void);
    };
     
    struct command commands[] =
    {
    COMMAND (quit),
    COMMAND (help),
    ...
    };

    上述命令会被扩展为:

    1
    2
    3
    4
    5
    6
    struct command commands[] =
    {
    { "quit", quit_command },
    { "help", help_command },
    ...
    };

    变参数宏(Variadic Macros)

    宏函数还可以像函数一样接受可变参数。语法和可变参数的函数一样:

    1
    2
    #define eprintf(...) fprintf(stderr, __VA_ARGS__)

    这时调用宏的所有参数都会代替__VA_ARGS__被展开:

    eprintf("%s:%d: ", input_file, lineno)会被展开为fprintf(stderr, "%s:%d: ", input_file, lineno)

    以上宏定义不够直观,我们可以指定参数名:

    1
    2
    #define eprintf(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)

    但是还有一个问题,调用上述宏定义时若省略可选参数,会报错。例如eprintf("success! ",);会展开为fprintf(stderr, "success! ", );,原因在于字符串末尾会多出来一个逗号。这时我们就需要用到前面提到的##了,将宏定义改写为:

    1
    2
    #define eprintf(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)

    当省略参数时,多余的逗号就会被删除。

    typedef

    c语言中的 typedef 用来给数据类型取别名,目的是使代码易读和易理解。有简化声明的作用。

    简化声明

    定义了一个结构体如下:

    1
    2
    3
    struct _Point {
    double x, y;
    };

    每次创建一个 _Point 类型的变量,得这么写:

    1
    struct _Point a;

    每次都要带上 struct 关键字增加击键数,这时可以利用 typedef 取别名简化变量声明。

    在 C++ 中定义结构体已经隐含了这层含义,因此可以直接使用 _Point a; 来声明变量。

    1
    typedef struct _Point Point;

    这样的话,定义新的变量可以写为

    1
    Point a;

    更易阅读和理解。

    与数组和指针一起使用

    先来看几个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef int iArr[6];
     
    typedef struct Node Node;
    struct Node {
    int data;
    Node *pNext;
    };
    typedef struct Node* pNode;
     
    typedef int (*pfunc)(int a, int b);

    这几个例子其实和c语言的复杂声明是一样的。去掉 typedef 关键字后就得到一个正常的 变量声明语句。

    typedef int iArr[6]; 变为 int iArr[6];,表示声明一个包含6个元素的int数组。 而加上 typedef 后就得到了一个新的类型名,iArr 不再是变量名,而是新的类型名 iArr, 用 iArr 可以去定义与原来的 iArr 变量相同类型的变量。

    1
    iArr a; /* a 为 包含6个元素的int数组 */

    理解了这个后,剩下其他 typedef 就都好理解了。

    1
    2
    3
    4
    5
    6
    pNode pNew; /* pNew 为指向 struct Node 类型的指针 */
     
    int add(int a, int b) {
    return a + b;
    }
    pfunc func = add; /* func 为一个函数指针,其指向的函数有两个int参数,返回int */

    另外,在 C++11 标准中,引入了新的为类型取别名的关键字 using,可以用来代替 typedef, 而且更好理解:

    1
    2
    3
    using iArr = int [6];
    using pNode = Node *;
    using pfunc = int (*) (int a, int b);

    从可读性上 using 好于 typedef 。此外 typedef 和 using 不是完全等价的,using 可以用来 给模板取别买,typdef 则不行。

    1
    2
    3
    4
    5
    template <typename T>
    using Vec = MyVector<T, MyAlloc<T>>;
     
    // usage
    Vec<int> ivec;

    用起来非常自然。若是使用 typedef,则是这样:

    1
    2
    3
    4
    5
    template <typename T>
    typedef MyVector<T, MyAlloc<T>> Vec;
     
    // usage
    Vec<int> vec;

    编译的时候,会得到类似 error: a typedef cannot be a template 的错误信息。

    总结起来,你只要弄懂了复杂声明,typedef 就很好理解!

    此外,在 C++11 中推荐使用 using 代替 typedef

    参考

    诸位正值青春年少,一定恣情放纵,贪恋香艳梅施之情,喜欢风流雅韵之事,洒脱木拘。然而诸位可知,草上露一碰即落,竹上霜一触即溶,此种风情难于长久。
  • 相关阅读:
    Java基本数据类型
    Java入门
    JavaSE概述
    ORACLE中的自治事务
    JDWP Transport dt socket failed to initialize
    jinfo命令 Java Configuration Info
    mysql的bind-address设置为127 0 0 1,通过localhost连接访问不了
    与MQ通讯的完整JAVA程序
    Hadoop入门进阶步步高(三)-配置Hadoop
    GC Root
  • 原文地址:https://www.cnblogs.com/shilipojianshen/p/13231660.html
Copyright © 2020-2023  润新知