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_tNodePtr_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_ttime_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;myintint 是完全兼容的,可以互相赋值。
  • 结构体自引用时必须用完整形式——比如链表节点里,next 指针必须写成 struct Node *next,不能用 Node_t *next,因为此时 Node_t 还没定义完。

23.7 知识体系总览

下面这张图把本章的核心内容串起来了,你可以对照着回顾一下:

typedef 与结构体/联合体 结构体 typedef • 定义时直接 typedef • 先定义后 typedef • 省略结构体标签 联合体 typedef • 字节拆分场景 • 注意字节序问题 • 节省内存空间 指针类型 typedef • 链表节点指针 • 函数回调指针 • 团队风格统一 命名规范 • _t 后缀(推荐) • _s / _u 后缀 • 驼峰命名法

23.8 总结

typedef 这个工具,说简单也简单,说讲究也讲究。我用了十几年 C 语言,总结下来就三条原则:

  • 能用就用——特别是结构体和联合体,别让自己手指头受累。
  • 风格统一——团队里定好规矩,别今天用 _t 明天用驼峰。
  • 别滥用——指针类型要不要 typedef,跟团队商量着来。

记住,好的代码是让人读的,不是让编译器通过的。typedef 就是帮你把代码写得更像人话的工具。


公众号:蓝海资料掘金营,微信 deep3321