# 第十四章 重载运算与类型转换 ## 练习14.1 > 在什么情况下重载的运算符与内置运算符有所区别?在什么情况下重载的运算符又与内置运算符一样? 解: 我们可以直接调用重载运算符函数。重置运算符与内置运算符有一样的优先级与结合性。 ## 练习14.2 > 为 `Sales_data` 编写重载的输入、输出、加法和复合赋值运算符。 解: 头文件: ```cpp #include #include class Sales_data { friend std::istream& operator>>(std::istream&, Sales_data&); // input friend std::ostream& operator<<(std::ostream&, const Sales_data&); // output friend Sales_data operator+(const Sales_data&, const Sales_data&); // addition public: Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p){ } Sales_data() : Sales_data("", 0, 0.0f){ } Sales_data(const std::string &s) : Sales_data(s, 0, 0.0f){ } Sales_data(std::istream &is); Sales_data& operator+=(const Sales_data&); // compound-assignment std::string isbn() const { return bookNo; } private: inline double avg_price() const; std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; std::istream& operator>>(std::istream&, Sales_data&); std::ostream& operator<<(std::ostream&, const Sales_data&); Sales_data operator+(const Sales_data&, const Sales_data&); inline double Sales_data::avg_price() const { return units_sold ? revenue/units_sold : 0; } ``` 主函数: ```cpp #include "ex_14_02.h" Sales_data::Sales_data(std::istream &is) : Sales_data() { is >> *this; } Sales_data& Sales_data::operator+=(const Sales_data &rhs) { units_sold += rhs.units_sold; revenue += rhs.revenue; return *this; } std::istream& operator>>(std::istream &is, Sales_data &item) { double price = 0.0; is >> item.bookNo >> item.units_sold >> price; if (is) item.revenue = price * item.units_sold; else item = Sales_data(); return is; } std::ostream& operator<<(std::ostream &os, const Sales_data &item) { os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price(); return os; } Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) { Sales_data sum = lhs; sum += rhs; return sum; } ``` ## 练习14.3 > `string` 和 `vector` 都定义了重载的`==`以比较各自的对象,假设 `svec1` 和 `svec2` 是存放 `string` 的 `vector`,确定在下面的表达式中分别使用了哪个版本的`==`? ```cpp (a) "cobble" == "stone" (b) svec1[0] == svec2[0] (c) svec1 == svec2 (d) svec1[0] == "stone" ``` 解: * (a) 都不是。 * (b) `string` * (c) `vector` * (d) `string` ## 练习14.4 > 如何确定下列运算符是否应该是类的成员? ```cpp (a) % (b) %= (c) ++ (d) -> (e) << (f) && (g) == (h) () ``` 解: * (a) 不需要是成员。 * (b) 是成员。 * (c) 是成员。 * (d) 必须是成员。 * (e) 不需要是成员。 * (f) 不需要是成员。 * (g) 不需要是成员。 * (h) 必须是成员。 ## 练习14.5 > 在7.5.1节中的练习7.40中,编写了下列类中某一个的框架,请问在这个类中应该定义重载的运算符吗?如果是,请写出来。 ```cpp (a) Book (b) Date (c) Employee (d) Vehicle (e) Object (f) Tree ``` 解: `Book`,应该重载。 头文件: ```cpp #include #include class Book { friend std::istream& operator>>(std::istream&, Book&); friend std::ostream& operator<<(std::ostream&, const Book&); friend bool operator==(const Book&, const Book&); friend bool operator!=(const Book&, const Book&); public: Book() = default; Book(unsigned no, std::string name, std::string author, std::string pubdate) :no_(no), name_(name), author_(author), pubdate_(pubdate) {} Book(std::istream &in) { in >> *this; } private: unsigned no_; std::string name_; std::string author_; std::string pubdate_; }; std::istream& operator>>(std::istream&, Book&); std::ostream& operator<<(std::ostream&, const Book&); bool operator==(const Book&, const Book&); bool operator!=(const Book&, const Book&); ``` 实现: ```cpp #include "ex_14_5.h" std::istream& operator>>(std::istream &in, Book &book) { in >> book.no_ >> book.name_ >> book.author_ >> book.pubdate_; if (!in) book = Book(); return in; } std::ostream& operator<<(std::ostream &out, const Book &book) { out << book.no_ << " " << book.name_ << " " << book.author_ << " " << book.pubdate_; return out; } bool operator==(const Book &lhs, const Book &rhs) { return lhs.no_ == rhs.no_; } bool operator!=(const Book &lhs, const Book &rhs) { return !(lhs == rhs); } ``` 测试: ```cpp #include "ex_14_5.h" int main() { Book book1(123, "CP5", "Lippman", "2012"); Book book2(123, "CP5", "Lippman", "2012"); if (book1 == book2) std::cout << book1 << std::endl; } ``` ## 练习14.6 > 为你的 `Sales_data` 类定义输出运算符。 解: 参考14.2。 ## 练习14.7 > 你在13.5节的练习中曾经编写了一个`String`类,为它定义一个输出运算符。 解: 头文件: ```cpp #include #include class String { friend std::ostream& operator<<(std::ostream&, const String&); public: String() : String("") {} String(const char *); String(const String&); String& operator=(const String&); ~String(); const char *c_str() const { return elements; } size_t size() const { return end - elements; } size_t length() const { return end - elements - 1; } private: std::pair alloc_n_copy(const char*, const char*); void range_initializer(const char*, const char*); void free(); private: char *elements; char *end; std::allocator alloc; }; std::ostream& operator<<(std::ostream&, const String&); ``` 实现: ```cpp #include "ex_14_7.h" #include #include std::pair String::alloc_n_copy(const char *b, const char *e) { auto str = alloc.allocate(e - b); return{ str, std::uninitialized_copy(b, e, str) }; } void String::range_initializer(const char *first, const char *last) { auto newstr = alloc_n_copy(first, last); elements = newstr.first; end = newstr.second; } String::String(const char *s) { char *sl = const_cast(s); while (*sl) ++sl; range_initializer(s, ++sl); } String::String(const String& rhs) { range_initializer(rhs.elements, rhs.end); std::cout << "copy constructor" << std::endl; } void String::free() { if (elements) { std::for_each(elements, end, [this](char &c) { alloc.destroy(&c); }); alloc.deallocate(elements, end - elements); } } String::~String() { free(); } String& String::operator = (const String &rhs) { auto newstr = alloc_n_copy(rhs.elements, rhs.end); free(); elements = newstr.first; end = newstr.second; std::cout << "copy-assignment" << std::endl; return *this; } std::ostream& operator<<(std::ostream &os, const String &s) { char *c = const_cast(s.c_str()); while (*c) os << *c++; return os; } ``` 测试: ```cpp #include "ex_14_7.h" int main() { String str("Hello World"); std::cout << str << std::endl; } ``` ## 练习14.8 > 你在7.5.1节中的练习中曾经选择并编写了一个类,为它定义一个输出运算符。 解: 参考14.5。 ## 练习14.9 > 为你的 `Sales_data` 类定义输入运算符。 解: 参考14.2。 ## 练习14.10 > 对于 `Sales_data` 的输入运算符来说如果给定了下面的输入将发生什么情况? ```cpp (a) 0-201-99999-9 10 24.95 (b) 10 24.95 0-210-99999-9 ``` 解: * (a) 格式正确。 * (b) 不合法的输入。因为程序试图将 `0-210-99999-9` 转换为 `float`。 ## 练习14.11 > 下面的 `Sales_data` 输入运算符存在错误吗?如果有,请指出来。对于这个输入运算符如果仍然给定上个练习的输入将会发生什么情况? ```cpp istream& operator>>(istream& in, Sales_data& s) { double price; in >> s.bookNo >> s.units_sold >> price; s.revence = s.units_sold >> price; return in; } ``` 解: 没有输入检查,什么也不会发生。 ## 练习14.12 > 你在7.5.1节的练习中曾经选择并编写了一个类,为它定义一个输入运算符并确保该运算符可以处理输入错误。 解: 参考14.5。 ## 练习14.13 > 你认为 `Sales_data` 类还应该支持哪些其他算术运算符?如果有的话,请给出它们的定义。 解: 没有其他了。 ## 练习14.14 > 你觉得为什么调用 `operator+=` 来定义`operator+` 比其他方法更有效? 解: 因为用 `operator+=` 会避免使用一个临时对象,而使得更有效。 ## 练习14.15 > 你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有其他算术运算符吗?如果是,请实现它们;如果不是,解释原因。 解: 头文件: ```cpp #include #include class Book { friend std::istream& operator>>(std::istream&, Book&); friend std::ostream& operator<<(std::ostream&, const Book&); friend bool operator==(const Book&, const Book&); friend bool operator!=(const Book&, const Book&); friend bool operator<(const Book&, const Book&); friend bool operator>(const Book&, const Book&); friend Book operator+(const Book&, const Book&); public: Book() = default; Book(unsigned no, std::string name, std::string author, std::string pubdate, unsigned number) :no_(no), name_(name), author_(author), pubdate_(pubdate), number_(number) {} Book(std::istream &in) { in >> *this; } Book& operator+=(const Book&); private: unsigned no_; std::string name_; std::string author_; std::string pubdate_; unsigned number_; }; std::istream& operator>>(std::istream&, Book&); std::ostream& operator<<(std::ostream&, const Book&); bool operator==(const Book&, const Book&); bool operator!=(const Book&, const Book&); bool operator<(const Book&, const Book&); bool operator>(const Book&, const Book&); Book operator+(const Book&, const Book&); ``` 实现: ```cpp #include "ex_14_15.h" std::istream& operator>>(std::istream &in, Book &book) { in >> book.no_ >> book.name_ >> book.author_ >> book.pubdate_ >> book.number_; return in; } std::ostream& operator<<(std::ostream &out, const Book &book) { out << book.no_ << " " << book.name_ << " " << book.author_ << " " << book.pubdate_ << " " << book.number_ << std::endl; return out; } bool operator==(const Book &lhs, const Book &rhs) { return lhs.no_ == rhs.no_; } bool operator!=(const Book &lhs, const Book &rhs) { return !(lhs == rhs); } bool operator<(const Book &lhs, const Book &rhs) { return lhs.no_ < rhs.no_; } bool operator>(const Book &lhs, const Book &rhs) { return rhs < lhs; } Book& Book::operator+=(const Book &rhs) { if (rhs == *this) this->number_ += rhs.number_; return *this; } Book operator+(const Book &lhs, const Book &rhs) { Book book = lhs; book += rhs; return book; } ``` 测试: ```cpp #include "ex_14_15.h" int main() { Book cp5_1(12345, "CP5", "Lippmen", "2012", 2); Book cp5_2(12345, "CP5", "Lippmen", "2012", 4); std::cout << cp5_1 + cp5_2 << std::endl; } ``` ## 练习14.16 > 为你的 `StrBlob` 类、`StrBlobPtr` 类、`StrVec` 类和 `String` 类分别定义相等运算符和不相等运算符。 解: 略 ## 练习14.17 > 你在7.5.1节中的练习7.40中曾经选择并编写了一个类,你认为它应该含有相等运算符吗?如果是,请实现它;如果不是,解释原因。 解: 参考14.15。 ## 练习14.18 > 为你的 `StrBlob` 类、`StrBlobPtr` 类、`StrVec` 类和 `String` 类分别定义关系运算符。 解: 略 ## 练习14.19 > 你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有关系运算符吗?如果是,请实现它;如果不是,解释原因。 解: 参考14.15。 ## 练习14.20 > 为你的 `Sales_data` 类定义加法和复合赋值运算符。 解: 参考14.2。 ## 练习14.21 > 编写 `Sales_data` 类的`+` 和`+=` 运算符,使得 `+` 执行实际的加法操作而 `+=` 调用`+`。相比14.3节和14.4节对这两个运算符的定义,本题的定义有何缺点?试讨论之。 解: 缺点:使用了一个 `Sales_data` 的临时对象,但它并不是必须的。 ## 练习14.22 > 定义赋值运算符的一个新版本,使得我们能把一个表示 `ISBN` 的 `string` 赋给一个 `Sales_data` 对象。 解: 头文件: ```cpp #include #include class Sales_data { friend std::istream& operator>>(std::istream&, Sales_data&); friend std::ostream& operator<<(std::ostream&, const Sales_data&); friend Sales_data operator+(const Sales_data&, const Sales_data&); public: Sales_data(const std::string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(n*p) {} Sales_data() : Sales_data("", 0, 0.0f) {} Sales_data(const std::string &s) : Sales_data(s, 0, 0.0f) {} Sales_data(std::istream &is); Sales_data& operator=(const std::string&); Sales_data& operator+=(const Sales_data&); std::string isbn() const { return bookNo; } private: inline double avg_price() const; std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; std::istream& operator>>(std::istream&, Sales_data&); std::ostream& operator<<(std::ostream&, const Sales_data&); Sales_data operator+(const Sales_data&, const Sales_data&); inline double Sales_data::avg_price() const { return units_sold ? revenue / units_sold : 0; } ``` 实现: ```cpp #include "ex_14_22.h" Sales_data::Sales_data(std::istream &is) : Sales_data() { is >> *this; } Sales_data& Sales_data::operator+=(const Sales_data &rhs) { units_sold += rhs.units_sold; revenue += rhs.revenue; return *this; } std::istream& operator>>(std::istream &is, Sales_data &item) { double price = 0.0; is >> item.bookNo >> item.units_sold >> price; if (is) item.revenue = price * item.units_sold; else item = Sales_data(); return is; } std::ostream& operator<<(std::ostream &os, const Sales_data &item) { os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price(); return os; } Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) { Sales_data sum = lhs; sum += rhs; return sum; } Sales_data& Sales_data::operator=(const std::string &isbn) { *this = Sales_data(isbn); return *this; } ``` 测试: ```cpp #include "ex_4_22.h" int main() { std::string strCp5("C++ Primer 5th"); Sales_data cp5 = strCp5; std::cout << cp5 << std::endl; } ``` ## 练习14.23 > 为你的`StrVec` 类定义一个 `initializer_list` 赋值运算符。 解: 头文件: ```cpp #include #include #include #ifndef _MSC_VER #define NOEXCEPT noexcept #else #define NOEXCEPT #endif class StrVec { friend bool operator==(const StrVec&, const StrVec&); friend bool operator!=(const StrVec&, const StrVec&); friend bool operator< (const StrVec&, const StrVec&); friend bool operator> (const StrVec&, const StrVec&); friend bool operator<=(const StrVec&, const StrVec&); friend bool operator>=(const StrVec&, const StrVec&); public: StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {} StrVec(std::initializer_list); StrVec(const StrVec&); StrVec& operator=(const StrVec&); StrVec(StrVec&&) NOEXCEPT; StrVec& operator=(StrVec&&)NOEXCEPT; ~StrVec(); StrVec& operator=(std::initializer_list); void push_back(const std::string&); size_t size() const { return first_free - elements; } size_t capacity() const { return cap - elements; } std::string *begin() const { return elements; } std::string *end() const { return first_free; } std::string& at(size_t pos) { return *(elements + pos); } const std::string& at(size_t pos) const { return *(elements + pos); } void reserve(size_t new_cap); void resize(size_t count); void resize(size_t count, const std::string&); private: std::pair alloc_n_copy(const std::string*, const std::string*); void free(); void chk_n_alloc() { if (size() == capacity()) reallocate(); } void reallocate(); void alloc_n_move(size_t new_cap); void range_initialize(const std::string*, const std::string*); private: std::string *elements; std::string *first_free; std::string *cap; std::allocator alloc; }; bool operator==(const StrVec&, const StrVec&); bool operator!=(const StrVec&, const StrVec&); bool operator< (const StrVec&, const StrVec&); bool operator> (const StrVec&, const StrVec&); bool operator<=(const StrVec&, const StrVec&); bool operator>=(const StrVec&, const StrVec&); ``` 实现: ```cpp #include "ex_14_23.h" #include void StrVec::push_back(const std::string &s) { chk_n_alloc(); alloc.construct(first_free++, s); } std::pair StrVec::alloc_n_copy(const std::string *b, const std::string *e) { auto data = alloc.allocate(e - b); return{ data, std::uninitialized_copy(b, e, data) }; } void StrVec::free() { if (elements) { for_each(elements, first_free, [this](std::string &rhs) { alloc.destroy(&rhs); }); alloc.deallocate(elements, cap - elements); } } void StrVec::range_initialize(const std::string *first, const std::string *last) { auto newdata = alloc_n_copy(first, last); elements = newdata.first; first_free = cap = newdata.second; } StrVec::StrVec(const StrVec &rhs) { range_initialize(rhs.begin(), rhs.end()); } StrVec::StrVec(std::initializer_list il) { range_initialize(il.begin(), il.end()); } StrVec::~StrVec() { free(); } StrVec& StrVec::operator = (const StrVec &rhs) { auto data = alloc_n_copy(rhs.begin(), rhs.end()); free(); elements = data.first; first_free = cap = data.second; return *this; } void StrVec::alloc_n_move(size_t new_cap) { auto newdata = alloc.allocate(new_cap); auto dest = newdata; auto elem = elements; for (size_t i = 0; i != size(); ++i) alloc.construct(dest++, std::move(*elem++)); free(); elements = newdata; first_free = dest; cap = elements + new_cap; } void StrVec::reallocate() { auto newcapacity = size() ? 2 * size() : 1; alloc_n_move(newcapacity); } void StrVec::reserve(size_t new_cap) { if (new_cap <= capacity()) return; alloc_n_move(new_cap); } void StrVec::resize(size_t count) { resize(count, std::string()); } void StrVec::resize(size_t count, const std::string &s) { if (count > size()) { if (count > capacity()) reserve(count * 2); for (size_t i = size(); i != count; ++i) alloc.construct(first_free++, s); } else if (count < size()) { while (first_free != elements + count) alloc.destroy(--first_free); } } StrVec::StrVec(StrVec &&s) NOEXCEPT : elements(s.elements), first_free(s.first_free), cap(s.cap) { // leave s in a state in which it is safe to run the destructor. s.elements = s.first_free = s.cap = nullptr; } StrVec& StrVec::operator = (StrVec &&rhs) NOEXCEPT { if (this != &rhs) { free(); elements = rhs.elements; first_free = rhs.first_free; cap = rhs.cap; rhs.elements = rhs.first_free = rhs.cap = nullptr; } return *this; } bool operator==(const StrVec &lhs, const StrVec &rhs) { return (lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin())); } bool operator!=(const StrVec &lhs, const StrVec &rhs) { return !(lhs == rhs); } bool operator<(const StrVec &lhs, const StrVec &rhs) { return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); } bool operator>(const StrVec &lhs, const StrVec &rhs) { return rhs < lhs; } bool operator<=(const StrVec &lhs, const StrVec &rhs) { return !(rhs < lhs); } bool operator>=(const StrVec &lhs, const StrVec &rhs) { return !(lhs < rhs); } StrVec& StrVec::operator=(std::initializer_list il) { *this = StrVec(il); return *this; } ``` 测试: ```cpp #include "ex_14_23.h" #include #include int main() { StrVec vec; vec.reserve(6); std::cout << "capacity(reserve to 6): " << vec.capacity() << std::endl; vec.reserve(4); std::cout << "capacity(reserve to 4): " << vec.capacity() << std::endl; vec.push_back("hello"); vec.push_back("world"); vec.resize(4); for (auto i = vec.begin(); i != vec.end(); ++i) std::cout << *i << std::endl; std::cout << "-EOF-" << std::endl; vec.resize(1); for (auto i = vec.begin(); i != vec.end(); ++i) std::cout << *i << std::endl; std::cout << "-EOF-" << std::endl; StrVec vec_list{ "hello", "world", "pezy" }; for (auto i = vec_list.begin(); i != vec_list.end(); ++i) std::cout << *i << " "; std::cout << std::endl; // Test operator== const StrVec const_vec_list = { "hello", "world", "pezy" }; if (vec_list == const_vec_list) for (const auto &str : const_vec_list) std::cout << str << " "; std::cout << std::endl; // Test operator< const StrVec const_vec_list_small = { "hello", "pezy", "ok" }; std::cout << (const_vec_list_small < const_vec_list) << std::endl; } ``` ## 练习14.24 > 你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有拷贝赋值和移动赋值运算符吗?如果是,请实现它们。 解: 头文件: ```cpp #ifndef DATE_H #define DATE_H #ifndef _MSC_VER #define NOEXCEPT noexcept #else #define NOEXCEPT #endif #include #include class Date { friend bool operator ==(const Date& lhs, const Date& rhs); friend bool operator < (const Date &lhs, const Date &rhs); friend bool check(const Date &d); friend std::ostream& operator <<(std::ostream& os, const Date& d); public: typedef std::size_t Size; // default constructor Date() = default; // constructor taking Size as days explicit Date(Size days); // constructor taking three Size Date(Size d, Size m, Size y) : day(d), month(m), year(y) {} // constructor taking iostream Date(std::istream &is, std::ostream &os); // copy constructor Date(const Date& d); // move constructor Date(Date&& d) NOEXCEPT; // copy operator= Date& operator= (const Date& d); // move operator= Date& operator= (Date&& rhs) NOEXCEPT; // destructor -- in this case, user-defined destructor is not nessary. ~Date() { std::cout << "destroying\n"; } // members Size toDays() const; //not implemented yet. Date& operator +=(Size offset); Date& operator -=(Size offset); private: Size day = 1; Size month = 1; Size year = 0; }; static const Date::Size YtoD_400 = 146097; //365*400 + 400/4 -3 == 146097 static const Date::Size YtoD_100 = 36524; //365*100 + 100/4 -1 == 36524 static const Date::Size YtoD_4 = 1461; //365*4 + 1 == 1461 static const Date::Size YtoD_1 = 365; //365 // normal year static const std::vector monthsVec_n = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // leap year static const std::vector monthsVec_l = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // non-member operators: << >> - == != < <= > >= // std::ostream& operator <<(std::ostream& os, const Date& d); std::istream& operator >>(std::istream& is, Date& d); int operator - (const Date& lhs, const Date& rhs); bool operator ==(const Date& lhs, const Date& rhs); bool operator !=(const Date& lhs, const Date& rhs); bool operator < (const Date& lhs, const Date& rhs); bool operator <=(const Date& lhs, const Date& rhs); bool operator >(const Date& lhs, const Date& rhs); bool operator >=(const Date& lhs, const Date& rhs); Date operator - (const Date& lhs, Date::Size rhs); Date operator +(const Date& lhs, Date::Size rhs); // utillities: bool check(const Date &d); inline bool isLeapYear(Date::Size y); // check if the date object passed in is valid inline bool check(const Date &d) { if (d.month == 0 || d.month >12) return false; else { // month == 1 3 5 7 8 10 12 if (d.month == 1 || d.month == 3 || d.month == 5 || d.month == 7 || d.month == 8 || d.month == 10 || d.month == 12) { if (d.day == 0 || d.day > 31) return false; else return true; } else { // month == 4 6 9 11 if (d.month == 4 || d.month == 6 || d.month == 9 || d.month == 11) { if (d.day == 0 || d.day > 30) return false; else return true; } else { // month == 2 if (isLeapYear(d.year)) { if (d.day == 0 || d.day >29) return false; else return true; } else { if (d.day == 0 || d.day >28) return false; else return true; } } } } } inline bool isLeapYear(Date::Size y) { if (!(y % 400)) { return true; } else { if (!(y % 100)) { return false; } else return !(y % 4); } } #endif // DATE_H ``` 实现: ```cpp #include "ex_14_24.h" #include // constructor taking Size as days // the argument must be within (0, 2^32) Date::Date(Size days) { // calculate the year Size y400 = days / YtoD_400; Size y100 = (days - y400*YtoD_400) / YtoD_100; Size y4 = (days - y400*YtoD_400 - y100*YtoD_100) / YtoD_4; Size y = (days - y400*YtoD_400 - y100*YtoD_100 - y4*YtoD_4) / 365; Size d = days - y400*YtoD_400 - y100*YtoD_100 - y4*YtoD_4 - y * 365; this->year = y400 * 400 + y100 * 100 + y4 * 4 + y; // check if leap and choose the months vector accordingly std::vectorcurrYear = isLeapYear(this->year) ? monthsVec_l : monthsVec_n; // calculate day and month using find_if + lambda Size D_accumu = 0, M_accumu = 0; // @bug fixed: the variabbles above hade been declared inside the find_if as static // which caused the bug. It works fine now after being move outside. std::find_if(currYear.cbegin(), currYear.cend(), [&](Size m) { D_accumu += m; M_accumu++; if (d < D_accumu) { this->month = M_accumu; this->day = d + m - D_accumu; return true; } else return false; }); } // construcotr taking iostream Date::Date(std::istream &is, std::ostream &os) { is >> day >> month >> year; if (is) { if (check(*this)) return; else { os << "Invalid input! Object is default initialized."; *this = Date(); } } else { os << "Invalid input! Object is default initialized."; *this = Date(); } } // copy constructor Date::Date(const Date &d) : day(d.day), month(d.month), year(d.year) {} // move constructor Date::Date(Date&& d) NOEXCEPT : day(d.day), month(d.month), year(d.year) { std::cout << "copy moving"; } // copy operator= Date &Date::operator= (const Date &d) { this->day = d.day; this->month = d.month; this->year = d.year; return *this; } // move operator= Date &Date::operator =(Date&& rhs) NOEXCEPT { if (this != &rhs) { this->day = rhs.day; this->month = rhs.month; this->year = rhs.year; } std::cout << "moving ="; return *this; } // conver to days Date::Size Date::toDays() const { Size result = this->day; // check if leap and choose the months vector accordingly std::vectorcurrYear = isLeapYear(this->year) ? monthsVec_l : monthsVec_n; // calculate result + days by months for (auto it = currYear.cbegin(); it != currYear.cbegin() + this->month - 1; ++it) result += *it; // calculate result + days by years result += (this->year / 400) * YtoD_400; result += (this->year % 400 / 100) * YtoD_100; result += (this->year % 100 / 4) * YtoD_4; result += (this->year % 4) * YtoD_1; return result; } // member operators: += -= Date &Date::operator +=(Date::Size offset) { *this = Date(this->toDays() + offset); return *this; } Date &Date::operator -=(Date::Size offset) { if (this->toDays() > offset) *this = Date(this->toDays() - offset); else *this = Date(); return *this; } // non-member operators: << >> - == != < <= > >= std::ostream& operator <<(std::ostream& os, const Date& d) { os << d.day << " " << d.month << " " << d.year; return os; } std::istream& operator >>(std::istream& is, Date& d) { if (is) { Date input = Date(is, std::cout); if (check(input)) d = input; } return is; } int operator -(const Date &lhs, const Date &rhs) { return lhs.toDays() - rhs.toDays(); } bool operator ==(const Date &lhs, const Date &rhs) { return (lhs.day == rhs.day) && (lhs.month == rhs.month) && (lhs.year == rhs.year); } bool operator !=(const Date &lhs, const Date &rhs) { return !(lhs == rhs); } bool operator < (const Date &lhs, const Date &rhs) { return lhs.toDays() < rhs.toDays(); } bool operator <=(const Date &lhs, const Date &rhs) { return (lhs < rhs) || (lhs == rhs); } bool operator >(const Date &lhs, const Date &rhs) { return !(lhs <= rhs); } bool operator >=(const Date &lhs, const Date &rhs) { return !(lhs < rhs); } Date operator - (const Date &lhs, Date::Size rhs) { // ^^^ rhs must not be larger than 2^32-1 // copy lhs Date result(lhs); result -= rhs; return result; } Date operator + (const Date &lhs, Date::Size rhs) { // ^^^ rhs must not be larger than 2^32-1 // copy lhs Date result(lhs); result += rhs; return result; } ``` 测试: ```cpp #include "ex_14_24.h" #include int main() { Date lhs(9999999), rhs(1); std::cout << (lhs -= 12000) << "\n"; return 0; } ``` ## 练习14.25 > 上题的这个类还需要定义其他赋值运算符吗?如果是,请实现它们;同时说明运算对象应该是什么类型并解释原因。 解: 是。如上题。 ## 练习14.26 > 为你的 `StrBlob` 类、`StrBlobPtr` 类、`StrVec` 类和 `String` 类定义下标运算符。 解: 略 ## 练习14.27 > 为你的 `StrBlobPtr` 类添加递增和递减运算符。 解: 只显示添加的代码: ```cpp class StrBlobPtr { public: string& deref() const; StrBlobPtr& operator++(); StrBlobPtr& operator--(); StrBlobPtr operator++(int); StrBlobPtr operator--(int); StrBlobPtr& operator+=(size_t); StrBlobPtr& operator-=(size_t); StrBlobPtr operator+(size_t) const; StrBlobPtr operator-(size_t) const; }; inline StrBlobPtr& StrBlobPtr::operator++() { check(curr, "increment past end of StrBlobPtr"); ++curr; return *this; } inline StrBlobPtr& StrBlobPtr::operator--() { check(--curr, "decrement past begin of StrBlobPtr"); return *this; } inline StrBlobPtr StrBlobPtr::operator++(int) { StrBlobPtr ret = *this; ++*this; return ret; } inline StrBlobPtr StrBlobPtr::operator--(int) { StrBlobPtr ret = *this; --*this; return ret; } inline StrBlobPtr& StrBlobPtr::operator+=(size_t n) { curr += n; check(curr, "increment past end of StrBlobPtr"); return *this; } inline StrBlobPtr& StrBlobPtr::operator-=(size_t n) { curr -= n; check(curr, "increment past end of StrBlobPtr"); return *this; } inline StrBlobPtr StrBlobPtr::operator+(size_t n) const { StrBlobPtr ret = *this; ret += n; return ret; } inline StrBlobPtr StrBlobPtr::operator-(size_t n) const { StrBlobPtr ret = *this; ret -= n; return ret; } ``` ## 练习14.28 > 为你的 `StrBlobPtr` 类添加加法和减法运算符,使其可以实现指针的算术运算。 解: 参考14.27。 ## 练习14.29 > 为什么不定义`const` 版本的递增和递减运算符? 解: 因为递增和递减会改变对象本身,所以定义 `const` 版本的毫无意义。 ## 练习14.30 > 为你的 `StrBlobPtr` 类和在12.1.6节练习12.22中定义的 `ConstStrBlobPtr` 的类分别添加解引用运算符和箭头运算符。注意:因为 `ConstStrBlobPtr` 的数据成员指向`const vector`,所以`ConstStrBlobPtr` 中的运算符必须返回常量引用。 解: 略。 ## 练习14.31 > 我们的 `StrBlobPtr` 类没有定义拷贝构造函数、赋值运算符以及析构函数,为什么? 解: 因为使用合成的足够了。 ## 练习14.32 > 定义一个类令其含有指向 `StrBlobPtr` 对象的指针,为这个类定义重载的箭头运算符。 解: 头文件: ```cpp class StrBlobPtr; class StrBlobPtr_pointer { public: StrBlobPtr_pointer() = default; StrBlobPtr_pointer(StrBlobPtr* p) : pointer(p) { } StrBlobPtr& operator *() const; StrBlobPtr* operator->() const; private: StrBlobPtr* pointer = nullptr; }; ``` 实现: ```cpp #include "ex_14_32.h" #include "ex_14_30_StrBlob.h" #include StrBlobPtr& StrBlobPtr_pointer::operator *() const { return *pointer; } StrBlobPtr* StrBlobPtr_pointer::operator ->() const { return pointer; } ``` ## 练习14.33 > 一个重载的函数调用运算符应该接受几个运算对象? 解: 一个重载的函数调用运算符接受的运算对象应该和该运算符拥有的操作数一样多。 ## 练习14.34 > 定义一个函数对象类,令其执行`if-then-else` 的操作:该类的调用运算符接受三个形参,它首先检查第一个形参,如果成功返回第二个形参值;如果不成功返回第三个形参的值。 解: ```cpp struct Test { int operator()(bool b, int iA, int iB) { return b ? iA : iB; } }; ``` ## 练习14.35 > 编写一个类似于 `PrintString` 的类,令其从 `istream` 中读取一行输入,然后返回一个表示我们所读内容的`string`。如果读取失败,返回空`string`。 解: ```cpp #include #include class GetInput { public: GetInput(std::istream &i = std::cin) : is(i) {} std::string operator()() const { std::string str; std::getline(is, str); return is ? str : std::string(); } private: std::istream &is; }; int main() { GetInput getInput; std::cout << getInput() << std::endl; } ``` ## 练习14.36 > 使用前一个练习定义的类读取标准输入,将每一行保存为 `vector` 的一个元素。 解: ```cpp #include #include #include class GetInput { public: GetInput(std::istream &i = std::cin) : is(i) {} std::string operator()() const { std::string str; std::getline(is, str); return is ? str : std::string(); } private: std::istream &is; }; int main() { GetInput getInput; std::vector vec; for (std::string tmp; !(tmp = getInput()).empty();) vec.push_back(tmp); for (const auto &str : vec) std::cout << str << " "; std::cout << std::endl; } ``` ## 练习14.37 > 编写一个类令其检查两个值是否相等。使用该对象及标准库算法编写程序,令其替换某个序列中具有给定值的所有实例。 解: ```cpp #include #include #include class IsEqual { int value; public: IsEqual(int v) : value(v) {} bool operator()(int elem) { return elem == value; } }; int main() { std::vector vec = { 3, 2, 1, 4, 3, 7, 8, 6 }; std::replace_if(vec.begin(), vec.end(), IsEqual(3), 5); for (int i : vec) std::cout << i << " "; std::cout << std::endl; } ``` ## 练习14.38 > 编写一个类令其检查某个给定的 `string` 对象的长度是否与一个阈值相等。使用该对象编写程序,统计并报告在输入的文件中长度为1的单词有多少个,长度为2的单词有多少个、......、长度为10的单词有多少个。 解: ```cpp #include #include #include #include #include struct IsInRange { IsInRange(std::size_t lower, std::size_t upper) :_lower(lower), _upper(upper) {} bool operator()(std::string const& str) const { return str.size() >= _lower && str.size() <= _upper; } std::size_t lower_limit() const { return _lower; } std::size_t upper_limit() const { return _upper; } private: std::size_t _lower; std::size_t _upper; }; int main() { //create predicates with various upper limits. std::size_t lower = 1; auto uppers = { 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u }; std::vector predicates; for (auto upper : uppers) predicates.push_back(IsInRange{ lower, upper }); //create count_table to store counts std::map count_table; for (auto upper : uppers) count_table[upper] = 0; //read file and count std::ifstream fin("storyDataFile.txt"); for (std::string word; fin >> word; /* */) for (auto is_size_in_range : predicates) if (is_size_in_range(word)) ++count_table[is_size_in_range.upper_limit()]; //print for (auto pair : count_table) std::cout << "count in range [1, " << pair.first << "] : " << pair.second << std::endl; return 0; } ``` ## 练习14.39 > 修改上一题的程序令其报告长度在1到9之间的单词有多少个、长度在10以上的单词有多少个。 解: 参考14.38。 ## 练习14.40 > 重新编写10.3.2节的`biggies` 函数,使用函数对象替换其中的 `lambda` 表达式。 解: ```cpp #include #include #include #include using namespace std; class ShorterString { public: bool operator()(string const& s1, string const& s2) const { return s1.size() < s2.size(); } }; class BiggerEqual { size_t sz_; public: BiggerEqual(size_t sz) : sz_(sz) {} bool operator()(string const& s) { return s.size() >= sz_; } }; class Print { public: void operator()(string const& s) { cout << s << " "; } }; string make_plural(size_t ctr, string const& word, string const& ending) { return (ctr > 1) ? word + ending : word; } void elimDups(vector &words) { sort(words.begin(), words.end()); auto end_unique = unique(words.begin(), words.end()); words.erase(end_unique, words.end()); } void biggies(vector &words, vector::size_type sz) { elimDups(words); stable_sort(words.begin(), words.end(), ShorterString()); auto wc = find_if(words.begin(), words.end(), BiggerEqual(sz)); auto count = words.end() - wc; cout << count << " " << make_plural(count, "word", "s") << " of length " << sz << " or longer" << endl; for_each(wc, words.end(), Print()); cout << endl; } int main() { vector vec{ "fox", "jumps", "over", "quick", "red", "red", "slow", "the", "turtle" }; biggies(vec, 4); } ``` ## 练习14.41 > 你认为 C++ 11 标准为什么要增加 `lambda`?对于你自己来说,什么情况下会使用 `lambda`,什么情况下会使用类? 解: 使用 `lambda` 是非常方便的,当需要使用一个函数,而这个函数不常使用并且简单时,使用`lambda` 是比较方便的选择。 ## 练习14.42 > 使用标准库函数对象及适配器定义一条表达式,令其 ``` (a) 统计大于1024的值有多少个。 (b) 找到第一个不等于pooh的字符串。 (c) 将所有的值乘以2。 ``` 解: ```cpp std::count_if(ivec.cbegin(), ivec.cend(), std::bind(std::greater(), _1, 1024)); std::find_if(svec.cbegin(), svec.cend(), std::bind(std::not_equal_to(), _1, "pooh")); std::transform(ivec.begin(), ivec.end(), ivec.begin(), std::bind(std::multiplies(), _1, 2)); ``` ## 练习14.43 > 使用标准库函数对象判断一个给定的`int`值是否能被 `int` 容器中的所有元素整除。 解: ```cpp #include #include #include #include int main() { auto data = { 2, 3, 4, 5 }; int input; std::cin >> input; std::modulus mod; auto predicator = [&](int i) { return 0 == mod(input, i); }; auto is_divisible = std::any_of(data.begin(), data.end(), predicator); std::cout << (is_divisible ? "Yes!" : "No!") << std::endl; return 0; } ``` ## 练习14.44 > 编写一个简单的桌面计算器使其能处理二元运算。 解: ```cpp #include #include #include #include int add(int i, int j) { return i + j; } auto mod = [](int i, int j) { return i % j; }; struct Div { int operator ()(int i, int j) const { return i / j; } }; auto binops = std::map> { { "+", add }, // function pointer { "-", std::minus() }, // library functor { "/", Div() }, // user-defined functor { "*", [](int i, int j) { return i*j; } }, // unnamed lambda { "%", mod } // named lambda object }; int main() { while (std::cout << "Pls enter as: num operator num :\n", true) { int lhs, rhs; std::string op; std::cin >> lhs >> op >> rhs; std::cout << binops[op](lhs, rhs) << std::endl; } return 0; } ``` ## 练习14.45 > 编写类型转换运算符将一个 `Sales_data` 对象分别转换成 `string` 和 `double`,你认为这些运算符的返回值应该是什么? 解: 头文件: ```cpp #include #include class Sales_data { friend std::istream& operator>>(std::istream&, Sales_data&); friend std::ostream& operator<<(std::ostream&, const Sales_data&); friend Sales_data operator+(const Sales_data&, const Sales_data&); public: Sales_data(const std::string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(n*p) {} Sales_data() : Sales_data("", 0, 0.0f) {} Sales_data(const std::string &s) : Sales_data(s, 0, 0.0f) {} Sales_data(std::istream &is); Sales_data& operator=(const std::string&); Sales_data& operator+=(const Sales_data&); explicit operator std::string() const { return bookNo; } explicit operator double() const { return avg_price(); } std::string isbn() const { return bookNo; } private: inline double avg_price() const; std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; std::istream& operator>>(std::istream&, Sales_data&); std::ostream& operator<<(std::ostream&, const Sales_data&); Sales_data operator+(const Sales_data&, const Sales_data&); inline double Sales_data::avg_price() const { return units_sold ? revenue / units_sold : 0; } ``` ## 练习14.46 > 你认为应该为 `Sales_data` 类定义上面两种类型转换运算符吗?应该把它们声明成 `explicit` 的吗?为什么? 解: 上面的两种类型转换有歧义,应该声明成 `explicit` 的。 ## 练习14.47 > 说明下面这两个类型转换运算符的区别。 ```cpp struct Integral { operator const int(); operator int() const; } ``` 解: 第一个无意义,会被编译器忽略。第二个合法。 ## 练习14.48 > 你在7.5.1节的练习7.40中曾经选择并编写了一个类,你认为它应该含有向 `bool` 的类型转换运算符吗?如果是,解释原因并说明该运算符是否应该是 `explicit`的;如果不是,也请解释原因。 解: `Date` 类应该含有向 `bool` 的类型转换运算符,并且应该声明为 `explicit` 的。 ## 练习14.49 > 为上一题提到的类定义一个转换目标是 `bool` 的类型转换运算符,先不用在意这么做是否应该。 解: ```cpp explicit operator bool() { return (year<4000) ? true : false; } ``` ## 练习14.50 > 在初始化 `ex1` 和 `ex2` 的过程中,可能用到哪些类类型的转换序列呢?说明初始化是否正确并解释原因。 ```cpp struct LongDouble { LongDouble(double = 0.0); operator double(); operator float(); }; LongDouble ldObj; int ex1 = ldObj; float ex2 = ldObj; ``` 解: `ex1` 转换不合法,没有定义从 `LongDouble` 到 `int` 的转换,从`double`转换还是`float`转换存在二义性。`ex2` 合法。 ## 练习14.51 > 在调用 `calc` 的过程中,可能用到哪些类型转换序列呢?说明最佳可行函数是如何被选出来的。 ```cpp void calc(int); void calc(LongDouble); double dval; calc(dval); // 调用了哪个calc? ``` 解: 最佳可行函数是 `void calc(int)`。 转换的优先级如下: 1. 精确匹配 2. `const` 转换。 3. 类型提升 4. 算术转换 5. 类类型转换 ## 练习14.52 > 在下面的加法表达式中分别选用了哪个`operator+`?列出候选函数、可行函数及为每个可行函数的实参执行的类型转换: ```cpp struct Longdouble { //用于演示的成员operator+;在通常情况下是个非成员 LongDouble operator+(const SmallInt&); //其他成员与14.9.2节一致 }; LongDouble operator+(LongDouble&, double); SmallInt si; LongDouble ld; ld = si + ld; ld = ld + si; ``` 解: `ld = si + ld;` 不合法。`ld = ld + si` 两个都可以调用,但是第一个调用更精确一些。 ## 练习14.53 > 假设我们已经定义了如第522页所示的`SmallInt`,判断下面的加法表达式是否合法。如果合法,使用了哪个加法运算符?如果不合法,应该怎样修改代码才能使其合法? ```cpp SmallInt si; double d = si + 3.14; ``` 解: 不合法,存在二义性。 应该该为: ```cpp SmallInt s1; double d = s1 + SmallInt(3.14); ```