刚入行的时候,我被问过一个特别基础的问题:“C 语言的结构体和 C++ 的结构体有什么区别?” 我当时的回答是“C++ 的结构体可以写函数”,面试官点了点头,但我后来发现这个回答只是冰山一角。C 语言的结构体是纯粹的数据容器,而 C++ 的结构体已经演变成了一个近乎完整的类,它和 class 唯一的差别只剩默认访问权限。如果只是在面试时背几个区别点,实际写代码很容易踩坑。这篇文章我会从语法、访问控制、内存布局、继承、初始化、工程实战这几个维度完整拆解 C 语言结构体和 C++ 结构体的差异,同时结合内存对齐、链表、联合体、cdev 结构体等实操场景,讲讲两种语言里结构体各自的正确打开方式。
1. 语法表面差异:struct 在 C++ 里变成了什么
很多人第一次从 C 转到 C++,最直观的感受就是 struct 的“能力”变大了,但能力的增长不是一步到位的,而是语言设计者在保留 C 兼容性的基础上逐步扩展出来的。这一章我们从最表面的语法差异开始聊,先建立一个整体认知。
1.1 声明习惯和使用方式的差别
在 C 语言里,声明一个结构体变量时,标准写法是带上 struct 关键字:
c复制struct Point {
int x;
int y;
};
struct Point p1; /* 标准写法 */
当然,你可以用 typedef 把它别名成普通类型名,这是很多 C 工程里最常见的做法:
c复制typedef struct Point {
int x;
int y;
} Point;
Point p1; /* 省略关键字 */
这本质上是一个语法糖,C 语言并没有因为 typedef 而改变 struct 的底层语义,它仍然只是一个“数据打包”工具。而到了 C++,struct 一旦定义完成,类型名本身就进入了普通标识符空间,不需要再添加关键字:
cpp复制struct Point {
int x;
int y;
};
Point p1; // 直接使用
struct Point p2; // 依然兼容,但没人这么写
这里涉及一个 C 语言老手必须注意的点:C 语言里 struct 标签和普通 typedef 名称分属不同的命名空间,所以下面的代码是合法的:
c复制typedef struct Node Node;
struct Node {
int data;
Node* next;
};
这里的 Node* 其实是 typedef 出来的别名,而 struct Node 是标签。如果你在 C++ 里写类似代码,编译器对命名空间的解析规则不同,很容易出现“意想不到的重定义”问题。
1.2 成员函数、构造函数、析构函数与运算符重载
C 语言的结构体只能有数据成员,这是硬件级别的限制,因为 C 编译器只把 struct 看作一块连续内存的“视图”。C++ 则允许在结构体内部定义成员函数,这是对 C 结构体能力最直观的扩展:
cpp复制struct Point {
int x;
int y;
double length() const {
return sqrt(x * x + y * y);
}
};
更进一步,C++ 结构体可以有构造函数、析构函数、拷贝构造、赋值运算符重载。这意味着结构体不再只是被动地存储数据,它拥有了管理自身生命周期的能力。举一个实际场景:你在 C 语言里如果需要一个自动初始化的结构体,通常要写一个初始化函数:
c复制typedef struct Point {
int x;
int y;
} Point;
void point_init(Point* p, int x, int y) {
p->x = x;
p->y = y;
}
每次创建变量都要手动调用 point_init(&p, 1, 2);,一旦忘记调用,结构体里就是随机值。而 C++ 里构造逻辑直接绑定在类型上:
cpp复制struct Point {
int x;
int y;
Point(int a, int b) : x(a), y(b) {}
};
创建对象时编译器保证构造函数必然被调用,这个“必然性”是 C 语言无法提供给结构体的。
1.3 空结构体的大小
C 语言标准没有明确规定空结构体的大小,不同的编译器行为不一致,GNU C 里空结构体大小为 0,而某些嵌入式编译器可能报错或按 1 字节处理。C++ 标准则明确规定,空结构体(没有成员、没有基类)的 sizeof 至少为 1,目的很简单:让每个对象都有独一无二的地址。
这个差异在 C/C++ 混合编程时特别容易踩坑。假设你在 C 侧定义了一个空结构体做标记,然后用 C++ 侧的头文件复用同一份定义,如果两边编译器给出的 sizeof 不一致,整个结构体数组的内存布局就会错位。实际工程里,碰到这种问题我会尽量在结构体里保留一个占位成员,比如 int _reserved;,既对齐了内存,也避免了跨语言语义不一致。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 访问控制与封装:C 的裸数据和 C++ 的隐藏逻辑
很多程序员认为访问控制只是语言层面的“语法装饰”,这种观点在 C 语言里勉强成立,因为 C 的 struct 确实没有访问控制。但 C++ 结构体的访问控制直接影响了你怎么设计接口、怎么写测试、怎么防止用户误用,它是工程稳健性的基石。
2.1 C 结构体全员公开,靠命名约定维持秩序
C 语言里,结构体的所有成员都是公开的,没有任何机制能阻止用户直接改字段:
c复制typedef struct Account {
double balance;
} Account;
Account a;
a.balance = -100; /* 没有任何拦截 */
为了避免这种问题,C 项目通常靠两招:一是在头文件里只暴露类型和不透明指针,二是在结构体成员命名上使用约定的前缀,暗示“这个字段别乱动”。比如 Linux 内核里大量结构体都有 __ 前缀的字段,比如 __state、__revision,就是在告诉开发者:这个字段是内部实现细节,不要直接访问。
不透明指针是 C 语言模拟封装的最经典手段。例如你在头文件里写:
c复制typedef struct Context Context;
Context* ctx_create(void);
void ctx_destroy(Context* ctx);
调用方只知道 Context 存在,但不知道里面有什么,所有字段操作都通
