C/C++ 中的 struct:从 C 到 C++ 的进化

C/C++ 中的 struct:从 C 到 C++ 的进化

struct 就是用来把一堆数据打包到一起的东西。C 和 C++ 里它长不一样,一个一个说。

1. C 语言中的 struct------纯粹的数据打包工具

C 语言没有 class,没有面向对象那套东西。struct 就是 把多个不同类型的变量组合成一个整体 的自定义数据类型。

1.1 基本语法

c 复制代码
struct 结构体名 {
    类型 成员1;
    类型 成员2;
    // ...
};   // 分号不能少

举个例子:

c 复制代码
struct Student {
    char name[20];
    int age;
    float score;
};

1.2 定义变量

C 语言里定义结构体变量时,struct 关键字不能省

c 复制代码
struct Student s1;  // 必须带 struct

嫌麻烦可以用 typedef 重命名:

c 复制代码
typedef struct Student {
    char name[20];
    int age;
    float score;
} Stu;

Stu s1;  // 现在不用写 struct 了

1.3 初始化

按成员顺序依次赋值:

c 复制代码
struct Student s1 = {"张三", 20, 90.5};

1.4 访问与赋值

. 操作符:

c 复制代码
s1.age = 21;

相同类型的结构体变量可以整体赋值:

c 复制代码
struct Student s2 = s1;  // 整体拷贝

1.5 传参

传值 :拷贝整个结构体,浪费空间和时间。

传址:传指针,只传 4/8 个字节,更快。

c 复制代码
void printStudent(const struct Student* s) {
    printf("%s, %d\n", s->name, s->age);
}

所以结构体传参最好传指针

1.6 C 语言 struct 的局限

  • 不能有成员函数,只能放函数指针
  • 没有访问控制,所有成员对外可见
  • 没有继承,没有多态
  • 不能有静态成员

2. C++ 中的 struct------升级版

C++ 的 struct 是 C 的 struct超集。几乎所有合法的 C 结构体在 C++ 里也合法。

C++ 的 structclass 几乎一样,唯一区别是默认访问权限

特性 struct class
默认成员访问权限 public private
默认继承方式 public 继承 private 继承

2.1 可以有成员函数

cpp 复制代码
struct Student {
    char name[20];
    int age;
    
    void print() {
        cout << name << ", " << age << endl;
    }
};

2.2 可以有构造函数和析构函数

cpp 复制代码
struct Student {
    string name;
    int age;
    
    Student(const string& n, int a) : name(n), age(a) {}
    ~Student() { /* 清理资源 */ }
};

2.3 可以有访问控制

cpp 复制代码
struct Student {
private:
    int id;          // private,外部不能访问
public:
    string name;
    int age;
};

2.4 可以继承

cpp 复制代码
struct Person {
    string name;
};

struct Student : Person {   // 默认 public 继承
    int grade;
};

2.5 定义变量不用写 struct

C++ 里直接用结构体名:

cpp 复制代码
Student s1;  // 不用写 struct

2.6 空结构体

C++ 允许空结构体,大小为 1 字节:

cpp 复制代码
struct Empty {};
sizeof(Empty);  // 1

3. 内存对齐------为什么 sizeof 比你算的大

这是 C 和 C++ 里最容易踩的坑。

3.1 什么是对齐

CPU 读取内存不是逐字节读的,而是按"块"读的(32 位系统一次 4 字节,64 位一次 8 字节)。

如果数据没对齐到合适的边界,CPU 可能需要两次读取再加拼接,效率大打折扣。有些 ARM 平台甚至直接崩溃。

所以编译器会在成员之间插入空白字节(padding),用空间换时间。

3.2 对齐规则

规则一:第一个成员放在偏移量为 0 的位置。

规则二 :其他成员的起始偏移量必须是其自身大小的整数倍

规则三 :结构体总大小必须是最宽成员大小的整数倍

看例子:

c 复制代码
struct Test {
    char a;   // 1 字节
    int b;    // 4 字节
    char c;   // 1 字节
};

直觉:1 + 4 + 1 = 6 字节。

实际 sizeof(struct Test) 通常是 12 字节

手动算一遍:

步骤 操作 偏移量
1 char a 0
2 int b,对齐要求 4,跳到偏移 4 4~7
3 char c 8
4 结构体总大小必须是最大成员(int,4)的整数倍 8→12(补 3 字节)

布局:[a][_][_][_][b][b][b][b][c][_][_][_]

3.3 优化技巧:大类型放前面

同样的成员,换个顺序能省空间:

c 复制代码
struct Bad {
    char a;   // 偏移 0
    int b;    // 偏移 4~7
    char c;   // 偏移 8,总大小 12
};

struct Good {
    int b;    // 偏移 0~3
    char a;   // 偏移 4
    char c;   // 偏移 5,总大小 8(6 对齐到 8)
};

把大的成员放前面,小的放后面,能减少填充。

3.4 #pragma pack------强行改对齐

#pragma pack(n) 可以控制对齐:

c 复制代码
#pragma pack(1)  // 1 字节对齐,取消所有填充
struct Packed {
    char a;
    int b;
    char c;
};  // 大小 = 1+4+1 = 6
#pragma pack()   // 恢复默认

注意#pragma pack() 直接恢复默认,不保存旧值,可能污染后续代码。安全写法是用 push/pop

c 复制代码
#pragma pack(push, 1)
struct Protocol {
    char head;
    int len;
    short cmd;
};  // 大小 1+4+2=7
#pragma pack(pop)

使用场景

  • 日常开发:默认对齐就行
  • 网络报文、串口通信、驱动、文件存储:必须 pack(1) 精确控制布局

4. 位段(位域)------按 bit 分配

位段让你按二进制位分配空间,而不是按字节。

