C/C++编程:联合体

联合体声明

联合体是特殊的类类型,它在一个时刻只能保有一个非静态数据成员

union attr 类头名 {
    
     成员说明 }		

联合体union的定义方式与结构体一样,但是二者有根本区别。

  • 在结构体中各成员有自己的内存空间,一个结构变量的总长度是各个成员长度之和
  • 在联合体中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最大的长度

共用体使不同的类型变量存放到同一段内存单元中,所以共用体在同一时刻只能存储一个数据成员的值,共用体的大小等于最大成员的大小(结构体变量的大小是所有数据成员大小的总和)。

#include <iostream>
union var{
    
    
    long int l;
    int i;
};

int main()
{
    
    
    union  var v;
    v.l = 5;
    printf("v.l is %d\n",v.i);
    v.i = 6;
    printf("now v.l is %ld! the address is %p\n",v.l,&v.l);
    printf("now v.i is %d! the address is %p\n",v.i,&v.i);
}

在这里插入图片描述

  • 从上面可以看出,联合体是共用一个内存首地址。
  • 在程序中改变共用体的一个成员,其他成员也会随之改变。不是强制类型转换!而是因为他们的值的每一个二进制位都被新值所覆盖。

联合体的大小仅足以保有其最大的数据成员。其他的数据成员分配于该最大成员的一部分相同的字节。分配的细节是实现定义的,而且从非最近写u人的联合体读取数据是未定义行为。许多编译器作为非标准语言扩展,实现读取联合体不活跃成员的能力

#include <variant>
#include <iostream>

union S{
    
    
    std::int32_t  n; // 4字节
    std::uint16_t s[2]; // 4字节
    std::uint8_t c; // 1字节
}; // 整个联合体占用4字节



int main()
{
    
    
  S s = {
    
    0x12345678}; // 初始化首个成员,s.n现在是活跃成员
  // 于此点,从s.s和s.c读取是未定义行为
  std::cout << std::hex << "s.n = " << s.n << "\n";
  s.s[0] = 0x0011; // s.s现在是活跃成员
  // 在这里,从s.n或者s.c读取是UB但是的大叔编译器对齐都有地暖管一
    std::cout << "s.c is now " << +s.c << '\n' // 11 或 00,取决于平台
              << "s.n is now " << s.n << '\n'; // 12340011 或 00115678
}

在这里插入图片描述
每个成员都如同她是类的仅有成员一样分配

在C++11起,如果联合体的成员是拥有用户定义的构造函数和析构函数的类,则为了切换其活跃成员,通常需要显式析构函数和布置 new:

#include <vector>
#include <iostream>

union S{
    
    
    std::string str;
    std::vector<int> vec;
    ~S(){
    
    }; // 需要知道哪个成员活跃,仅在联合体式的类中可行
};  // 整个联合体占有 max(sizeof(string), sizeof(vector<int>)) 的内存

int main()
{
    
    
    S s = {
    
    "Hello world"};
    // 在此点,从s.vec读取是未定义行为
    std::cout << "s.srr = " << s.str << "\n";
    s.str.~basic_string();
    new (&s.vec) std::vector<int>;
    // 现在, s.vec是联合体活跃成员
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector();
}

在这里插入图片描述
若两个联合体成员均为标准布局类型,则在任何编译器上检验其公共子序列都是良好定义的

应用

将一个32位的整型数拆分为4个单字节的数

如将一个数0x12345678拆分为4个单字节的数为:0x78、0x56、0x34、0x12,则主要实现代码如下:

#include <iostream>
union data{
    
    
    int i;
    char ch[4];
}data;
int main()
{
    
    
    data.i = 0x12345678;
    std::cout << std::showbase << std::hex
    << "data:          " << data.i << "\n"
    << "ch[0] - ch[3]: " << int(data.ch[0]) << "\t"
            << int(data.ch[1]) << "\t"
            << int(data.ch[2]) << "\t"
            << int(data.ch[3]) << "\t" ;
}

在这里插入图片描述

判断当前操作系统字节序的大小端问题

