【C++随笔01】联合体union —— 一种节省空间的类

news/2024/7/7 23:59:16

【C++随笔01】联合体union —— 一种节省空间的类

  • 一、联合体(union)
  • 二、定义
  • 三、用法
    • 1、定义union、访问union成员
    • 2、匿名union
    • 3、使用类管理union成员
    • 4、管理并销毁string

一、联合体(union)

  • 联合体是一种特殊的类。一个union可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。
  • 在c++新标准中,含有构造函数或析构函数的类型也可以作为union的成员类型。
  • union的成员都是公有的。与struct相同。

当然,union也可以为其成员指定public、protected、private

  • union可以定义包括构造函数和析构函数在内从成员函数。

但是,由于union既不能继承自其他类,也不能作为基类使用,所以在union中不能含有虚函数。

联合体也被叫做共用体

二、定义

  1. 定义
    union定义的语法如下:
c++
union UnionName {
    unionMember1 member1;
    unionMember2 member2;
    // ...
};

其中,UnionName为该union类型的名称,unionMember1、unionMember2等为union的成员,可以是任何 C++ 数据类型(包括自定义类型),也可以是数组或指针。union的成员占用同一块内存空间,它们的大小取决于成员中占用内存最大的类型。

三、用法

1、定义union、访问union成员

  • 可以使用 .和-> 运算符来访问union中的成员变量。但需要注意的是,不同的成员变量共享同一段内存,因此只能同时访问一个成员变量
// demo1
#include<iostream>
using namespace std;

union nameID
{
    int m_id;
    double m_guid;
    char m_name[20];
};

int main() 
{
    nameID myID;
    cout << "sizeof(myID) = " << sizeof(myID) << endl << endl;

    
    cout << "myID.m_id 的地址是 = " << (void*)&myID.m_id << endl;
    cout << "myID.m_guid 的地址是 = " << (void*)&myID.m_guid << endl;
    cout << "myID.m_name 的地址是 = " << (void*)&myID.m_name << endl << endl;

    myID.m_guid = 3;
    cout << "myID.m_id = " << myID.m_id << endl;
    cout << "myID.m_guid = " << myID.m_guid << endl;
    cout << "myID.m_name = " << myID.m_name << endl << endl;

    return 0;
}

输出

sizeof(myID) = 24


myID.m_id 的地址是 = 000000075FB2F8C8
myID.m_guid 的地址是 = 000000075FB2F8C8
myID.m_name 的地址是 = 000000075FB2F8C8


myID.m_id = 0
myID.m_guid = 3
myID.m_name =

2、匿名union

在C++11标准中,还支持匿名union的声明。这种情况下,union没有名称,其中的每个成员都可以直接访问,不需要指定union名称

// demo2
#include<iostream>
//#include<string>
using namespace std;
struct nameID
{
    int i;
    union
    {
        int m_id;
        double m_guid;
        char m_name;
    };
};


int main() 
{
    nameID myID;
    
    myID.m_name = 'a';
    myID.m_guid = 3.0;
     
    myID.i = 8;
    cout << "myID.m_id = " << myID.m_id << endl;
    cout << "myID.m_guid = " << myID.m_guid << endl;
    cout << "myID.m_name = " << myID.m_name << endl << endl;

    cout << "myID.i = " << myID.i << endl << endl;

    return 0;
}

myID.m_id = 0
myID.m_guid = 3
myID.m_name =


myID.i = 8

3、使用类管理union成员

  • 对于union来说,要想构造或销毁类类型的成员必须执行非常复杂的操作,因此我们通常把含有类类型成员的union放在另一个类当中。这个类可以管理并控制与union的类类型成员有关的状态转换。
  • 我们的类将定义一个枚举类型的成员来追踪union成员的状态。
 demo3
#include<iostream>
//#include<string>
using namespace std;
class nameID
{
public:
    enum nameIDType
    {
        ONLY_ID,
        ONLY_GUID,
        ONLY_NAME,
    };
    nameIDType unionType;

