1. 结构体基础:从零理解struct
在C++编程中,结构体(struct)是一种将不同类型的数据组合成一个整体的复合数据类型。它允许我们将多个相关的变量打包成一个逻辑单元,这在处理复杂数据时特别有用。想象一下你要管理学生信息,每个学生有学号、姓名、成绩等多个属性,如果为每个属性都单独定义变量,代码会变得难以维护。而结构体就像是一个数据容器,把这些属性打包在一起。
结构体的基本定义语法如下:
cpp复制struct 结构体名 {
数据类型 成员1;
数据类型 成员2;
// 更多成员...
};
例如,定义一个表示学生的结构体:
cpp复制struct Student {
int id; // 学号
string name; // 姓名
float score; // 成绩
};
这里有几个关键点需要注意:
struct是定义结构体的关键字Student是这个结构体的类型名- 大括号内是结构体的成员列表
- 最后的分号不能省略
结构体定义完成后,就可以像使用基本数据类型一样声明结构体变量:
cpp复制Student stu1; // 声明一个Student类型的变量
提示:在C++中,struct定义后可以直接使用类型名声明变量,不需要像C语言那样在前面加上struct关键字。
2. 结构体成员的访问与初始化
2.1 访问结构体成员
结构体成员通过点运算符(.)来访问。继续上面的例子:
cpp复制stu1.id = 1001;
stu1.name = "张三";
stu1.score = 89.5;
我们也可以直接输出结构体成员的值:
cpp复制cout << "学号:" << stu1.id << endl;
cout << "姓名:" << stu1.name << endl;
cout << "成绩:" << stu1.score << endl;
2.2 结构体初始化
结构体有多种初始化方式:
- 声明时初始化:
cpp复制Student stu2 = {1002, "李四", 92.0};
- C++11之后的统一初始化语法:
cpp复制Student stu3 {1003, "王五", 85.5};
-
逐个成员初始化(如前所述)
-
使用构造函数初始化(后面会详细介绍)
注意:按顺序初始化时,提供的值必须与成员声明顺序一致,且数量不能超过成员数量。
2.3 结构体的大小与内存对齐
了解结构体在内存中的布局对编写高效代码很重要。结构体的大小不是简单等于各成员大小之和,因为存在内存对齐的问题。例如:
cpp复制struct Example {
char a; // 1字节
int b; // 4字节
short c; // 2字节
};
在32位系统上,这个结构体的大小可能是12字节而不是7字节,因为编译器会在成员之间插入填充字节以满足对齐要求。我们可以使用sizeof运算符查看结构体大小:
cpp复制cout << sizeof(Example) << endl; // 输出可能是12
3. 结构体数组的定义与使用
3.1 什么是结构体数组
结构体数组是指数组中的每个元素都是一个结构体变量。它结合了数组和结构体的特点,非常适合存储和管理一组相似结构的记录。
定义结构体数组的语法:
cpp复制结构体类型 数组名[数组长度];
例如,定义一个包含5个学生信息的数组:
cpp复制Student class1[5]; // 包含5个Student元素的数组
3.2 结构体数组的初始化
结构体数组可以在声明时初始化:
cpp复制Student class2[3] = {
{1001, "张三", 89.5},
{1002, "李四", 92.0},
{1003, "王五", 85.5}
};
也可以只初始化部分元素,剩余元素会被默认初始化:
cpp复制Student class3[5] = {
{1001, "张三", 89.5},
{1002, "李四", 92.0}
}; // 后3个元素的所有成员被初始化为0或空
3.3 访问结构体数组元素
访问结构体数组元素需要结合数组下标和结构体成员访问运算符:
cpp复制// 设置第二个学生的成绩
class1[1].score = 95.0;
// 输出第三个学生的信息
cout << "学号:" << class2[2].id << endl;
cout << "姓名:" << class2[2].name << endl;
cout << "成绩:" << class2[2].score << endl;
3.4 遍历结构体数组
我们可以用循环来遍历结构体数组:
cpp复制for(int i = 0; i < 3; i++) {
cout << "学生" << i+1 << ":" << class2[i].name
<< ",成绩:" << class2[i].score << endl;
}
或者使用C++11的范围for循环:
cpp复制for(const auto &student : class2) {
cout << "姓名:" << student.name
<< ",成绩:" << student.score << endl;
}
4. 结构体高级用法
4.1 结构体嵌套
结构体可以包含其他结构体作为成员,这称为结构体嵌套。例如:
cpp复制struct Date {
int year;
int month;
int day;
};
struct StudentEx {
int id;
string name;
Date birthday; // 嵌套Date结构体
float score;
};
访问嵌套结构体成员需要使用多个点运算符:
cpp复制StudentEx stu4;
stu4.birthday.year = 2000;
stu4.birthday.month = 6;
stu4.birthday.day = 15;
4.2 结构体与函数
结构体可以作为函数参数和返回值:
- 作为函数参数:
cpp复制void printStudent(const Student &s) {
cout << "学号:" << s.id << endl;
cout << "姓名:" << s.name << endl;
cout << "成绩:" << s.score << endl;
}
- 作为函数返回值:
cpp复制Student createStudent(int id, string name, float score) {
Student s;
s.id = id;
s.name = name;
s.score = score;
return s;
}
提示:传递大型结构体时,使用引用或指针比传值更高效,可以避免不必要的拷贝。
4.3 结构体中的构造函数
我们可以为结构体定义构造函数,提供更方便的初始化方式:
cpp复制struct Student {
int id;
string name;
float score;
// 构造函数
Student(int i, string n, float s)
: id(i), name(n), score(s) {}
};
这样创建结构体变量时可以这样写:
cpp复制Student stu5(1005, "赵六", 88.0);
5. 结构体数组的实际应用案例
5.1 学生成绩管理系统
下面是一个简单的学生成绩管理程序,演示了结构体数组的实际应用:
cpp复制#include <iostream>
#include <string>
using namespace std;
struct Student {
int id;
string name;
float score;
};
void inputStudents(Student arr[], int size) {
for(int i = 0; i < size; i++) {
cout << "输入第" << i+1 << "个学生的信息:" << endl;
cout << "学号:";
cin >> arr[i].id;
cout << "姓名:";
cin >> arr[i].name;
cout << "成绩:";
cin >> arr[i].score;
}
}
void printStudents(const Student arr[], int size) {
cout << "学生信息列表:" << endl;
for(int i = 0; i < size; i++) {
cout << "学号:" << arr[i].id
<< " 姓名:" << arr[i].name
<< " 成绩:" << arr[i].score << endl;
}
}
float averageScore(const Student arr[], int size) {
float sum = 0;
for(int i = 0; i < size; i++) {
sum += arr[i].score;
}
return sum / size;
}
int main() {
const int NUM = 3;
Student students[NUM];
inputStudents(students, NUM);
printStudents(students, NUM);
float avg = averageScore(students, NUM);
cout << "平均成绩:" << avg << endl;
return 0;
}
5.2 结构体数组排序
我们经常需要对结构体数组进行排序,例如按成绩从高到低排序:
cpp复制#include <algorithm> // 用于sort函数
bool compareByScore(const Student &a, const Student &b) {
return a.score > b.score; // 降序排列
}
void sortStudents(Student arr[], int size) {
sort(arr, arr + size, compareByScore);
}
然后在main函数中调用:
cpp复制sortStudents(students, NUM);
cout << "按成绩排序后的结果:" << endl;
printStudents(students, NUM);
6. 常见问题与解决方案
6.1 结构体与类的区别
在C++中,struct和class非常相似,主要区别在于默认访问权限:
- struct的成员默认是public的
- class的成员默认是private的
除此之外,它们都可以包含成员函数、构造函数、继承等。通常,struct用于主要包含数据的简单结构,而class用于更复杂的对象。
6.2 结构体数组越界问题
和普通数组一样,结构体数组也要注意不要越界访问。例如:
cpp复制Student arr[5];
arr[5].id = 100; // 错误,越界访问
越界访问会导致未定义行为,可能引发程序崩溃或数据损坏。可以使用std::array或std::vector等容器来避免这类问题。
6.3 结构体中的字符串处理
当结构体包含字符串时,要特别注意内存管理。使用std::string通常比C风格的字符数组更安全方便:
cpp复制struct SafeExample {
string name; // 推荐使用string
char address[100]; // 需要小心缓冲区溢出
};
6.4 结构体作为映射键
如果想用结构体作为std::map或std::unordered_map的键,需要定义比较操作或哈希函数。例如:
cpp复制struct Point {
int x, y;
// 定义小于运算符,用于std::map
bool operator<(const Point &other) const {
return x < other.x || (x == other.x && y < other.y);
}
};
对于std::unordered_map,需要特化std::hash:
cpp复制namespace std {
template<>
struct hash<Point> {
size_t operator()(const Point &p) const {
return hash<int>()(p.x) ^ hash<int>()(p.y);
}
};
}
7. 性能优化与最佳实践
7.1 减少结构体大小
对于包含大量结构体数组的程序,减少单个结构体的大小可以显著提高性能。方法包括:
- 按对齐要求合理安排成员顺序
- 使用位域(bit-field)压缩数据
- 使用较小的数据类型(如
int16_t代替int)
例如:
cpp复制struct OptimizedStudent {
int16_t id; // 2字节
char grade; // 1字节
float score; // 4字节
// 总共7字节,但实际可能是8字节(对齐到4字节边界)
};
7.2 避免不必要的拷贝
传递结构体时,尽量使用引用或指针:
cpp复制void processStudent(const Student &s); // 推荐
void processStudent(Student *s); // 也可以
void processStudent(Student s); // 不推荐,会有拷贝开销
对于结构体数组也是如此:
cpp复制void printAllStudents(const Student arr[], int size);
7.3 使用移动语义
对于包含动态资源的复杂结构体,可以实现移动构造函数和移动赋值运算符来提高效率:
cpp复制struct ResourceHolder {
int *data;
size_t size;
// 移动构造函数
ResourceHolder(ResourceHolder &&other)
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// 移动赋值运算符
ResourceHolder &operator=(ResourceHolder &&other) {
if(this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
~ResourceHolder() {
delete[] data;
}
};
7.4 结构体与多态
虽然struct可以像class一样支持继承和多态,但通常不建议这样做。如果需要多态行为,使用class更为合适。例如:
cpp复制// 不推荐的做法
struct Base {
virtual void foo() = 0;
};
struct Derived : Base {
void foo() override {}
};
// 推荐的做法
class Animal {
public:
virtual void speak() = 0;
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
};