定义

一种节省空间的类。一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。给其中一个成员赋值,其他成员将变成未定义状态。分配给一个union对象的存储空间至少要能容纳它的最大的数据成员。
不能含有引用类型成员,其他类型几乎没有限制,可以含有类类型成员。
成员访问权限默认是公有的,和struct一样。
union不能是派生类,也不能是基类,所以也不能有虚函数。

  1. // Token类型的对象只有一个成员,该成员的类型可能是下列类型中的任意一种
  2. union Token {
  3. // 默认情况下成员是公有的,和struct一样。
  4. char cval;
  5. int ival;
  6. double dval;
  7. };
  8. Token first_token = {'a'); // 初始化 cval 成员,ival、dval未定义。
  9. Token last_token; // 未初始化的 Token 对象
  10. last_token.cval ='z';
  11. Token *pt = new Token; // 指 向一个未初始化的 Token 对象的指针
  12. pt->ival = 42; // cval、dval成员为定义。

匿名union

成员作用域和union本身相同,相当于定义了一组互斥数据。

  1. union { // 匿名union,定义一个未命名的对象,我们可以直接访问它的成员
  2. // 不能有成员函数,不能有protected成员、private成员。
  3. char cval;
  4. int ival;
  5. double dval;
  6. );
  7. cval = 'c';
  8. ival = 42; // cval将变成未定义状态。

成员有类类型

  1. union {
  2. char cval;
  3. Fucker shit;
  4. double dval;
  5. string str;
  6. // union含有类成员,且成员有默认构造或者拷贝控制成员
  7. // union的所有对应的合成版本都是delete的。
  8. // 因此如果类含有一个这样的union时,类的对应合成拷贝控制成员也是delete的。
  9. // 一句话,必须手动设置拷贝控制,不能用编译器默认合成的。
  10. );
  11. Fucker newShit;
  12. shit = newShit; // 调用拷贝构造函数构造对象,注意不是赋值
  13. cval = 'c'; // shit调用析构函数,销毁shit成员。

管理union成员

如果一个union有类类型时,我们可以通过另一个普通类,里面声明匿名union成员来管理。

  1. class Token{
  2. public:
  3. // 因为union含有一个string成员,所以Token必须定义拷贝控制成员
  4. Token();
  5. Token(const Token &t); // 拷贝构造
  6. Token &operator=(const Token &); // 拷贝赋值
  7. virtual ~Token();
  8. //下面的赋值运算符负责设置union的不同成员
  9. Token &operator=(const std::string &);
  10. Token &operator=(char);
  11. Token &operator=(int);
  12. Token &operator=(double);
  13. private:
  14. enum{INT, CHAR, DBL, STR} tok; // 判别式
  15. union{ // 匿名union,每个Token对象含有一个该未命名union类型的未命名成员
  16. char cval;
  17. int ival;
  18. double dval;
  19. std::string sval;
  20. }
  21. void copyUnion(const Token &); // 检查判别式,然后酌情拷贝union成员
  22. }
  23. // 因为union含有一个string成员,所以Token必须定义拷贝控制成员
  24. Token::Token():tok(INT),ival(0){ }
  25. Token::Token(const Token &t)
  26. :tok(t.tok){
  27. copyUnion(t);
  28. }
  29. Token &Token::operator=(const Token &t)
  30. // 如果此对象的值是string而t的值不是,则我们必须释放原来的string
  31. if(tok == STR && t.tok != STR) sval.~string();
  32. else if(tok == STR && t.tok == STR) sval = t.sval; //无须构造一个新string
  33. else {
  34. copyUnion(t); // 如果t.tok是STR,则需要构造一个string
  35. tok = t.tok;
  36. }
  37. return*this;
  38. }
  39. Token::~Token(){
  40. // 如果union含有一个string成员,则我们必须销毁它
  41. iftok == STR) sval.~string();
  42. }
  43. Token &Token::operator=(int i)
  44. if(tok == STR) sval.~string(); // 如果当前存储的是 string, 释放它
  45. ival = i; // 为成员赋值
  46. tok = INT; // 更新判别式
  47. return *this;
  48. }
  49. Token &Token::operator=(const string &s)
  50. if(tok == STR) sval = s; // 如果当前存储的是 string, 可以直接赋值
  51. else {
  52. new(&sval) string(s); // new的定位形式,传入一个地址进行构造,类似allocator.construct
  53. tok = STR; // 更新判别式
  54. }
  55. return *this;
  56. }
  57. void Token::copyUnion(const Token &t){
  58. switch(t.tok){
  59. case Token::INT:
  60. ival = t.ival;
  61. break;
  62. case Token::CHAR:
  63. cval = t.cval;
  64. break;
  65. case Token::DBL:
  66. dval = t.dval;
  67. break;
  68. case Token::STR: // 要想拷贝一个string可以使用定位new表达式构造它。
  69. new (&sval) string(t.sval);
  70. break;
  71. }
  72. }