std::variant以获得类型安全和自动管理功能。c="https://img.php.
cn/upload/article/000/969/633/175806727020232.png" alt="C++联合体定义与成员访问规则">
C++联合体,在我看来,它就是一种特殊的“变色龙”式的数据结构,它允许你在同一块内存空间里,根据需要“变身”成不同的数据类型。但请记住,它一次只能“变身”成一种形态,如果你试图去访问它当前没有“变身”成的那个成员,那结果往往是出乎意料的,甚至可以说是未定义行为。简单来说,联合体让你用最紧凑的方式存储互斥的数据,但代价是你必须自己管理当前哪个成员是有效的。
解决方案
要理解和正确使用C++联合体,我们得从它的定义和核心特性说起。
定义与内存分配: 联合体使用
<codeclass='language-de<codeclass='language-default'>fcode>ault'>unioncode>
关键字来定义,它看起来和结构体(
<codeclass='language-de<codeclass='language-default'>fcode>ault'>structcode>
)非常相似,只是关键字不同。
<codeclass='language-de<codeclass='language-default'>fcode>ault'>unioncode> <codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode> { <codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code> i; <codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-default'>fcode>loatcode> <codeclass='language-default'>fcode>; <codeclass='language-de<codeclass='language-default'>fcode>ault'>charc[4]code>; // 假设char占1字节,这里是为了演示不同大小的成员 };
当我们定义一个
<codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>
类型的联合体变量时,例如
<codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode> my<codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>;
,编译器会为它分配一块内存。这块内存的大小,不是所有成员大小之和,而是所有成员中占用内存最大的那个成员的大小。在上面的例子中,如果
<codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code>
和
<codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-default'>fcode>loatcode>
都占4字节,
<codeclass='language-de<codeclass='language-default'>fcode>ault'>charc[4]code>
也占4字节,那么
my<codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>
就会占用4字节的内存。所有成员都从这块内存的起始地址开始共享。
成员访问规则: 这是联合体的核心,也是最容易出错的地方。
- 写入成员: 当你向联合体的一个成员写入数据时,比如
my<
codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>.i =10;,这块共享内存就会被
<
codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code>类型的数据占据。此时,其他成员(
<
codeclass='language-default'>fcode>和
c)的值就变得不确定了,它们不再是“有效”的状态。
- 读取成员: 你只能安全地读取你最近一次写入的那个成员。例如,如果你刚刚写入了
my<
codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>.i =10;,那么读取
my<
codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>.i(
std::
cout << my<codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>.i;) 是完全正确的。
- 访问非活跃成员: 如果你写入了
my<
codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>.i =10;,然后尝试去读取
my<
codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>.<codeclass='language-default'>fcode>(
std::
cout << my<codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>.<codeclass='language-default'>fcode>;),这就是所谓的未定义行为(
Unde<codeclass='language-default'>fcode>ined Behavior,UB)。你可能会得到一个随机的浮点数,或者0.0,甚至程序崩溃,这完全取决于编译器、操作系统和当时内存的状态。它不会报错,但结果不可预测,这是联合体使用中最需要警惕的地方。
总结一下: 联合体就像一个多功能插槽,你插入了U盘,就不能同时插入SD卡。如果你强行去读SD卡的数据,那读到的可能就是U盘的二进制乱码。
为什么C++联合体能节省内存?它与结构体有何本质区别?
联合体之所以能节省内存,核心就在于它那独特的内存分配策略。与结构体(
<codeclass='language-de<codeclass='language-default'>fcode>ault'>structcode>
)不同,结构体是为它的每个成员都分配独立的、不重叠的内存空间,所以结构体的总大小通常是其所有成员大小之和(或者更大,考虑到字节对齐)。而联合体,就像前面提到的,它只为所有成员中最大的那个成员分配内存,然后让所有成员共享这同一块起始地址的内存。
举个例子可能更直观:
class='language-default'>fcode>="https://phps.yycxw.com/ai/%E4%B8%80%E5%B8%A7%E7%A7%92%E5%88%9B">cn/upload/ai_manual/000/969/633/68b6d37de74c4152.png" alt="C++联合体定义与成员访问规则">
class='language-default'>fcode>="https://phps.yycxw.com/ai/%E4%B8%80%E5%B8%A7%E7%A7%92%E5%88%9B">一帧秒创
基于秒创AIGC引擎的AI内容生成平台,图文转视频,无需剪辑,一键成片,零门槛创作视频。
cxw.com/static/images/card_xiazai.png" alt="C++联合体定义与成员访问规则">41 <codeclass='language-de<codeclass='language-default'>fcode>ault'>structcode>S{ <codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code> a; // 4 bytes <codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-default'>fcode>loatcode> b; // 4 bytescharc; // 1 byte }; // sizeo<codeclass='language-default'>fcode>(S) 可能是 12 bytes (取决于对齐) <codeclass='language-de<codeclass='language-default'>fcode>ault'>unioncode>U{ <codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code> a; // 4 bytes <codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-default'>fcode>loatcode> b; // 4 bytescharc; // 1 byte }; // sizeo<codeclass='language-default'>fcode>(U) 必然是 4 bytes (取最大成员<codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code>/<codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-default'>fcode>loatcode>的大小)
你看,
U
的大小明显小于
S
。这就是联合体节省内存的秘诀。它牺牲了同时存储所有成员的能力,换取了极致的内存紧凑性。我个人觉得,这就像是同一个柜子,结构体是为每个物品都单独开辟了一个抽屉,而联合体则是所有物品共用一个最大的抽屉,但你一次只能放一件物品进去。这种设计理念,在内存资源非常有限的嵌入式系统或者需要处理大量异构数据的场景下,显得尤为有价值。
在C++联合体中,访问非活跃成员会发生什么?如何避免这种未定义行为?
访问联合体的非活跃成员,简单来说,就是踩到了C++标准中的“未定义行为”地雷。它不会像语法错误那样直接阻止你编译,但运行时可能会导致各种难以预料的后果。
到底会发生什么? 当你写入
my<codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>.i =10;
后,这4字节的内存被解释为
<codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code>
类型的
10
。如果此时你尝试读取
my<codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>.<codeclass='language-default'>fcode>
,编译器会尝试将这4字节的二进制数据按照
<codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-default'>fcode>loatcode>
的IEEE 754标准来解释。结果呢?大概率是一个毫无意义的浮点数值,因为它根本就不是按照浮点数格式存储的。更糟糕的是,如果你的联合体成员类型有构造函数、析构函数或更复杂的行为,访问非活跃成员可能导致内存损坏、程序崩溃,或者其他难以追踪的bug。这就像你把一张图片文件用文本编辑器打开,看到的是一堆乱码,只不过在程序里,这种乱码可能会引发更严重的连锁反应。
如何避免这种未定义行为? 避免这种问题的核心在于追踪当前哪个成员是活跃的。C++本身不会自动为你做这件事,所以你通常需要自己动手:
-
使用判别器(Dis
criminator): 这是最常见也是最推荐的做法。通常,我们会把联合体嵌套在一个结构体中,并在结构体中添加一个枚举类型(或者其他简单的类型)作为判别器,用来指示当前联合体中哪个成员是有效的。enum
class <codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>Type { INT, FLOAT, CHAR_ARRAY }; <codeclass='language-de<codeclass='language-default'>fcode>ault'>structcode> MyVariant { <codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>Typetype; <codeclass='language-de<codeclass='language-default'>fcode>ault'>unioncode> { <codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code> i; <codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-default'>fcode>loatcode> <codeclass='language-default'>fcode>;charc_arr[4]; } data; }; // 使用示例 MyVariant mv; mv.type= <codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>Type::INT; mv.data.i = 42; i<codeclass='language-default'>fcode> (mv.type== <codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>Type::INT) { std::cout << "Int value: " << mv.data.i << std::endl; } else i<codeclass='language-default'>fcode> (mv.type== <codeclass='language-de<codeclass='language-default'>fcode>ault'>Datacode>Type::FLOAT) { // ... }这样,每次访问前先检查
type字段,就能确保你总是访问正确的成员。
-
C++17
std::variant: 如果你的项目允许使用C++17或更高版本,那么
std::variant是一个更安全、更现代的替代品。它在底层可能也使用了联合体的思想,但它提供了类型安全、自动管理活跃成员、值语义以及访问机制(如
std::get或
std::visit),极大地降低了出错的风险。
#in
clude <variant> #include <iostream> #include <array> // For std::array<<std::variantcodeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code>, <codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-default'>fcode>loatcode>, std::array<char, 4>> v; v = 42; // 此时<codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code>是活跃成员 try { std::cout << "Int value: " <<std::get<<codeclass='language-de<codeclass='language-default'>fcode>ault'><codeclass='language-de<codeclass='language-default'>fcode>ault'>intcode>code>>(v) << std::endl
ck="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16016.html" target="_blank">操作系统 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16298.html" target="_blank">字节 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16673.html" target="_blank">u盘 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/17603.html" target="_blank">c++ click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/17719.html" target="_blank">ios click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/27988.html" target="_blank">区别 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/92702.html" target="_blank">为什么 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=数据类型" target="_blank">数据类型 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=Float" target="_blank">Float click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=构造函数" target="_blank">构造函数 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=析构函数" target="_blank">析构函数 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=枚举类型" target="_blank">枚举类型 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=结构体" target="_blank">结构体 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=<code class='language-de<code class='language-default'>fcode>ault'>unioncode>" target="_blank"><code class='language-de<code class='language-default'>fcode>ault'>unioncode> click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=char" target="_blank">char click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>" target="_blank"><code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code> click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=数据结构" target="_blank">数据结构 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=堆" target="_blank">堆 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=Struct" target="_blank">Struct click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=unde<code class='language-default'>fcode>ined" target="_blank">unde<code class='language-default'>fcode>ined click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=嵌入式系统" target="_blank">嵌入式系统 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=bug" target="_blank">bug 大家都在看:
class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1525915.html" title="C++联合体定义与成员访问规则">C++联合体定义与成员访问规则 code class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1524717.html" title="C++中深拷贝和浅拷贝在内存管理上的区别是什么">C++中深拷贝和浅拷贝在内存管理上的区别是什么 code class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1524710.html" title="C++智能指针引用计数变化观察方法">C++智能指针引用计数变化观察方法 code class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1524698.html" title="C++如何开发学生信息管理系统">C++如何开发学生信息管理系统 code class='language-default'>fcode>="https://phps.yycxw.com/<code class='language-default'>fcode>aq/1524691.html" title="C++异常处理与标准库算法结合">C++异常处理与标准库算法结合 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16016.html" target="_blank">操作系统 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16298.html" target="_blank">字节 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/16673.html" target="_blank">u盘 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/17603.html" target="_blank">c++ click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/17719.html" target="_blank">ios click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/27988.html" target="_blank">区别 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/zt/92702.html" target="_blank">为什么 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=数据类型" target="_blank">数据类型 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=Float" target="_blank">Float click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=构造函数" target="_blank">构造函数 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=析构函数" target="_blank">析构函数 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=枚举类型" target="_blank">枚举类型 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=结构体" target="_blank">结构体 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=<code class='language-de<code class='language-default'>fcode>ault'>unioncode>" target="_blank"><code class='language-de<code class='language-default'>fcode>ault'>unioncode> click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=char" target="_blank">char click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=<code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code>" target="_blank"><code class='language-de<code class='language-default'>fcode>ault'><code class='language-de<code class='language-default'>fcode>ault'>intcode>code> click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=数据结构" target="_blank">数据结构 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=堆" target="_blank">堆 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=Struct" target="_blank">Struct click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=unde<code class='language-default'>fcode>ined" target="_blank">unde<code class='language-default'>fcode>ined click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=嵌入式系统" target="_blank">嵌入式系统 click="hits_log(2,'www',this);" hre<code class='language-default'>fcode>-data="/search?word=bug" target="_blank">bug