第十五章 面向对象程序设计

练习15.1

什么是虚成员?

解:

对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明成虚函数。

练习15.2

protected 访问说明符与 private 有何区别?

解:

  • protected : 基类和和其派生类还有友元可以访问。
  • private : 只有基类本身和友元可以访问。

练习15.3

定义你自己的 Quote 类和 print_total 函数。

解:

Quote:

  1. #include <string>
  2. class Quote
  3. {
  4. public:
  5. Quote() = default;
  6. Quote(const std::string &b, double p) :
  7. bookNo(b), price(p){}
  8. std::string isbn() const { return bookNo; }
  9. virtual double net_price(std::size_t n) const { return n * price; }
  10. virtual ~Quote() = default;
  11. private:
  12. std::string bookNo;
  13. protected:
  14. double price = 0.0;
  15. };

主函数:

  1. #include "ex_15_3.h"
  2. #include <iostream>
  3. #include <string>
  4. #include <map>
  5. #include <functional>
  6. double print_total(std::ostream& os, const Quote& item, size_t n);
  7. int main()
  8. {
  9. return 0;
  10. }
  11. double print_total(std::ostream &os, const Quote &item, size_t n)
  12. {
  13. double ret = item.net_price(n);
  14. os << "ISBN:" << item.isbn()
  15. << "# sold: " << n << " total due: " << ret << std::endl;
  16. return ret;
  17. }

练习15.4

下面哪条声明语句是不正确的?请解释原因。

  1. class Base { ... };
  2. (a) class Derived : public Derived { ... };
  3. (b) class Derived : private Base { ... };
  4. (c) class Derived : public Base;

解:

  • (a) 不正确。类不能派生自身。
  • (b) 不正确。这是定义而非声明。
  • (c) 不正确。派生列表不能出现在这。

练习15.5

定义你自己的 Bulk_quote 类。

解:

  1. #include "ex_15_3.h"
  2. class Bulk_quote : public Quote
  3. {
  4. public:
  5. Bulk_quote() = default;
  6. Bulk_quote(const std::string& b, double p, std::size_t q, double disc) :
  7. Quote(b, p), min_qty(q), discount(disc) {}
  8. double net_price(std::size_t n) const override;
  9. private:
  10. std::size_t min_qty = 0;
  11. double discount = 0.0;
  12. };

练习15.6

QuoteBulk_quote 的对象传给15.2.1节练习中的 print_total 函数,检查该函数是否正确。

解:

  1. #include "ex_15_3.h"
  2. #include "ex_15_5.h"
  3. #include <iostream>
  4. #include <string>
  5. double print_total(std::ostream& os, const Quote& item, size_t n);
  6. int main()
  7. {
  8. // ex15.6
  9. Quote q("textbook", 10.60);
  10. Bulk_quote bq("textbook", 10.60, 10, 0.3);
  11. print_total(std::cout, q, 12);
  12. print_total(std::cout, bq, 12);
  13. return 0;
  14. }
  15. double print_total(std::ostream &os, const Quote &item, size_t n)
  16. {
  17. double ret = item.net_price(n);
  18. os << "ISBN:" << item.isbn()
  19. << "# sold: " << n << " total due: " << ret << std::endl;
  20. return ret;
  21. }

练习15.7

定义一个类使其实现一种数量受限的折扣策略,具体策略是:当购买书籍的数量不超过一个给定的限量时享受折扣,如果购买量一旦超过了限量,则超出的部分将以原价销售。

解:

  1. #include "ex_15_5.h"
  2. class Limit_quote : public Quote
  3. {
  4. public:
  5. Limit_quote();
  6. Limit_quote(const std::string& b, double p, std::size_t max, double disc) :
  7. Quote(b, p), max_qty(max), discount(disc)
  8. {}
  9. double net_price(std::size_t n) const override;
  10. private:
  11. std::size_t max_qty = 0;
  12. double discount = 0.0;
  13. };
  14. double Limit_quote::net_price(std::size_t n) const
  15. {
  16. if (n > max_qty)
  17. return max_qty * price * discount + (n - max_qty) * price;
  18. else
  19. return n * discount *price;
  20. }