    union type
    {
        int m_id;
        double m_guid;
        char m_name;
    }m_nameID;

    nameID() :unionType(ONLY_ID), m_nameID(){}
};



int main() 
{
    nameID myID;

    myID.m_nameID.m_id = 8;
    //myID.unionType = nameID::ONLY_ID;
    cout << "myID.m_id = " << myID.m_nameID.m_id << endl;
    cout << "myID.unionType = ONLY_ID" << endl << endl;

    myID.m_nameID.m_name = 'a';
    //myID.unionType = nameID::ONLY_NAME;
    cout << "myID.m_name = " << myID.m_nameID.m_name << endl;
    cout << "myID.unionType = ONLY_NAME" << endl << endl;

    myID.m_nameID.m_guid = 3.0;
    // myID.unionType = nameID::ONLY_GUID;
    cout << "myID.m_guid = " << myID.m_nameID.m_guid << endl;
    cout << "myID.unionType = ONLY_GUID" << endl << endl;

    return 0;
}

myID.m_id = 8
myID.unionType = ONLY_ID


myID.m_name = a
myID.unionType = ONLY_NAME


myID.m_guid = 3
myID.unionType = ONLY_GUID

  • 下面两种写法等价,
    • type 是 union 类型的名称。在这个 union 类型中,包含了 int 类型的 m_id、double类型的 m_guid 和 char 数组类型的 m_name。
    • 同时,m_nameID 是定义在 nameID 类中的一个对象,它的类型是 type,即上述union 类型。
    • 因此,m_nameID 可以存储三种不同类型的数据:整数、浮点数或者字符串。具体是哪种类型由 nameID 类中的 enum 类型 nameIDType 定义的枚举值决定。
union type
{
    int m_id;
    double m_guid;
    char m_name;
}m_nameID;
union type
{
    int m_id;
    double m_guid;
    char m_name;
};
type m_nameID;
  • 如果不设置unionType只设置m_nameID的值,可以吗
    • 在理论上,是可以不设置 unionType 而直接设置 m_nameID 的值的。因为 union 类型的成员变量的所有数据成员共享同一个内存空间,因此可以通过访问不同的数据成员来改变存储在 union 中的值。但是如果您不设置 unionType ,则可能会在读取 m_nameID 成员的值时出现困难,因为您无法确定当前 m_nameID 成员中存储的数据类型是什么。
    • 所以建议在使用 union 类型时,尽可能的设置枚举类型或者其他方式来标识 union 类型中存储的具体数据类型,这样能够更加清晰、明确地表达程序的意图,并且能够避免由于类型错误引起的问题。

4、管理并销毁string

new (&m_name) std::string 是 C++ 中的一个用于在已分配内存上构造对象的语法,也称为“定位 new”(Placement New)。

在默认情况下,当我们使用 new 关键字创建一个对象时,C++ 会为该对象分配新的内存,然后返回其指针。但有时我们需要在已经分配的内存上构造对象,比如在使用共享内存、内存映射文件或者自定义的内存分配器分配内存时。这时,我们可以使用定位 new 进行对象的构造。

在这个代码示例中,new (&m_name) std::string 创建了一个 std::string 对象,并将其存储到 m_name 所指向的内存地址。这里用到了 C++ 中的引用语法 &,表示获取 m_name 变量的地址。由于 m_name 是 union 的一个成员变量,因此它在不同的 unionType 下所表示的含义不同,可能作为 std::string、int 或者其他类型的存储位置。通过使用定位 new,我们能够在 m_name 所表示的内存位置上创建 std::string 对象,而不必重新分配新的内存。

需要注意的是,使用定位 new 进行对象构造后,在对象生命周期结束之前,我们应该手动调用其析构函数进行销毁,否则就会导致内存泄漏问题。在这个代码示例中,我们在 union 的析构函数中根据 unionType 的值来判断是否需要手动调用 std::string 的析构函数,从而正确地销毁内存。

 demo4
