结构体
by.Qin3Yu
特别声明:本文为了尽可能使用简单描述,以求简单明了,可能部分专有名词定义不准确。
本文将不涉及具体案例,若您需要阅读具体案例,可以点击以下链接。
涉及结构体的具体项目案例可以参考我的往期博文:
【C++数据结构 | 单链表速通】使用单链表完成数据的输入和返回元素之和.by.Qin3Yu
【C++数据结构 | 顺序表速通】使用顺序表完成简单的成绩管理系统.by.Qin3Yu
【C++数据结构 | 队列速通】图解BFS算法走迷宫最短路径问题.by.Qin3Yu
【算法详解 | DFS算法】深度优先搜索解走迷宫问题 | 深度优先图遍历.by.Qin3Yu
文中所有代码默认已使用std命名空间:
using namespace std;
针对文章示例代码,我们还要导入以下头文件:
#include <string>
概念速览
什么是结构体?
-
结构体(Struct) 是一种自定义的数据类型,它允许您将不同的数据类型组合在一起,形成一个新的复合数据类型。
-
结构体由一组具有不同数据类型的成员变量组成,每个成员变量可以是基本数据类型(如整数、浮点数等)或其他结构体类型。通过结构体,您可以将相关的数据打包到一个变量中,使其更加组织化和易于操作。您可以定义结构体,指定每个成员变量的名称和类型,并在需要时实例化结构体对象。
-
结构体可以用于创建更复杂的数据结构,例如链表、树、图等。它还可以用于表示复合实体对象,如图书(编号、价格、索引)、学生(学号、姓名、班级、成绩)、汽车(发动机、轮胎、车架)等。
优势:
- 组织性: 将多个不同类型的数据组合在一起,形成逻辑相关的数据单元,提高数据的组织性和可读性。
- 灵活性: 创建自定义的数据类型,使得代码更加模块化和可维护,提高代码的灵活性和复用性。
- 传递性: 结构体可以作为参数传递给函数或方法,便于在不同的代码模块之间进行数据交换和共享。
缺点:
- 内存占用: 每个实例都会占用一定的内存,当结构体成员变量较多时会导致内存消耗较大。
- 复制开销: 结构体进行传递时通常会进行值拷贝,可能会带来大量的性能开销。
- 可变限制: 结构体是值类型,如果需要在函数内部修改结构体的成员变量,需要使用指针或引用等方式。
基础结构体操作
1. 定义结构体( struct )
- 我们可以直接使用struct关键字定义一个结构体。结构体与普通的数据类型相同,可以根据需求定义在函数内或函数外。在下面这段代码中,我们定义一个学生(Student) 结构体,其成员变量有姓名(name)、年龄(old)、性别(sex)、学号(ID)、绩点(score):
struct Student {
string name;
int age;
char sex; // 为简化代码,我们用字符M表示男,W表示女
string ID;
float score;
};
- 注意:结构体也算一个语句,所以大括号外要加上分号!
2. 结构体实例化
- 现在我们已经有了一个Student结构体,我们可以将其看作一个Student数据类型。现在,我们要实例化出一个Student。所谓实例化,即创造一个此变量类型的变量(如根据带有汽车组装步骤图纸组装出一辆汽车),在下面这段代码中,我们实例化出两个Studnt变量:
Student XiaoMing = {"小明", 18, 'M', "000001", 4.26f};
Student XiaoHong = {"小红", 18, 'W', "000002", 4.31f};
- 在上面的代码中,我们实例化出了一个名为XiaoMing的Student变量,他的姓名、年龄等分别为大括号中的值。与其他数据类型一样,结构体也需要一个变量名,在代码中,为了方便访问变量,我们使用姓名的拼音作为变量名。
3. 成员变量的访问和修改
- 在访问成员变量时,我们可以直接使用 变量名.成员变量 的方式:
cout << "小明的年龄为:" << XiaoMing.age; // 输出:18
cout << "小红的学号为:" << XiaoHong.ID; // 输出:000002
- 若要修改成员变量,也能使用相同的方式:
cout << "小明的绩点为:" << XiaoMing.score; // 输出:4.26
XiaoMing.score = 4.28; // 将XiaoMing的score属性修改为4.28
cout << "小明的绩点为:" << XiaoMing.score; // 输出:4.28
4. 构造函数
- 在上文实例化的代码中,我们根据结构体内的成员变量传入了相对应的参数。那么如果我们需要大量的给不同的成员的同一个成员变量传入同一个值(如所有学生的年龄都为18),那么我们就能给结构体中的成员变量设置一个默认值,从而省去了传入部分参数的过程:
struct Student {
......
// 默认构造函数
Student() : name("Unknown"), age(18), sex('x'), ID("000000"), score(1.00f) {}
};
- 在此代码中,我们默认在不传入参数的情况下学生的信息如下:
属性 | 值 |
---|---|
name | Unknown |
age | 18 |
sex | x |
ID | 000000 |
score | 1.00 |
- 然后我们便可以通过默认的方式实例化出结构体成员:
Student XiaoGang; // 其成员变量的值如上表所示
- 但这明显不是我们想要的结果,因为我们只是想省去传入age的参数,而此方法将所有成员属性都默认了。因此,我们需要写入一个仅不接受age参数的构造函数,如下面的代码所示,我们在构造函数的括号中填入要接受的参数,在冒号后填入给成员属性赋值的方法。构造函数将会根据方法中每个成员属性括号中的值给对应的属性赋值:
struct Student {
......
// 默认构造函数
Student() : name("Unknown"), age(18), sex('x'), ID("000000"), score(1.00f) {}
// 接受除age外所有参数的构造函数
Student(const string& n, int a, char s, string& i, float s) : //需要传入的参数
name(n), age(18), sex(s), ID(i), score(s) {} //给成员属性赋值
};
- 然后我们便可以通过此构造函数实例化出结构体成员,在下面的代码中,我们将本该填写age的地方空出,程序将会根据构造函数自动填充默认值为18:
Student XiaoGang = {"小刚", , 'M', "000003", 4.08f};
cout << "小明的年龄为:" << XiaoMing.age; // 输出:18
- 根据此方式,您也可以写出其他的构造函数,原理都是相同的。但要注意:结构体默认是传入所有参数的,因此,不必写接受所有参数的构造函数!
struct Student {
......
// 默认构造函数
Student() : name("Unknown"), age(18), sex('x'), ID("000000"), score(1.00f) {}
// 接受除age外所有参数的构造函数
Student(const string& n, int a, char s, string& i, float s) :
name(n), age(18), sex(s), ID(i), score(s) {}
// 接受除age和score外所有参数的构造函数
Student(const string& n, int a, char s, string& i) :
name(n), age(18), sex(s), ID(i), score(1.00f) {}
// 只接受sex参数的构造函数
Student(const char s) :
name("Unknown"), age(18), sex(s), ID("0000xx"), score(1.00f) {}
};
至此结构体基础操作已讲解完毕
如果您的需求较低(如应付家庭作业和考试的学生),到此为止就能退出了
下文为结构体的进阶操作(=
进阶结构体操作
注意: 为方便讲解,在下文中我们会重新声明结构体,且每个知识点中的结构体互不相同
1. 结构体作为参数传递
- 在C++中,结构体默认为值类型。因此,在函数中调用调用其他函数中的结构体时,如果不涉及值的修改则可以直接按值传递,但如果涉及修改则需按引用类型(如指针)传递,我们先定义以下结构体:
struct Student {
string name;
int age;
};
打印相关内容(不涉及值修改)则可以直接按值传递:
void PrintName(Student s){ //按值接受
cout << s.name;
}
void main(){
Student XiaoMing = {"小明", 18};
PrintName(XiaoMing); //按值传递
}
修改相关属性则必须按引用类型传递:
void ChangAge(Student* s){ //按引用类型接受
int i; cin >> i;
s.age = i;
}
void main(){
Student XiaoMing = {"小明", 18};
ChangAge(&XiaoMing); //按指针传递
}
2. 结构体嵌套与匿名成员
- 结构体的本质其实也是数据结构,所以我们也可以在结构体中嵌套另一个结构体,在下面的代码中我们定义一个学生结构体,其中成员包含学生的学号字符串和成绩结构体,其中成绩结构体又包含语文、数学、英语的成绩:
struct Student {
string ID;
struct Score {
int Chinese;
int math;
int English;
};
};
- 匿名成员,则为没有名字的成员,通常广泛用于结构体的定义中适当的使用,匿名成员可以让代码更简洁,提高代码的可读性。在上面的代码写法中,相互嵌套的结构体会显得代码不直观,尤其在复杂的结构体中会显得可读性降低。因此,我们先将 Score 结构体定义出来,再定义出 Student 结构体,让 Score 成为 Student 的匿名成员:
struct Score {
int Chinese;
int math;
int English;
};
struct Student {
string ID;
Score; // 匿名成员
};
3. 方法与结构体关联
- 在结构体中,方法(函数)也可以是结构体的成员之一。在下面的代码中,我们定义一个 Rect 矩形结构体,他有三个成员,分别是 L 长、W 宽、和 S 面积,我们可以直接使用函数在结构体内计算出面积,若修改了 L 或 W ,则 S 也将相应的自动更改,从而更方便的操作结构体的数据:
struct Rect {
float L;
float W;
float S(){ return L*W; } // S = L × W
};
4. 结构体之间的比较
- 结构体之间并不存在直接比较的方法,我们需要通过重载==操作符(operator==) 来定义如何将结构体进行比较。如代码所示,我们定义一个 Point 点结构体,其中的成员变量为 x 和 y ,在重载==操作符中我们规定:如果此结构体与比较的结构体(other)的成员x的值相等且成员y的值也相等,则二者是相等的:
struct Point {
int x;
int y;
//重载==操作符,规定为此结构体与另一结构体的x、y属性的值均相同则返回true
bool operator==(const Point& other) const {
return x == other.x && y == other.y;} //other表示被比较的结构体
};
void main() {
Point A = {2, 3};
Point B = {2, 3};
if (p1 == p2) //调用结构体的重载==操作符
cout << "相等";
else
cout << "不相等";
}