23. 结构体与联合体的 typedef:用别名让代码更清爽
说实话,我刚入行那会儿,写结构体总是这么干:
struct Student {
char name[32];
int age;
float score;
};
// 每次用都要带 struct 关键字
struct Student stu1;
struct Student *p = &stu1;
每次敲 struct Student 这串字符,我都觉得手指头在抗议。后来学会了 typedef,才算是解放了。今天咱们就聊聊这个让代码变清爽的小工具。
23.1 typedef 的基本用法
typedef 是 C 语言的关键字,它的作用就是给已有的类型起个新名字。说白了,就是给类型取个外号。
对于结构体,最常见的写法有两种。
写法一:先定义结构体,再 typedef
struct Point {
int x;
int y;
};
typedef struct Point Point_t;
写法二:定义结构体的同时 typedef
typedef struct {
int x;
int y;
} Point_t;
我个人更推荐第二种写法。为什么?因为少写一行,而且结构体标签名都省了,干净利落。我在项目中维护过几十万行代码,这种写法让头文件看起来特别清爽。
用上 typedef 之后,声明变量就变成了:
Point_t p1;
Point_t p2 = {10, 20};
Point_t *ptr = &p1;
你看,不用再写 struct 了。手指头舒服多了吧?
23.2 联合体的 typedef
联合体也是一样的道理。我记得有一次做通信协议解析,需要把一个 4 字节的整数拆成字节数组,联合体加 typedef 简直是绝配:
typedef union {
uint32_t value;
uint8_t bytes[4];
} Data32_t;
用的时候:
Data32_t d;
d.value = 0x12345678;
// d.bytes[0] 就是 0x78(小端模式)
嗯,这里要注意:联合体的字节序跟平台有关。我在做嵌入式项目时踩过这个坑,在 x86 上调试好好的,移植到 ARM 上就全乱了。所以跨平台代码里,这种用法要谨慎。
23.3 指针类型的 typedef
结构体指针也经常用 typedef 简化。比如链表节点:
typedef struct Node {
int data;
struct Node *next;
} Node_t, *NodePtr_t;
这里我同时定义了 Node_t 和 NodePtr_t 两个别名。用的时候:
Node_t node;
NodePtr_t head = NULL;
不过说实话,把指针类型也 typedef 掉,在团队里是有争议的。有些人觉得这样会隐藏指针的语义,让人搞不清到底传的是值还是指针。我个人习惯是:只在回调函数指针这种复杂类型上用,普通指针还是写 * 更直观。
23.4 常见命名规范
命名这件事,每个团队都有自己的规矩。我待过几个项目组,总结下来有这么几种主流风格:
| 风格 | 示例 | 特点 |
|---|---|---|
| _t 后缀 | Student_t |
POSIX 风格,嵌入式领域最常见 |
| _s 后缀 | Student_s |
有些公司用 s 表示 struct |
| _u 后缀 | Data_u |
表示 union |
| 大写驼峰 | Student |
Windows 风格,MFC 代码里常见 |
| 全大写 | STUDENT |
宏定义风格,不推荐用于类型 |
我个人推荐:使用 _t 后缀。原因很简单——一眼就能看出这是个类型别名,而不是变量名。我在阅读代码时,看到 xxx_t 就知道这是个类型,不用去翻定义。
小技巧:如果你用 _t 后缀,注意不要跟 POSIX 标准保留的命名冲突。比如 size_t、time_t 这些是系统保留的,别自己定义。
23.5 函数指针的 typedef
这个稍微复杂一点,但特别实用。比如定义回调函数类型:
// 传统的写法,看着就头疼
void register_callback(void (*callback)(int, char*));
// 用 typedef 之后
typedef void (*Callback_t)(int, char*);
void register_callback(Callback_t callback);
你想想看,第二种写法是不是清晰多了?Callback_t 就是一个函数指针类型,参数和返回值一目了然。
我曾经在一个物联网项目里,用这种方式定义了十几个回调函数类型。代码的可读性提升了一大截,新同事接手时也不用对着复杂的函数指针声明发呆了。
23.6 避坑指南
我曾经踩过的坑:
- 不要对结构体本身 typedef 后又用原名——容易造成混淆。选一个风格,保持一致。
- 头文件里不要重复 typedef——如果多个头文件都定义了同一个
xxx_t,链接时会报重复定义错误。 - typedef 不创建新类型——它只是别名。比如
typedef int myint;,myint和int是完全兼容的,可以互相赋值。 - 结构体自引用时必须用完整形式——比如链表节点里,
next指针必须写成struct Node *next,不能用Node_t *next,因为此时Node_t还没定义完。
23.7 知识体系总览
下面这张图把本章的核心内容串起来了,你可以对照着回顾一下:
23.8 总结
typedef 这个工具,说简单也简单,说讲究也讲究。我用了十几年 C 语言,总结下来就三条原则:
- 能用就用——特别是结构体和联合体,别让自己手指头受累。
- 风格统一——团队里定好规矩,别今天用
_t明天用驼峰。 - 别滥用——指针类型要不要 typedef,跟团队商量着来。
记住,好的代码是让人读的,不是让编译器通过的。typedef 就是帮你把代码写得更像人话的工具。
公众号:蓝海资料掘金营,微信 deep3321