练习15.8

给出静态类型和动态类型的定义。

解:

表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型。动态类型则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知。

练习15.9

在什么情况下表达式的静态类型可能与动态类型不同?请给出三个静态类型与动态类型不同的例子。

解:

基类的指针或引用的静态类型可能与其动态类型不一致。

练习15.10

回忆我们在8.1节进行的讨论,解释第284页中将 ifstream 传递给 Sales_dataread 函数的程序是如何工作的。

解:

std::ifstreamstd::istream 的派生基类,因此 read 函数能够正常工作。

练习15.11

为你的 Quote 类体系添加一个名为 debug 的虚函数,令其分别显示每个类的数据成员。

解:

  1. void Quote::debug() const
  2. {
  3. std::cout << "data members of this class:\n"
  4. << "bookNo= " <<this->bookNo << " "
  5. << "price= " <<this->price<< " ";
  6. }

练习15.12

有必要将一个成员函数同时声明成 overridefinal 吗?为什么?

解:

有必要。override 的含义是重写基类中相同名称的虚函数,final 是阻止它的派生类重写当前虚函数。

练习15.13

给定下面的类,解释每个 print 函数的机理:

  1. class base {
  2. public:
  3. string name() { return basename;}
  4. virtual void print(ostream &os) { os << basename; }
  5. private:
  6. string basename;
  7. };
  8. class derived : public base {
  9. public:
  10. void print(ostream &os) { print(os); os << " " << i; }
  11. private:
  12. int i;
  13. };

在上述代码中存在问题吗?如果有,你该如何修改它?

解:

有问题。应该改为:

  1. void print(ostream &os) override { base::print(os); os << " derived\n " << i; }

练习15.14

给定上一题中的类以及下面这些对象,说明在运行时调用哪个函数:

  1. base bobj; base *bp1 = &bobj; base &br1 = bobj;
  2. derived dobj; base *bp2 = &dobj; base &br2 = dobj;
  3. (a) bobj.print(); (b)dobj.print(); (c)bp1->name();
  4. (d)bp2->name(); (e)br1.print(); (f)br2.print();

解:

  • (a) 编译时。
  • (b) 编译时。
  • (c) 编译时。
  • (d) 编译时。
  • (e) 运行时。base::print()
  • (f) 运行时。derived::print()

练习15.15

定义你自己的 Disc_quoteBulk_quote

解:

Disc_quote:

  1. #include "quote.h"
  2. class Disc_quote : public Quote
  3. {
  4. public:
  5. Disc_quote();
  6. Disc_quote(const std::string& b, double p, std::size_t q, double d) :
  7. Quote(b, p), quantity(q), discount(d) { }
  8. virtual double net_price(std::size_t n) const override = 0;
  9. protected:
  10. std::size_t quantity;
  11. double discount;
  12. };

Bulk_quote:

  1. #include "disc_quote.h"
  2. class Bulk_quote : public Disc_quote
  3. {
  4. public:
  5. Bulk_quote() = default;
  6. Bulk_quote(const std::string& b, double p, std::size_t q, double disc) :
  7. Disc_quote(b, p, q, disc) { }
  8. double net_price(std::size_t n) const override;
  9. void debug() const override;
  10. };

练习15.16

改写你在15.2.2节练习中编写的数量受限的折扣策略,令其继承 Disc_quote

解:

Limit_quote

  1. #include "disc_quote.h"
  2. class Limit_quote : public Disc_quote
  3. {
  4. public:
  5. Limit_quote() = default;
  6. Limit_quote(const std::string& b, double p, std::size_t max, double disc):
  7. Disc_quote(b, p, max, disc) { }
  8. double net_price(std::size_t n) const override
  9. { return n * price * (n < quantity ? 1 - discount : 1 ); }
  10. void debug() const override;
  11. };

练习15.17

尝试定义一个 Disc_quote 的对象,看看编译器给出的错误信息是什么?

解:

