《Hello C++》4.2 结构体

《Hello C++》4.2 结构体
0x3ff18a通过 4.1 数组初识的学习,相信大家已经基本掌握了如何在 C++ 中何利用数组存储多个数据类型相同的元素。但请大家试想一下,假如我们要存储一个员工的(可能涉及到:员工姓名、年龄、籍贯和工资等)信息。在这种场景下我们希望有一种可以不同于数组只能存储单一数据类型的工具,而是可以将多种复杂的数据类型集中存储的方法 —— 结构体应运而生。
结构体是一种比数组更灵活的数据操作工具,因为同一个结构体可以存储多种数据类型的数据,这使得我们可以实现诸如上文提到的员工信息管理的效果,从而将同类信息当作一个集合来看待。我们甚至可以通过结构数组将整个企业的信息进行集合。结构体也是我们在学习第七章类与对象前的基石,通过这块垫脚石对我们进行铺垫后,我们将离 C++ 的核心内容面向对象编程(OOP)更近一步。
4.2.1 C++ 结构体的基本概念
结构体(struct)是 C++ 中一种用户自定义的数据类型,它允许将多个相关的数据项组合在一起,形成一个完整的逻辑实体。结构体可以包含各种类型的变量,这些变量称为成员变量,结构体提供了一种将相关数据封装在一起的方式,从而使数据管理变得更加高效和有序。
在 C 语言中,结构体只能包含数据成员,但在 C++ 中,结构体得到了极大的增强,它可以包含成员函数、构造函数、析构函数,甚至支持继承和多态等面向对象特性。这意味着结构体不仅可以存储数据,还可以由程序员自己定义操作这些数据的行为。例如,一个员工信息的结构体中可以包含计算员工工资的成员函数。这一增强使得数据和行为紧密的结合起来了,大大提高了代码的可读性和模块化程度。
结构体的定义使用 struct 关键字,后面跟着结构体的名称和一对花括号,花括号内包含成员变量的声明。结构体的定义通常放在头文件中或函数的顶部,以便在多个地方使用。定义结构体后,可以像使用内置类型一样使用它来声明变量。结构体实例的成员通过点运算符(.)[1]访问,如果使用指针访问结构体成员,则使用箭头运算符(->)[2]。结构体的这种特性使得它非常适合用于表示现实世界中的实体,学生、教师以及员工等,这些实体通常具有多个属性,结构体能够将这些属性有机地组织在一起。
4.2.2 结构体的定义与声明
在 C++ 中,结构体的定义是通过 struct 关键字实现的,这标志着一种新数据类型的创建。定义结构体时,需要指定其名称和成员列表,成员列表可以包含变量、函数甚至其他结构体。结构体的定义不仅描述了数据的组织方式,还定义了该类型变量的蓝图。