#include<iostream>
#include<string>
using namespace std;

class nameID {
public:
    enum nameIDType {
        ONLY_ID,
        ONLY_GUID,
        ONLY_NAME,
    };
    nameIDType unionType;

    union type {
        int m_id;
        double m_guid;
        string m_name;
        type() noexcept
        {  new(&m_name)string; }
        ~type() {}
    } m_nameID;

    // 默认构造函数
    nameID() : unionType(ONLY_ID), m_nameID() {}

    // 含参构造函数
    nameID(int id) : unionType(ONLY_ID) {
        m_nameID.m_id = id;
    }

    nameID(double guid) : unionType(ONLY_GUID) {
        m_nameID.m_guid = guid;
    }

    nameID(const string& name) : unionType(ONLY_NAME) {
        m_nameID.m_name = name;
    }

    // 拷贝构造函数
    nameID(const nameID& other) : unionType(other.unionType) {
        switch (unionType) {
        case ONLY_ID:
            m_nameID.m_id = other.m_nameID.m_id;
            break;
        case ONLY_GUID:
            m_nameID.m_guid = other.m_nameID.m_guid;
            break;
        case ONLY_NAME:
            m_nameID.m_name = other.m_nameID.m_name;
            break;
        }
    }

    // 赋值运算符重载函数
    nameID& operator=(const nameID& other) {
        if (this != &other) {
            unionType = other.unionType;
            switch (unionType) {
            case ONLY_ID:
                m_nameID.m_id = other.m_nameID.m_id;
                break;
            case ONLY_GUID:
                m_nameID.m_guid = other.m_nameID.m_guid;
                break;
            case ONLY_NAME:
                m_nameID.m_name = other.m_nameID.m_name;
                break;
            }
        }
        return *this;
    }

    // 析构函数
    ~nameID() {
        if (ONLY_NAME == unionType)
        {
            m_nameID.m_name.~string();
        }
    }

    // 类型转换函数
    operator int() const {
        int res;
        switch (unionType) {
        case ONLY_ID:
            res = m_nameID.m_id;
            break;
        case ONLY_GUID:
            res = static_cast<int>(m_nameID.m_guid);
            break;
        case ONLY_NAME:
            res = 0;
            break;
        }
        return res;
    }

    operator double() const {
        double res;
        switch (unionType) {
        case ONLY_ID:
            res = m_nameID.m_id;
            break;
        case ONLY_GUID:
            res = m_nameID.m_guid;
            break;
        case ONLY_NAME:
            res = 0.0;
            break;
        }
        return res;
    }

    operator string() const {
        string res;
        switch (unionType) {
        case ONLY_ID:
            res = to_string(m_nameID.m_id);
            break;
        case ONLY_GUID:
            res = to_string(m_nameID.m_guid);
            break;
        case ONLY_NAME:
            res = m_nameID.m_name;
            break;
        }
        return res;
    }
};

int main()
{
    nameID id1(123);
    nameID id2(3.14);
    nameID id3("Alice");

    cout << static_cast<int>(id1) << endl; // 输出:123
    cout << static_cast<double>(id2) << endl; // 输出:3.14
    cout << static_cast<string>(id3) << endl; // 输出:Alice

    nameID id4 = id3;
    cout << static_cast<string>(id4) << endl; // 输出:Alice

    id4 = id2;
    cout << static_cast<double>(id4) << endl; // 输出:3.14

    return 0;
}

也可以看看下面的写法,

class nameID {
public:
    enum nameIDType {
        ONLY_ID,
        ONLY_GUID,
        ONLY_NAME,
    };
    nameIDType unionType;

    union type {
        int m_id;
        double m_guid;
        string* m_name;
        type(int id) : m_id(id) {}
        type(double guid) : m_guid(guid) {}
        type(const string& name) : m_name(new string(name)) {} // 在 type 的构造函数中创建新的 string 对象
        ~type() { delete m_name; } // 在 type 的析构函数中释放资源
    } m_nameID;

