《C++ Primer》第19章 特殊工具与技术
19.6节 union:一种节省空间的类习题答案
练习19.21:编写你自己的Token类。
【出题思路】
通过编写Token类,练习使用类对union成员进行管理控制。
【解答】
class Token {
public:
// 因为union含有一个string成员,所以Token必须定义拷贝控制成员
Token(): tok(INT), ival{0} { }
Token(const Token &t): tok(t.tok) { copyUnion(t); }
Token &operator=(const Token&);
~Token() { if (tok == STR) sval.~string(); }
Token &operator=(const std::string&);
Token &operator=(char);
Token &operator=(int);
Token &operator=(double);
private:
enum {INT, CHAR, DBL, STR} tok; // 判断式
union { // 匿名联合
char cval;
int ival;
double dval;
std::string sval;
}; // 每个Token对象含有一个该未命名联合类型的未命名成员
// 检查判别式,然后酌情拷贝union成员
void copyUnion(const Token&);
};
练习19.22:为你的Token类添加一个Sales_data类型的成员。
【出题思路】
练习在类中添加成员,其中包括对成员的管理判别式、赋新值、销毁等操作。
【解答】
#ifndef PROGRAM19_22_H #define PROGRAM19_22_H #includeusing std::string; #include class Token { friend std::ostream &operator<<(std::ostream&, const Token&); public: // 因为union含有一个string成员,所以Token必须定义拷贝控制成员 // 定义移动构造函数和移动赋值运算符的任务留待本节练习完成 Token(): tok(INT), ival{0} { } Token(const Token &t): tok(t.tok) { copyUnion(t); } Token &operator=(const Token&); // 如果union含有一个Sales_data成员,则我们必须销毁它 Token &operator=(Sales_data); Token &operator=(char); Token &operator=(int); Token &operator=(double); private: enum {INT, CHAR, DBL, SDATA} tok; // 判断式 union { // 匿名联合 char cval; int ival; double dval; Sales_data sval; }; //每个Token对象含有一个该未命名联合类型的未命名成员 // 检查判别式,然后酌情拷贝union成员 void copyUnion(const Token&); }; inline 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: new(&sval) Sales_data(t.sval); break; } } inline std::ostream &operator<<(std::ostream &os, const Token &t) { switch (t.tok) { case Token::INT: return os << t.ival; case Token::CHAR: return os << t.cval; case Token::DBL: return os << t.dval; case Token::STR: return os << t.sval; } } inline Token &Token::operator=(double d) { if (tok == STR) sval.~string(); // if we have a string, free it dval = d; tok = DBL; return *this; } inline Token &Token::operator=(char c) { //如果当前存储的是Sales_data,释放它 if (tok == STR) sval.~Sales_data(); cval = c;//为成员赋值 tok = CHAR;//更新判别式 return *this; } inline Token &Token::operator=(int i) { //如果当前存储的是Sales_data,释放它 if (tok == STR) sval.~Sales_data(); ival = i; // 为成员赋值 tok = INT; // 更新判别式 return *this; } inline Token &Token::operator=(const Sales_data &s) { //如果当前存储的是Sales_data,可以直接赋值 if (tok == SDATA) sval = s; else new(&sval) Sales_data(s); // 否则需要先构造一个Sales_data tok = SDATA; // 更新判别式 return *this; } inline Token &Token::operator=(const Token &t) { //如果此对象的值是string而t的值不是,则我们必须释放原来的string if (tok == STR && t.tok != STR) sval.~Sales_data(); if (tok == STR && t.tok == STR) sval = t.sval;//无须构造一个新string else copyUnion(t);//如果t.tok是STR,则需要构造一个 tok = t.tok; return *this; } #endif // PROGRAM19_22_H
练习19.23:为你的Token类添加移动构造函数和移动赋值运算符。
【出题思路】
定义移动构造函数和移动赋值运算符。
【解答】
#includeusing std::string; #include class Token { friend std::ostream &operator<<(std::ostream&, const Token&); public: // 因为union含有一个string成员,所以Token必须定义拷贝控制成员 Token(): tok(INT), ival{0} { } Token(const Token &t): tok(t.tok) { copyUnion(t); } Token &operator=(const Token&); // 移动构造函数和移动赋值运算符 Token(Token &&other); Token &operator=(Token &&other); //如果union含有一个Sales_data成员,则我们必须销毁它 ~Token() { if(tok == SDATA) savl.~Sales_data(); } Token &operator=(Sales_data); Token &operator=(char); Token &operator=(int); Token &operator=(double); private: enum {INT, CHAR, DBL, SDATA} tok; // 判断式 union { // 匿名联合 char cval; int ival; double dval; Sales_data sval; }; //每个Token对象含有一个该未命名联合类型的未命名成员 // 检查判别式,然后酌情拷贝union成员 void copyUnion(const Token&); }; inline 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: new(&sval) Sales_data(t.sval); break; } } inline std::ostream &operator<<(std::ostream &os, const Token &t) { switch (t.tok) { case Token::INT: return os << t.ival; case Token::CHAR: return os << t.cval; case Token::DBL: return os << t.dval; case Token::STR: return os << t.sval; } } inline Token &Token::operator=(double d) { if (tok == STR) sval.~string(); // if we have a string, free it dval = d; tok = DBL; return *this; } inline Token &Token::operator=(char c) { //如果当前存储的是Sales_data,释放它 if (tok == STR) sval.~Sales_data(); cval = c;//为成员赋值 tok = CHAR;//更新判别式 return *this; } inline Token &Token::operator=(int i) { //如果当前存储的是Sales_data,释放它 if (tok == STR) sval.~Sales_data(); ival = i; // 为成员赋值 tok = INT; // 更新判别式 return *this; } inline Token &Token::operator=(const Sales_data &s) { //如果当前存储的是Sales_data,可以直接赋值 if (tok == SDATA) sval = s; else new(&sval) Sales_data(s); // 否则需要先构造一个Sales_data tok = SDATA; // 更新判别式 return *this; } inline Token &Token::operator=(const Token &t) { //如果此对象的值是string而t的值不是,则我们必须释放原来的string if (tok == STR && t.tok != STR) sval.~Sales_data(); if (tok == STR && t.tok == STR) sval = t.sval;//无须构造一个新string else copyUnion(t);//如果t.tok是STR,则需要构造一个 tok = t.tok; return *this; }
练习19.24:如果我们将一个Token对象赋给它自己将发生什么情况?
【出题思路】
熟悉理解移动赋值运算符的原理。
【解答】
因为在移动赋值运算符上添加了检测机制,当一个对象自己给自己赋值时,会先检测,避免不必要或者是灾难性的后果。
练习19.25:编写一系列赋值运算符,令其分别接受union中各种类型的值。
【出题思路】
熟悉理解赋值运算符的处理机制的原理。
【解答】
#ifndef PROGRAM19_25_H #define PROGRAM19_25_H #includeusing std::string; #include class Token { friend std::ostream &operator<<(std::ostream&, const Token&); public: Token(): tok(INT), ival{0} { } Token(const Token &t): tok(t.tok) { copyUnion(t); } Token &operator=(const Token&); ~Token() { if (tok == STR) sval.~string(); } Token &operator=(const std::string&); Token &operator=(char); Token &operator=(int); Token &operator=(double); private: enum {INT, CHAR, DBL, STR} tok; union { char cval; int ival; double dval; std::string sval; }; void copyUnion(const Token&); }; inline 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: new(&sval) std::string(t.sval); break; } } inline std::ostream &operator<<(std::ostream &os, const Token &t) { switch (t.tok) { case Token::INT: return os << t.ival; case Token::CHAR: return os << t.cval; case Token::DBL: return os << t.dval; case Token::STR: return os << t.sval; } } inline Token &Token::operator=(double d) { if (tok == STR) sval.~string(); dval = d; tok = DBL; return *this; } inline Token &Token::operator=(char c) { if (tok == STR) sval.~string(); cval = c; tok = CHAR; return *this; } inline Token &Token::operator=(int i) { if (tok == STR) sval.~string(); ival = i; tok = INT; return *this; } inline Token &Token::operator=(const std::string &s) { if (tok == STR) sval = s; else new(&sval) std::string(s); tok = STR; return *this; } inline Token &Token::operator=(const Token &t) { if (tok == STR && t.tok != STR) sval.~string(); if (tok == STR && t.tok == STR) sval = t.sval; else copyUnion(t); tok = t.tok; return *this; } #endif // PROGRAM19_25_H
#include "program19_25.h" #includeusing std::string; #include using std::cout; using std::endl; int main() { Token token; Token t2; Token t3; cout << t2 << " " << t3 << endl; t2 = string("tim world!"); t3 = "pineapple bye"; token = t2; token = "cmale"; cout << token << endl; t2 = t3; cout << t2 << endl; token = 58; cout << token << endl; }
运行结果:



