| 特性 | typedef |
#define |
|---|---|---|
| 本质 | 关键字,编译器处理的语句 | 预处理器指令,编译前处理的文本替换 |
| 时机 | 编译时 | 预编译时 |
| 作用域 | 遵循C语言作用域规则(块、函数、文件) | 全局有效,从定义点到文件末尾 |
| 类型检查 | 会进行类型检查 | 纯文本替换,不进行类型检查 |
| 复杂类型 | 非常适合定义复杂类型别名(如函数指针、数组指针) | 不适合,容易出错,可读性差 |
| “重定义” | 可以多次声明,只要类型一致即可 | 如果宏体不同,会产生警告或错误 |
typedef (类型定义)
typedef 是 C 语言的一个关键字,它的作用是为一个已有的数据类型创建一个新的名字(别名),它是在编译阶段由编译器处理的。

工作原理
typedef 的基本语法是:
typedef existing_type new_type_name;
编译器在遇到 typedef 时,并不会进行简单的文本替换,而是会创建一个新的类型名称,并将其与原有的类型绑定在一起,后续代码中使用 new_type_name 时,编译器会把它当作 existing_type 来处理,并进行严格的类型检查。
示例
示例 1:为基本类型创建别名
typedef unsigned long ulong;
int main() {
ulong a; // 等同于 unsigned long a;
a = 123456789UL;
return 0;
}
这里 ulong 成为了 unsigned long 的一个别名,编译器知道 ulong 是一个类型。

示例 2:为结构体创建别名 (非常常用)
struct Point {
int x;
int y;
};
// 为 struct Point 创建一个别名叫 POINT
typedef struct Point POINT;
int main() {
POINT p1; // 等同于 struct Point p1;
p1.x = 10;
p1.y = 20;
return 0;
}
一个更简洁的写法(在C++和现代C中常见):
typedef struct {
int x;
int y;
} POINT; // 直接在定义结构体时起别名
int main() {
POINT p1;
p1.x = 10;
p1.y = 20;
return 0;
}
示例 3:为指针类型创建别名
typedef int* IntPtr;
int main() {
int a = 10;
IntPtr p1 = &a; // IntPtr 被解释为 int*
int* p2 = &a; // 两者完全等价
*p1 = 20;
printf("a = %d\n", a); // 输出 a = 20
return 0;
}
示例 4:为函数指针创建别名 (这是 typedef 的强项)

// 定义一个函数指针类型,它指向一个接收int参数、返回int的函数
typedef int (*FuncPtr)(int);
// 一个具体的函数
int square(int x) {
return x * x;
}
int main() {
FuncPtr ptr; // ptr 是一个函数指针变量
ptr = square;
int result = ptr(5); // 调用 square(5)
printf("result = %d\n", result); // 输出 result = 25
return 0;
}
如果用 #define 来实现函数指针的别名,会非常复杂且容易出错。
作用域
typedef 遵循标准的 C 语言作用域规则。
#include <stdio.h>
// 在全局作用域定义
typedef int Integer;
void func1() {
// 在函数作用域内,Integer 仍然可用
Integer a = 100;
printf("func1: a = %d\n", a);
}
void func2() {
// 可以在局部作用域重新定义(不推荐,但语法允许)
// 只要类型兼容,编译器不会报错
typedef double Integer;
Integer b = 3.14;
printf("func2: b = %f\n", b);
}
int main() {
Integer x = 50; // 使用全局的 Integer (int)
printf("main: x = %d\n", x);
func1();
func2();
return 0;
}
#define (宏定义)
#define 是一个预处理器指令,它发生在编译之前,预处理器会扫描整个源代码文件,将所有出现的 MACRO_NAME 替换为 macro_text,这个过程是纯粹的文本替换,不涉及任何语法分析或类型检查。
工作原理
#define 的基本语法是:
#define MACRO_NAME macro_text
当预处理器处理代码时,它会找到所有 MACRO_NAME 并将其替换为 macro_text。
示例
示例 1:为常量定义别名
#define PI 3.14159
int main() {
double radius = 5.0;
double circumference = 2 * PI * radius; // 预处理器替换为 2 * 3.14159 * radius
printf("Circumference = %f\n", circumference);
return 0;
}
注意:#define 定义的常量没有类型,只是一个文本。PI 本身不是一个变量。
示例 2:为类型创建别名 (不推荐,但可行)
#define IntPtr int*
int main() {
int a = 10;
IntPtr p1 = &a; // 预处理器替换为 int* p1 = &a;
int* p2 = &a;
// 这里有一个经典的陷阱!
IntPtr p3, p4; // 预处理器替换为 int* p3, p4;
// 这意味着 p3 是 int*,而 p4 只是 int!
// 这通常不是我们想要的结果。
p4 = 100; // 编译通过,但逻辑错误
return 0;
}
这个例子清晰地展示了 #define 作为类型别名的危险性,它只是简单的文本替换,不理解语法结构。
示例 3:定义带参数的宏
// 定义一个计算最大值的宏
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int x = 10, y = 20;
int max_val = MAX(x, y); // 预处理器替换为 ((x) > (y) ? (x) : (y))
// 注意宏定义中的括号!
int z = MAX(x + 1, y); // 替换为 ((x + 1) > (y) ? (x + 1) : (y)),结果正确
// 如果没有括号 #define MAX(a, b) a > b ? a : b
// z = x + 1 > y ? x + 1 : y; // 优先级错误,结果可能不对
return 0;
}
带参数的宏虽然强大,但容易出错,且可读性差,在C++中,应优先使用内联函数 (inline)。
关键区别深入剖析
时机不同
#define: 在代码被编译之前,由预处理器处理,它根本不进入编译阶段。typedef: 在编译时,由编译器处理,它是语法的一部分。
类型安全
#define: 不安全,它是盲目的文本替换。#define IntPtr int*导致IntPtr p3, p4变成int* p3, p4就是一个典型的错误。typedef: 安全,编译器知道typedef创建的是一个类型,会进行严格的语法和类型检查。typedef int* IntPtr;后,IntPtr p3, p4会被正确地解释为int* p3, int* p4。
作用域
#define: 全局的,从#define出现的位置开始,直到文件末尾都有效,它不受 代码块的限制。typedef: 遵循C作用域,在 内定义的typedef只在该块内有效。
复杂类型处理
#define: 非常笨拙,对于函数指针、数组指针等复杂类型,用#define几乎无法写出正确、可读的代码。typedef: 非常优雅和强大,它是定义复杂类型别名的标准方式。
总结与最佳实践
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| 为类型创建别名 | typedef |
类型安全,作用域清晰,适合所有类型,尤其是复杂类型。 |
| 定义常量 | const 变量 或 enum (现代C/C++) 或 #define (C语言) |
const 和 enum 有类型和作用域,更安全。#define 是传统做法,但需注意其全局性和无类型性。 |
| 定义简单宏(如代码块) | #define |
当需要多行文本替换时,#define 是唯一选择。 |
| 定义带参数的宏(函数) | inline 函数 (C++) 或 static inline 函数 (C99) |
内联函数有类型检查,更安全,调试更方便,仅在必须兼容纯C且无法使用内联函数时,才考虑宏。 |
在为类型创建别名时,永远优先使用 typedef,它更安全、更强大,也更符合现代编程语言的习惯。
#define 的主要用途是定义常量和宏,但在现代C/C++编程中,const、enum 和 inline 函数已经能在很大程度上取代它的功能,并提供了更好的安全性,只有在 #define 独有的功能领域(如多行文本替换)才应考虑使用它。
