c/c++ 结构体

 

定义

结构体(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++结构体成员也默认公有, 但可使用访问控制(如privateprotected)

使用

初始化

聚合初始化 (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 访问内存的效率, 编译器会进行内存对齐

对齐规则

  1. 首地址对齐: 结构体的首地址必须是其最宽基本类型成员大小的整数倍

  2. 成员偏移对齐: 每个成员相对于结构体首地址的偏移量, 必须是该成员自身大小(或编译器设定的对齐系数, 取两者较小值)的整数倍

  3. 整体大小对齐: 结构体的总大小必须是其最宽基本类型成员大小的整数倍

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