c 复制代码
struct Flags {
    unsigned int flag1 : 1;   // 占 1 bit
    unsigned int flag2 : 1;   // 占 1 bit
    unsigned int flag3 : 3;   // 占 3 bits
    unsigned int data   : 8;  // 占 8 bits
};

适合节省内存的场景,比如协议头、状态标志。

注意:位段的跨平台性不好,不同编译器对位段的内存布局处理不一样。嵌入式里用得比较多。


5. 柔性数组------C99 的变长数组

C99 引入的特性:结构体的最后一个成员 可以是一个没有指定大小的数组

c 复制代码
struct flex {
    int len;
    int data[];   // 柔性数组成员,不占结构体大小
};

5.1 特点

  • 柔性数组前面必须至少有一个其他成员
  • sizeof 不包含柔性数组的大小
  • 需要用 malloc 动态分配额外空间
c 复制代码
struct flex* p = malloc(sizeof(struct flex) + 10 * sizeof(int));
p->len = 10;
for (int i = 0; i < 10; i++) {
    p->data[i] = i;
}

5.2 为什么用柔性数组而不是指针

对比两种方案:

方案一:柔性数组

c 复制代码
struct S {
    int n;
    int arr[];
};
// 一次 malloc,一次 free

方案二:指针

c 复制代码
struct S {
    int n;
    int* arr;
};
// 两次 malloc,两次 free

柔性数组的好处:

  1. 内存释放更方便 :一次 free 全释放,不容易漏
  2. 访问速度更快:数据在结构体后面连续存放,缓存友好

6. 完整示例

C 语言版本------学生管理系统(纯数据)

c 复制代码
#include <stdio.h>
#include <string.h>

typedef struct Student {
    char name[20];
    int age;
    float score;
} Student;

// 传指针,省拷贝
void printStudent(const Student* s) {
    printf("姓名: %s, 年龄: %d, 成绩: %.1f\n", s->name, s->age, s->score);
}

int main() {
    Student s1 = {"张三", 20, 90.5};
    Student s2 = s1;  // 整体赋值
    
    s2.age = 21;
    strcpy(s2.name, "李四");
    
    printStudent(&s1);
    printStudent(&s2);
    
    // 内存对齐演示
    struct Test {
        char a;
        int b;
        char c;
    };
    printf("sizeof(Test) = %zu\n", sizeof(struct Test));  // 通常是 12
    
    return 0;
}

C++ 版本------带成员函数和构造

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Student {
private:
    int id;          // private,外部不能直接访问
    
public:
    string name;
    int age;
    vector<float> scores;  // C++ 可以用 STL
    
    // 构造函数
    Student(const string& n, int a, int id) : name(n), age(a), id(id) {}
    
    // 成员函数
    void addScore(float s) {
        scores.push_back(s);
    }
    
    float getAverage() const {
        if (scores.empty()) return 0;
        float sum = 0;
        for (float s : scores) sum += s;
        return sum / scores.size();
    }
    
    void print() const {
        cout << "ID: " << id << ", 姓名: " << name << ", 年龄: " << age << endl;
        cout << "平均分: " << getAverage() << endl;
    }
};

// 继承
struct GraduateStudent : Student {  // 默认 public 继承
    string researchTopic;
    
    GraduateStudent(const string& n, int a, int id, const string& topic)
        : Student(n, a, id), researchTopic(topic) {}
};

int main() {
    Student s1("张三", 20, 1001);
    s1.addScore(85.5);
    s1.addScore(92.0);
    s1.addScore(78.5);
    s1.print();
    
    GraduateStudent gs("李四", 25, 2001, "人工智能");
    gs.addScore(95.0);
    gs.print();
    cout << "研究方向: " << gs.researchTopic << endl;
    
    // C++ struct 和 class 的唯一区别:默认 public
    struct Point { int x, y; };  // 默认 public
    class Point2 { int x, y; };  // 默认 private,编译报错
    
    return 0;
}

7. 总结与建议

场景 用什么
纯 C 项目,只打包数据 struct
C++ 项目,简单的 POD 类型(Plain Old Data) struct
C++ 项目,有复杂行为、需要封装 class
需要和 C 代码交互 struct(两边兼容)
网络协议、文件格式解析 struct + #pragma pack(1)

一句话总结

  • C 语言struct 就是数据容器,简单直接
  • C++ 语言struct 约等于 class,区别只是默认 public vs private
  • 内存对齐是编译器干的活,但你知道规则才能写出内存紧凑的代码
  • 柔性数组是 C 语言里处理变长数据的利器,比指针方案更优雅
相关推荐
浩浩kids15 小时前
C•exercises
c语言
春生野草16 小时前
个人笔记——C语言字符串、树
c语言·开发语言·笔记
ysu_031417 小时前
用Vibe-Coding从零实现俄罗斯方块
c语言·游戏程序
一只旭宝17 小时前
C++手写shared_ptr共享智能指针|原子引用计数、强弱引用控制块、赋值重载底层深度剖析
开发语言·c++·面试
开发者联盟league18 小时前
Java 通过 JNA 调用 C++ DLL:关键流程总结
java·c++·jna
库克克19 小时前
【C++】智能指针
java·开发语言·c++
一拳一个呆瓜21 小时前
【STL】iostream 编程:输入流成员函数
c++·stl
冻柠檬飞冰走茶21 小时前
PTA基础编程题目集 7-24 约分最简分式(C语言实现)
c语言·开发语言·数据结构·算法
xiaoye-duck21 小时前
《Linux系统编程》Linux 系统多线程(七): C++ 线程安全日志系统封装:基于策略模式解耦,兼容 glog 使用风格
linux·c++·日志系统
weixin_585951171 天前
CH592F 2.4G 低功耗
c语言·开发语言