【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中不能含有虚函数。
联合体也被叫做共用体
二、定义
- 定义
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; // 输出:123cout << static_cast<double>(id2) << endl; // 输出:3.14cout << static_cast<string>(id3) << endl; // 输出:AlicenameID id4 = id3;cout << static_cast<string>(id4) << endl; // 输出:Aliceid4 = id2;cout << static_cast<double>(id4) << endl; // 输出:3.14return 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 对象}}
}