定义
一种节省空间的类。一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。给其中一个成员赋值,其他成员将变成未定义状态。分配给一个union对象的存储空间至少要能容纳它的最大的数据成员。
不能含有引用类型成员,其他类型几乎没有限制,可以含有类类型成员。
成员访问权限默认是公有的,和struct一样。
union不能是派生类,也不能是基类,所以也不能有虚函数。
// Token类型的对象只有一个成员,该成员的类型可能是下列类型中的任意一种union Token {// 默认情况下成员是公有的,和struct一样。char cval;int ival;double dval;};Token first_token = {'a'); // 初始化 cval 成员,ival、dval未定义。Token last_token; // 未初始化的 Token 对象last_token.cval ='z';Token *pt = new Token; // 指 向一个未初始化的 Token 对象的指针pt->ival = 42; // cval、dval成员为定义。
匿名union
成员作用域和union本身相同,相当于定义了一组互斥数据。
union { // 匿名union,定义一个未命名的对象,我们可以直接访问它的成员// 不能有成员函数,不能有protected成员、private成员。char cval;int ival;double dval;);cval = 'c';ival = 42; // cval将变成未定义状态。
成员有类类型
union {char cval;Fucker shit;double dval;string str;// union含有类成员,且成员有默认构造或者拷贝控制成员// union的所有对应的合成版本都是delete的。// 因此如果类含有一个这样的union时,类的对应合成拷贝控制成员也是delete的。// 一句话,必须手动设置拷贝控制,不能用编译器默认合成的。);Fucker newShit;shit = newShit; // 调用拷贝构造函数构造对象,注意不是赋值cval = 'c'; // shit调用析构函数,销毁shit成员。
管理union成员
如果一个union有类类型时,我们可以通过另一个普通类,里面声明匿名union成员来管理。
class Token{public:// 因为union含有一个string成员,所以Token必须定义拷贝控制成员Token();Token(const Token &t); // 拷贝构造Token &operator=(const Token &); // 拷贝赋值virtual ~Token();//下面的赋值运算符负责设置union的不同成员Token &operator=(const std::string &);Token &operator=(char);Token &operator=(int);Token &operator=(double);private:enum{INT, CHAR, DBL, STR} tok; // 判别式union{ // 匿名union,每个Token对象含有一个该未命名union类型的未命名成员char cval;int ival;double dval;std::string sval;}void copyUnion(const Token &); // 检查判别式,然后酌情拷贝union成员}// 因为union含有一个string成员,所以Token必须定义拷贝控制成员Token::Token():tok(INT),ival(0){ }Token::Token(const Token &t):tok(t.tok){copyUnion(t);}Token &Token::operator=(const Token &t)// 如果此对象的值是string而t的值不是,则我们必须释放原来的stringif(tok == STR && t.tok != STR) sval.~string();else if(tok == STR && t.tok == STR) sval = t.sval; //无须构造一个新stringelse {copyUnion(t); // 如果t.tok是STR,则需要构造一个stringtok = t.tok;}return*this;}Token::~Token(){// 如果union含有一个string成员,则我们必须销毁它if(tok == STR) sval.~string();}Token &Token::operator=(int i)if(tok == STR) sval.~string(); // 如果当前存储的是 string, 释放它ival = i; // 为成员赋值tok = INT; // 更新判别式return *this;}Token &Token::operator=(const string &s)if(tok == STR) sval = s; // 如果当前存储的是 string, 可以直接赋值else {new(&sval) string(s); // new的定位形式,传入一个地址进行构造,类似allocator.constructtok = STR; // 更新判别式}return *this;}void Token::copyUnion(const Token &t){switch(t.tok){case Token::INT:ival = t.ival;break;case Token::CHAR:cval = t.cval;break;case Token::DBL:dval = t.dval;break;case Token::STR: // 要想拷贝一个string可以使用定位new表达式构造它。new (&sval) string(t.sval);break;}}