error: cannot declare variable 'd' to be of abstract type 'Disc_quote': Disc_quote d;

练习15.18

假设给定了第543页和第544页的类,同时已知每个对象的类型如注释所示,判断下面的哪些赋值语句是合法的。解释那些不合法的语句为什么不被允许:

  1. Base *p = &d1; //d1 的类型是 Pub_Derv
  2. p = &d2; //d2 的类型是 Priv_Derv
  3. p = &d3; //d3 的类型是 Prot_Derv
  4. p = &dd1; //dd1 的类型是 Derived_from_Public
  5. p = &dd2; //dd2 的类型是 Derived_from_Private
  6. p = &dd3; //dd3 的类型是 Derived_from_Protected

解:

  1. Base *p = &d1; 合法
  2. p = &d2; 不合法
  3. p = &d3; 不合法
  4. p = &dd1; 合法
  5. p = &dd2; 不合法
  6. p = &dd3; 不合法

只有在派生类是使用public的方式继承基类时,用户代码才可以使用派生类到基类(derived-to-base)的转换。

练习15.19

假设543页和544页的每个类都有如下形式的成员函数:

  1. void memfcn(Base &b) { b = *this; }

对于每个类,分别判断上面的函数是否合法。

解:

合法:

  • Pub_Derv
  • Priv_Derv
  • Prot_Derv
  • Derived_from_Public
  • Derived_from_Protected
    不合法:
  • Derived_from_Private

这段代码是在成员函数中使用BasePriv_Drev中的Base部分虽然是private的,但其成员函数依然可以访问;Derived_from_Private继承自Priv_Drev,不能访问Priv_Drev中的private成员,因此不合法。

练习15.20

编写代码检验你对前面两题的回答是否正确。

解:

  1. #include <iostream>
  2. #include <string>
  3. #include "exercise15_5.h"
  4. #include "bulk_quote.h"
  5. #include "limit_quote.h"
  6. #include "disc_quote.h"
  7. class Base
  8. {
  9. public:
  10. void pub_mem(); // public member
  11. protected:
  12. int prot_mem; // protected member
  13. private:
  14. char priv_mem; // private member
  15. };
  16. struct Pub_Derv : public Base
  17. {
  18. void memfcn(Base &b) { b = *this; }
  19. };
  20. struct Priv_Derv : private Base
  21. {
  22. void memfcn(Base &b) { b = *this; }
  23. };
  24. struct Prot_Derv : protected Base
  25. {
  26. void memfcn(Base &b) { b = *this; }
  27. };
  28. struct Derived_from_Public : public Pub_Derv
  29. {
  30. void memfcn(Base &b) { b = *this; }
  31. };
  32. struct Derived_from_Private : public Priv_Derv
  33. {
  34. //void memfcn(Base &b) { b = *this; }
  35. };
  36. struct Derived_from_Protected : public Prot_Derv
  37. {
  38. void memfcn(Base &b) { b = *this; }
  39. };
  40. int main()
  41. {
  42. Pub_Derv d1;
  43. Base *p = &d1;
  44. Priv_Derv d2;
  45. //p = &d2;
  46. Prot_Derv d3;
  47. //p = &d3;
  48. Derived_from_Public dd1;
  49. p = &dd1;
  50. Derived_from_Private dd2;
  51. //p =& dd2;
  52. Derived_from_Protected dd3;
  53. //p = &dd3;
  54. return 0;
  55. }

练习15.21

