定义
一种节省空间的类。一个 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的值不是,则我们必须释放原来的string
if(tok == STR && t.tok != STR) sval.~string();
else if(tok == STR && t.tok == STR) sval = t.sval; //无须构造一个新string
else {
copyUnion(t); // 如果t.tok是STR,则需要构造一个string
tok = 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.construct
tok = 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;
}
}