深入理解C——#define与typedef

#define名为宏定义,工作在预处理阶段,实际是对字符串的宏替换;typedef工作在编译阶段,是对自定义数据类型的自定义别名定义。

深入理解C——#define与typedef

1 #define

1.1 概念

宏定义的实质就是替换。

宏定义在预处理阶段由预处理器进行替换,这个替换是原封不动的替换。

1.2 特点

设置宏定义时,需要特别注意括号的使用。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
/* 正确案例 */
#define abs(x) ((x)>=0?(x):-(x))

/* 错误案例 */
#define abs(x) x>=0?x:-x


/* 正确的abs(x)展开abs(a-b) */
((a-b)>=0?(a-b):-(a-b))

/* 错误的abs(x)展开abs(a-b) */
a-b>=0?a-b:-a-b
  • 因为C语言中,符号优先级在此处为:单目运算符+/- > 双目运算符+/- > >= > ?: ,因此实际上成了 (a-b)>=0?(a-b):(-a-b)。与原义不符。

1.3 作用

1.3.1 实现NULL在C++和C的不同定义

简例如下:

1
2
3
4
5
#ifdef _cplusplus
#define NULL 0
#else
#define NULL (void*)0
#endif
  • 通过宏定义,实现编译时的自动兼容和适应替换。
  • 在C++中,NULL定义为立即数0,特点是编译器不做严格类型检查;
  • 在C语言中,NULL定义为(void*)型的0,特点是编译器会执行严格的类型检查,需要做好类型转换工作。

1.3.2 Debug和Release的版本控制

程序的在Debug阶段调试,会包含调试代码,用于显示输入,如:IDE中的调试时数据显示。而Release时需要去除这些冗余的调试输出代码。通过宏替换可以实现轻松的Debug/Release版本切换。

原理如下:

1
2
3
4
5
#ifdef DEBUG
#define dbg() printf()
#else
#define dbg()
#endif
  • 如果源代码中包含一句#define DEBUG,则编译前的预处理阶段,所有代码中的dbg()调试输出函数都会被替换成printf()显示输出函数。
  • 如果源代码中没有对DEBUG进行宏定义,则预处理阶段会将所有的dbg()函数替换为空白,此时,被编译的代码中不包含dbg()调试输出语句。

2 typedef

2.1 概念

typedef#define的核心区别在于处理阶段不同——#define是预处理器识别读取并执行宏替换的宏语句,工作在编译阶段之前;typedef是编译器实现的类型定义语句,工作在编译时。

因此,#define眼中只有字符串的概念,只知道字符串存不存在宏定义,如何替换源代码中的字符串。而typedef工作在编译阶段,因此存在 变量数据类型、数组、函数 等具体的语言层面的概念。

2.2 实例

2.2.1 基本数据类型

1
2
3
4
5
6
7
......
typedef int size;

void main()
{
size i; // 意义:int i;
}
  • 最简单的typedef应用形式,定义变量名的别名,通过#define也可以实现。

2.2.2 数组类型

1
2
3
4
5
6
7
......
typedef char Line[81];

void main()
{
Line t; // 意义:char t[81];
}
  • typedef能够处理数组的语义,实际上Linetypedef中被定义为了一个char[81]的”类“,因此Line t实际上定义了一个名为tLine型对象(即char[81]字符数组对象),此处就不是简单地进行宏替换能实现的了。

2.2.3 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
......
typedef int (*fun_ptr)(int,int);

/* 示例:定义一个简单的示例函数 */
int maxInt(int a, int b)
{
return a>=b?a:b;
}

void main()
{
fun_ptr fp; // 意义:int (*fp)(int, int);

/**
* 延伸:函数指针的使用方法
*/
/* 初始化函数指针变量 */
fp = maxInt; // 或 fp = &maxInt,意义相同
/* 调用函数(通过函数指针变量) */
fp(1,2); // 通过函数指针fp调用maxInt函数

}
  • 通过typedef定义了一个函数指针类型fun_ptr,这是一个参数表为两个int,返回值为int的函数的函数指针类型。fun_ptr在定义此类型的函数指针变量时,显然更加简洁明了。

2.2.4 struct结构体

1
2
3
4
5
6
7
8
9
......
typedef struct node{
/* 变量定义 */
}Node;

void main()
{
Node n; // 意义:struct node n;
}
  • 此例,Node被定义为了struct node的别名,通过typedef简化变量定义。
  • 注意:如果去除typedef,则按照结构体定义语法,Node将成为该结构体类型的全局变量。

3 小结

3.1 区别#define和typedef

联系指针的概念,理解一下#definetypedef的区别。

1
2
3
4
5
6
7
8
9
#define dpChar char*

typedef char* tpChar;

void main()
{
dpChar p1, p2; // 宏替换后:char* p1, p2;
tpChar p3, p4; // p3, p4均为(char*)
}
  • dpChar为宏定义,预处理阶段,该字符串dpChar就被替换为字符串char*,最终定义变量的结果是,p1char*字符指针变量,p2char字符变量。
  • tpChartypedef类型定义,编译阶段,tpChar作为一个字符指针(char*)类型的别名,所定义的变量p3p4均为字符指针变量。