从下面这些一般性抽象概念中任选一个(或者选一个你自己的),将其对应的一组类型组织成一个继承体系:

  1. (a) 图形文件格式(如giftiffjpegbmp
  2. (b) 图形基元(如方格、圆、球、圆锥)
  3. (c) C++语言中的类型(如类、函数、成员函数)

解:

  1. #include <iostream>
  2. #include <string>
  3. #include "quote.h"
  4. #include "bulk_quote.h"
  5. #include "limit_quote.h"
  6. #include "disc_quote.h"
  7. // just for 2D shape
  8. class Shape
  9. {
  10. public:
  11. typedef std::pair<double, double> Coordinate;
  12. Shape() = default;
  13. Shape(const std::string& n) :
  14. name(n) { }
  15. virtual double area() const = 0;
  16. virtual double perimeter() const = 0;
  17. virtual ~Shape() = default;
  18. private:
  19. std::string name;
  20. };
  21. class Rectangle : public Shape
  22. {
  23. public:
  24. Rectangle() = default;
  25. Rectangle(const std::string& n,
  26. const Coordinate& a,
  27. const Coordinate& b,
  28. const Coordinate& c,
  29. const Coordinate& d) :
  30. Shape(n), a(a), b(b), c(c), d(d) { }
  31. ~Rectangle() = default;
  32. protected:
  33. Coordinate a;
  34. Coordinate b;
  35. Coordinate c;
  36. Coordinate d;
  37. };
  38. class Square : public Rectangle
  39. {
  40. public:
  41. Square() = default;
  42. Square(const std::string& n,
  43. const Coordinate& a,
  44. const Coordinate& b,
  45. const Coordinate& c,
  46. const Coordinate& d) :
  47. Rectangle(n, a, b, c, d) { }
  48. ~Square() = default;
  49. };
  50. int main()
  51. {
  52. return 0;
  53. }

练习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

定义 QuoteBulk_quote 的拷贝控制成员,令其与合成的版本行为一致。为这些成员以及其他构造函数添加打印状态的语句,使得我们能够知道正在运行哪个程序。使用这些类编写程序,预测程序将创建和销毁哪些对象。重复实验,不断比较你的预测和实际输出结果是否相同,直到预测完全准确再结束。

解:

Quote:

  1. #include <string>
  2. #include <iostream>
  3. class Quote
  4. {
  5. friend bool operator !=(const Quote& lhs, const Quote& rhs);
  6. public:
  7. Quote() { std::cout << "default constructing Quote\n"; }
  8. Quote(const std::string &b, double p) :
  9. bookNo(b), price(p)
  10. {
  11. std::cout << "Quote : constructor taking 2 parameters\n";
  12. }
  13. // copy constructor
  14. Quote(const Quote& q) : bookNo(q.bookNo), price(q.price)
  15. {
  16. std::cout << "Quote: copy constructing\n";
  17. }
  18. // move constructor
  19. Quote(Quote&& q) noexcept : bookNo(std::move(q.bookNo)), price(std::move(q.price))
  20. { std::cout << "Quote: move constructing\n"; }
  21. // copy =
  22. Quote& operator =(const Quote& rhs)
  23. {
  24. if (*this != rhs)
  25. {
  26. bookNo = rhs.bookNo;
  27. price = rhs.price;
  28. }
  29. std::cout << "Quote: copy =() \n";
  30. return *this;
  31. }
  32. // move =
  33. Quote& operator =(Quote&& rhs) noexcept
  34. {
  35. if (*this != rhs)
  36. {
  37. bookNo = std::move(rhs.bookNo);
  38. price = std::move(rhs.price);
  39. }
  40. std::cout << "Quote: move =!!!!!!!!! \n";
  41. return *this;
  42. }
  43. std::string isbn() const { return bookNo; }
  44. virtual double net_price(std::size_t n) const { return n * price; }
  45. virtual void debug() const;
  46. virtual ~Quote()
  47. {
  48. std::cout << "destructing Quote\n";
  49. }
  50. private:
  51. std::string bookNo;
  52. protected:
  53. double price = 10.0;
  54. };
  55. bool inline
  56. operator !=(const Quote& lhs, const Quote& rhs)
  57. {
  58. return lhs.bookNo != rhs.bookNo
  59. &&
  60. lhs.price != rhs.price;
  61. }

Bulk_quote:

  1. #include "Disc_quote.h"
  2. #include <iostream>
  3. class Bulk_quote : public Disc_quote
  4. {
  5. public:
  6. Bulk_quote() { std::cout << "default constructing Bulk_quote\n"; }
  7. Bulk_quote(const std::string& b, double p, std::size_t q, double disc) :
  8. Disc_quote(b, p, q, disc)
  9. {
  10. std::cout << "Bulk_quote : constructor taking 4 parameters\n";
  11. }
  12. // copy constructor
  13. Bulk_quote(const Bulk_quote& bq) : Disc_quote(bq)
  14. {
  15. std::cout << "Bulk_quote : copy constructor\n";
  16. }
  17. // move constructor
  18. Bulk_quote(Bulk_quote&& bq) : Disc_quote(std::move(bq)) noexcept
  19. {
  20. std::cout << "Bulk_quote : move constructor\n";
  21. }
  22. // copy =()
  23. Bulk_quote& operator =(const Bulk_quote& rhs)
  24. {
  25. Disc_quote::operator =(rhs);
  26. std::cout << "Bulk_quote : copy =()\n";
  27. return *this;
  28. }
  29. // move =()
  30. Bulk_quote& operator =(Bulk_quote&& rhs) noexcept
  31. {
  32. Disc_quote::operator =(std::move(rhs));
  33. std::cout << "Bulk_quote : move =()\n";
  34. return *this;
  35. }
  36. double net_price(std::size_t n) const override;
  37. void debug() const override;
  38. ~Bulk_quote() override
  39. {
  40. std::cout << "destructing Bulk_quote\n";
  41. }
  42. };

程序输出结果:

  1. default constructing Quote
  2. default constructing Disc_quote
  3. default constructing Bulk_quote
  4. Quote : constructor taking 2 parameters
  5. Disc_quote : constructor taking 4 parameters.
  6. Bulk_quote : constructor taking 4 parameters
  7. Quote: copy constructing
  8. Quote: copy constructing
  9. destructing Quote
  10. destructing Quote
  11. Disc_quote : move =()
  12. Bulk_quote : move =()
  13. destructing Bulk_quote
  14. destructing Dis_quote
  15. destructing Quote
  16. destructing Bulk_quote
  17. destructing Dis_quote
  18. destructing Quote

练习15.27

重新定义你的 Bulk_quote 类,令其继承构造函数。

解:

  1. #include "disc_quote.h"
  2. #include <iostream>
  3. class Bulk_quote : public Disc_quote
  4. {
  5. public:
  6. Bulk_quote() { std::cout << "default constructing Bulk_quote\n"; }
  7. // changed the below to the inherited constructor for ex15.27.
  8. // rules: 1. only inherit from the direct base class.
  9. // 2. default, copy and move constructors can not inherit.
  10. // 3. any data members of its own are default initialized.
  11. // 4. the rest details are in the section section 15.7.4.
  12. /*
  13. Bulk_quote(const std::string& b, double p, std::size_t q, double disc) :
  14. Disc_quote(b, p, q, disc) { std::cout << "Bulk_quote : constructor taking 4 parameters\n"; }
  15. */
  16. using Disc_quote::Disc_quote;
  17. // copy constructor
  18. Bulk_quote(const Bulk_quote& bq) : Disc_quote(bq)
  19. {
  20. std::cout << "Bulk_quote : copy constructor\n";
  21. }
  22. // move constructor
  23. Bulk_quote(Bulk_quote&& bq) : Disc_quote(std::move(bq))
  24. {
  25. std::cout << "Bulk_quote : move constructor\n";
  26. }
  27. // copy =()
  28. Bulk_quote& operator =(const Bulk_quote& rhs)
  29. {
  30. Disc_quote::operator =(rhs);
  31. std::cout << "Bulk_quote : copy =()\n";
  32. return *this;
  33. }
  34. // move =()
  35. Bulk_quote& operator =(Bulk_quote&& rhs)
  36. {
  37. Disc_quote::operator =(std::move(rhs));
  38. std::cout << "Bulk_quote : move =()\n";
  39. return *this;
  40. }
  41. double net_price(std::size_t n) const override;
  42. void debug() const override;
  43. ~Bulk_quote() override
  44. {
  45. std::cout << "destructing Bulk_quote\n";
  46. }
  47. };

练习15.28

定义一个存放 Quote 对象的 vector,将 Bulk_quote 对象传入其中。计算 vector 中所有元素总的 net_price

解:

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <memory>
  5. #include "quote.h"
  6. #include "bulk_quote.h"
  7. #include "limit_quote.h"
  8. #include "disc_quote.h"
  9. int main()
  10. {
  11. /**
  12. * @brief ex15.28 outcome == 9090
  13. */
  14. std::vector<Quote> v;
  15. for (unsigned i = 1; i != 10; ++i)
  16. v.push_back(Bulk_quote("sss", i * 10.1, 10, 0.3));
  17. double total = 0;
  18. for (const auto& b : v)
  19. {
  20. total += b.net_price(20);
  21. }
  22. std::cout << total << std::endl;
  23. std::cout << "======================\n\n";
  24. /**
  25. * @brief ex15.29 outccome == 6363
  26. */
  27. std::vector<std::shared_ptr<Quote>> pv;
  28. for (unsigned i = 1; i != 10; ++i)
  29. pv.push_back(std::make_shared<Bulk_quote>(Bulk_quote("sss", i * 10.1, 10, 0.3)));
  30. double total_p = 0;
  31. for (auto p : pv)
  32. {
  33. total_p += p->net_price(20);
  34. }
  35. std::cout << total_p << std::endl;
  36. return 0;
  37. }

练习15.29

再运行一次你的程序,这次传入 Quote 对象的 shared_ptr 。如果这次计算出的总额与之前的不一致,解释为什么;如果一直,也请说明原因。

解:

因为智能指针导致了多态性的产生,所以这次计算的总额不一致。

练习15.30

编写你自己的 Basket 类,用它计算上一个练习中交易记录的总价格。

解:

Basket h:

  1. #include "quote.h"
  2. #include <set>
  3. #include <memory>
  4. // 购物篮
  5. // a basket of objects from Quote hierachy, using smart pointers.
  6. class Basket
  7. {
  8. public:
  9. // Basket使用合成的默认构造函数和拷贝控制成员
  10. // copy verison
  11. void add_item(const Quote& sale)
  12. {
  13. items.insert(std::shared_ptr<Quote>(sale.clone()));
  14. }
  15. // move version
  16. void add_item(Quote&& sale)
  17. {
  18. items.insert(std::shared_ptr<Quote>(std::move(sale).clone()));
  19. }
  20. // 打印每本书的总价和购物篮中所有书的总价
  21. double total_receipt(std::ostream& os) const;
  22. private:
  23. // function to compare needed by the multiset member
  24. static bool compare(const std::shared_ptr<Quote>& lhs,
  25. const std::shared_ptr<Quote>& rhs)
  26. {
  27. return lhs->isbn() < rhs->isbn();
  28. }
  29. // hold multiple quotes, ordered by the compare member
  30. std::multiset<std::shared_ptr<Quote>, decltype(compare)*>
  31. items{ compare };
  32. };

Basket cpp:

  1. #include "basket.h"
  2. double Basket::total_receipt(std::ostream &os) const
  3. {
  4. double sum = 0.0; // 保存实时计算出的总价格
  5. // iter指向ISBN相同的一批元素中的第一个
  6. // upper_bound返回一个迭代器,该迭代器指向这批元素的尾后位置
  7. for (auto iter = items.cbegin(); iter != items.cend();
  8. iter = items.upper_bound(*iter))
  9. // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  10. // @note this increment moves iter to the first element with key
  11. // greater than *iter.
  12. {
  13. sum += print_total(os, **iter, items.count(*iter));
  14. } // ^^^^^^^^^^^^^ using count to fetch
  15. // the number of the same book.
  16. os << "Total Sale: " << sum << std::endl;
  17. return sum;
  18. }

main:

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <memory>
  5. #include <fstream>
  6. #include "quote.h"
  7. #include "bulk_quote.h"
  8. #include "limit_quote.h"
  9. #include "disc_quote.h"
  10. #include "basket.h"
  11. int main()
  12. {
  13. Basket basket;
  14. for (unsigned i = 0; i != 10; ++i)
  15. basket.add_item(Bulk_quote("Bible", 20.6, 20, 0.3));
  16. for (unsigned i = 0; i != 10; ++i)
  17. basket.add_item(Bulk_quote("C++Primer", 30.9, 5, 0.4));
  18. for (unsigned i = 0; i != 10; ++i)
  19. basket.add_item(Quote("CLRS", 40.1));
  20. std::ofstream log("log.txt", std::ios_base::app | std::ios_base::out);
  21. basket.total_receipt(log);
  22. return 0;
  23. }

练习15.31

已知 s1s2s3s4 都是 string,判断下面的表达式分别创建了什么样的对象:

  1. (a) Query(s1) | Query(s2) & ~Query(s3);
  2. (b) Query(s1) | (Query(s2) & ~Query(s3));
  3. (c) (Query(s1) & (Query(s2)) | (Query(s3) & Query(s4)));

解:

  1. (a) OrQuery, AndQuery, NotQuery, WordQuery
  2. (b) OrQuery, AndQuery, NotQuery, WordQuery
  3. (c) OrQuery, AndQuery, WordQuery

练习15.32

当一个 Query 类型的对象被拷贝、移动、赋值或销毁时,将分别发生什么?

解:

  • 拷贝:当被拷贝时,合成的拷贝构造函数被调用。它将拷贝两个数据成员至新的对象。而在这种情况下,数据成员是一个智能指针,当拷贝时,相应的智能指针指向相同的地址,计数器增加1.
  • 移动:当移动时,合成的移动构造函数被调用。它将移动数据成员至新的对象。这时新对象的智能指针将会指向原对象的地址,而原对象的智能指针为 nullptr,新对象的智能指针的引用计数为 1。
  • 赋值:合成的赋值运算符被调用,结果和拷贝的相同的。
  • 销毁:合成的析构函数被调用。对象的智能指针的引用计数递减,当引用计数为 0 时,对象被销毁。

练习15.33

当一个 Query_base 类型的对象被拷贝、移动赋值或销毁时,将分别发生什么?

解:

由合成的版本来控制。然而 Query_base 是一个抽象类,它的对象实际上是它的派生类对象。

练习15.34

针对图15.3构建的表达式:

  1. (a) 例举出在处理表达式的过程中执行的所有构造函数。
  2. (b) 例举出 cout << q 所调用的 rep
  3. (c) 例举出 q.eval() 所调用的 eval

解:

  • aQuery 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_base> 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_base> 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:

  1. #ifndef QUERY_H
  2. #define QUERY_H
  3. #include <iostream>
  4. #include <string>
  5. #include <memory>
  6. #include "query_base.h"
  7. #include "queryresult.h"
  8. #include "textquery.h"
  9. #include "wordquery.h"
  10. /**
  11. * @brief interface class to manage the Query_base inheritance hierachy
  12. */
  13. class Query
  14. {
  15. friend Query operator~(const Query&);
  16. friend Query operator|(const Query&, const Query&);
  17. friend Query operator&(const Query&, const Query&);
  18. public:
  19. // build a new WordQuery
  20. Query(const std::string& s) : q(new WordQuery(s))
  21. {
  22. std::cout << "Query::Query(const std::string& s) where s=" + s + "\n";
  23. }
  24. // interface functions: call the corresponding Query_base operatopns
  25. QueryResult eval(const TextQuery& t) const
  26. {
  27. return q->eval(t);
  28. }
  29. std::string rep() const
  30. {
  31. std::cout << "Query::rep() \n";
  32. return q->rep();
  33. }
  34. private:
  35. // constructor only for friends
  36. Query(std::shared_ptr<Query_base> query) :
  37. q(query)
  38. {
  39. std::cout << "Query::Query(std::shared_ptr<Query_base> query)\n";
  40. }
  41. std::shared_ptr<Query_base> q;
  42. };
  43. inline std::ostream&
  44. operator << (std::ostream& os, const Query& query)
  45. {
  46. // make a virtual call through its Query_base pointer to rep();
  47. return os << query.rep();
  48. }
  49. #endif // QUERY_H

Query_base:

  1. #ifndef QUERY_BASE_H
  2. #define QUERY_BASE_H
  3. #include "textquery.h"
  4. #include "queryresult.h"
  5. /**
  6. * @brief abstract class acts as a base class for all concrete query types
  7. * all members are private.
  8. */
  9. class Query_base
  10. {
  11. friend class Query;
  12. protected:
  13. using line_no = TextQuery::line_no; // used in the eval function
  14. virtual ~Query_base() = default;
  15. private:
  16. // returns QueryResult that matches this query
  17. virtual QueryResult eval(const TextQuery&) const = 0;
  18. // a string representation of this query
  19. virtual std::string rep() const = 0;
  20. };
  21. #endif // QUERY_BASE_H

练习15.36

在构造函数和 rep 成员中添加打印语句,运行你的代码以检验你对本节第一个练习中(a)、(b)两小题的回答是否正确。

解:

  1. Query q = Query("fiery") & Query("bird") | Query("wind");
  2. WordQuery::WordQuery(wind)
  3. Query::Query(const std::string& s) where s=wind
  4. WordQuery::WordQuery(bird)
  5. Query::Query(const std::string& s) where s=bird
  6. WordQuery::WordQuery(fiery)
  7. Query::Query(const std::string& s) where s=fiery
  8. BinaryQuery::BinaryQuery() where s=&
  9. AndQuery::AndQuery()
  10. Query::Query(std::shared_ptr<Query_base> query)
  11. BinaryQuery::BinaryQuery() where s=|
  12. OrQuery::OrQuery
  13. Query::Query(std::shared_ptr<Query_base> query)
  14. Press <RETURN> to close this window...
  1. std::cout << q <<std::endl;
  2. Query::rep()
  3. BinaryQuery::rep()
  4. Query::rep()
  5. WodQuery::rep()
  6. Query::rep()
  7. BinaryQuery::rep()
  8. Query::rep()
  9. WodQuery::rep()
  10. Query::rep()
  11. WodQuery::rep()
  12. ((fiery & bird) | wind)
  13. Press <RETURN> to close this window...

练习15.37

如果在派生类中含有 shared_ptr<Query_base> 类型的成员而非 Query 类型的成员,则你的类需要做出怎样的改变?

解:

参考15.35。

练习15.38

下面的声明合法吗?如果不合法,请解释原因;如果合法,请指出该声明的含义。

  1. BinaryQuery a = Query("fiery") & Query("bird");
  2. AndQuery b = Query("fiery") & Query("bird");
  3. OrQuery c = Query("fiery") & Query("bird");

解:

  1. 不合法。因为 BinaryQuery 是抽象类。
  2. 不合法。& 操作返回的是一个 Query 对象。
  3. 不合法。& 操作返回的是一个 Query 对象。

练习15.39

实现 Query 类和 Query_base 类,求图15.3中表达式的值并打印相关信息,验证你的程序是否正确。

练习15.40

OrQueryeval 函数中,如果 rhs 成员返回的是空集将发生什么?

解:

不会发生什么。代码如下:

  1. std::shared_ptr<std::set<line_no>> ret_lines =
  2. std::make_shared<std::set<line_no>>(left.begin(), left.end());

如果 rhs 成员返回的是空集,在 set 当中不会添加什么。

练习15.41

重新实现你的类,这次使用指向 Query_base 的内置指针而非 shared_ptr。请注意,做出上述改动后你的类将不能再使用合成的拷贝控制成员。

解:

练习15.42

从下面的几种改进中选择一种,设计并实现它:

  1. (a) 按句子查询并打印单词,而不再是按行打印。
  2. (b) 引入一个历史系统,用户可以按编号查阅之前的某个查询,并可以在其中添加内容或者将其余其他查询组合。
  3. (c) 允许用户对结果做出限制,比如从给定范围的行中跳出匹配的进行显示。

解:

TextQuery最终项目

见 cpp_source/cha5/text_query