C 语言结构体
内容简介
struct 用于把多个相关数据组合成一个自定义类型。C 语言没有类、构造函数、析构函数、成员函数和访问控制,结构体主要负责保存数据;行为通常由普通函数完成。
本篇重点讲 C 结构体的用法、内存布局、和 C++ struct 的区别。联合体见 C语言联合体,位域见 C语言位域。
用法
基本定义
typedef struct Student {
char name[32];
int age;
double score;
} Student;
Student stu = {"Alice", 18, 95.5};结构体对象包含所有成员,每个成员都有自己的存储空间。
成员访问
Student stu = {"Bob", 20, 88.0};
Student *p = &stu;
printf("%d\n", stu.age);
printf("%d\n", p->age); // 等价于 (*p).age指定初始化
Student a = {"Alice", 18, 95.5};
Student b = {.age = 20, .name = "Bob", .score = 88.0};指定初始化器适合成员较多、成员顺序可能调整的结构体。
结构体和函数组织模块
typedef struct Counter {
int value;
} Counter;
void counter_init(Counter *counter)
{
if (counter != NULL) {
counter->value = 0;
}
}
void counter_add(Counter *counter, int delta)
{
if (counter != NULL) {
counter->value += delta;
}
}C 中常用“结构体保存状态,普通函数操作状态”的方式组织模块。
内存对齐和填充
typedef struct Example {
char a;
int b;
char c;
} Example;sizeof(Example) 通常大于 sizeof(char) + sizeof(int) + sizeof(char),因为编译器会插入填充字节满足对齐要求。
减少填充的常见方法是按成员对齐需求从大到小排列:
typedef struct Better {
int b;
char a;
char c;
} Better;offsetof
#include <stddef.h>
size_t offset = offsetof(Student, score);offsetof 用于获取结构体成员偏移,常见于底层容器、序列化和系统接口。
C struct 和 C++ struct 的区别
| 对比点 | C 语言 struct | C++ struct |
|---|---|---|
| 成员函数 | 不支持 | 支持 |
| 构造/析构函数 | 不支持 | 支持 |
| 访问控制 | 不支持,成员都可直接访问 | 支持 public / private / protected,默认 public |
| 继承 | 不支持 | 支持继承 |
| 多态 | 不支持虚函数 | 支持虚函数和运行期多态 |
| 默认初始化 | 依赖初始化器或手动赋值 | 可写默认成员初始化、构造函数 |
| 类型名使用 | 未 typedef 时要写 struct Student | 可直接写 Student |
| 设计定位 | 数据聚合和 C 风格模块 | 与 class 几乎同等能力,常用于简单公开数据对象 |
C 示例:
struct Student {
int age;
};
struct Student stu; // C 中未 typedef 时要带 structC++ 示例:
struct Point {
int x = 0;
int y = 0;
void move(int dx, int dy)
{
x += dx;
y += dy;
}
};
Point p;
p.move(1, 2);关键理解:C 的结构体只是数据布局工具;C++ 的 struct 是一种默认 public 的类。
最佳代码实践
- 把强相关的数据放进结构体,例如学生的姓名、年龄、成绩。
- C 中常用
xxx_init、xxx_destroy、xxx_print这类普通函数管理结构体。 - 对外接口优先传
const T *表示只读,传T *表示可能修改。 - 对外暴露不完整类型可以隐藏内部布局,模拟封装。
- 需要跨平台存储或网络传输时,不要直接把结构体内存写入文件或网络。
错误用法
typedef struct User {
char *name;
} User;
User user;
strcpy(user.name, "Alice"); // 错误:name 没有指向有效缓冲区Student stu;
printf("%d\n", stu.age); // 错误:stu 未初始化,age 值不确定fwrite(&stu, sizeof(stu), 1, fp); // 错误倾向:填充字节、端序、对齐都不可移植注意事项
- 结构体可以整体赋值,但如果成员里有指针,复制的是地址,不是指向内容的深拷贝。
- 结构体末尾可以使用柔性数组成员,常见于变长对象,但需要配合动态分配。
#pragma pack或编译器打包属性会改变对齐,可能降低性能或导致某些平台访问异常。- C++ 中如果结构体开始承担复杂行为和不变量,通常应考虑
class和封装边界。
相关:CandCpp结构体、C语言联合体、C语言位域、C语言存储类型、CandCpp底层知识