这两种写法本质上都是定义结构体类型 ,但在 C 语言中有一些重要区别,主要体现在类型名、标签名(tag)、作用域和兼容性。
1. 第一种写法
c
typedef struct {
} xxx_t;
这是:
定义一个匿名结构体,并给它起一个类型别名
xxx_t
等价于:
c
struct <匿名> {
};
typedef struct <匿名> xxx_t;
但是这个结构体没有名字(tag)。
使用:
c
xxx_t a;
可以。
但是:
c
struct xxx_t b;
不可以。
因为根本不存在 struct xxx_t 这个类型。
例如:
c
typedef struct {
int age;
char name[20];
} person_t;
person_t p1; // 正确
struct person_t p2; // 错误
2. 第二种写法
c
struct xxx_t {
};
typedef struct xxx_t xxx_t;
这里做了两件事:
第一步:
定义一个有名字的结构体标签:
c
struct xxx_t {
};
第二步:
给它定义一个别名:
c
typedef struct xxx_t xxx_t;
所以现在有两个名字:
| 名字 | 含义 |
|---|---|
struct xxx_t |
结构体标签名 |
xxx_t |
typedef类型名 |
两个都可以使用:
c
struct xxx_t a;
xxx_t b;
都合法。
3. 最大区别:前向声明
这是工程中最重要的区别。
例如链表:
c
typedef struct node_t
{
int data;
struct node_t *next;
} node_t;
这里必须使用第二种。
为什么?
因为结构体里面包含自己:
c
struct node_t *next;
编译器需要提前知道:
存在一个叫 node_t 的结构体
如果写:
c
typedef struct {
int data;
struct xxx_t *next;
} xxx_t;
会失败。
因为:
c
struct xxx_t
根本不存在。
4. 嵌套结构体引用区别
例如:
匿名结构体
c
typedef struct
{
int x;
} point_t;
只能:
c
point_t p;
不能:
c
struct point_t p;
有标签结构体
c
typedef struct point_t
{
int x;
} point_t;
可以:
c
point_t p1;
struct point_t p2;
5. 实际工程推荐写法
在嵌入式项目(STM32、FreeRTOS等)中,一般推荐:
c
typedef struct
{
uint8_t id;
float temperature;
uint32_t timestamp;
} SensorData_t;
适合:
- 数据结构
- 配置结构
- 参数结构
例如:
c
SensorData_t sensor;
简洁。
对于可能扩展、需要互相引用的结构体,推荐:
c
typedef struct Device_t Device_t;
struct Device_t
{
uint8_t id;
Device_t *parent;
};
或者:
c
typedef struct Device_t
{
uint8_t id;
struct Device_t *parent;
} Device_t;
适合:
- 链表
- 树
- 驱动对象
- C语言面向对象写法
例如很多 HAL 驱动:
c
typedef struct
{
SPI_HandleTypeDef *hspi;
uint8_t state;
} SPI_Device_t;
或者:
c
typedef struct UART_Device UART_Device;
struct UART_Device
{
UART_Device *next;
void (*send)(UART_Device *);
};
总结
| 区别 | typedef struct{} xxx_t |
struct xxx_t{}; typedef ... |
|---|---|---|
| 结构体是否有名字 | ❌匿名 | ✅有tag |
能否写 struct xxx_t |
❌不能 | ✅可以 |
| 能否自引用 | ❌困难 | ✅可以 |
| 代码简洁 | ✅更简洁 | 稍复杂 |
| 适合普通数据结构 | ✅ | ✅ |
| 适合链表/对象模型 | ❌ | ✅ |
在 STM32 驱动、FreeRTOS、协议栈这类大型 C 工程里,我更推荐第二种:
c
typedef struct xxx_t xxx_t;
struct xxx_t
{
...
};
扩展性更好。对于简单的数据包、配置参数,第一种更常见。