目录
入门
一、结构体(struct)
定义结构体类型
创建结构体变量
访问结构体成员
结构体的用途
二、联合体(union)
概念
定义联合体类型
创建联合体变量和访问成员
联合体与结构体在存储方式上的区别
联合体的用途
进阶
一、结构体(struct)
结构体的嵌套
结构体的初始化列表
结构体的对齐和大小计算(高级内容)
结构体与函数
二、联合体(union)
联合体的大小细节
联合体中的匿名联合体(C++11 及以上)
联合体在类型转换中的应用(高级用法)
入门
一、结构体(struct)
在 C++ 中,结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起。
-
定义结构体类型
- 语法:
struct结构体名{成员类型 成员名1;成员类型 成员名2;...};
- 例如:
struct Student { std::string name; int age; };
,这里定义了一个名为Student
的结构体类型,它包含一个std::string
类型的成员name
和一个int
类型的成员age
。
- 语法:
-
创建结构体变量
- 方式一:在定义结构体类型后,像定义普通变量一样定义结构体变量。例如:
Student stu1;
,这里stu1
是Student
类型的一个变量。 - 方式二:在定义结构体类型的同时定义变量。例如:
struct Point { int x; int y; } p1, p2;
,这里定义了Point
结构体类型,同时定义了p1
和p2
两个Point
类型的变量。
- 方式一:在定义结构体类型后,像定义普通变量一样定义结构体变量。例如:
-
访问结构体成员
- 通过点运算符(.)访问。例如:
stu1.name = "John"; stu1.age = 20;
,这就分别给stu1
变量的name
成员赋值为"John"
,age
成员赋值为20
。 - 如果是通过指针访问结构体成员,需要使用箭头运算符(->)。例如,假设有
Student* pStu=&stu1;
,那么可以通过pStu->name
和pStu->age
来访问stu1
的成员。
- 通过点运算符(.)访问。例如:
-
结构体的用途
- 用于表示复杂的数据结构。比如在图形编程中,可以用结构体来表示一个点(包含 x 和 y 坐标)、一个矩形(包含左上角点和右下角点等)等。例如:
struct Rectangle {Point upperLeft;Point lowerRight;};
- 方便函数之间传递多个相关的数据。比如一个函数需要传递一个学生的姓名、年龄和成绩等信息,就可以将这些信息组合成一个结构体,然后将结构体作为参数传递给函数。
二、联合体(union)
-
概念
- 联合体是一种特殊的数据类型,它的所有成员共用同一段内存空间。这意味着在某一时刻,联合体只能存储其中一个成员的值。
-
定义联合体类型
- 语法:
union联合体名{成员类型 成员名1;成员类型 成员名2;...};
- 例如:
union Data { int i; float f; char c; };
,定义了一个名为Data
的联合体类型,它有三个成员i
(int
类型)、f
(float
类型)和c
(char
类型)。
- 语法:
-
创建联合体变量和访问成员
- 创建变量方式与结构体类似。例如:
Data myData;
,创建了一个Data
类型的变量myData
。 - 访问成员也是通过点运算符(.)。例如,可以这样使用:
myData.i = 10;
,此时联合体存储的是int
类型的值。如果接着执行myData.f = 3.14f;
,那么myData
中存储的int
值就会被覆盖,现在存储的是float
类型的值。
- 创建变量方式与结构体类似。例如:
-
联合体与结构体在存储方式上的区别
- 结构体每个成员都有自己独立的内存空间,整个结构体变量的大小是所有成员大小之和(考虑对齐等因素)。例如,
struct Test { char a; int b; };
,通常情况下,char
占 1 个字节,int
占 4 个字节,考虑对齐,Test
类型变量大小可能是 8 个字节。 - 联合体所有成员共用同一段内存,联合体变量的大小是其最大成员的大小。对于前面定义的
Data
联合体,int
通常占 4 个字节,float
通常占 4 个字节,char
占 1 个字节,所以Data
类型变量大小是 4 个字节。因为在存储时,联合体以能够容纳最大成员的空间来分配内存,以确保能够存储任何一个成员的值。
- 结构体每个成员都有自己独立的内存空间,整个结构体变量的大小是所有成员大小之和(考虑对齐等因素)。例如,
-
联合体的用途
- 当需要节省内存空间,并且在不同时刻只会使用其中一种数据类型时可以使用联合体。例如,在处理通信协议时,一个数据包可能有多种不同的格式,根据数据包的类型,其中的数据部分可能是整数、浮点数或者字符数组等,就可以用联合体来存储这个数据部分,根据具体情况来使用联合体中的不同成员来解释这块内存中的数据。
进阶
一、结构体(struct)
-
结构体的嵌套
- 结构体可以嵌套其他结构体。例如,在一个学校管理系统中,除了
Student
结构体,还可以有一个Course
结构体,并且有一个StudentRecord
结构体来关联学生和课程信息。
- 结构体可以嵌套其他结构体。例如,在一个学校管理系统中,除了
struct Course {std::string courseName;int courseCredit;};struct StudentRecord {Student studentInfo;Course enrolledCourse;float grade;};
- 访问嵌套结构体成员可以通过连续的点运算符。例如,对于
StudentRecord record;
,可以使用record.studentInfo.name
来访问学生姓名,使用record.enrolledCourse.courseName
来访问课程名称。
-
结构体的初始化列表
- 可以在定义结构体变量时使用初始化列表进行初始化。例如,对于
Student
结构体,可以这样初始化:Student stu2 = {"Alice", 22};
。初始化列表中的值按照结构体成员定义的顺序依次赋值给成员。 - 对于有嵌套结构体的情况,也可以使用初始化列表。例如,对于
StudentRecord
结构体:
- 可以在定义结构体变量时使用初始化列表进行初始化。例如,对于
Course mathCourse = {"Mathematics", 3};StudentRecord record2 = {{"Bob", 21}, mathCourse, 3.5f};
-
结构体的对齐和大小计算(高级内容)
- 编译器为了提高内存访问效率,会对结构体成员进行对齐。例如,在一个 32 位系统中,
int
类型通常是 4 字节对齐的。这意味着如果一个char
类型(1 字节)后面跟着一个int
类型,编译器可能会在char
后面填充 3 个字节,使得int
能够从 4 字节对齐的地址开始存储。 - 可以使用
sizeof
运算符来计算结构体的大小。例如,sizeof(Student)
会返回Student
结构体变量所占用的字节数。这个大小可能会因为编译器的对齐规则而比简单的成员大小相加要大。 - 可以通过
#pragma pack
指令来改变编译器的默认对齐方式。例如,#pragma pack(1)
可以让编译器按照 1 字节对齐,这样可以减少结构体因为对齐而产生的空间浪费,但可能会降低内存访问效率。
- 编译器为了提高内存访问效率,会对结构体成员进行对齐。例如,在一个 32 位系统中,
-
结构体与函数
- 结构体可以作为函数的参数和返回值。当结构体作为参数传递时,默认是值传递,这意味着会复制整个结构体的内容。例如:
void printStudent(Student stu) {std::cout << "Name: " << stu.name << ", Age: " << stu.age << std::endl;}
- 如果结构体比较大,这种值传递可能会导致性能问题。可以使用指针或引用传递来避免复制。例如:
void modifyStudentAge(Student* pStu, int newAge) {pStu->age = newAge;}void printStudentRef(const Student& stu) {std::cout << "Name: " << stu.name << ", Age: " << stu.age << std::endl;}
二、联合体(union)
-
联合体的大小细节
- 虽然联合体的大小是其最大成员的大小,但要注意内存对齐的影响。例如,如果一个联合体中有一个
double
(通常 8 字节对齐)和一个char
,那么联合体的大小可能是 8 字节而不是 1 字节,因为要满足double
的对齐要求。 - 可以使用
offsetof
宏(定义在<cstddef>
头文件中)来获取联合体成员的偏移量。例如,对于union Data
,offsetof(Data, i)
可以获取i
成员在联合体中的偏移量(通常是 0)。
- 虽然联合体的大小是其最大成员的大小,但要注意内存对齐的影响。例如,如果一个联合体中有一个
-
联合体中的匿名联合体(C++11 及以上)
- 在 C++11 中,可以定义匿名联合体。例如:
struct SomeStruct {int a;union {float f;int i;};};
- 匿名联合体的成员可以直接访问,就好像它们是
SomeStruct
的成员一样。例如,对于SomeStruct s;
,可以直接使用s.f
或s.i
,而不需要通过联合体变量名来访问。不过要注意,匿名联合体的成员仍然共用同一段内存,并且只能同时存储其中一个值。
-
联合体在类型转换中的应用(高级用法)
- 联合体可以用于实现一些简单的类型转换技巧。例如,在某些嵌入式系统或者底层编程中,可能需要将一个整数的字节顺序进行转换。可以利用联合体来实现,假设要将一个 32 位整数的字节顺序从大端序转换为小端序(或者反之)。
union EndianConverter {uint32_t i;uint8_t bytes[4];};uint32_t bigEndianToLittleEndian(uint32_t bigEndianValue) {EndianConverter converter;converter.i = bigEndianValue;std::swap(converter.bytes[0], converter.bytes[3]);std::swap(converter.bytes[1], converter.bytes[2]);return converter.i;}
- 这里利用了联合体中
i
和bytes
数组共用内存的特点,通过操作bytes
数组来改变字节顺序,然后返回转换后的整数值。不过这种用法要谨慎,因为它依赖于具体的字节序和整数的存储方式等实现细节。