原理: 在联合体中定义一个4字节整数i和一个单字节整数ch,而且赋值i为1(16禁止表示0x00000001),利用联合体中所有变量共享内存区域的特性,如果系统端是小端序,则该联合体的低字节段存放的是地位字节(0x01),反之存在的是高位字节(0x00),那么可以根据ch的值(存放在该联合体的低地址端,非0即1)来判断当前系统的字节序问题。


#include <iostream>
bool checkByteOrder(){
    
    
    union {
    
    
        int i;
        char ch;
    } a;

    a.i == 1;
    return (a.ch == 1);
}
int main()
{
    
    
    if(checkByteOrder() == 1){
    
    
        printf("当前操作系统是小端序");
    }else{
    
    
        printf("当前操作系统是大端序");
    }
}

  • 联合体可以拥有成员函数(包含构造函数和析构函数),但不能有虚函数
  • 联合体不能有基类而且不能用作基类
  • 联合体不能有引用类型的非静态数据成员
  • 正如结构体的声明中一般,联合体的默认成员访问是 public

在C++11前,联合体不能拥有带平凡特殊成员函数(赋值构造函数、赋值赋值运算符或者析构函数)的非静态数据成员

这句话怎么理解呢?比如string可以作为结构体的成员但是不能作为共用体的成员

union DateUnion
{
    
    
    int iInt1;
    int iInt2;
    double douDou;
    std::string strChar;
};

在这里插入图片描述
错误原因:String 是一个类,有自己的构造函数,析构函数。
分析:Union的一大特征在于,一个Union类中的所有数据共享同一段内存。如果union类的成员包含自己的构造函数,析构函数,那么同一Union类的成员在初始化时,就有可能会执行不同的构造函数。这是无法预料的。所以,我们在定义Union类时要尽量避免成员变量是对象(含有自己的构造函数)

在C++11起,若联合体含有带非平凡默认构造函数非静态数据成员,则该联合体的默认构造函数默认被弃置,除非联合体的变体成员拥有一个默认成员初始化器。

我们来看个例子

#include <iostream>
struct TestUnion
{
    
    
    TestUnion() {
    
    }
};

typedef union
{
    
    
    TestUnion obj;
} UT;

int main ()
{
    
    
    return 0;
}

在这里插入图片描述
而如果去掉那个什么也没干的构造函数,则一切OK。

问:为什么编译器不允许union成员有构造函数呢
推测:如果C++标准允许union的成员有构造函数,那么,在进行空间分配的时候要不要执行这个构造函数呢?如果要,它们如果TestUnion的构造函数中包含了一些内存分配的操作,或者其它对整个application状态的修改,那么,如果我今后要用到obj的话,事情可能还比较合理,但是如果我根本就不使用obj这个成员呢?由于obj的引入造成的对系统状态的修改显然是不合理的;反之,如果不允许有,那么一旦今后我们选中了obj来进行操作,那么所有的信息都没有初始化(如果是普通的struct,没什么问题,但是,如果有虚函数呢?)。更进一步,假设现在我们的union不是只有一个 TestUnion obj,还有一个TestUnion2 obj2,二者均有构造函数,并且都在构造函数中执行了一些内存分配的工作(甚至干了很多其它事情),那么,如果先构造obj,后构造obj2,则执行的 结果几乎可以肯定会造成内存的泄漏。

鉴于以上诸多麻烦(可能还有更多麻烦),在构造union函数时,编译器只负责分配空间,而不负责去执行附加的初始化工作。为了简化工作,只要我们提供了构造函数,就会错误:error C2620: union '__unnamed' : member 'obj' has user-defined constructor or non-trivial default constructor

同理,除了不能加构造函数,析构函数/拷贝构造函数/赋值运算符也不可以加

https://www.pianshen.com/article/9233174821/
https://www.icode9.com/content-1-560781.html
https://www.jb51.net/article/66711.htm(没有看完)
官方文档

猜你喜欢

转载自blog.csdn.net/zhizhengguan/article/details/115259616