个人主页:PingdiGuo_guo
收录专栏:C++干货专栏
大家伙新年快乐,今天我们来了解一下C++联合体。
文章目录
1.联合体
1.1联合体的概念
1.2联合体的思想
1.3联合体的作用
1.3.1内存优化
1.3.2二进制数据操作
1.3.3类型转换
1.3.4解决特定问题
1.4联合体的操作
1.4.1定义联合体
1.4.2初始化联合体
1.4.3赋值操作
1.4.4访问联合体成员
1.4.5注意事项
1.5联合体的分类
1.5.1 匿名联合体(Anonymous Union)
1.5.2 命名联合体(Named Union)
1.5.3 嵌套联合体(Nested Union)
1.6联合体和结构体的区别
1.6.1 相同之处
1.6.2 区别
1.7联合体的练习
1.7.1 题目
1.7.2 解题步骤
1.7.3 代码示例
1.8总结
1.联合体
1.1联合体的概念
联合体是一种特殊的用户自定义数据类型,在C++中,通过`union`关键字来定义。它由多个成员组成,但所有成员共享同一段内存空间。联合体的大小等于其内部最大的成员所占的内存大小。
1.2联合体的思想
联合体的设计思想在于高效地利用内存,特别是在需要在不同时间点以不同类型的值来解释同一块内存的情况下。联合体确保了同一内存区域可被看作是多种数据类型的任何一种,但在任何时刻只有一个成员的数据是有意义的。
1.3联合体的作用
1.3.1内存优化
当不同的数据类型在某一时间段内互斥使用同一内存空间时,联合体可以避免不必要的内存开销。
1.3.2二进制数据操作
在处理底层数据,如网络通信协议、文件格式解析等领域,联合体常用于根据不同上下文对同一内存区域的不同解读。
1.3.3类型转换
在一些场景下,联合体可以作为一种简便的类型转换手段,尤其是从一种数据类型到另一种数据类型直接“重映射”内存内容。
1.3.4解决特定问题
在某些特定的应用场景中,联合体可以帮助程序员实现特定的需求,比如在有限的硬件资源条件下,或者在设计特定算法时需要灵活改变数据视图。
需要注意的是,使用联合体时应当谨慎,因为没有机制保证同时访问不同类型的成员是安全的,因此通常要求开发者自行管理联合体内存的正确使用。此外,C++11标准引入了std::variant,它是联合体的一种更安全的替代品,它可以持有多种类型并跟踪当前存储的有效类型。
1.4联合体的操作
1.4.1定义联合体
// 定义一个联合体,包含整型、浮点型和字符型成员
union MyUnion {
int integerValue;
float floatValue;
char characterValue;
};
在这个例子中,MyUnion是一个联合体,它有三个成员:一个整数、一个浮点数和一个字符。这三个成员共享相同的内存空间。
1.4.2初始化联合体
// 初始化联合体
MyUnion myU;
// 或者直接初始化某个成员
MyUnion myUInit = { .integerValue = 100 }; // C++11开始支持指定初始化标签
这里,myU创建了一个未初始化的联合体实例。而myUInit则直接初始化了integerValue为100。
1.4.3赋值操作
// 赋值操作,只能对当前活动成员赋值
myUInit.integerValue = 42; // 正确,现在联合体中保存的是整数值42
// 尝试对非活动成员赋值(此时活动成员为integerValue)
myUInit.floatValue = 3.14f; // 实际上可能会覆盖掉integerValue的值
// 更改活动成员
myUInit.characterValue = 'A'; // 现在联合体中保存的是字符'A',之前保存的整数值已丢失
由于联合体在同一时间只有一个活跃成员,所以对非活跃成员的赋值会覆盖当前活跃成员的数据,而且编译器不会发出警告或错误。
1.4.4访问联合体成员
// 访问联合体成员
int intValue = myUInit.integerValue; // 取回最后一次赋值给integerValue的值
char charValue = myUInit.characterValue; // 取回最后一次赋值给characterValue的值
访问联合体成员时,应确保了解当前哪个成员是活跃的,否则获取的数据可能是无意义的。
1.4.5注意事项
- 在实际使用联合体时,需要清楚知道当前活跃成员是什么,以免发生未预期的数据覆盖。
- 联合体本身不提供机制追踪当前活跃成员,这需要程序员自己管理和记录。在C++17中引入了std::variant作为更安全的替代方案,它能保持类型安全并跟踪当前存储的有效类型。
1.5联合体的分类
1.5.1 匿名联合体(Anonymous Union)
匿名联合体没有名称,只能定义在一个结构体或类的内部,并且可以与该结构体或类的其他成员共享相同的命名空间。
示例代码:
struct MyStruct {
int type;
union {
int intValue;
float floatValue;
};
};
int main() {
MyStruct myVar;
myVar.type = 0;
myVar.intValue = 10;
printf("%d\n", myVar.intValue);
myVar.type = 1;
myVar.floatValue = 3.14;
printf("%.2f\n", myVar.floatValue);
return 0;
}
1.5.2 命名联合体(Named Union)
命名联合体有一个特定的名称,可以在全局作用域或局部作用域中定义,并可以作为其他结构体,类等的成员。
示例代码:
union MyUnion {
int intValue;
float floatValue;
};
int main() {
MyUnion myVar;
myVar.intValue = 10;
printf("%d\n", myVar.intValue);
myVar.floatValue = 3.14;
printf("%.2f\n", myVar.floatValue);
return 0;
}
1.5.3 嵌套联合体(Nested Union)
嵌套联合体是指一个联合体的成员也可以是另一个联合体。
示例代码:
union Union1 {
int intValue;
float floatValue;
};
union Union2 {
int intValue;
Union1 nestedUnion;
};
int main() {
Union2 myVar;
myVar.nestedUnion.intValue = 10;
printf("%d\n", myVar.nestedUnion.intValue);
myVar.nestedUnion.floatValue = 3.14;
printf("%.2f\n", myVar.nestedUnion.floatValue);
return 0;
}
1.6联合体和结构体的区别
联合体(Union)与结构体(Struct)在C++中都是复合数据类型,它们都可以容纳多个不同类型的成员变量,但在内存布局和使用方式上有显著区别:
1.6.1 相同之处
1. 定义:两者都通过关键字定义,并且都能包含不同类型的成员变量。
2. 命名:都可以通过`.`运算符或者指向它们的指针的`->`运算符来访问成员变量。
1.6.2 区别
1. 内存布局
- 结构体:结构体的每个成员变量在内存中都有自己的独立空间,按照声明顺序依次排列。
- 联合体:联合体的所有成员变量共享同一片内存区域,同一时刻只能存放一个成员变量的值,改变一个成员变量会影响到其他成员变量的值,因为它们都在同一个内存位置上。
2. 使用
- 结构体:允许同时存储所有成员变量的值,每个成员变量的值都是独立存在的。
- 联合体:在同一时间只能有一个成员有效,也就是说,虽然可以声明多个类型的成员,但只能激活其中的一个用于存储数据。
3. 大小
- 结构体的大小至少是其所有成员变量大小之和(可能还要加上对齐导致的额外空间)。
- 联合体的大小等于其所有成员中最大的成员的大小,因为它只分配足够的空间来容纳最大成员。
4. 应用场景
- 结构体常用于打包相关数据到一起,创建一个新的数据类型,适用于需要同时维护多个状态的情况。
- 联合体常用于节省内存空间,尤其是在需要根据上下文临时存储不同类型数据而又不需要同时保存多种数据的情况下,例如在网络编程中转换不同类型的协议数据包。
总结来说,结构体是为数据聚合和分离提供便利,而联合体则是为了在有限的空间内存储不同类型的单个数据。
1.7联合体的练习
1.7.1 题目
编写一个程序,定义一个联合体类型,它可以存储一个整数值或一个浮点数值。然后编写函数分别读取用户输入的整数和浮点数,并通过联合体打印出两种不同的表示方式。最后,设计程序逻辑以确保在同一时间只有一个类型的值被正确存储和显示。
1.7.2 解题步骤
1. 定义一个联合体类型,包含整数(int)和浮点数(float)两个成员变量。
2. 编写一个函数,接收用户输入的整数,将其存入联合体,并打印整数值。
3. 编写另一个函数,接收用户输入的浮点数,将其存入联合体,并打印浮点数值。
4. 在主函数中,先读取整数并打印,然后读取浮点数并打印,注意每次存入新值时旧值会被覆盖。
1.7.3 代码示例
#include <iostream>
// 定义联合体
union Number {
int intValue;
float floatValue;
};
// 函数:读取整数并打印
void readAndPrintInt(Number &num) {
std::cout << "Enter an integer value: ";
std::cin >> num.intValue;
std::cout << "The integer value is: " << num.intValue << std::endl;
}
// 函数:读取浮点数并打印
void readAndPrintFloat(Number &num) {
std::cout << "Enter a floating-point value: ";
std::cin >> num.floatValue;
std::cout << "The floating-point value is: " << num.floatValue << std::endl;
}
int main() {
Number myNumber;
// 先读取并打印整数
readAndPrintInt(myNumber);
// 现在读取并打印浮点数
// 注意:由于联合体内存区域共用,此处会覆盖之前存储的整数值
readAndPrintFloat(myNumber);
return 0;
}
大家请注意,尽管上述代码展示了如何使用联合体,但在实际应用中,直接读取不同类型的值并期望联合体能正确处理可能会有未定义行为的风险,特别是在没有显式地清除旧值的情况下。这是因为联合体不会自动跟踪当前生效的成员类型,因此从非活跃成员读取可能导致未定义的行为。在实际编程中,通常会配合额外的信息(如枚举或其他标记)来确定联合体当前存储的是哪种类型的值。
1.8总结
本篇博客到这里就结束了,感谢大家的支持与观看,如果有好的建议欢迎留言,谢谢大家啦!