# 第十五章 面向对象程序设计 ## 练习15.1 > 什么是虚成员? 解: 对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明成虚函数。 ## 练习15.2 > `protected` 访问说明符与 `private` 有何区别? 解: * `protected` : 基类和和其派生类还有友元可以访问。 * `private` : 只有基类本身和友元可以访问。 ## 练习15.3 > 定义你自己的 `Quote` 类和 `print_total` 函数。 解: `Quote`: ```cpp #include class Quote { public: Quote() = default; Quote(const std::string &b, double p) : bookNo(b), price(p){} std::string isbn() const { return bookNo; } virtual double net_price(std::size_t n) const { return n * price; } virtual ~Quote() = default; private: std::string bookNo; protected: double price = 0.0; }; ``` 主函数: ```cpp #include "ex_15_3.h" #include #include #include #include double print_total(std::ostream& os, const Quote& item, size_t n); int main() { return 0; } double print_total(std::ostream &os, const Quote &item, size_t n) { double ret = item.net_price(n); os << "ISBN:" << item.isbn() << "# sold: " << n << " total due: " << ret << std::endl; return ret; } ``` ## 练习15.4 > 下面哪条声明语句是不正确的?请解释原因。 ```cpp class Base { ... }; (a) class Derived : public Derived { ... }; (b) class Derived : private Base { ... }; (c) class Derived : public Base; ``` 解: * (a) 不正确。类不能派生自身。 * (b) 不正确。这是定义而非声明。 * (c) 不正确。派生列表不能出现在这。 ## 练习15.5 > 定义你自己的 `Bulk_quote` 类。 解: ```cpp #include "ex_15_3.h" class Bulk_quote : public Quote { public: Bulk_quote() = default; Bulk_quote(const std::string& b, double p, std::size_t q, double disc) : Quote(b, p), min_qty(q), discount(disc) {} double net_price(std::size_t n) const override; private: std::size_t min_qty = 0; double discount = 0.0; }; ``` ## 练习15.6 > 将 `Quote` 和 `Bulk_quote` 的对象传给15.2.1节练习中的 `print_total` 函数,检查该函数是否正确。 解: ```cpp #include "ex_15_3.h" #include "ex_15_5.h" #include #include double print_total(std::ostream& os, const Quote& item, size_t n); int main() { // ex15.6 Quote q("textbook", 10.60); Bulk_quote bq("textbook", 10.60, 10, 0.3); print_total(std::cout, q, 12); print_total(std::cout, bq, 12); return 0; } double print_total(std::ostream &os, const Quote &item, size_t n) { double ret = item.net_price(n); os << "ISBN:" << item.isbn() << "# sold: " << n << " total due: " << ret << std::endl; return ret; } ``` ## 练习15.7 > 定义一个类使其实现一种数量受限的折扣策略,具体策略是:当购买书籍的数量不超过一个给定的限量时享受折扣,如果购买量一旦超过了限量,则超出的部分将以原价销售。 解: ```cpp #include "ex_15_5.h" class Limit_quote : public Quote { public: Limit_quote(); Limit_quote(const std::string& b, double p, std::size_t max, double disc) : Quote(b, p), max_qty(max), discount(disc) {} double net_price(std::size_t n) const override; private: std::size_t max_qty = 0; double discount = 0.0; }; double Limit_quote::net_price(std::size_t n) const { if (n > max_qty) return max_qty * price * discount + (n - max_qty) * price; else return n * discount *price; } ``` ## 练习15.8 > 给出静态类型和动态类型的定义。 解: 表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型。动态类型则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知。 ## 练习15.9 > 在什么情况下表达式的静态类型可能与动态类型不同?请给出三个静态类型与动态类型不同的例子。 解: 基类的指针或引用的静态类型可能与其动态类型不一致。 ## 练习15.10 > 回忆我们在8.1节进行的讨论,解释第284页中将 `ifstream` 传递给 `Sales_data` 的`read` 函数的程序是如何工作的。 解: `std::ifstream` 是 `std::istream` 的派生基类,因此 `read` 函数能够正常工作。 ## 练习15.11 > 为你的 `Quote` 类体系添加一个名为 `debug` 的虚函数,令其分别显示每个类的数据成员。 解: ```cpp void Quote::debug() const { std::cout << "data members of this class:\n" << "bookNo= " <bookNo << " " << "price= " <price<< " "; } ``` ## 练习15.12 > 有必要将一个成员函数同时声明成 `override` 和 `final` 吗?为什么? 解: 有必要。`override` 的含义是重写基类中相同名称的虚函数,`final` 是阻止它的派生类重写当前虚函数。 ## 练习15.13 > 给定下面的类,解释每个 `print` 函数的机理: ```cpp class base { public: string name() { return basename;} virtual void print(ostream &os) { os << basename; } private: string basename; }; class derived : public base { public: void print(ostream &os) { print(os); os << " " << i; } private: int i; }; ``` 在上述代码中存在问题吗?如果有,你该如何修改它? 解: 有问题。应该改为: ```cpp void print(ostream &os) override { base::print(os); os << " derived\n " << i; } ``` ## 练习15.14 > 给定上一题中的类以及下面这些对象,说明在运行时调用哪个函数: ```cpp base bobj; base *bp1 = &bobj; base &br1 = bobj; derived dobj; base *bp2 = &dobj; base &br2 = dobj; (a) bobj.print(); (b)dobj.print(); (c)bp1->name(); (d)bp2->name(); (e)br1.print(); (f)br2.print(); ``` 解: * (a) 编译时。 * (b) 编译时。 * (c) 编译时。 * (d) 编译时。 * (e) 运行时。`base::print()` * (f) 运行时。`derived::print()` ## 练习15.15 > 定义你自己的 `Disc_quote` 和 `Bulk_quote`。 解: `Disc_quote`: ```cpp #include "quote.h" class Disc_quote : public Quote { public: Disc_quote(); Disc_quote(const std::string& b, double p, std::size_t q, double d) : Quote(b, p), quantity(q), discount(d) { } virtual double net_price(std::size_t n) const override = 0; protected: std::size_t quantity; double discount; }; ``` `Bulk_quote`: ```cpp #include "disc_quote.h" class Bulk_quote : public Disc_quote { public: Bulk_quote() = default; Bulk_quote(const std::string& b, double p, std::size_t q, double disc) : Disc_quote(b, p, q, disc) { } double net_price(std::size_t n) const override; void debug() const override; }; ``` ## 练习15.16 > 改写你在15.2.2节练习中编写的数量受限的折扣策略,令其继承 `Disc_quote`。 解: `Limit_quote`: ```cpp #include "disc_quote.h" class Limit_quote : public Disc_quote { public: Limit_quote() = default; Limit_quote(const std::string& b, double p, std::size_t max, double disc): Disc_quote(b, p, max, disc) { } double net_price(std::size_t n) const override { return n * price * (n < quantity ? 1 - discount : 1 ); } void debug() const override; }; ``` ## 练习15.17 > 尝试定义一个 `Disc_quote` 的对象,看看编译器给出的错误信息是什么? 解: `error: cannot declare variable 'd' to be of abstract type 'Disc_quote': Disc_quote d;` ## 练习15.18 > 假设给定了第543页和第544页的类,同时已知每个对象的类型如注释所示,判断下面的哪些赋值语句是合法的。解释那些不合法的语句为什么不被允许: ```cpp Base *p = &d1; //d1 的类型是 Pub_Derv p = &d2; //d2 的类型是 Priv_Derv p = &d3; //d3 的类型是 Prot_Derv p = &dd1; //dd1 的类型是 Derived_from_Public p = &dd2; //dd2 的类型是 Derived_from_Private p = &dd3; //dd3 的类型是 Derived_from_Protected ``` 解: ```cpp Base *p = &d1; 合法 p = &d2; 不合法 p = &d3; 不合法 p = &dd1; 合法 p = &dd2; 不合法 p = &dd3; 不合法 ``` 只有在派生类是使用`public`的方式继承基类时,用户代码才可以使用派生类到基类(`derived-to-base`)的转换。 ## 练习15.19 > 假设543页和544页的每个类都有如下形式的成员函数: ```cpp void memfcn(Base &b) { b = *this; } ``` 对于每个类,分别判断上面的函数是否合法。 解: 合法: * Pub_Derv * Priv_Derv * Prot_Derv * Derived_from_Public * Derived_from_Protected 不合法: * Derived_from_Private 这段代码是在成员函数中使用`Base`。`Priv_Drev`中的`Base`部分虽然是`private`的,但其成员函数依然可以访问;`Derived_from_Private`继承自`Priv_Drev`,不能访问`Priv_Drev`中的`private`成员,因此不合法。 ## 练习15.20 > 编写代码检验你对前面两题的回答是否正确。 解: ```cpp #include #include #include "exercise15_5.h" #include "bulk_quote.h" #include "limit_quote.h" #include "disc_quote.h" class Base { public: void pub_mem(); // public member protected: int prot_mem; // protected member private: char priv_mem; // private member }; struct Pub_Derv : public Base { void memfcn(Base &b) { b = *this; } }; struct Priv_Derv : private Base { void memfcn(Base &b) { b = *this; } }; struct Prot_Derv : protected Base { void memfcn(Base &b) { b = *this; } }; struct Derived_from_Public : public Pub_Derv { void memfcn(Base &b) { b = *this; } }; struct Derived_from_Private : public Priv_Derv { //void memfcn(Base &b) { b = *this; } }; struct Derived_from_Protected : public Prot_Derv { void memfcn(Base &b) { b = *this; } }; int main() { Pub_Derv d1; Base *p = &d1; Priv_Derv d2; //p = &d2; Prot_Derv d3; //p = &d3; Derived_from_Public dd1; p = &dd1; Derived_from_Private dd2; //p =& dd2; Derived_from_Protected dd3; //p = &dd3; return 0; } ``` ## 练习15.21 > 从下面这些一般性抽象概念中任选一个(或者选一个你自己的),将其对应的一组类型组织成一个继承体系: ```cpp (a) 图形文件格式(如gif、tiff、jpeg、bmp) (b) 图形基元(如方格、圆、球、圆锥) (c) C++语言中的类型(如类、函数、成员函数) ``` 解: ```cpp #include #include #include "quote.h" #include "bulk_quote.h" #include "limit_quote.h" #include "disc_quote.h" // just for 2D shape class Shape { public: typedef std::pair Coordinate; Shape() = default; Shape(const std::string& n) : name(n) { } virtual double area() const = 0; virtual double perimeter() const = 0; virtual ~Shape() = default; private: std::string name; }; class Rectangle : public Shape { public: Rectangle() = default; Rectangle(const std::string& n, const Coordinate& a, const Coordinate& b, const Coordinate& c, const Coordinate& d) : Shape(n), a(a), b(b), c(c), d(d) { } ~Rectangle() = default; protected: Coordinate a; Coordinate b; Coordinate c; Coordinate d; }; class Square : public Rectangle { public: Square() = default; Square(const std::string& n, const Coordinate& a, const Coordinate& b, const Coordinate& c, const Coordinate& d) : Rectangle(n, a, b, c, d) { } ~Square() = default; }; int main() { return 0; } ``` ## 练习15.22 > 对于你在上一题中选择的类,为其添加函数的虚函数及公有成员和受保护的成员。 解: 参考15.21。 ## 练习15.23 > 假设第550页的 `D1` 类需要覆盖它继承而来的 `fcn` 函数,你应该如何对其进行修改?如果你修改之后 `fcn` 匹配了 `Base` 中的定义,则该节的那些调用语句将如何解析? 解: 移除 `int` 参数。 ## 练习15.24 > 哪种类需要虚析构函数?虚析构函数必须执行什么样的操作? 解: 基类通常应该定义一个虚析构函数。 ## 练习15.25 > 我们为什么为 `Disc_quote` 定义一个默认构造函数?如果去掉该构造函数的话会对 `Bulk_quote` 的行为产生什么影响? 解: 因为`Disc_quote`的默认构造函数会运行`Quote`的默认构造函数,而`Quote`默认构造函数会完成成员的初始化工作。 如果去除掉该构造函数的话,`Bulk_quote`的默认构造函数而无法完成`Disc_quote`的初始化工作。 ## 练习15.26 > 定义 `Quote` 和 `Bulk_quote` 的拷贝控制成员,令其与合成的版本行为一致。为这些成员以及其他构造函数添加打印状态的语句,使得我们能够知道正在运行哪个程序。使用这些类编写程序,预测程序将创建和销毁哪些对象。重复实验,不断比较你的预测和实际输出结果是否相同,直到预测完全准确再结束。 解: `Quote`: ```cpp #include #include class Quote { friend bool operator !=(const Quote& lhs, const Quote& rhs); public: Quote() { std::cout << "default constructing Quote\n"; } Quote(const std::string &b, double p) : bookNo(b), price(p) { std::cout << "Quote : constructor taking 2 parameters\n"; } // copy constructor Quote(const Quote& q) : bookNo(q.bookNo), price(q.price) { std::cout << "Quote: copy constructing\n"; } // move constructor Quote(Quote&& q) noexcept : bookNo(std::move(q.bookNo)), price(std::move(q.price)) { std::cout << "Quote: move constructing\n"; } // copy = Quote& operator =(const Quote& rhs) { if (*this != rhs) { bookNo = rhs.bookNo; price = rhs.price; } std::cout << "Quote: copy =() \n"; return *this; } // move = Quote& operator =(Quote&& rhs) noexcept { if (*this != rhs) { bookNo = std::move(rhs.bookNo); price = std::move(rhs.price); } std::cout << "Quote: move =!!!!!!!!! \n"; return *this; } std::string isbn() const { return bookNo; } virtual double net_price(std::size_t n) const { return n * price; } virtual void debug() const; virtual ~Quote() { std::cout << "destructing Quote\n"; } private: std::string bookNo; protected: double price = 10.0; }; bool inline operator !=(const Quote& lhs, const Quote& rhs) { return lhs.bookNo != rhs.bookNo && lhs.price != rhs.price; } ``` `Bulk_quote`: ```cpp #include "Disc_quote.h" #include class Bulk_quote : public Disc_quote { public: Bulk_quote() { std::cout << "default constructing Bulk_quote\n"; } Bulk_quote(const std::string& b, double p, std::size_t q, double disc) : Disc_quote(b, p, q, disc) { std::cout << "Bulk_quote : constructor taking 4 parameters\n"; } // copy constructor Bulk_quote(const Bulk_quote& bq) : Disc_quote(bq) { std::cout << "Bulk_quote : copy constructor\n"; } // move constructor Bulk_quote(Bulk_quote&& bq) : Disc_quote(std::move(bq)) noexcept { std::cout << "Bulk_quote : move constructor\n"; } // copy =() Bulk_quote& operator =(const Bulk_quote& rhs) { Disc_quote::operator =(rhs); std::cout << "Bulk_quote : copy =()\n"; return *this; } // move =() Bulk_quote& operator =(Bulk_quote&& rhs) noexcept { Disc_quote::operator =(std::move(rhs)); std::cout << "Bulk_quote : move =()\n"; return *this; } double net_price(std::size_t n) const override; void debug() const override; ~Bulk_quote() override { std::cout << "destructing Bulk_quote\n"; } }; ``` 程序输出结果: ``` default constructing Quote default constructing Disc_quote default constructing Bulk_quote Quote : constructor taking 2 parameters Disc_quote : constructor taking 4 parameters. Bulk_quote : constructor taking 4 parameters Quote: copy constructing Quote: copy constructing destructing Quote destructing Quote Disc_quote : move =() Bulk_quote : move =() destructing Bulk_quote destructing Dis_quote destructing Quote destructing Bulk_quote destructing Dis_quote destructing Quote ``` ## 练习15.27 > 重新定义你的 `Bulk_quote` 类,令其继承构造函数。 解: ```cpp #include "disc_quote.h" #include class Bulk_quote : public Disc_quote { public: Bulk_quote() { std::cout << "default constructing Bulk_quote\n"; } // changed the below to the inherited constructor for ex15.27. // rules: 1. only inherit from the direct base class. // 2. default, copy and move constructors can not inherit. // 3. any data members of its own are default initialized. // 4. the rest details are in the section section 15.7.4. /* Bulk_quote(const std::string& b, double p, std::size_t q, double disc) : Disc_quote(b, p, q, disc) { std::cout << "Bulk_quote : constructor taking 4 parameters\n"; } */ using Disc_quote::Disc_quote; // copy constructor Bulk_quote(const Bulk_quote& bq) : Disc_quote(bq) { std::cout << "Bulk_quote : copy constructor\n"; } // move constructor Bulk_quote(Bulk_quote&& bq) : Disc_quote(std::move(bq)) { std::cout << "Bulk_quote : move constructor\n"; } // copy =() Bulk_quote& operator =(const Bulk_quote& rhs) { Disc_quote::operator =(rhs); std::cout << "Bulk_quote : copy =()\n"; return *this; } // move =() Bulk_quote& operator =(Bulk_quote&& rhs) { Disc_quote::operator =(std::move(rhs)); std::cout << "Bulk_quote : move =()\n"; return *this; } double net_price(std::size_t n) const override; void debug() const override; ~Bulk_quote() override { std::cout << "destructing Bulk_quote\n"; } }; ``` ## 练习15.28 > 定义一个存放 `Quote` 对象的 `vector`,将 `Bulk_quote` 对象传入其中。计算 `vector` 中所有元素总的 `net_price`。 解: ```cpp #include #include #include #include #include "quote.h" #include "bulk_quote.h" #include "limit_quote.h" #include "disc_quote.h" int main() { /** * @brief ex15.28 outcome == 9090 */ std::vector v; for (unsigned i = 1; i != 10; ++i) v.push_back(Bulk_quote("sss", i * 10.1, 10, 0.3)); double total = 0; for (const auto& b : v) { total += b.net_price(20); } std::cout << total << std::endl; std::cout << "======================\n\n"; /** * @brief ex15.29 outccome == 6363 */ std::vector> pv; for (unsigned i = 1; i != 10; ++i) pv.push_back(std::make_shared(Bulk_quote("sss", i * 10.1, 10, 0.3))); double total_p = 0; for (auto p : pv) { total_p += p->net_price(20); } std::cout << total_p << std::endl; return 0; } ``` ## 练习15.29 > 再运行一次你的程序,这次传入 `Quote` 对象的 `shared_ptr` 。如果这次计算出的总额与之前的不一致,解释为什么;如果一直,也请说明原因。 解: 因为智能指针导致了多态性的产生,所以这次计算的总额不一致。 ## 练习15.30 > 编写你自己的 `Basket` 类,用它计算上一个练习中交易记录的总价格。 解: `Basket h`: ```cpp #include "quote.h" #include #include // 购物篮 // a basket of objects from Quote hierachy, using smart pointers. class Basket { public: // Basket使用合成的默认构造函数和拷贝控制成员 // copy verison void add_item(const Quote& sale) { items.insert(std::shared_ptr(sale.clone())); } // move version void add_item(Quote&& sale) { items.insert(std::shared_ptr(std::move(sale).clone())); } // 打印每本书的总价和购物篮中所有书的总价 double total_receipt(std::ostream& os) const; private: // function to compare needed by the multiset member static bool compare(const std::shared_ptr& lhs, const std::shared_ptr& rhs) { return lhs->isbn() < rhs->isbn(); } // hold multiple quotes, ordered by the compare member std::multiset, decltype(compare)*> items{ compare }; }; ``` `Basket cpp`: ```cpp #include "basket.h" double Basket::total_receipt(std::ostream &os) const { double sum = 0.0; // 保存实时计算出的总价格 // iter指向ISBN相同的一批元素中的第一个 // upper_bound返回一个迭代器,该迭代器指向这批元素的尾后位置 for (auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound(*iter)) // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ // @note this increment moves iter to the first element with key // greater than *iter. { sum += print_total(os, **iter, items.count(*iter)); } // ^^^^^^^^^^^^^ using count to fetch // the number of the same book. os << "Total Sale: " << sum << std::endl; return sum; } ``` `main`: ```cpp #include #include #include #include #include #include "quote.h" #include "bulk_quote.h" #include "limit_quote.h" #include "disc_quote.h" #include "basket.h" int main() { Basket basket; for (unsigned i = 0; i != 10; ++i) basket.add_item(Bulk_quote("Bible", 20.6, 20, 0.3)); for (unsigned i = 0; i != 10; ++i) basket.add_item(Bulk_quote("C++Primer", 30.9, 5, 0.4)); for (unsigned i = 0; i != 10; ++i) basket.add_item(Quote("CLRS", 40.1)); std::ofstream log("log.txt", std::ios_base::app | std::ios_base::out); basket.total_receipt(log); return 0; } ``` ## 练习15.31 > 已知 `s1`、`s2`、`s3` 和 `s4` 都是 `string`,判断下面的表达式分别创建了什么样的对象: ```cpp (a) Query(s1) | Query(s2) & ~Query(s3); (b) Query(s1) | (Query(s2) & ~Query(s3)); (c) (Query(s1) & (Query(s2)) | (Query(s3) & Query(s4))); ``` 解: ```cpp (a) OrQuery, AndQuery, NotQuery, WordQuery (b) OrQuery, AndQuery, NotQuery, WordQuery (c) OrQuery, AndQuery, WordQuery ``` ## 练习15.32 > 当一个 `Query` 类型的对象被拷贝、移动、赋值或销毁时,将分别发生什么? 解: * **拷贝**:当被拷贝时,合成的拷贝构造函数被调用。它将拷贝两个数据成员至新的对象。而在这种情况下,数据成员是一个智能指针,当拷贝时,相应的智能指针指向相同的地址,计数器增加1. * **移动**:当移动时,合成的移动构造函数被调用。它将移动数据成员至新的对象。这时新对象的智能指针将会指向原对象的地址,而原对象的智能指针为 `nullptr`,新对象的智能指针的引用计数为 1。 * **赋值**:合成的赋值运算符被调用,结果和拷贝的相同的。 * **销毁**:合成的析构函数被调用。对象的智能指针的引用计数递减,当引用计数为 0 时,对象被销毁。 ## 练习15.33 > 当一个 `Query_base` 类型的对象被拷贝、移动赋值或销毁时,将分别发生什么? 解: 由合成的版本来控制。然而 `Query_base` 是一个抽象类,它的对象实际上是它的派生类对象。 ## 练习15.34 > 针对图15.3构建的表达式: ```cpp (a) 例举出在处理表达式的过程中执行的所有构造函数。 (b) 例举出 cout << q 所调用的 rep。 (c) 例举出 q.eval() 所调用的 eval。 ``` 解: * **a**: `Query q = Query("fiery") & Query("bird") | Query("wind");` 1. `Query::Query(const std::string& s)` where s == "fiery","bird" and "wind" 2. `WordQuery::WordQuery(const std::string& s)` where s == "fiery","bird" and "wind" 3. `AndQuery::AndQuery(const Query& left, const Query& right);` 4. `BinaryQuery(const Query&l, const Query& r, std::string s);` 5. `Query::Query(std::shared_ptr query)` 2times 6. `OrQuery::OrQuery(const Query& left, const Query& right);` 7. `BinaryQuery(const Query&l, const Query& r, std::string s);` 8. `Query::Query(std::shared_ptr query)` 2times * **b**: 1. `query.rep()` inside the operator <<(). 2. `q->rep()` inside the member function rep(). 3. `OrQuery::rep()` which is inherited from `BinaryQuery`. 4. `Query::rep()` for `lhs` and `rhs`: for `rhs` which is a `WordQuery` : `WordQuery::rep()` where `query_word("wind")` is returned.For `lhs` which is an `AndQuery`. 5. `AndQuery::rep()` which is inherited from `BinaryQuery`. 6. `BinaryQuer::rep()`: for `rhs: WordQuery::rep()` where query_word("fiery") is returned. For `lhs: WordQuery::rep()` where query_word("bird" ) is returned. * **c**: 1. `q.eval()` 2. `q->rep()`: where q is a pointer to `OrQuary`. 3. `QueryResult eval(const TextQuery& )const override`: is called but this one has not been defined yet. ## 练习15.35 > 实现 `Query` 类和 `Query_base` 类,其中需要定义`rep` 而无须定义 `eval`。 解: `Query`: ```cpp #ifndef QUERY_H #define QUERY_H #include #include #include #include "query_base.h" #include "queryresult.h" #include "textquery.h" #include "wordquery.h" /** * @brief interface class to manage the Query_base inheritance hierachy */ class Query { friend Query operator~(const Query&); friend Query operator|(const Query&, const Query&); friend Query operator&(const Query&, const Query&); public: // build a new WordQuery Query(const std::string& s) : q(new WordQuery(s)) { std::cout << "Query::Query(const std::string& s) where s=" + s + "\n"; } // interface functions: call the corresponding Query_base operatopns QueryResult eval(const TextQuery& t) const { return q->eval(t); } std::string rep() const { std::cout << "Query::rep() \n"; return q->rep(); } private: // constructor only for friends Query(std::shared_ptr query) : q(query) { std::cout << "Query::Query(std::shared_ptr query)\n"; } std::shared_ptr q; }; inline std::ostream& operator << (std::ostream& os, const Query& query) { // make a virtual call through its Query_base pointer to rep(); return os << query.rep(); } #endif // QUERY_H ``` `Query_base`: ```cpp #ifndef QUERY_BASE_H #define QUERY_BASE_H #include "textquery.h" #include "queryresult.h" /** * @brief abstract class acts as a base class for all concrete query types * all members are private. */ class Query_base { friend class Query; protected: using line_no = TextQuery::line_no; // used in the eval function virtual ~Query_base() = default; private: // returns QueryResult that matches this query virtual QueryResult eval(const TextQuery&) const = 0; // a string representation of this query virtual std::string rep() const = 0; }; #endif // QUERY_BASE_H ``` ## 练习15.36 > 在构造函数和 `rep` 成员中添加打印语句,运行你的代码以检验你对本节第一个练习中(a)、(b)两小题的回答是否正确。 解: ```cpp Query q = Query("fiery") & Query("bird") | Query("wind"); WordQuery::WordQuery(wind) Query::Query(const std::string& s) where s=wind WordQuery::WordQuery(bird) Query::Query(const std::string& s) where s=bird WordQuery::WordQuery(fiery) Query::Query(const std::string& s) where s=fiery BinaryQuery::BinaryQuery() where s=& AndQuery::AndQuery() Query::Query(std::shared_ptr query) BinaryQuery::BinaryQuery() where s=| OrQuery::OrQuery Query::Query(std::shared_ptr query) Press to close this window... ``` ```cpp std::cout << q < to close this window... ``` ## 练习15.37 > 如果在派生类中含有 `shared_ptr` 类型的成员而非 `Query` 类型的成员,则你的类需要做出怎样的改变? 解: 参考15.35。 ## 练习15.38 > 下面的声明合法吗?如果不合法,请解释原因;如果合法,请指出该声明的含义。 ```cpp BinaryQuery a = Query("fiery") & Query("bird"); AndQuery b = Query("fiery") & Query("bird"); OrQuery c = Query("fiery") & Query("bird"); ``` 解: 1. 不合法。因为 `BinaryQuery` 是抽象类。 2. 不合法。`&` 操作返回的是一个 `Query` 对象。 3. 不合法。`&` 操作返回的是一个 `Query` 对象。 ## 练习15.39 > 实现 `Query` 类和 `Query_base` 类,求图15.3中表达式的值并打印相关信息,验证你的程序是否正确。 ## 练习15.40 > 在 `OrQuery` 的 `eval` 函数中,如果 `rhs` 成员返回的是空集将发生什么? 解: 不会发生什么。代码如下: ```cpp std::shared_ptr> ret_lines = std::make_shared>(left.begin(), left.end()); ``` 如果 `rhs` 成员返回的是空集,在 `set` 当中不会添加什么。 ## 练习15.41 > 重新实现你的类,这次使用指向 `Query_base` 的内置指针而非 `shared_ptr`。请注意,做出上述改动后你的类将不能再使用合成的拷贝控制成员。 解: 略 ## 练习15.42 > 从下面的几种改进中选择一种,设计并实现它: ```cpp (a) 按句子查询并打印单词,而不再是按行打印。 (b) 引入一个历史系统,用户可以按编号查阅之前的某个查询,并可以在其中添加内容或者将其余其他查询组合。 (c) 允许用户对结果做出限制,比如从给定范围的行中跳出匹配的进行显示。 ``` 解: 略 ## TextQuery最终项目 见 cpp_source/cha5/text_query