定义
结构体(struct)是一种用户自定义的复合数据类型, 允许将不同类型的数据组合成一个有机的整体
在c语言中, 定义结构体变量时通常需要带上 struct 关键字, 为简化书写常结合 typedef 使用:
// C 语言标准定义
struct Student {
const char* name;
int age;
};
struct Student s1; // 必须带 struct
// C 语言推荐做法: 使用 typedef
typedef struct {
const char* name;
int age;
} Student;
Student s2; // 可直接使用别名
在 c++ 中, 结构体名自动成为类型名, 无需 typedef:
struct Student {
std::string name;
int age;
};
Student s1; // 直接使用
c结构体成员默认公有
c++结构体成员也默认公有, 但可使用访问控制(如private、protected)
使用
初始化
聚合初始化 (aggregate initialization)
最基础的初始化方式, 按成员声明顺序依次赋值
struct Point { int x; int y; };
Point p1 = {10, 20}; // C/c++ 通用
Point p2{30, 40}; // c++11 统一初始化语法
指定初始化(designated initializer)
允许通过成员名进行赋值, 顺序可以打乱, 未指定的成员自动置零
极大地提高多成员结构体的可读性
struct Config {
int timeout;
int retry_count;
bool enable_log;
};
// C99 标准支持, c++20 正式引入(GCC/Clang 在 c++ 中作为扩展早已支持)
struct Config cfg = {
.timeout = 30,
.enable_log = true,
// retry_count 未指定, 自动初始化为 0
};
c++构造函数与默认成员初始化
在 c++ 中, 结构体可以拥有构造函数
c++11 引入非静态数据成员默认初始化 (NSDMI)
struct User {
std::string name = "Guest"; // c++11 默认成员初始化
int age = 18;
bool is_active;
// 自定义构造函数
User(std::string n, int a) : name(std::move(n)), age(a), is_active(true) {}
// 默认构造函数(为支持聚合初始化或无参构造)
User() = default;
};
User u1; // 调用默认构造, name="Guest", age=18
User u2("Alice", 25); // 调用自定义构造
结构体数组与指针
#include <iostream>
#include <string>
struct Student {
std::string name;
std::string id;
int age;
};
int main() {
Student class_a[3] = {
{"Wang", "A-234", 19},
{"Han", "A-235", 19},
{"Liu", "A-236", 20}
};
for (int i = 0; i < 3; i++) {
std::cout << class_a[i].name << " "
<< class_a[i].id << " "
<< class_a[i].age << "\n";
}
return 0;
}
结构体指针与 -> 操作符
当通过指针访问结构体成员时, 使用箭头操作符 ->(等价于 (*ptr).member)
Student s = {"Tom", "B-001", 21};
Student* ptr = &s;
std::cout << ptr->name << "\n"; // 输出 Tom
ptr->age = 22; // 修改 age
内存对齐(memory alignment)
结构体的大小(sizeof)通常不等于其成员大小之和, 为提高 CPU 访问内存的效率, 编译器会进行内存对齐
对齐规则
-
首地址对齐: 结构体的首地址必须是其最宽基本类型成员大小的整数倍
-
成员偏移对齐: 每个成员相对于结构体首地址的偏移量, 必须是该成员自身大小(或编译器设定的对齐系数, 取两者较小值)的整数倍
-
整体大小对齐: 结构体的总大小必须是其最宽基本类型成员大小的整数倍
struct Test {
char a; // 1 byte
int b; // 4 bytes
short c; // 2 bytes
};
内存布局分析(假设 32/64 位系统, 默认对齐系数为 8):
-
a放在偏移0处(占 1 字节) -
b是 4 字节, 偏移量必须是 4 的倍数, 所以放在偏移4处(中间填充 3 字节) -
c是 2 字节, 偏移量必须是 2 的倍数, 放在偏移8处(占 2 字节) -
目前总大小为 10 字节, 但整体大小必须是最宽成员(
int, 4字节)的倍数, 因此尾部填充 2 字节 -
最终
sizeof(Test)= 12 字节
高级特性
位域 (bit-fields)
允许将一个字节划分为多个“位”区域, 用于极端压缩存储空间(如硬件寄存器映射、协议头解析)
struct Flags {
unsigned int is_visible : 1; // 占 1 bit
unsigned int is_active : 1; // 占 1 bit
unsigned int priority : 3; // 占 3 bits (0-7)
unsigned int reserved : 3; // 占 3 bits
}; // 总共只占 1 个字节 (8 bits)
注意: 位域的内存布局(从高位到低位还是低位到高位)依赖于编译器和CPU大小端, 不具备跨平台可移植性
柔性数组 (Flexible Array Member)
C99 引入的特性, 允许结构体的最后一个成员是一个未知大小的数组, 常用于动态分配连续内存
struct Buffer {
int length;
char data[]; // 柔性数组, 不占用结构体本身的 sizeof
};
// 分配内存: 结构体大小 + 实际需要的数组大小
struct Buffer* buf = (struct Buffer*)malloc(sizeof(struct Buffer) + 100);
buf->length = 100;
buf->data[0] = 'A';
注: c++ 标准不支持柔性数组, 但 GCC/Clang 作为扩展支持零长数组 char data[0];
匿名结构体 / 匿名联合体
没有类型标签, 且直接作为另一个结构体的成员, 可以像访问外部结构体成员一样直接访问其内部成员
struct Vector3 {
union {
struct { float x, y, z; }; // 匿名结构体嵌套在匿名联合体中
struct { float r, g, b; };
float data[3];
};
};
Vector3 v;
v.x = 1.0f; // 直接访问
v.r = 1.0f; // 修改 r 会同时覆盖 x (联合体特性)
v.data[2] = 3.0f; // 修改 z/b