typedef 的作用就一句话:给一个已经存在的类型起个新名字。格式是 typedef 原类型 新名字;——注意新名字写在最后,而且没有变量、没有 =。它最常见的两个用途:把长得吓人的类型名变短(unsigned long long → uint64),把结构体名变成"像内置类型一样好用"(Student s; 而不是 struct Student s;)。它只是换名字,不产生新类型。
8.1 里我们一直写 Student s1;——其实在纯粹的老 C 里必须写 struct Student s1;,那个 struct 关键字很累赘。本节就是解决这类"名字太啰嗦"的痛点。你会看到它和我们 2.2.5 学过的 #define 很像,但两者有本质区别(最后会给一张对照表,这是本节最容易考也最容易错的地方)。
一、基本用法:typedef 原类型 新名字;
typedef unsigned long long uint64; // 给长名字起短名
typedef int Score; // 给 int 起一个业务名
读法:typedef 后面先是原类型,最后是新名字,中间用空格隔开,末尾分号。
用起来就跟原来的类型完全一样:
typedef unsigned int uint;
uint a = 10; // 等同于 unsigned int a = 10;
uint b, c; // 一次声明多个也行
一句话记住顺序:
typedef 旧的 新的;——新的在最后。很多人写成typedef uint unsigned long long;就错了(编译器会当成"给uint起名叫unsigned")。
二、给结构体起名:本章最关心的一种
typedef struct Student {
char name[20];
int age;
double score;
} Student; // ← 新名字写在最后
这样之后就可以省掉 struct 关键字,写起来和内置类型一模一样:
Student s1; // 不用再写 struct Student s1;
说明:
- 在 C 语言里这几乎是必需的,因为老 C 不给结构体自动生成类型名,每次都得写 struct Student;
- 在 C++ 里 struct Student {...}; 之后就能直接用 Student 了(这就是我们从 8.1 开头一直这么写的原因)——所以 C++ 里给别人写好的结构体加 typedef,主要是为了兼容 C 代码或统一风格;
- 上面这种"定义 + 别名"一把做完的写法,在教材里非常常见,看到要知道它等价于"定义结构体 + typedef 起名"两件事。
三、还能给数组、指针起名字
typedef 的"原类型"位置可以放任何类型描述,包括数组和指针:
typedef int Arr10[10]; // Arr10 是"10 个 int"这种类型
typedef int *IntPtr; // IntPtr 是"指向 int 的指针"这种类型
Arr10 a; // 等于 int a[10];
IntPtr p, q; // p、q 都是 int*(关键,见下节对比)
typedef int Arr10[10]; 读法:把 Arr10 替换进原文,就得到 int a[10];——新名字就是"变量名位置"上的那个词。
四、和 #define 的区别(重点,容易考)
两者都能"换个名字",但本质完全不同:
typedef char *PChar;
PChar a, b; // ✅ a 和 b 都是 char*
#define PCHAR char *
PCHAR c, d; // ❌ c 是 char*,但 d 只是 char!
为什么?
typedef |
#define |
|
|---|---|---|
| 谁处理 | 编译器(语言的一部分) | 预处理器(编译前的文字替换) |
| 替换方式 | 建立"类型别名" | 纯文本替换,直接贴进去 |
| 上面那个例子 | PChar a, b; → a、b 都是 char* |
PCHAR c, d; → 变成 char *c, d; → d 只是 char |
| 有类型检查吗 | 有 | 没有,它根本不知道类型 |
| 作用域 | 有(可以写在函数里) | 无(从定义处到文件末尾全局生效) |
| 末尾分号 | 必须有 | 不能有(加了就把分号一起替换进去) |
结论:只要能用 typedef,就别用 #define 去定义类型别名。 这是 2.2.5 那条"const 优先于 #define"的同一道理——让编译器认识它,比让预处理器瞎贴强。
五、C++ 的更现代写法:using
C++11 起还有一种更好读的别名写法:
using uint64 = unsigned long long; // 新名字在前,等号在中间
using Student = struct Student; // 也可以给结构体起名
和 typedef 的差别:using 新名字写在前面、用 =,读起来更顺("uint64 就是 unsigned long long")。
教材环境(老 MinGW)可能不支持
using别名——学习阶段先掌握typedef,见到using知道是同一回事就行。
六、动手 Demo:三种别名一起用
#include <iostream>
#include <cstring>
using namespace std;
// ① 给长类型起短名
typedef unsigned long long uint64;
typedef unsigned char byte;
// ② 给结构体起名(教材最常见写法)
typedef struct Student {
char name[20];
int age;
double score;
} Student;
// ③ 给数组类型起名
typedef int Arr5[5];
int main() {
uint64 big = 1234567890123ULL; // 比 unsigned long long 短得多
byte b = 200;
Student s; // 不用写 struct Student
strcpy(s.name, "张三");
s.age = 18; s.score = 92.5;
cout << s.name << " " << s.age << " " << s.score << endl;
Arr5 nums = {1, 2, 3, 4, 5}; // 等于 int nums[5];
for (int i = 0; i < 5; i++) cout << nums[i] << " ";
cout << endl;
cout << "big = " << big << ", b = " << (int)b << endl;
cout << "sizeof(uint64) = " << sizeof(uint64) << endl;
return 0;
}
看到什么算成功:张三 18 92.5、1 2 3 4 5、big = 1234567890123, b = 200、sizeof(uint64) = 8。
30 秒实验:把 ③ 的 typedef int Arr5[5]; 删掉,改成直接写 int nums[5] = {...};——输出一模一样。这证明 typedef 不改变任何行为,只是让名字好写。
七、常见误区
- "
typedef会创建新类型"——不会。它只是别名,uint和unsigned int是完全相同的类型,可以互相赋值、不会触发类型检查报错。(真正"造新类型"的是enum、class。) - "新名字写在前面"——写在最后:
typedef 原类型 新名字;。写反了语义就变了,而且往往不报错、只是"起了个奇怪的名字"。 - "
typedef和#define差不多"——差得很远:一个在编译期由编译器处理、有类型有作用域;一个在预处理期做纯文本替换。指针别名那个例子就是照妖镜。 - "
typedef后面可以跟="——不行,那是using的写法(using A = B;)。 - "给结构体起了别名,原来的名字就不能用了"——两个名字并存,
struct Student和Student都能用。
自测三问(点开对答案)
1. 写一句"把 `unsigned int` 起名叫 `uint`"。——`typedef unsigned int uint;`(新名字在最后) 2. `typedef char *PChar; PChar a, b;` 里 `b` 是什么类型?用 `#define` 写会怎样?——`b` 是 `char*`。改用 `#define PCHAR char *` 后,`PCHAR c, d;` 会展开成 `char *c, d;`,**`d` 只是 char**。 3. `typedef` 和 `#define` 最本质的区别是什么?——`typedef` 由**编译器**处理,是真正的**类型别名**(有类型、有作用域);`#define` 由**预处理器**做**纯文本替换**(无类型、无作用域)。- ☐ 8.3 结构体与函数(下一节)
相关笔记
- 结构体的大小 —— 上一节:对齐与 sizeof
- 结构体与函数 —— 下一节:把结构体交给函数
- 其他常量 ——
#define与const的老对比 - 函数的定义 —— 参数表里写的"类型"从此可以是别名
- 🔷 C++基础 · 索引 —— 全课路线图