    // 默认构造函数
    nameID() : unionType(ONLY_ID), m_nameID(0) {}

    // 含参构造函数
    nameID(int id) : unionType(ONLY_ID), m_nameID(id) {}

    nameID(double guid) : unionType(ONLY_GUID), m_nameID(guid) {}

    nameID(const string& name) : unionType(ONLY_NAME), m_nameID(name) {}

    // 拷贝构造函数
    nameID(const nameID& other) : unionType(other.unionType), m_nameID(other.m_nameID) {
        if (unionType == ONLY_NAME && m_nameID.m_name != nullptr) {
            m_nameID.m_name = new string(*other.m_nameID.m_name);  // 在拷贝构造函数中创建新的 string 对象
        }
    }
}

http://lihuaxi.xjx100.cn/news/1465258.html

相关文章

茂名 湛江阳江某学校 ibm x3850服务器维修经历

简介&#xff1a;中国广东省阳江市某中学联想 IBM System x3850 x6服务器维修 io板故障处理经历分享&#xff1a; 这一天一位阳江的老师经其他学校老师介绍推荐对接我&#xff0c;说他们学校有一台ibm服务器出问题了&#xff0c;老师大致跟我描述了一下这台服务器发生故障的前…

5.7.webrtc线程的启动与运行

那在上一节课中呢&#xff1f;我向你介绍了web rtc的三大线程&#xff0c;包括了信令线程&#xff0c;工作线程以及网络线程。那同时呢&#xff0c;我们知道了web rtc 3大线程创建的位置以及运行的时机。 对吧&#xff0c;那么今天呢&#xff1f;我们再继续深入了解一下&#…

AUTOSAR规范与ECU软件开发(实践篇)4.4 软件组件代码及描述文件配置生成(下)

目录 4、Simulink-AUTOSAR Mapping配置 (1) Inports/Outports (2) Entry Point Functions (3) Function Callers (4) Data Transfers</

LLM提示词工程和提示词工程师Prompting and prompt engineering

你输入模型的文本被称为提示&#xff0c;生成文本的行为被称为推断&#xff0c;输出文本被称为完成。用于提示的文本或可用的内存的全部量被称为上下文窗口。尽管这里的示例显示模型表现良好&#xff0c;但你经常会遇到模型在第一次尝试时无法产生你想要的结果的情况。你可能需…

语法篇--XML数据传输格式

一、XML概述 1.1简介 XML&#xff0c;全称为Extensible Markup Language&#xff0c;即可扩展标记语言&#xff0c;是一种用于存储和传输数据的文本格式。它是由W3C&#xff08;万维网联盟&#xff09;推荐的标准&#xff0c;广泛应用于各种系统中&#xff0c;如Web服务、数据…

图床项目进度(一)——UI首页

1. 前言 前面我不是说了要做一个图床吗&#xff0c;现在在做ui。 我vue水平不够高&#xff0c;大部分参考b站项目照猫画虎。 vue实战后台 我使用ts&#xff0c;vite&#xff0c;vue3进行了重构。 当然&#xff0c;我对这些理解并不深刻&#xff0c;许多代码都是游离于表面&am…

jps(JVM Process Status Tool):虚拟机进程状况工具

jps&#xff08;JVM Process Status Tool&#xff09;&#xff1a;虚拟机进程状况工具 列出正在运行的虚拟机进程&#xff0c;并显示虚拟机执行主类名称&#xff08;Main Class&#xff0c;main()函数所在的类&#xff09;以及这些进程的本地虚拟机唯一ID&#xff08;LVMID&am…

Dubbo之Wrapper源码解析

功能概述 为了减少反射的调用&#xff0c;Dubbo会为每个服务提供者的实现生成一个Wrapper类&#xff0c;通过Wrapper类去调用真正的接口实现类。 功能分析 核心类Wrapper分析 主要成员变量分析 private static final Map<Class<?>, Wrapper> WRAPPER_MAP ne…