#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

参考

诸位正值青春年少,一定恣情放纵,贪恋香艳梅施之情,喜欢风流雅韵之事,洒脱木拘。然而诸位可知,草上露一碰即落,竹上霜一触即溶,此种风情难于长久。
原文地址:https://www.cnblogs.com/shilipojianshen/p/13231660.html