图 4-2-1 结构体的组成部分
结构体变量的创建有多种方式,每种方式都有其适用场景。最直接的方式是使用 struct 关键字后跟结构体名称和变量名,但 C++ 中创建结构体变量时 struct 键字可以省略,这使得代码更加简洁。另一种常见的方式是在定义结构体的同时创建变量,这种方式适用于需要立即使用结构体的情况。此外,还可以使用初始化列表或构造函数来创建结构体变量,这两种方式允许在创建变量时直接初始化成员的值。
- 下面我们通过一个具体的结构体定义来演示前三种创建方式。
1 |
|
- 对于需要更复杂初始化逻辑的场景,可以为结构体定义构造函数。这属于面向对象编程的范畴,让结构体的使用更像一个"类"。
1 |
|
4.2.3 结构体的高级特性
结构体在 C++ 中不仅限于存储基本数据,还支持一系列高级特性,使其能够处理更复杂的数据结构和逻辑。结构体嵌套是其中之一,它允许在一个结构体中包含另一个结构体作为成员。这种特性使得程序能够更好地组织复杂的数据结构。例如,在一个表示教师的结构体中,可以嵌套一个表示学生的结构体,从而描述教师辅导学生的关系。嵌套结构体的成员通过多重点运算符访问,例如:teacher1.student.name,这种访问方式直观地反映了数据的层次结构。
结构体与数组的结合使用创造了结构体数组,它能够存储多个相同类型的结构体实例。结构体数组非常适合表示同类事物的集合,如多个点的坐标。通过数组索引,可以访问每个结构体实例,进而访问其成员变量。结构体数组使得批量处理相似数据变得简单,例如,可以使用循环遍历数组中的所有元素,对每个元素执行相同的操作。
结构体与指针的结合使用是 C++ 中的重要概念,可以通过指针高效地处理动态内存分配的结构体。使用指针访问结构体成员时,需要使用箭头运算符(->),这简化了通过指针访问成员的操作。结构体指针特别适合用于动态内存分配,例如使用 new 关键字在堆上创建结构体实例,这种方式允许手动控制结构体的生命周期。此外,结构体还可以作为函数参数传递,传递方式包括值传递、引用传递和指针传递。值传递会创建结构体的副本,适合保护原始数据;引用传递和指针传递则避免复制开销,允许函数修改原始结构体。通过 const 关键字,可以防止函数意外修改结构体内容,进一步提高代码的安全性。
4.2.4 结构体与类的区别及选择
在 C++ 中,结构体(struct)和类(class)是两种相似但又有区别的用户自定义数据类型。它们最大的区别在于访问控制的默认设置:结构体的成员默认是公有的(public),而类的成员默认是私有的(private)。这代表在结构体中,除非显式指定,否则所有成员都可以从外部直接访问;而在类中,成员通常被封装起来,只能通过公有成员函数访问。这一区别反映了结构体和类在设计哲学上的差异:结构体强调数据的透明访问,而类强调数据的封装和行为。
另一个重要区别体现在继承的默认方式上。当结构体用于继承时,默认的继承方式是公有继承,而类则默认为私有继承。这意味着从结构体派生的新结构体会自动继承基结构体的所有公有成员,而从类派生的新类则会继承基类的私有成员(但访问受限)。这一区别使得结构体更适合实现简单的数据层次结构,而类则更适合构建复杂的继承体系。
在选择使用结构体还是类时,需要考虑数据的特性和使用场景。结构体适合用于主要包含数据成员、行为简单的场景,如坐标点、颜色值或数据库记录。结构体的默认公有访问使得数据操作直接明了,适合数据导向的设计。类则适合需要复杂逻辑、严格封装和多态行为的场景,如游戏角色、银行账户等实体。类通过隐藏实现细节,提供了更好的封装性和可维护性。在实际编程中,选择结构体还是类取决于具体需求,但理解它们的区别有助于做出更合理的设计决策。
4.2.5 结构体的内存布局与性能考虑
构体在内存中的布局方式直接影响程序的性能和内存使用效率。结构体的成员在内存中是连续存储的,所有成员变量紧密排列。这种连续性使得访问结构体成员时效率较高,因为相邻成员可能位于同一缓存行中,减少了缓存未命中的概率。然而,这种连续存储也带来了内存对齐的问题,即成员变量需要存储在特定地址边界上,以满足硬件访问要求。
内存对齐是编译器为了优化内存访问速度而采取的一种策略。对齐规则要求每个成员变量存储在自身大小整数倍的地址上。例如:一个 4 字节的 int 变量需要存储在 4 的整数倍地址上。为了满足对齐要求,编译器可能在成员之间插入填充字节,这会增加结构体的总大小。结构体的总大小通常是最大成员对齐数的整数倍,这意味着即使所有成员的总和小于这个值,结构体也会占用对齐后的完整空间。
为了优化结构体的内存布局,可以采取一些策略。例如,按照成员类型的大小降序排列成员,将较大的数据类型放在前面,较小的放在后面,这样可以减少填充字节的数量。另外,对于频繁传递的结构体,可以考虑使用引用或指针来避免拷贝开销。
4.2.6 结构体总结
C++ 结构体是一个功能强大且灵活的数据组织工具,它融合了 C 语言中结构体的简洁性和 C++ 面向对象编程的强大特性。从基本的数据封装到高级的面向对象特性,结构体为程序员提供了一种有效管理复杂数据的方式。通过结构体,我们可以创建更加符合问题域概念的数据结构,提高代码的可读性、可维护性和重用性。结构体与类的区别使得它们各有适用的场景:结构体适合数据透明、行为简单的场合,而类适合封装复杂、行为丰富的对象。
在现代 C++ 编程中,结构体的应用范围不断扩大,从简单的数据容器到复杂的系统接口,处处可见其身影。随着 C++ 标准的演进,结构体的功能也在不断增强,如列表初始化、默认成员初始化等特性使得结构体的使用更加方便和安全。理解结构体的内存布局和对齐机制有助于编写出高效且节省内存的代码。无论是初学者还是经验丰富的程序员,掌握结构体的各种特性和应用场景都是提高 C++ 编程水平的关键。




