
C++ 结构体(struct)
- 一、结构体的核心背景与用途
-
-
- [// 糟糕的方式:变量分散,无关联性](#// 糟糕的方式:变量分散,无关联性)
- [1.2 结构体与其他类型的区别](#1.2 结构体与其他类型的区别)
-
- 二、结构体的基础用法
-
- [2.1 结构体的定义与声明](#2.1 结构体的定义与声明)
- [2.2 结构体变量的创建与初始化](#2.2 结构体变量的创建与初始化)
-
- (1)默认初始化(成员变量为默认值)
- [(2)聚合初始化(C++11 前 / 后)](#(2)聚合初始化(C++11 前 / 后))
-
- [// 方式1:按成员顺序初始化(C++11 前)](#// 方式1:按成员顺序初始化(C++11 前))
- [// 方式2:列表初始化(C++11,推荐,可省略等号)](#// 方式2:列表初始化(C++11,推荐,可省略等号))
- [// 方式3:部分初始化(未指定的成员为默认值)](#// 方式3:部分初始化(未指定的成员为默认值))
- (3)指针方式创建
- [2.3 结构体的访问与使用](#2.3 结构体的访问与使用)
- 三、结构体的内存布局
-
- [3.1 基本内存布局(无对齐)](#3.1 基本内存布局(无对齐))
- [3.2 内存对齐(核心!)](#3.2 内存对齐(核心!))
- [3.3 手动控制对齐(#pragma pack)](#pragma pack))
- [四、结构体的高级特性(C++ 扩展)](#四、结构体的高级特性(C++ 扩展))
-
- [4.1 结构体中定义成员函数](#4.1 结构体中定义成员函数)
- [4.2 构造函数与默认构造函数](#4.2 构造函数与默认构造函数)
- [4.3 结构体的继承](#4.3 结构体的继承)
- [4.4 结构体与 STL 容器](#4.4 结构体与 STL 容器)
- 五、结构体的常见应用场景
-
- [5.1 封装实体数据(最常用)](#5.1 封装实体数据(最常用))
- [5.2 函数参数 / 返回值](#5.2 函数参数 / 返回值)
- [5.3 联合使用(union)与位域](#5.3 联合使用(union)与位域)
- [5.4 与指针 / 数组结合](#5.4 与指针 / 数组结合)
- 六、常见错误与最佳实践
-
- [6.1 常见错误](#6.1 常见错误)
- [6.2 最佳实践](#6.2 最佳实践)
- 七、结构体与类的对比
- 八、总结
结构体(struct)是 C++ 中用于封装不同类型数据的自定义复合类型,是面向过程编程中组织数据的核心工具,也是 C++ 面向对象特性的基础(可包含成员函数、继承等)。本文将从结构体的基础定义、内存布局、核心特性到高级用法,全面解析 C++ 结构体的设计与实战技巧。
一、结构体的核心背景与用途
1.1 问题引入:单一类型的局限性
C++ 内置类型(int、char、double 等)仅能表示单一数据,而实际场景中常需将关联的不同类型数据组合(如学生信息包含姓名、年龄、成绩)。
例如:若用单独变量存储学生信息,代码冗余且关联性差:
cpp
运行
// 糟糕的方式:变量分散,无关联性
string stu_name = "Alice";
int stu_age = 18;
double stu_score = 92.5;
结构体的核心价值:将关联数据封装为一个整体,提升代码的可读性、可维护性。
1.2 结构体与其他类型的区别
类型 核心特性 适用场景
结构体(struct) 可包含不同类型数据 / 成员函数,默认访问权限 public 封装关联数据(如实体信息)
类(class) 与结构体几乎一致,默认访问权限 private 面向对象编程(封装 / 继承)
数组 仅存储相同类型数据,固定大小 同类型数据的有序集合
二、结构体的基础用法
2.1 结构体的定义与声明
语法:
cpp
运行
// 定义结构体类型(自定义类型)
struct 结构体名 {
// 成员变量(数据成员):类型 + 名称
类型1 成员名1;
类型2 成员名2;
// ... 更多成员
}; // 注意末尾分号!
示例:定义学生结构体
cpp
运行
#include
#include
using namespace std;
// 定义结构体类型:Student(自定义类型)
struct Student {
// 成员变量(不同类型)
string name; // 姓名
int age; // 年龄
double score; // 成绩
}; // 分号不可省略
2.2 结构体变量的创建与初始化
结构体变量的创建有多种方式,核心是为成员变量赋值:
(1)默认初始化(成员变量为默认值)
cpp
运行
// 创建结构体变量(默认初始化:string为空串,int/double为随机值)
Student stu1;
// 赋值:通过 . 访问成员变量
stu1.name = "Bob";
stu1.age = 19;
stu1.score = 88.0;
(2)聚合初始化(C++11 前 / 后)
cpp
运行
// 方式1:按成员顺序初始化(C++11 前)
Student stu2 = {"Alice", 18, 92.5};
// 方式2:列表初始化(C++11,推荐,可省略等号)
Student stu3{"Charlie", 20, 79.5};
// 方式3:部分初始化(未指定的成员为默认值)
Student stu4 = {"David"}; // name="David",age=0,score=0.0
(3)指针方式创建
cpp
运行
// 动态分配结构体(堆内存)
Student* stu_ptr = new Student{"Ella", 17, 95.0};
// 指针访问成员:-> 运算符
cout << stu_ptr->name << endl; // Ella
cout << stu_ptr->age << endl; // 17
// 释放堆内存
delete stu_ptr;
stu_ptr = nullptr;
2.3 结构体的访问与使用
普通变量:通过 . 运算符访问成员。
指针变量:通过 -> 运算符访问成员(等价于 (*指针).成员)。
完整示例:
cpp
运行
int main() {
Student stu = {"Alice", 18, 92.5};
// 访问成员
cout << "姓名:" << stu.name << endl;
cout << "年龄:" << stu.age << endl;
cout << "成绩:" << stu.score << endl;
// 修改成员
stu.score = 94.0;
cout << "修改后成绩:" << stu.score << endl;
// 指针访问
Student* p = &stu;
cout << "指针访问姓名:" << p->name << endl; // 等价于 (*p).name
return 0;
}
三、结构体的内存布局
结构体的成员在内存中连续存储,但可能因 内存对齐 产生 "空洞"(padding),这是理解结构体内存占用的关键。
3.1 基本内存布局(无对齐)
若成员类型的大小为 1/2/4/8 字节,且无对齐优化,内存连续:
cpp
运行
// 示例:无对齐的内存布局(假设地址从 0x1000 开始)
struct Test {
char a; // 1字节:0x1000
int b; // 4字节:0x1001 ~ 0x1004
double c; // 8字节:0x1005 ~ 0x100C
};
3.2 内存对齐(核心!)
为提升 CPU 访问效率,编译器会自动对结构体成员进行内存对齐(默认按 "最大成员大小" 对齐)。
示例:对齐后的内存布局
cpp
运行
struct Test {
char a; // 1字节:0x1000,后面填充3字节(0x1001~0x1003)
int b; // 4字节:0x1004 ~ 0x1007
double c; // 8字节:0x1008 ~ 0x100F
};
// 总大小:1 + 3(padding) + 4 + 8 = 16 字节(而非 13 字节)
验证内存大小:
cpp
运行
cout << sizeof(Test) << endl; // 输出 16(而非 1+4+8=13)
3.3 手动控制对齐(#pragma pack)
可通过编译器指令 #pragma pack 强制指定对齐大小:
cpp
运行
#pragma pack(1) // 按1字节对齐(取消填充)
struct Test {
char a;
int b;
double c;
};
#pragma pack() // 恢复默认对齐
cout << sizeof(Test) << endl; // 输出 13(1+4+8)
注意:手动取消对齐可能降低访问效率,仅在需要紧凑存储(如网络传输、文件存储)时使用。
四、结构体的高级特性(C++ 扩展)
C++ 对 C 语言的结构体进行了大幅扩展,使其支持成员函数、构造函数、继承等面向对象特性。
4.1 结构体中定义成员函数
结构体可包含成员函数(包括普通函数、构造函数、析构函数),行为与类完全一致:
cpp
运行
struct Student {
// 成员变量
string name;
int age;
double score;
// 普通成员函数:打印信息
void printInfo() {
cout << "姓名:" << name << ",年龄:" << age << ",成绩:" << score << endl;
}
// 构造函数:初始化成员(替代手动赋值)
Student(string n, int a, double s) : name(n), age(a), score(s) {}
// 析构函数(可选,用于释放资源)
~Student() {}
};
// 使用构造函数创建对象
int main() {
Student stu("Alice", 18, 92.5);
stu.printInfo(); // 调用成员函数
return 0;
}
4.2 构造函数与默认构造函数
构造函数:与结构体同名,无返回值,用于初始化成员变量。
默认构造函数:若未自定义构造函数,编译器会自动生成(无参数,成员变量默认初始化);若自定义构造函数,默认构造函数会被覆盖(需手动声明)。
cpp
运行
struct Student {
string name;
int age;
// 自定义构造函数
Student(string n) : name(n) {}
// 手动声明默认构造函数(C++11)
Student() = default;
};
// 合法:调用默认构造函数
Student stu1;
// 合法:调用自定义构造函数
Student stu2("Bob");
4.3 结构体的继承
C++ 结构体支持继承(默认 public 继承,区别于类的 private 继承):
cpp
运行
// 基结构体
struct Person {
string name;
int age;
};
// 派生结构体:继承 Person 的成员
struct Student : Person {
double score; // 新增成员
// 构造函数:调用基结构体构造函数
Student(string n, int a, double s) : Person{n, a}, score(s) {}
};
int main() {
Student stu("Charlie", 20, 85.0);
cout << stu.name << endl; // 继承的成员
cout << stu.score << endl; // 新增成员
return 0;
}
4.4 结构体与 STL 容器
结构体可作为 STL 容器的元素(需满足可拷贝 / 可赋值):
cpp
运行
#include
int main() {
// 存储结构体的vector
vector stu_list;
stu_list.emplace_back("Alice", 18, 92.5); // 直接构造
stu_list.emplace_back("Bob", 19, 88.0);
// 遍历容器
for (const auto& stu : stu_list) {
stu.printInfo();
}
return 0;
}
五、结构体的常见应用场景
5.1 封装实体数据(最常用)
用于表示具有多个属性的实体(如学生、商品、坐标):
cpp
运行
// 表示二维坐标
struct Point {
int x;
int y;
// 成员函数:计算距离原点的平方
int distanceSq() {
return x*x + y*y;
}
};
5.2 函数参数 / 返回值
将多个关联数据打包为结构体,简化函数参数 / 返回值:
cpp
运行
// 函数返回多个值(通过结构体)
struct Result {
int sum;
int max_val;
int min_val;
};
Result calcArray(const vector& arr) {
Result res{0, arr[0], arr[0]};
for (int num : arr) {
res.sum += num;
res.max_val = max(res.max_val, num);
res.min_val = min(res.min_val, num);
}
return res;
}
5.3 联合使用(union)与位域
结构体可包含 union(共用体)或位域,用于紧凑存储:
cpp
运行
// 位域:用二进制位存储数据(节省空间)
struct Flags {
unsigned int is_valid : 1; // 1位:是否有效
unsigned int is_admin : 1; // 1位:是否管理员
unsigned int level : 3; // 3位:等级(0~7)
};
int main() {
Flags f;
f.is_valid = 1;
f.is_admin = 0;
f.level = 5;
cout << sizeof(f) << endl; // 输出 4(int 大小)
return 0;
}
5.4 与指针 / 数组结合
结构体数组常用于批量存储同类型实体:
cpp
运行
// 结构体数组
Student stu_arr[3] = {
{"Alice", 18, 92.5},
{"Bob", 19, 88.0},
{"Charlie", 20, 85.0}
};
// 遍历数组
for (int i = 0; i < 3; ++i) {
stu_arr[i].printInfo();
}
六、常见错误与最佳实践
6.1 常见错误
遗漏末尾分号结构体定义末尾的分号是语法要求,遗漏会导致编译错误:
cpp
运行
struct Test { int a; } // 错误:无分号
struct Test { int a; }; // 正确
内存对齐误解误以为结构体大小等于成员大小之和,忽略编译器的自动填充:
cpp
运行
struct Test { char a; int b; };
cout << sizeof(Test); // 输出 8(而非 5)
指针访问错误对结构体指针误用 . 运算符(应使用 ->):
cpp
运行
Student* p = new Student();
p.name = "Alice"; // 错误!应为 p->name
(*p).name = "Alice"; // 正确(等价于 p->name)
构造函数覆盖默认构造自定义构造函数后,无法直接创建无参对象(需手动声明默认构造):
cpp
运行
struct Student {
Student(string n) : name(n) {}
string name;
};
Student stu; // 错误!无默认构造函数
6.2 最佳实践
命名规范结构体名采用 帕斯卡命名法(首字母大写,如 Student),成员变量采用小驼峰(如 stuName)或下划线(如 stu_name)。
优先使用构造函数初始化避免手动赋值,通过构造函数确保成员变量被正确初始化:
cpp
运行
// 推荐
Student stu("Alice", 18, 92.5);
// 不推荐(易遗漏赋值)
Student stu;
stu.name = "Alice";
const 修饰只读成员函数不修改成员变量的成员函数,应加 const 修饰,提升代码安全性:
cpp
运行
struct Student {
void printInfo() const { // const 成员函数
cout << name << endl; // 仅读取成员,不修改
}
};
避免过大的结构体结构体若包含大量成员(如数十个),可拆分为多个小结构体(按功能分类),提升可读性。
合理控制内存对齐仅在需要紧凑存储时使用 #pragma pack,否则保持默认对齐(兼顾效率)。
七、结构体与类的对比
C++ 中结构体(struct)和类(class)的唯一核心区别是 默认访问权限:
struct:成员默认 public(包括继承)。
class:成员默认 private(包括继承)。
其余特性(构造函数、析构函数、继承、多态等)完全一致。
选择建议:
若仅封装数据(无复杂逻辑),用 struct(更符合直觉)。
若需封装数据 + 逻辑,且强调封装性(隐藏实现),用 class。
八、总结
结构体是 C++ 中封装复合数据的核心工具,其核心特性可总结为:
基础特性:封装不同类型数据,内存连续存储(可能有对齐填充)。
C++ 扩展:支持成员函数、构造函数、继承、const 修饰等面向对象特性。
核心优势:将关联数据整合为整体,提升代码的可读性和可维护性。
掌握结构体的关键:
理解内存布局与对齐规则(避免内存计算错误)。
熟练使用构造函数初始化成员(避免未初始化问题)。
区分结构体与类的细微差异(默认访问权限)。
结构体是连接 C 语言 "面向过程" 和 C++ "面向对象" 的桥梁,是编写高效、清晰代码的基础工具。