15.1
【出题思路】
熟悉理解虚函数、虚成员的定义。
【解答】
在类中被声明为 virtual 的成员,基类希望这种成员在派生类中重定义。除了构造函数外,任意非 static 成员都可以为虚成员。
15.2
【出题思路】
区分 protected 和 private 的访问权限控制的不同之处。
【解答】
protected 为受保护的访问说明符。protected 成员可以被该类的成员、友元和派生类成员(非友元)访问,而不可以被该类类型的普通用户访问。而 private 成员只能被基类的成员和友元访问,派生类不能直接访问。
15.3
【出题思路】
书中示例,作为基类用于后续练习题。
【解答】
Quote.h
#ifndef TEST_QUOTE_H #define TEST_QUOTE_H #include <string> #include <iostream> class Quote { public: Quote() = default; Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) { } 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; // 书籍的 ISBN 编号 protected: double price = 0.0; // 代表普通状态下不打折的价格 }; // 计算并打印销售给定数量的某种书籍所得的费用 double print_total(std::ostream &os, const Quote &item, std::size_t n) { // 根据传入 item 形参的对象类型调用 Quote::net_price // 或者 Bulk_quote::net_price double ret = item.net_price(n); os << "ISBN: " << item.isbn() << " # sold: " << n << " total due: " << ret << std::endl; return ret; } #endif //TEST_QUOTE_H
15.4
【出题思路】
熟悉派生类的定义、声明要求。
【解答】
(a)错误,一个类不能派生它本身。
(c)声明类时,不可以包含派生列表。
15.5
【出题思路】
继承方式练习,Quote 作为基类。
【解答】
代码如下所示:
Quote.h 同练习 15.3
Bulk_quote.h
#ifndef TEST_BULK_QUOTE_H #define TEST_BULK_QUOTE_H #include "Quote.h" #include <string> class Bulk_quote : public Quote { public: Bulk_quote() = default; Bulk_quote(const std::string&, double, std::size_t, double); // 覆盖基类的函数版本以实现基于大量购买的折扣政策 virtual double net_price(std::size_t) const override; private: std::size_t min_qty = 0; double discount = 0.0; }; Bulk_quote::Bulk_quote(const std::string &book, double p, std::size_t qty, double disc) : Quote(book, p), min_qty(qty), discount(disc){ } double Bulk_quote::net_price(std::size_t cnt) const { if (cnt >= min_qty) return cnt * (1 - discount) * price; else return cnt * price; } #endif //TEST_BULK_QUOTE_H
15.6
【出题思路】
练习基类和派生类的使用。
【解答】
编写简单的主函数,声明 Quote 和 Bulk_quote 对象,调用 print_total 即可。
代码如下所示:
Quote.h 和 Bulk_quote.h 同练习 15.5
main.cpp
#include "Bulk_quote.h" int main() { Quote item("978-7-121-15535-2", 100); // 基类对象 Bulk_quote bulk("978-7-121-15535-2", 100, 2, 0.2); // 派生类对象 print_total(std::cout, item, 2); print_total(std::cout, bulk, 2); return 0; }
// 运行结果 ISBN: 978-7-121-15535-2 # sold: 2 total due: 200 ISBN: 978-7-121-15535-2 # sold: 2 total due: 160 Process finished with exit code 0
15.7
【出题思路】
本题练习特定策略的类的定义。
【解答】
程序如下所示:
Quote.h 同练习 15.3
Limited_quote.h
#ifndef TEST_LIMITED_QUOTE_H #define TEST_LIMITED_QUOTE_H #include "Quote.h" class Limited_quote : public Quote { public: Limited_quote() = default; Limited_quote(const std::string&, double, std::size_t, double); // 覆盖基类的函数版本以实现基于大量购买的折扣政策 virtual double net_price(std::size_t) const override; private: std::size_t max_qty = 0; double discount = 0.0; }; Limited_quote::Limited_quote(const std::string &book, double p, std::size_t qty, double disc) : Quote(book, p), max_qty(qty), discount(disc){ } double Limited_quote::net_price(std::size_t cnt) const { if (cnt <= max_qty) return cnt * (1 - discount) * price; else return (max_qty * (1 - discount) + (cnt - max_qty)) * price; } #endif //TEST_LIMITED_QUOTE_H
main.cpp
#include "Limited_quote.h" int main() { Quote item("978-7-121-15535-2", 100); // 基类对象 Limited_quote limited("978-7-121-15535-2", 100, 2, 0.2); // 派生类对象 print_total(std::cout, item, 2); print_total(std::cout, limited, 2); print_total(std::cout, limited, 3); return 0; }
// 运行结果 ISBN: 978-7-121-15535-2 # sold: 2 total due: 200 ISBN: 978-7-121-15535-2 # sold: 2 total due: 160 ISBN: 978-7-121-15535-2 # sold: 3 total due: 260 Process finished with exit code 0
15.8
【出题思路】
区分静态类型和动态类型,熟悉其定义内容。
【解答】
静态类型在编译时就已经确定了,它是变量声明时的类型或表达式生成的类型;而动态类型则是变量或表达式表示的内存中的对象的类型,动态类型直到运行时才能知道。如:Quote *pQuote = new Bulk_quote;
,指针 pQuote 的静态类型是 Quote,在编译时就已经确定了。但是它的动态类型是 Bulk_quote,直到运行时才能知道它指向的是基类还是派生类。如果一个变量非指针也非引用,则它的静态类型和动态类型永远一致。但基类的指针或引用的静态类型可能与其动态类型不一致。
15.9
【出题思路】
具体举例说明静态类型与动态类型的不同。
【解答】
class Base { /* ... */}; class Derived : public Base { /* ... */} Derived d; Base *baseP = &d; Base &baseRef = d; Derived *drivedP = &d; Base *baseP2 = drivedP;
baseP
, baseRef
和 baseP2
的静态类型与其动态类型不一致。
15.10
【出题思路】
理解静态类型和动态类型。
【解答】
我们通常可以将一个派生类对象当作其基类对象来使用。
类型 ifstream
继承自 istream
。因此,我们可以像使用 istream
对象一样来使用 ifstream
对象。也就是说,我们是如何使用 cin 的,就可以同样地使用这些类的对象。例如,可以对一个 ifstream
对象调用 getline,也可以使用 >> 从一个 ifstream
对象中读取数据。
read 函数是 istream
的成员,但是 ifstream
是 istream
的派生类。因此,istream
(基类)通过引用可以绑定到 ifstream
(派生类)的对象上。
15.11
【出题思路】
虚函数的构造练习。
补充练习 15.7
【解答】
代码如下所示:
Quote.h
#ifndef TEST_QUOTE_H #define TEST_QUOTE_H #include <string> #include <iostream> class Quote { public: Quote() = default; Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) { } std::string isbn() const { return bookNo; } // 返回给定数量的书籍的销售总额 // 派生类负责改写并使用不同的折扣计算算法 virtual double net_price(std::size_t n) const { return n * price; } virtual ~Quote() = default; // 对析构函数进行动态绑定 // 练习 15.11 virtual void debug() const { std::cout << "data members: \n" << "\tbookNo = " << bookNo << "\tprice = " << price << std::endl; } private: std::string bookNo; // 书籍的 ISBN 编号 protected: double price = 0.0; // 代表普通状态下不打折的价格 }; // 计算并打印销售给定数量的某种书籍所得的费用 double print_total(std::ostream &os, const Quote &item, std::size_t n) { // 根据传入 item 形参的对象类型调用 Quote::net_price // 或者 Bulk_quote::net_price double ret = item.net_price(n); os << "ISBN: " << item.isbn() << " # sold: " << n << " total due: " << ret << std::endl; return ret; } #endif //TEST_QUOTE_H
Limited_quote.h
#ifndef TEST_LIMITED_QUOTE_H #define TEST_LIMITED_QUOTE_H #include "Quote.h" class Limited_quote : public Quote { public: Limited_quote() = default; Limited_quote(const std::string&, double, std::size_t, double); // 覆盖基类的函数版本以实现基于大量购买的折扣政策 virtual double net_price(std::size_t) const override; // 练习 15.11 virtual void debug() const override { Quote::debug(); // bookNo 变量为 private,所以不能直接访问 // 只能调用基类的 debug() 函数来显示 std::cout << "\tmax_qty = " << max_qty << "\tdiscount = " << discount << std::endl; } private: std::size_t max_qty = 0; double discount = 0.0; }; Limited_quote::Limited_quote(const std::string &book, double p, std::size_t qty, double disc) : Quote(book, p), max_qty(qty), discount(disc){ } double Limited_quote::net_price(std::size_t cnt) const { if (cnt <= max_qty) return cnt * (1 - discount) * price; else return (max_qty * (1 - discount) + (cnt - max_qty)) * price; } #endif //TEST_LIMITED_QUOTE_H
main.cpp
#include "Limited_quote.h" int main() { Quote item("978-7-121-15535-2", 100); // 基类对象 Limited_quote limited("978-7-121-15535-2", 100, 2, 0.2); // 派生类对象 print_total(std::cout, item, 2); print_total(std::cout, limited, 2); print_total(std::cout, limited, 3); // 练习 15.11 测试 item.debug(); limited.debug(); return 0; }
// 运行结果 ISBN: 978-7-121-15535-2 # sold: 2 total due: 200 ISBN: 978-7-121-15535-2 # sold: 2 total due: 160 ISBN: 978-7-121-15535-2 # sold: 3 total due: 260 data members: bookNo = 978-7-121-15535-2 price = 100 data members: bookNo = 978-7-121-15535-2 price = 100 max_qty = 2 discount = 0.2 Process finished with exit code 0
15.12
【出题思路】
熟悉 override 和 final 说明符的使用场景。
【解答】
有必要。
override:在 C++11 新标准中我们可以使用 override 关键字来说明派生类中的虚函数。这么做的好处是在使得我们的意图更加清晰,即明确地告诉编译器我们想要覆盖掉基类中已存在的虚函数。如果定义了一个函数与基类中的名字相同但是形参列表不同,在不使用 override 关键字的时候这种定义是合法的,在使用了 override 关键字之后这种行为是非法的,编译器会提示出错。
final:如果我们将某个函数定义成 final,则不允许后续的派生类来覆盖这个函数,否则会报错。
因此,同时将一个成员函数声明成 override 和 final 能够使我们的意图更加清晰。
15.13
【出题思路】
熟悉掌握派生类的应用及工作原理。
【解答】
The print
in derived::print
wanted to call the print
from the base class. However, the class scope base::
was omitted. As a result, it will cause an infinite recursion.
派生类 derived 中的 print 函数体中想调用基类 base 中的虚函数 print。然而,在派生类 derived 中的 print 函数体中却忽略了作用域运算符 ::
,这样做的结果是该 print 调用将被解析为对派生类 derived 的 print 函数自身的调用,从而导致无限递归。
derived 的成员函数 print 修改为:
void print(ostream &os) { base::print(os); os << " " << i; }
15.14
【出题思路】
熟悉派生类和基类在运行过程中的调用机制。
【解答】
base bobj; base *bp1 = &bobj; base &br1 = bobj; derived dobj; base *bp2 = &dobj; base &br2 = dobj; (a) bobj.print(); // base::print() (b) dobj.print(); // derived::print() (c) bp1->name(); // base::name() (d) bp2->name(); // base::name() (e) br1.print(); // base::print() (f) br2.print(); // derived::print() e and f are called at runtime.
15.15
【出题思路】
本题练习实现不同折扣策略。
重写练习 15.6
【解答】
代码如下所示:
Quote.h 不变
Disc_quote.h
#ifndef TEST_DISC_QUOTE_H #define TEST_DISC_QUOTE_H #include "Quote.h" class Disc_quote : public Quote { public: Disc_quote() = default; Disc_quote(const std::string &book, double price, std::size_t qty, double disc) : Quote(book, price), quantity(qty), discount(disc) { } double net_price(std::size_t) const = 0; protected: std::size_t quantity = 0; // 折扣适用的购买量 double discount = 0.0; // 表示折扣的小数值 }; #endif //TEST_DISC_QUOTE_H
Bulk_quote.h (不少于两本书籍,全部打折。否则,不打折)
#ifndef TEST_BULK_QUOTE_H #define TEST_BULK_QUOTE_H #include "Disc_quote.h" class Bulk_quote : public Disc_quote { public: Bulk_quote() = default; Bulk_quote(const std::string &book, double price, std::size_t qty, double disc) : Disc_quote(book, price, qty, disc) { } // 覆盖基类中的函数版本以实现一种新的折扣策略 double net_price(std::size_t) const override; }; double Bulk_quote::net_price(std::size_t cnt) const { if (cnt >= quantity) return cnt * (1 - discount) * price; else return cnt * price; } #endif //TEST_BULK_QUOTE_H
main.cpp
#include "Bulk_quote.h" int main() { Quote item("978-7-121-15535-2", 100); // 基类对象 Bulk_quote bulk("978-7-121-15535-2", 100, 2, 0.2); // 派生类对象,不少于 2 本打折 print_total(std::cout, item, 2); print_total(std::cout, bulk, 2); return 0; }
// 运行结果 ISBN: 978-7-121-15535-2 # sold: 2 total due: 200 ISBN: 978-7-121-15535-2 # sold: 2 total due: 160 Process finished with exit code 0
15.16
【出题思路】
本题练习折扣策略函数覆盖操作。
重写练习 15.7
【解答】
代码如下所示:
Quote.h 和 Disc_quote.h 如上一题
Limited_quote.h
#ifndef TEST_LIMITED_QUOTE_H #define TEST_LIMITED_QUOTE_H #include "Disc_quote.h" class Limited_quote : public Disc_quote { public: Limited_quote() = default; Limited_quote(const std::string&, double, std::size_t, double); // 覆盖基类的函数版本以实现基于大量购买的折扣政策 virtual double net_price(std::size_t) const override; }; Limited_quote::Limited_quote(const std::string &book, double p, std::size_t qty, double disc) : Disc_quote(book, p, qty, disc) { } double Limited_quote::net_price(std::size_t cnt) const { if (cnt <= quantity) return cnt * (1 - discount) * price; else return (quantity * (1 - discount) + (cnt - quantity)) * price; } #endif //TEST_LIMITED_QUOTE_H
main.cpp
#include "Limited_quote.h" int main() { Quote item("978-7-121-15535-2", 100); // 基类对象 Limited_quote limited("978-7-121-15535-2", 100, 2, 0.2); // 派生类对象 // 不超过两本全部有折扣,超过两本的话,超出部分按原价 print_total(std::cout, item, 2); print_total(std::cout, limited, 2); print_total(std::cout, limited, 3); return 0; }
// 运行结果 ISBN: 978-7-121-15535-2 # sold: 2 total due: 200 ISBN: 978-7-121-15535-2 # sold: 2 total due: 160 ISBN: 978-7-121-15535-2 # sold: 3 total due: 260 Process finished with exit code 0
15.17
【出题思路】
不能创建抽象基类的对象。
【解答】
在笔者的编译器中,给出的错误信息是:error: variable type 'Disc_quote' is an abstract class
15.18
【出题思路】
熟悉不同说明符导致基类与派生类间的不同访问控制。
【解答】
只有 d1 和 dd1 才能够赋值。这是因为:只有当派生类公有地继承基类时,用户代码才能使用派生类向基类的转换。也就是说,如果派生类继承基类的方式是受保护的或者私有的,则用户代码不能使用该转换。
在题中,只有 d1 和 dd1 类是公有的继承基类,故只有它们才能完成向基类的转换。
注:用户代码指的是非类的设计者编写的代码,如在主函数中编写测试程序就属于用户代码。
15.19
【出题思路】
熟悉继承的各种运用情况。
【解答】
Derived_from_Private : private Priv_Derv
这个类的函数不合法。
原因如下:
- 无论派生类以什么方式继承基类,派生类的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对于派生类的成员来说永远是可访问的。
- 如果派生类继承基类的方式是公有的或者受保护的,则派生类的成员和友元可以使用派生类向基类的类型转换;反之,如果派生类继承基类的方式是私有的,则不能使用。
15.20
【出题思路】
实际编程练习,判断继承运用是否掌握。
【解答】
程序如下所示:
Base.h
#ifndef TEST_BASE_H #define TEST_BASE_H #include <iostream> class Base { public: void pub_mem(); // public 成员 protected: int prot_mem; // protected 成员 private: int priv_mem; // private 成员 }; struct Pub_Derv : public Base { // 正确:派生类能访问 protected 成员 int f() { return prot_mem; } // 错误:private 成员对于派生类来说是不可访问的 // char g() { return priv_mem; } void memfcn(Base &b) { b = *this; std::cout << "Pub_Derv" << std::endl; } }; struct Priv_Derv : private Base { // private 不影响派生类的访问权限 int f1() { return prot_mem; } void memfcn(Base &b) { b = *this; std::cout << "Priv_Derv" << std::endl; } }; struct Prot_Derv : protected Base { int f2() { return prot_mem; } void memfcn(Base &b) { b = *this; std::cout << "Prot_Derv" << std::endl; } }; struct Derived_from_Public : public Pub_Derv { // 正确:Base::prot_mem 在 Pub_Derv 中仍然是 protected 的 int use_base() { return prot_mem; } void memfcn(Base &b) { b = *this; std::cout << "Derived_from_Public" << std::endl; } }; struct Derived_from_Private : public Priv_Derv { // 错误:Base::prot_mem 在 Priv_Derv 中仍然是 private 的 // int use_base() { return prot_mem; } // Base 无法访问。因为 Priv_Derv : private Base, Priv_Derv 以 // 私有方式继承的 Base;Priv_Derv 的派生类 Derived_from_Private // 无权访问其直接基类 Priv_Derv 的私有成员 // void memfcn(Base &b) { // b = *this; // std::cout << "Derived_from_Private" << std::endl; // } }; struct Derived_from_Protected : public Prot_Derv { // 正确:Base::prot_mem 在 Prot_Derv 中仍然是 protected 的 int use_base() { return prot_mem; } void memfcn(Base &b) { b = *this; std::cout << "Derived_from_Protected" << std::endl; } }; #endif //TEST_BASE_H
main.cpp
#include "Base.h" int main() { Pub_Derv d1; // 继承自 Base 的成员是 public 的 Priv_Derv d2; // 继承自 Base 的成员是 private 的 Prot_Derv d3; // 继承自 Base 的成员是 protected 的 // 练习 15.18 Derived_from_Public dd1; Derived_from_Private dd2; Derived_from_Protected dd3; Base base; Base *p = new 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 // 练习 15.19 d1.memfcn(base); d2.memfcn(base); d3.memfcn(base); dd1.memfcn(base); // dd2.memfcn(base); dd3.memfcn(base); return 0; }
// 运行结果 Pub_Derv Priv_Derv Prot_Derv Derived_from_Public Derived_from_Protected Process finished with exit code 0
15.21
【出题思路】
练习继承层次的构造。
【解答】
以 b 作为练习。代码如下所示:
GeometricPrimitives.h
#ifndef TEST_GEOMETRICPRIMITIVES_H #define TEST_GEOMETRICPRIMITIVES_H #include <form.h> static const float PI = 3.14159f; class Shape { public: virtual const char *shape_name() = 0; virtual void resize_by_percentage(float pct) = 0; virtual ~Shape() { }; }; class Shape_2D : public Shape { public: Shape_2D() = default; Shape_2D(float x, float y) : x_(x), y_(y) { } virtual float area() const = 0; // 面积 virtual float diameter() const = 0; // 直径 virtual float circumference() const = 0; // 周长 ~Shape_2D() override { } private: float x_ = 0.f; float y_ = 0.f; }; class Shape_3D : public Shape { public: Shape_3D() = default; Shape_3D(float x, float y, float z) : x_(x), y_(y), z_(z) { } virtual float volume() const = 0; // 体积 ~Shape_3D() override { } protected: float x_ = 0.f; float y_ = 0.f; float z_ = 0.f; }; class Box : public Shape_3D { public: Box() = default; explicit Box(float width) : half_len_x_(width * 0.5f), half_len_y_(width * 0.5f), half_len_z_(width * 0.5f) { } Box(float center_x, float center_y, float center_z, float len_x, float len_y, float len_z) : Shape_3D(center_x, center_y, center_z), half_len_x_(len_x * 0.5f), half_len_y_(len_y * 0.5f), half_len_z_(len_z * 0.5f) { } const char *shape_name() override { return "Box"; } void resize_by_percentage(float pct) override { half_len_x_ *= pct; half_len_y_ *= pct; half_len_z_ *= pct; } float volume() const override { return half_len_x_ * half_len_y_ * half_len_z_ * 8; } ~Box() override { } private: float half_len_x_ = 0.5f; float half_len_y_ = 0.5f; float half_len_z_ = 0.5f; }; class Circle : public Shape_2D { public: Circle() = default; explicit Circle(float radius) : radius_(radius) { } Circle(float center_x, float center_y, float radius) : Shape_2D(center_x, center_y), radius_(radius) { } float area() const override { return PI * radius_ * radius_; } float diameter() const override { return 2 * radius_; } float circumference() const override { return 2 * PI * radius_; } const char *shape_name() override { return "Circle" ; } void resize_by_percentage(float pct) override { radius_ *= pct; } ~Circle() override { }; protected: float radius_ = 1.f; // 半径 }; class Sphere : public Shape_3D { public: Sphere() = default; explicit Sphere(float radius) : radius_(radius) { } Sphere(float center_x, float center_y, float center_z, float radius) : Shape_3D(center_x, center_y, center_z), radius_(radius) { } const char *shape_name() override { return "Sphere"; } void resize_by_percentage(float pct) override { radius_ *= pct; } float volume() const override { return 4 * PI * radius_ * radius_ * radius_ / 3; } ~Sphere() override { } protected: float radius_ = 1.f; // 球体半径 }; class Cone : public Shape_3D { public: Cone() = default; Cone(float radius, float height) : radius_(radius), height_(height) { } Cone(float center_x, float center_y, float center_z, float radius, float height) : Shape_3D(center_x, center_y, center_z), radius_(radius), height_(height) { } const char *shape_name() override { return "Cone"; } void resize_by_percentage(float pct) override { radius_ *= pct; height_ *= pct; } float volume() const override { return PI * radius_ * radius_ * height_ / 3; } ~Cone() override { } protected: float radius_ = 1.f; // 圆锥体底面圆半径 float height_ = 1.f; // 圆锥体高 }; #endif //TEST_GEOMETRICPRIMITIVES_H
main.cpp
#include <iostream> #include "GeometricPrimitives.h" int main() { Box boxObj(10); // 正方体的边长为 10 std::cout << boxObj.shape_name() << std::endl; std::cout << boxObj.volume() << std::endl; boxObj.resize_by_percentage(0.3); // 边长改为 10 * 0.3 std::cout << boxObj.volume() << std::endl; std::cout << std::endl; Circle cirObj(10); // 圆的半径为 10 std::cout << cirObj.shape_name() << std::endl; std::cout << cirObj.area() << std::endl; // 面积 std::cout << cirObj.diameter() << std::endl; // 直径 std::cout << cirObj.circumference() << std::endl; // 周长 cirObj.resize_by_percentage(0.3); // 半径改为 10 * 0.3 std::cout << cirObj.area() << std::endl; // 面积 std::cout << cirObj.diameter() << std::endl; // 直径 std::cout << cirObj.circumference() << std::endl; // 周长 return 0; }
// 运行结果 Box 1000 27 Circle 314.159 20 62.8318 28.2743 6 18.8495 Process finished with exit code 0
15.22
【出题思路】
类构造练习。
【解答】
看上一题。
15.23
【出题思路】
虚函数与其作用域的练习。
【解答】
我们先对书中的例子进行验证。代码如下所示:
#include <iostream> #include <string> class Base { public: virtual int fcn() { std::cout << "Base::fcn()\n"; return 0; } }; class D1 : public Base { public: int fcn(int) { std::cout << "D1::fcn(int)\n"; return 0; } virtual void f2() { std::cout << "D1::f2()\n"; } }; class D2 : public D1 { public: int fcn(int); int fcn() override { std::cout << "D2::fcn()\n"; return 0; } void f2() override { std::cout << "D2::f2()\n"; } }; int main() { Base bobj; D1 d1obj; D2 d2obj; Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; bp1->fcn(); // virtual call, will call Base::fcn at run time bp2->fcn(); // virtual call, will call D1::fcn at run time bp3->fcn(); // virtual call, will call D2::fcn at run time D1 *d1p = &d1obj; D2 *d2p = &d2obj; // bp2->f2(); // ^^^^^^^^^^ // @note You are calling virtual member functions via a pointer // to Base. That means that you can only call methods that exist // in the Base class. You cannot simply add methods to a type // dynamically. d1p->f2(); // virtual call, will call D1::f2() at run time d2p->f2(); // virtual call, will call D2::f2() at run time return 0; }
// 运行结果 Base::fcn() Base::fcn() D2::fcn() D1::f2() D2::f2() Process finished with exit code 0
本题练习要求的代码如下所示:
#include <iostream> #include <string> class Base { public: virtual int fcn() { std::cout << "Base::fcn()\n"; return 0; } }; class D1 : public Base { public: int fcn() override { std::cout << "D1::fcn()\n"; return 0; } virtual void f2() { std::cout << "D1::f2()\n"; } }; class D2 : public D1 { public: int fcn(int); int fcn() override { std::cout << "D2::fcn()\n"; return 0; } void f2() override { std::cout << "D2::f2()\n"; } }; int main() { Base bobj; D1 d1obj; D2 d2obj; Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; bp1->fcn(); // virtual call, will call Base::fcn at run time bp2->fcn(); // virtual call, will call D1::fcn at run time bp3->fcn(); // virtual call, will call D2::fcn at run time D1 *d1p = &d1obj; D2 *d2p = &d2obj; // bp2->f2(); // ^^^^^^^^^^ // @note You are calling virtual member functions via a pointer // to Base. That means that you can only call methods that exist // in the Base class. You cannot simply add methods to a type // dynamically. d1p->f2(); // virtual call, will call D1::f2() at run time d2p->f2(); // virtual call, will call D2::f2() at run time return 0; }
// 运行结果 Base::fcn() D1::fcn() D2::fcn() D1::f2() D2::f2() Process finished with exit code 0
15.24
【出题思路】
熟悉虚析构函数的知识。
【解答】
一般来说,基类需要定义一个虚析构函数。这样我们就能动态分配继承体系中的对象了。
15.25
【出题思路】
理解基类或派生类的合成拷贝控制的知识。
【解答】
本练习和练习 15.16相关。若 Disc_quote.h 中 Disc_quote 类没有默认构造函数,即 Disc_quote() = default;
这句。将导致下列问题:
由于 Disc_quote 类定义了接受 4 个参数的构造函数,它将阻止编译器生成合成的默认构造函数。这样 Disc_quote 类的任何派生类的默认构造函数都将是删除的(因为 Disc_quote 类没有显示(explicitly)定义默认构造函数,也没有合成的默认构造函数)。
因此,基类的默认构造函数必须显示定义,确保其派生类在执行它的默认构造函数时能调用基类默认构造函数。
The reason is that a constructor taking 4 parameters has been defined, which prevented the compiler generate synthesized version default constructor. As a result, the default constructor of any class derived from it has been defined as deleted. Thus the default constructor must be defined explicitly so that the derived classes can call it when executing its default constructor.
15.26
【出题思路】
本题考查基类和派生类的构造函数与析构函数的调用过程。
【解答】
程序如下所示:
Quote.h
#ifndef TEST_QUOTE_H #define TEST_QUOTE_H #include <string> #include <iostream> class Quote { public: Quote() { std::cout << "Quote Default Constructor" << std::endl; } Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) { std::cout << "Quote Constructor taking two parameters" << std::endl; } std::string isbn() const { return bookNo; } // 返回给定数量的书籍的销售总额 // 派生类负责改写并使用不同的折扣计算算法 virtual double net_price(std::size_t n) const { return n * price; } virtual ~Quote() { // 对析构函数进行动态绑定 std::cout << "Quote Destructor" << std::endl; } Quote(const Quote &rhs) : bookNo(rhs.bookNo), price(rhs.price) { std::cout << "Quote Copy Constructor" << std::endl; } Quote &operator=(const Quote &rhs) { std::cout << "Quote Copy assignment operator" << std::endl; price = rhs.price; bookNo = rhs.bookNo; return *this; } Quote(Quote &&rhs) noexcept : bookNo(std::move(rhs.bookNo)), price(std::move(rhs.price)) { std::cout << "Quote Move Constructor" << std::endl; } Quote &operator=(Quote &&rhs) noexcept { std::cout << "Quote Move assignment operator" << std::endl; bookNo = std::move(rhs.bookNo); price = std::move(rhs.price); return *this; } private: std::string bookNo; // 书籍的 ISBN 编号 protected: double price = 0.0; // 代表普通状态下不打折的价格 }; // 计算并打印销售给定数量的某种书籍所得的费用 double print_total(std::ostream &os, const Quote &item, std::size_t n) { // 根据传入 item 形参的对象类型调用 Quote::net_price // 或者 Bulk_quote::net_price double ret = item.net_price(n); os << "ISBN: " << item.isbn() << " # sold: " << n << " total due: " << ret << std::endl; return ret; } #endif //TEST_QUOTE_H
Bulk_quote.h
#ifndef TEST_BULK_QUOTE_H #define TEST_BULK_QUOTE_H #include "Quote.h" class Bulk_quote : public Quote { public: Bulk_quote() { std::cout << "Bulk_quote Constructor" << std::endl; } Bulk_quote(const std::string &book, double price, std::size_t qty, double disc) : Quote(book, price), min_qty(qty), discount(disc) { std::cout << "Bulk_quote Constructor taking four parameters" << std::endl; } Bulk_quote(const Bulk_quote &rhs) : Quote(rhs), min_qty(rhs.min_qty), discount(rhs.discount) { std::cout << "Bulk_quote Copy Constructor" << std::endl; } Bulk_quote &operator=(const Bulk_quote &rhs) { std::cout << "Bulk_quote Copy assignment operator" << std::endl; Quote::operator=(rhs); min_qty = rhs.min_qty; discount = rhs.discount; return *this; } Bulk_quote(Bulk_quote &&rhs) noexcept : Quote(rhs), min_qty(std::move(rhs.min_qty)), discount(std::move(rhs.discount)) { std::cout << "Bulk_quote Move Constructor" << std::endl; } Bulk_quote &operator=(Bulk_quote &&rhs) noexcept { std::cout << "Bulk_quote Move assignment operator" << std::endl; Quote::operator=(rhs); min_qty = std::move(rhs.min_qty); discount = std::move(rhs.discount); return *this; } virtual ~Bulk_quote() { std::cout << "Bulk_quote Destructor" << std::endl; } // 覆盖基类中的函数版本以实现一种新的折扣策略 double net_price(std::size_t) const override; private: std::size_t min_qty = 0; double discount = 0.0; }; double Bulk_quote::net_price(std::size_t cnt) const { if (cnt >= min_qty) return cnt * (1 - discount) * price; else return cnt * price; } #endif //TEST_BULK_QUOTE_H
main.cpp
#include "Bulk_quote.h" int main() { Bulk_quote bulk("978-7-121-15535-2", 100, 2, 0.2); // 派生类对象 std::cout << std::endl; std::cout << "------Bulk_quote bulk1 = bulk------" << std::endl; Bulk_quote bulk1 = bulk; std::cout << std::endl; std::cout << "------Bulk_quote bulk2 = std::move(bulk)------" << std::endl; Bulk_quote bulk2 = std::move(bulk); std::cout << std::endl; std::cout << "------Quote quote1 = bulk------" << std::endl; Quote quote1 = bulk; std::cout << std::endl; std::cout << "------Quote quote2 = std::move(bulk)------" << std::endl; Quote quote2 = std::move(bulk); std::cout << std::endl; return 0; }
// 运行结果 Quote Constructor taking two parameters Bulk_quote Constructor taking four parameters ------Bulk_quote bulk1 = bulk------ Quote Copy Constructor Bulk_quote Copy Constructor ------Bulk_quote bulk2 = std::move(bulk)------ Quote Copy Constructor Bulk_quote Move Constructor ------Quote quote1 = bulk------ Quote Copy Constructor ------Quote quote2 = std::move(bulk)------ Quote Move Constructor Quote Destructor Quote Destructor Bulk_quote Destructor Quote Destructor Bulk_quote Destructor Quote Destructor Bulk_quote Destructor Quote Destructor Process finished with exit code 0
15.27
【出题思路】
本题练习继承构造函数的定义。
基于练习 15.15,只需修改 Bulk_quote.h 文件即可
【解答】
Bulk_quote.h
#ifndef TEST_BULK_QUOTE_H #define TEST_BULK_QUOTE_H #include "Disc_quote.h" class Bulk_quote : public Disc_quote { public: using Disc_quote::Disc_quote; // 覆盖基类中的函数版本以实现一种新的折扣策略 double net_price(std::size_t) const override; }; double Bulk_quote::net_price(std::size_t cnt) const { if (cnt >= quantity) return cnt * (1 - discount) * price; else return cnt * price; } #endif //TEST_BULK_QUOTE_H
15.28
【出题思路】
本题练习在容器中放置对象的使用练习。
本题 Quote.h、Disc_quote.h 和 Bulk_quote.h 代码均与上一题相同。main.cpp 代码下面已经列出。
【解答】
main.cpp
#include "Bulk_quote.h" #include <vector> int main() { // 不少于 2 本打折 Bulk_quote bulk1("978-7-121-15535-2", 100, 2, 0.2); // 派生类对象 // 不少于 5 本打折 Bulk_quote bulk2("978-7-121-15535-1", 100, 5, 0.5); // 派生类对象 print_total(std::cout, bulk1, 4); print_total(std::cout, bulk2, 6); std::cout << bulk1.net_price(4) + bulk2.net_price(6) << std::endl; std::vector<Quote> itemVec; itemVec.push_back(bulk1); itemVec.push_back(bulk2); print_total(std::cout, itemVec[0], 4); print_total(std::cout, itemVec[1], 6); std::cout << itemVec[0].net_price(4) + itemVec[1].net_price(6) << std::endl; return 0; }
// 运行结果 ISBN: 978-7-121-15535-2 # sold: 4 total due: 320 ISBN: 978-7-121-15535-1 # sold: 6 total due: 300 620 ISBN: 978-7-121-15535-2 # sold: 4 total due: 400 ISBN: 978-7-121-15535-1 # sold: 6 total due: 600 1000 Process finished with exit code 0
15.29
【出题思路】
本题是在容器中放置指针的使用练习,与放置对象的过程结果存在差异。
修改上一题的 main.cpp 即可
【解答】
main.cpp
#include "Bulk_quote.h" #include <vector> #include <memory> int main() { // 不少于 2 本打折 std::shared_ptr<Bulk_quote> bulk1 = std::make_shared<Bulk_quote>("978-7-121-15535-2", 100, 2, 0.2); // 不少于 5 本打折 std::shared_ptr<Bulk_quote> bulk2 = std::make_shared<Bulk_quote>("978-7-121-15535-1", 100, 5, 0.5); print_total(std::cout, *bulk1, 4); print_total(std::cout, *bulk2, 6); std::cout << bulk1->net_price(4) + bulk2->net_price(6) << std::endl; std::vector<std::shared_ptr<Quote>> itemVec; itemVec.push_back(bulk1); itemVec.push_back(bulk2); print_total(std::cout, *itemVec[0], 4); print_total(std::cout, *itemVec[1], 6); std::cout << itemVec[0]->net_price(4) + itemVec[1]->net_price(6) << std::endl; return 0; }
// 运行结果 ISBN: 978-7-121-15535-2 # sold: 4 total due: 320 ISBN: 978-7-121-15535-1 # sold: 6 total due: 300 620 ISBN: 978-7-121-15535-2 # sold: 4 total due: 320 ISBN: 978-7-121-15535-1 # sold: 6 total due: 300 620 Process finished with exit code 0
对比本题和上一题,观察程序的 main.cpp 及其输出结果。
程序产生的结果会有差异。因为当通过 Quote 类型的对象调用虚函数 net_price 时,不实行动态绑定,调用的是 Quote 类中定义的版本;而通过 Quote 类型的指针调用虚函数 net_price,实行动态绑定,而该指针实际指向 Bulk_quote 类中定义的版本。
15.30
【出题思路】
本题是类构造编程练习。
【解答】
程序如下所示:
Quote.h
#ifndef TEST_QUOTE_H #define TEST_QUOTE_H #include <string> #include <iostream> class Quote { public: Quote() = default; Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) { } std::string isbn() const { return bookNo; } // 返回给定数量的书籍的销售总额 // 派生类负责改写并使用不同的折扣计算算法 virtual double net_price(std::size_t n) const { return n * price; } virtual ~Quote() = default; // 对析构函数进行动态绑定 // 练习 15.30 // 该虚函数返回当前对象的一份动态分配的拷贝 virtual Quote *clone() const & { return new Quote(*this); } virtual Quote *clone() && { return new Quote(std::move(*this)); } private: std::string bookNo; // 书籍的 ISBN 编号 protected: double price = 0.0; // 代表普通状态下不打折的价格 }; // 计算并打印销售给定数量的某种书籍所得的费用 double print_total(std::ostream &os, const Quote &item, std::size_t n) { // 根据传入 item 形参的对象类型调用 Quote::net_price // 或者 Bulk_quote::net_price double ret = item.net_price(n); os << "ISBN: " << item.isbn() << " # sold: " << n << " total due: " << ret << std::endl; return ret; } #endif //TEST_QUOTE_H
Disc_quote.h
#ifndef TEST_DISC_QUOTE_H #define TEST_DISC_QUOTE_H #include "Quote.h" class Disc_quote : public Quote { public: Disc_quote() = default; Disc_quote(const std::string &book, double price, std::size_t qty, double disc) : Quote(book, price), quantity(qty), discount(disc) { } double net_price(std::size_t) const = 0; protected: std::size_t quantity = 0; // 折扣适用的购买量 double discount = 0.0; // 表示折扣的小数值 }; #endif //TEST_DISC_QUOTE_H
Bulk_quote.h
#ifndef TEST_BULK_QUOTE_H #define TEST_BULK_QUOTE_H #include "Disc_quote.h" class Bulk_quote : public Disc_quote { public: using Disc_quote::Disc_quote; // 覆盖基类中的函数版本以实现一种新的折扣策略 double net_price(std::size_t) const override; // 练习 15.30 // 该虚函数返回当前对象的一份动态分配的拷贝 Bulk_quote *clone() const & override { return new Bulk_quote(*this); } Bulk_quote *clone() && override { return new Bulk_quote(std::move(*this)); } }; double Bulk_quote::net_price(std::size_t cnt) const { if (cnt >= quantity) return cnt * (1 - discount) * price; else return cnt * price; } #endif //TEST_BULK_QUOTE_H
Basket.h
#ifndef TEST_BASKET_H #define TEST_BASKET_H #include <memory> #include <set> #include "Bulk_quote.h" class Basket { public: // Basket 使用合成的默认构造函数和拷贝控制成员 void add_item(const Quote &sale) { // 拷贝给定的对象 items.insert(std::shared_ptr<Quote>(sale.clone())); } void add_item(const Quote &&sale) { // 移动给定的对象 items.insert(std::shared_ptr<Quote>(std::move(sale).clone())); } // 打印每本书的总价和购物篮中所有书的总价 double total_receipt(std::ostream&) const; private: // 该函数用于比较 shared_ptr,multiset 成员会用到它 static bool compare(const std::shared_ptr<Quote> &lhs, const std::shared_ptr<Quote> &rhs) { return lhs->isbn() < rhs->isbn(); } // multiset 保存多个报价,按照 compare 成员排序 std::multiset<std::shared_ptr<Quote>, decltype(compare)*> items{compare}; }; 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)) { // 我们知道在当前的 Basket 中至少有一个该关键字的元素 // 打印该书籍对应的项目 sum += print_total(os, **iter, items.count(*iter)); } os << "Total Sale: " << sum << std::endl; return sum; } #endif //TEST_BASKET_H
main.cpp
#include "Basket.h" int main() { Basket item; for (auto i = 0; i < 4; ++i) // 不少于 2 本打折 item.add_item(Bulk_quote("978-7-121-15535-2", 100, 2, 0.2)); for (auto i = 0; i < 6; ++i) // 不少于 5 本打折 item.add_item(Bulk_quote("978-7-121-15535-1", 100, 5, 0.5)); item.total_receipt(std::cout); return 0; }
// 运行结果 ISBN: 978-7-121-15535-1 # sold: 6 total due: 300 ISBN: 978-7-121-15535-2 # sold: 4 total due: 320 Total Sale: 620 Process finished with exit code 0
注:结果已按 ISBN 降序排列
15.31
【出题思路】
熟悉对象创建的工作机理。
【解答】
- (a):
WordQuery
->NotQuery
->AndQuery
->OrQuery
- (b):
WordQuery
->NotQuery
->AndQuery
->OrQuery
(same as the previous one) - (c):
WordQuery
->AndQuery
->OrQuery
Preference:
15.32
【出题思路】
理解类层次中的拷贝、移动、赋值和销毁行为。
【解答】
Query 类未定义自己的拷贝/移动控制成员,当进行这些操作时,执行默认语义。而其唯一的数据成员是 Query_base 的 shared_ptr,因此,当拷贝、移动、赋值或销毁一个 Query 对象时,会调用 shared_ptr 的对应控制成员,从而实现多个 Query 对象正确共享一个 Query_base。而 shared_ptr 的控制成员调用 Query_base 的控制成员时,由于指向的可能是 Query_base 的派生类对象,因此可能在类层次中进行相应的拷贝/移动操作,调用 Query_base 的派生类的相应控制成员。
- copy: While being copied, the synthesized copy constructor is called. It copies the data member into the new object. Since in this case, the data member is a shared pointer, while copying, the corresponding shared pointer points to the same address and the use count from the both shared pointer becomes 2.
- move: while being moved, the synthesized move constructor is called. It moves the data member into the new object. In this case, the shared pointer from the newly created object will point to the address to which the original shared pointer pointed. After the move operation, the use count of the shared pointer in the new object is 1, whereas the pointer from the original object becomes
nullptr
.- copy assignment: The synthesized copy assignment will be called. The outcome of this operation is identical with the copy operation.
- move assignment: The synthesized move assignment will be called. The rest is the same as the move operation.
- destroy: The synthesized destructor will be called. It will call the destructor of
shared_ptr
which decrements the use count. If the count becomes zero, the destructor fromshared_ptr
will delete the resources it point to.
15.33
【出题思路】
理解类层次中的拷贝、移动、赋值和销毁行为。
【解答】
Query_base 是一个虚基类,不允许直接声明其对象。
当其派生类对象进行这些操作时,会调用 Query_base 的相应控制成员。而 Query_base 没有定义自己的拷贝/移动控制成员,实际上 Query_base 没有任何数据成员,无需定义这些操作。因此,进行这些操作时,执行默认语义,什么也不会发生。
Managed by the synthesized version. Since
Query_base
a abstract class, the object of this type is essentially a sub-object of its derived class.
15.34
【出题思路】
本题要求熟练掌握构造函数及其具体操作内容。
验证代码见练习 15.36
【解答】
(a)
WordQuery(const std::string&) -- (fiery) Query(const std::string&) -- (fiery) WordQuery(const std::string&) -- (bird) Query(const std::string&) -- (bird) BinaryQuery(const Query&, const Query&, std::string) -- (fiery, bird, &) AndQuery(const Query&, const Query&) -- (fiery, bird) Query(std::shared_ptr<Query_base>) WordQuery(const std::string&) -- (wind) Query(const std::string&) -- (wind) BinaryQuery(const Query&, const Query&, std::string) -- ((fiery & bird), wind, |) OrQuery(const Query&, const Query&) -- ((fiery & bird), wind) Query(std::shared_ptr<Query_base>)
(b)
Query BinaryQuery Query BinaryQuery Query WordQuery Query WordQuery Query WordQuery ((fiery & bird) | wind) BinaryQuery Query WordQuery Query WordQuery BinaryQuery Query BinaryQuery Query WordQuery Query WordQuery Query WordQuery
(c)
eval of Query eval of OrQuery eval of Query eval of WordQuery eval of Query eval of AndQuery eval of Query eval of WordQuery eval of Query eval of WordQuery
15.35
【出题思路】
本题是类构造的练习。
【解答】
查看练习 15.39
15.36
【出题思路】
检验类层次中成员函数的调用关系。
【解答】
在各个类的构造函数和 rep 中添加打印语句即可。注意,Query 类有一个 public 和一个 private 共两个构造函数。
(a)
在练习 15.39 中的 Query.h 相应构造函数添加输出语句。代码如下所示:
Query.h
#ifndef TEST_QUERY_H #define TEST_QUERY_H #include "TextQuery.h" #include <string> #include <set> #include <iostream> #include <fstream> #include <sstream> #include <memory> // abstract class acts as a base class for concrete query types; all members are private class Query_base { friend class Query; protected: typedef TextQuery::line_no line_no; // used in the eval functions virtual ~Query_base() {} private: // eval returns the QueryResult that matches this Query virtual QueryResult eval(const TextQuery &) const = 0; // rep is a string representation of the query virtual std::string rep() const = 0; }; // interface class to manage the Query_base inheritance hierarchy class Query { // these operators need access to the shared_ptr constructor friend Query operator~(const Query &); friend Query operator|(const Query &, const Query &); friend Query operator&(const Query &, const Query &); public: Query(const std::string &); // builds a new WordQuery // interface functions: call the corresponding Query_base operations QueryResult eval(const TextQuery &t) const { return q->eval(t); } std::string rep() const { return q->rep(); } private: Query(std::shared_ptr<Query_base> query) : q(query) { std::cout << "Query(std::shared_ptr<Query_base>)" << std::endl; } std::shared_ptr<Query_base> q; }; inline std::ostream & operator<<(std::ostream &os, const Query &query) { // Query::rep makes a virtual call through its Query_base pointer to rep() return os << query.rep(); } class WordQuery : public Query_base { friend class Query; // Query uses the WordQuery constructor WordQuery(const std::string &s) : query_word(s) { std::cout << "WordQuery(const std::string&) -- " << "(" << s << ")" << std::endl; } // concrete class: WordQuery defines all inherited pure virtual functions QueryResult eval(const TextQuery &t) const { return t.query(query_word); } std::string rep() const { return query_word; } std::string query_word; // word for which to search }; inline Query::Query(const std::string &s) : q(new WordQuery(s)) { std::cout << "Query(const std::string&) -- " << "(" << s << ")" << std::endl; } class NotQuery : public Query_base { friend Query operator~(const Query &); NotQuery(const Query &q) : query(q) {} // concrete class: NotQuery defines all inherited pure virtual functions std::string rep() const { return "~(" + query.rep() + ")"; } QueryResult eval(const TextQuery &) const; Query query; }; class BinaryQuery : public Query_base { protected: BinaryQuery(const Query &l, const Query &r, std::string s) : lhs(l), rhs(r), opSym(s) { std::cout << "BinaryQuery(const Query&, const Query&, std::string) -- " << "(" << l << ", " << r << ", " << s << ")" << std::endl; } // abstract class: BinaryQuery doesn't define eval std::string rep() const { return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; } Query lhs, rhs; // right- and left-hand operands std::string opSym; // name of the operator }; class AndQuery : public BinaryQuery { friend Query operator&(const Query &, const Query &); AndQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "&") { std::cout << "AndQuery(const Query&, const Query&) -- " << "(" << left << ", " << right << ")" << std::endl; } // concrete class: AndQuery inherits rep and defines the remaining pure virtual QueryResult eval(const TextQuery &) const; }; class OrQuery : public BinaryQuery { friend Query operator|(const Query &, const Query &); OrQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "|") { std::cout << "OrQuery(const Query&, const Query&) -- " << "(" << left << ", " << right << ")" << std::endl; } QueryResult eval(const TextQuery &) const; }; inline Query operator&(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new AndQuery(lhs, rhs)); } inline Query operator|(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new OrQuery(lhs, rhs)); } inline Query operator~(const Query &operand) { return std::shared_ptr<Query_base>(new NotQuery(operand)); } std::ifstream &open_file(std::ifstream &, const std::string &); TextQuery get_file(int, char **); bool get_word(std::string &); bool get_words(std::string &, std::string &); std::ostream &print(std::ostream &, const QueryResult &); #endif //TEST_QUERY_H
and_orQueryTest.cpp
#include "Query.h" #include "TextQuery.h" #include <string> #include <set> #include <iostream> using std::set; using std::string; using std::cin; using std::cout; using std::cerr; using std::endl; int main(int argc, char **argv) { // gets file to read and builds map to support queries TextQuery file = get_file(argc, argv); // iterate with the user: prompt for a word to find and print results while (true) { string sought1, sought2, sought3; if (!get_words(sought1, sought2)) break; cout << "\nenter third word: " ; cin >> sought3; // find all the occurrences of the requested string Query q = Query(sought1) & Query(sought2) | Query(sought3); cout << "\nExecuting Query for: " << q << endl; const QueryResult results = q.eval(file); // report matches print(cout, results); } return 0; }
// 运行结果 enter two words to search for, or q to quit: fiery bird enter third word: wind WordQuery(const std::string&) -- (fiery) Query(const std::string&) -- (fiery) WordQuery(const std::string&) -- (bird) Query(const std::string&) -- (bird) BinaryQuery(const Query&, const Query&, std::string) -- (fiery, bird, &) AndQuery(const Query&, const Query&) -- (fiery, bird) Query(std::shared_ptr<Query_base>) WordQuery(const std::string&) -- (wind) Query(const std::string&) -- (wind) BinaryQuery(const Query&, const Query&, std::string) -- ((fiery & bird), wind, |) OrQuery(const Query&, const Query&) -- ((fiery & bird), wind) Query(std::shared_ptr<Query_base>) Executing Query for: ((fiery & bird) | wind) ((fiery & bird) | wind) occurs 3 times (line 2)Her Daddy says when the wind blows (line 4)like a fiery bird in flight. (line 5)A beautiful fiery bird, he tells her, enter two words to search for, or q to quit: q Process finished with exit code 0
(b)
在练习 15.39 中的 Query.h 相应 rep 定义处添加输出语句。代码如下所示:
Query.h
#ifndef TEST_QUERY_H #define TEST_QUERY_H #include "TextQuery.h" #include <string> #include <set> #include <iostream> #include <fstream> #include <sstream> #include <memory> // abstract class acts as a base class for concrete query types; all members are private class Query_base { friend class Query; protected: typedef TextQuery::line_no line_no; // used in the eval functions virtual ~Query_base() {} private: // eval returns the QueryResult that matches this Query virtual QueryResult eval(const TextQuery &) const = 0; // rep is a string representation of the query virtual std::string rep() const = 0; }; // interface class to manage the Query_base inheritance hierarchy class Query { // these operators need access to the shared_ptr constructor friend Query operator~(const Query &); friend Query operator|(const Query &, const Query &); friend Query operator&(const Query &, const Query &); public: Query(const std::string &); // builds a new WordQuery // interface functions: call the corresponding Query_base operations QueryResult eval(const TextQuery &t) const { return q->eval(t); } std::string rep() const { std::cout << "Query" << std::endl; return q->rep(); } private: Query(std::shared_ptr<Query_base> query) : q(query) {} std::shared_ptr<Query_base> q; }; inline std::ostream & operator<<(std::ostream &os, const Query &query) { // Query::rep makes a virtual call through its Query_base pointer to rep() return os << query.rep(); } class WordQuery : public Query_base { friend class Query; // Query uses the WordQuery constructor WordQuery(const std::string &s) : query_word(s) {} // concrete class: WordQuery defines all inherited pure virtual functions QueryResult eval(const TextQuery &t) const { return t.query(query_word); } std::string rep() const { std::cout << "WordQuery" << std::endl; return query_word; } std::string query_word; // word for which to search }; inline Query::Query(const std::string &s) : q(new WordQuery(s)) {} class NotQuery : public Query_base { friend Query operator~(const Query &); NotQuery(const Query &q) : query(q) {} // concrete class: NotQuery defines all inherited pure virtual functions std::string rep() const { return "~(" + query.rep() + ")"; } QueryResult eval(const TextQuery &) const; Query query; }; class BinaryQuery : public Query_base { protected: BinaryQuery(const Query &l, const Query &r, std::string s) : lhs(l), rhs(r), opSym(s) {} // abstract class: BinaryQuery doesn't define eval std::string rep() const { std::cout << "BinaryQuery" << std::endl; return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; } Query lhs, rhs; // right- and left-hand operands std::string opSym; // name of the operator }; class AndQuery : public BinaryQuery { friend Query operator&(const Query &, const Query &); AndQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "&") {} // concrete class: AndQuery inherits rep and defines the remaining pure virtual QueryResult eval(const TextQuery &) const; }; class OrQuery : public BinaryQuery { friend Query operator|(const Query &, const Query &); OrQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "|") {} QueryResult eval(const TextQuery &) const; }; inline Query operator&(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new AndQuery(lhs, rhs)); } inline Query operator|(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new OrQuery(lhs, rhs)); } inline Query operator~(const Query &operand) { return std::shared_ptr<Query_base>(new NotQuery(operand)); } std::ifstream &open_file(std::ifstream &, const std::string &); TextQuery get_file(int, char **); bool get_word(std::string &); bool get_words(std::string &, std::string &); std::ostream &print(std::ostream &, const QueryResult &); #endif //TEST_QUERY_H
and_orQueryTest.cpp
#include "Query.h" #include "TextQuery.h" #include <string> #include <set> #include <iostream> using std::set; using std::string; using std::cin; using std::cout; using std::cerr; using std::endl; int main(int argc, char **argv) { // gets file to read and builds map to support queries TextQuery file = get_file(argc, argv); // iterate with the user: prompt for a word to find and print results while (true) { string sought1, sought2, sought3; if (!get_words(sought1, sought2)) break; cout << "\nenter third word: " ; cin >> sought3; // find all the occurrences of the requested string Query q = Query(sought1) & Query(sought2) | Query(sought3); //cout << "\nExecuting Query for: " << q << endl; cout << q << endl; const QueryResult results = q.eval(file); // report matches print(cout, results); } return 0; }
// 运行结果 enter two words to search for, or q to quit: fiery bird enter third word: wind Query BinaryQuery Query BinaryQuery Query WordQuery Query WordQuery Query WordQuery ((fiery & bird) | wind) BinaryQuery Query WordQuery Query WordQuery BinaryQuery Query BinaryQuery Query WordQuery Query WordQuery Query WordQuery ((fiery & bird) | wind) occurs 3 times (line 2)Her Daddy says when the wind blows (line 4)like a fiery bird in flight. (line 5)A beautiful fiery bird, he tells her, enter two words to search for, or q to quit: q Process finished with exit code 0
(c)
在练习 15.39 中的 Query.h 和 Query.cpp 相应 eval 定义处添加输出语句。代码如下所示:
Query.h
#ifndef TEST_QUERY_H #define TEST_QUERY_H #include "TextQuery.h" #include <string> #include <set> #include <iostream> #include <fstream> #include <sstream> #include <memory> // abstract class acts as a base class for concrete query types; all members are private class Query_base { friend class Query; protected: typedef TextQuery::line_no line_no; // used in the eval functions virtual ~Query_base() {} private: // eval returns the QueryResult that matches this Query virtual QueryResult eval(const TextQuery &) const = 0; // rep is a string representation of the query virtual std::string rep() const = 0; }; // interface class to manage the Query_base inheritance hierarchy class Query { // these operators need access to the shared_ptr constructor friend Query operator~(const Query &); friend Query operator|(const Query &, const Query &); friend Query operator&(const Query &, const Query &); public: Query(const std::string &); // builds a new WordQuery // interface functions: call the corresponding Query_base operations QueryResult eval(const TextQuery &t) const { std::cout << "eval of Query" << std::endl; return q->eval(t); } std::string rep() const { return q->rep(); } private: Query(std::shared_ptr<Query_base> query) : q(query) {} std::shared_ptr<Query_base> q; }; inline std::ostream & operator<<(std::ostream &os, const Query &query) { // Query::rep makes a virtual call through its Query_base pointer to rep() return os << query.rep(); } class WordQuery : public Query_base { friend class Query; // Query uses the WordQuery constructor WordQuery(const std::string &s) : query_word(s) {} // concrete class: WordQuery defines all inherited pure virtual functions QueryResult eval(const TextQuery &t) const { std::cout << "eval of WordQuery" << std::endl; return t.query(query_word); } std::string rep() const { return query_word; } std::string query_word; // word for which to search }; inline Query::Query(const std::string &s) : q(new WordQuery(s)) {} class NotQuery : public Query_base { friend Query operator~(const Query &); NotQuery(const Query &q) : query(q) {} // concrete class: NotQuery defines all inherited pure virtual functions std::string rep() const { return "~(" + query.rep() + ")"; } QueryResult eval(const TextQuery &) const; Query query; }; class BinaryQuery : public Query_base { protected: BinaryQuery(const Query &l, const Query &r, std::string s) : lhs(l), rhs(r), opSym(s) {} // abstract class: BinaryQuery doesn't define eval std::string rep() const { return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; } Query lhs, rhs; // right- and left-hand operands std::string opSym; // name of the operator }; class AndQuery : public BinaryQuery { friend Query operator&(const Query &, const Query &); AndQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "&") {} // concrete class: AndQuery inherits rep and defines the remaining pure virtual QueryResult eval(const TextQuery &) const; }; class OrQuery : public BinaryQuery { friend Query operator|(const Query &, const Query &); OrQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "|") {} QueryResult eval(const TextQuery &) const; }; inline Query operator&(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new AndQuery(lhs, rhs)); } inline Query operator|(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new OrQuery(lhs, rhs)); } inline Query operator~(const Query &operand) { return std::shared_ptr<Query_base>(new NotQuery(operand)); } std::ifstream &open_file(std::ifstream &, const std::string &); TextQuery get_file(int, char **); bool get_word(std::string &); bool get_words(std::string &, std::string &); std::ostream &print(std::ostream &, const QueryResult &); #endif //TEST_QUERY_H
Query.cpp
#include "Query.h" #include "TextQuery.h" #include <memory> using std::shared_ptr; #include <set> using std::set; #include <algorithm> using std::set_intersection; #include <iostream> using std::ostream; #include <cstddef> using std::size_t; #include <iterator> using std::inserter; #include <vector> using std::vector; #include <string> using std::string; // returns the lines not in its operand's result set QueryResult NotQuery::eval(const TextQuery &text) const { // virtual call to eval through the Query operand QueryResult result = query.eval(text); // start out with an empty result set shared_ptr<set<line_no> > ret_lines(new set<line_no>); // we have to iterate through the lines on which our operand appears QueryResult::line_it beg = result.begin(), end = result.end(); // for each line in the input file, if that line is not in result, // add that line number to ret_lines vector<string>::size_type sz = result.get_file()->size(); for (size_t n = 0; n != sz; ++n) { // if we haven't processed all the lines in result // check whether this line is present if (beg == end || *beg != n) ret_lines->insert(n); // if not in result, add this line else if (beg != end) ++beg; // otherwise get the next line number in result if there is one } return QueryResult(rep(), ret_lines, result.get_file()); } // returns the intersection of its operands' result sets QueryResult AndQuery::eval(const TextQuery &text) const { std::cout << "eval of AndQuery" << std::endl; // virtual calls through the Query operands to get result sets for the operands QueryResult left = lhs.eval(text), right = rhs.eval(text); // set to hold the intersection of left and right shared_ptr<set<line_no> > ret_lines(new set<line_no>); // writes the intersection of two ranges to a destination iterator // destination iterator in this call adds elements to ret set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(*ret_lines, ret_lines->begin())); return QueryResult(rep(), ret_lines, left.get_file()); } // returns the union of its operands' result sets QueryResult OrQuery::eval(const TextQuery &text) const { std::cout << "eval of OrQuery" << std::endl; // virtual calls through the Query members, lhs and rhs // the calls to eval return the QueryResult for each operand QueryResult right = rhs.eval(text), left = lhs.eval(text); // copy the line numbers from the left-hand operand into the result set shared_ptr<set<line_no> > ret_lines(new set<line_no>(left.begin(), left.end())); // insert lines from the right-hand operand ret_lines->insert(right.begin(), right.end()); // return the new QueryResult representing the union of lhs and rhs return QueryResult(rep(), ret_lines, left.get_file()); }
and_orQueryTest.cpp
#include "Query.h" #include "TextQuery.h" #include <string> #include <set> #include <iostream> using std::set; using std::string; using std::cin; using std::cout; using std::cerr; using std::endl; int main(int argc, char **argv) { // gets file to read and builds map to support queries TextQuery file = get_file(argc, argv); // iterate with the user: prompt for a word to find and print results while (true) { string sought1, sought2, sought3; if (!get_words(sought1, sought2)) break; cout << "\nenter third word: " ; cin >> sought3; // find all the occurrences of the requested string Query q = Query(sought1) & Query(sought2) | Query(sought3); cout << "\nExecuting Query for: " << q << endl; const QueryResult results = q.eval(file); // report matches print(cout, results); } return 0; }
// 运行结果 enter two words to search for, or q to quit: fiery bird enter third word: wind Executing Query for: ((fiery & bird) | wind) eval of Query eval of OrQuery eval of Query eval of WordQuery eval of Query eval of AndQuery eval of Query eval of WordQuery eval of Query eval of WordQuery ((fiery & bird) | wind) occurs 3 times (line 2)Her Daddy says when the wind blows (line 4)like a fiery bird in flight. (line 5)A beautiful fiery bird, he tells her, enter two words to search for, or q to quit: q Process finished with exit code 0
15.37
【出题思路】
练习类层次的不同实现方式。
【解答】
书中的实现方式是用 Query 类封装了 Query_base 指针,管理实际查询处理用到的不同 Query 类型对象。
如果不使用 Query 类,则涉及使用 Query 类型的地方,都要改成 Query_base 指针。如创建单个词查询时,就必须创建 WordQuery 类而不是 Query 对象。几个重载的布尔运算符也不能再针对 Query 对象,而需针对 Query_base 指针,从而复杂的查询请求无法写成目前的简单形式,而需逐个运算完成,将结果赋予 Query_base 指针,然后再进行下一步运算。资源管理方面也需要重新设计。
因此,当前的设计仍是最佳方式。
15.38
【出题思路】
理解虚函数和类层次的概念。
【解答】
第一条声明不合法,因为 BinaryQuery 中的 eval 是纯虚函数。
第二条声明不合法,不能将 Query 转换为 AndQuery。
第三条声明不合法,不能将 Query 转换为 OrQuery。
All three declarations are illegal. We cannot directly create object of
BinaryQuery
,AndQuery
orOrQuery
. All these class are used for implementation, and they are not part of the interface. User code cannot use them directly, because all members of these class are notpublic
, including constructors. Meanwhile, there is not constructor for classBinaryQuery
,AndQuery
orOrQuery
that takes ashared_ptr<Query_base>
as parameter.
15.39
【出题思路】
练习复杂类层次的实现。
【解答】
参考书中本节内容和配套网站中的代码即可。
下面是我从书籍配套网站下载的代码调试运行结果。4 个 .h
头文件和 6 个 .cpp
实现文件:
4 个
.h
头文件:QueryResult.h、TextQuery.h、make_plural.h 和 Query.h;6 个
.cpp
实现文件:TextQuery.cpp、Query.cpp、get_print.cpp,另外三个.cpp
文件 and_orQueryTest.cpp、andQueryTest.cpp 和 wordQueryTest.cpp 用于测试。
代码如下所示:
QueryResult.h、TextQuery.h、TextQuery.cpp 和 make_plural.h 同练习 12.27。
Query.h
#ifndef TEST_QUERY_H #define TEST_QUERY_H #include "TextQuery.h" #include <string> #include <set> #include <iostream> #include <fstream> #include <sstream> #include <memory> // abstract class acts as a base class for concrete query types; all members are private class Query_base { friend class Query; protected: typedef TextQuery::line_no line_no; // used in the eval functions virtual ~Query_base() {} private: // eval returns the QueryResult that matches this Query virtual QueryResult eval(const TextQuery &) const = 0; // rep is a string representation of the query virtual std::string rep() const = 0; }; // interface class to manage the Query_base inheritance hierarchy class Query { // these operators need access to the shared_ptr constructor friend Query operator~(const Query &); friend Query operator|(const Query &, const Query &); friend Query operator&(const Query &, const Query &); public: Query(const std::string &); // builds a new WordQuery // interface functions: call the corresponding Query_base operations QueryResult eval(const TextQuery &t) const { return q->eval(t); } std::string rep() const { return q->rep(); } private: Query(std::shared_ptr<Query_base> query) : q(query) {} std::shared_ptr<Query_base> q; }; inline std::ostream & operator<<(std::ostream &os, const Query &query) { // Query::rep makes a virtual call through its Query_base pointer to rep() return os << query.rep(); } class WordQuery : public Query_base { friend class Query; // Query uses the WordQuery constructor WordQuery(const std::string &s) : query_word(s) {} // concrete class: WordQuery defines all inherited pure virtual functions QueryResult eval(const TextQuery &t) const { return t.query(query_word); } std::string rep() const { return query_word; } std::string query_word; // word for which to search }; inline Query::Query(const std::string &s) : q(new WordQuery(s)) {} class NotQuery : public Query_base { friend Query operator~(const Query &); NotQuery(const Query &q) : query(q) {} // concrete class: NotQuery defines all inherited pure virtual functions std::string rep() const { return "~(" + query.rep() + ")"; } QueryResult eval(const TextQuery &) const; Query query; }; class BinaryQuery : public Query_base { protected: BinaryQuery(const Query &l, const Query &r, std::string s) : lhs(l), rhs(r), opSym(s) {} // abstract class: BinaryQuery doesn't define eval std::string rep() const { return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; } Query lhs, rhs; // right- and left-hand operands std::string opSym; // name of the operator }; class AndQuery : public BinaryQuery { friend Query operator&(const Query &, const Query &); AndQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "&") {} // concrete class: AndQuery inherits rep and defines the remaining pure virtual QueryResult eval(const TextQuery &) const; }; class OrQuery : public BinaryQuery { friend Query operator|(const Query &, const Query &); OrQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "|") {} QueryResult eval(const TextQuery &) const; }; inline Query operator&(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new AndQuery(lhs, rhs)); } inline Query operator|(const Query &lhs, const Query &rhs) { return std::shared_ptr<Query_base>(new OrQuery(lhs, rhs)); } inline Query operator~(const Query &operand) { return std::shared_ptr<Query_base>(new NotQuery(operand)); } std::ifstream &open_file(std::ifstream &, const std::string &); TextQuery get_file(int, char **); bool get_word(std::string &); bool get_words(std::string &, std::string &); std::ostream &print(std::ostream &, const QueryResult &); #endif //TEST_QUERY_H
Query.cpp
#include "Query.h" #include "TextQuery.h" #include <memory> using std::shared_ptr; #include <set> using std::set; #include <algorithm> using std::set_intersection; #include <iostream> using std::ostream; #include <cstddef> using std::size_t; #include <iterator> using std::inserter; #include <vector> using std::vector; #include <string> using std::string; // returns the lines not in its operand's result set QueryResult NotQuery::eval(const TextQuery &text) const { // virtual call to eval through the Query operand QueryResult result = query.eval(text); // start out with an empty result set shared_ptr<set<line_no> > ret_lines(new set<line_no>); // we have to iterate through the lines on which our operand appears QueryResult::line_it beg = result.begin(), end = result.end(); // for each line in the input file, if that line is not in result, // add that line number to ret_lines vector<string>::size_type sz = result.get_file()->size(); for (size_t n = 0; n != sz; ++n) { // if we haven't processed all the lines in result // check whether this line is present if (beg == end || *beg != n) ret_lines->insert(n); // if not in result, add this line else if (beg != end) ++beg; // otherwise get the next line number in result if there is one } return QueryResult(rep(), ret_lines, result.get_file()); } // returns the intersection of its operands' result sets QueryResult AndQuery::eval(const TextQuery &text) const { // virtual calls through the Query operands to get result sets for the operands QueryResult left = lhs.eval(text), right = rhs.eval(text); // set to hold the intersection of left and right shared_ptr<set<line_no> > ret_lines(new set<line_no>); // writes the intersection of two ranges to a destination iterator // destination iterator in this call adds elements to ret set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(*ret_lines, ret_lines->begin())); return QueryResult(rep(), ret_lines, left.get_file()); } // returns the union of its operands' result sets QueryResult OrQuery::eval(const TextQuery &text) const { // virtual calls through the Query members, lhs and rhs // the calls to eval return the QueryResult for each operand QueryResult right = rhs.eval(text), left = lhs.eval(text); // copy the line numbers from the left-hand operand into the result set shared_ptr<set<line_no> > ret_lines(new set<line_no>(left.begin(), left.end())); // insert lines from the right-hand operand ret_lines->insert(right.begin(), right.end()); // return the new QueryResult representing the union of lhs and rhs return QueryResult(rep(), ret_lines, left.get_file()); }
get_print.cpp
#include "Query.h" #include "TextQuery.h" #include <string> using std::string; #include <iostream> using std::cout; using std::cin; #include <fstream> using std::ifstream; #include <stdexcept> using std::runtime_error; // these functions are declared in Query.h TextQuery get_file(int argc, char **argv) { // get a file to read from which user will query words ifstream infile; if (argc == 2) infile.open(argv[1]); if (!infile) { throw runtime_error("No input file!"); } return TextQuery(infile); // builds query map } bool get_word(string &s1) { cout << "enter a word to search for, or q to quit: "; cin >> s1; if (!cin || s1 == "q") return false; else return true; } bool get_words(string &s1, string &s2) { // iterate with the user: prompt for a word to find and print results cout << "enter two words to search for, or q to quit: "; cin >> s1; // stop if hit eof on input or a "q" is entered if (!cin || s1 == "q") return false; cin >> s2; return true; }
and_orQueryTest.cpp
#include "Query.h" #include "TextQuery.h" #include <string> #include <set> #include <iostream> using std::set; using std::string; using std::cin; using std::cout; using std::cerr; using std::endl; int main(int argc, char **argv) { // gets file to read and builds map to support queries TextQuery file = get_file(argc, argv); // iterate with the user: prompt for a word to find and print results while (true) { string sought1, sought2, sought3; if (!get_words(sought1, sought2)) break; cout << "\nenter third word: " ; cin >> sought3; // find all the occurrences of the requested string Query q = Query(sought1) & Query(sought2) | Query(sought3); cout << "\nExecuting Query for: " << q << endl; const QueryResult results = q.eval(file); // report matches print(cout, results); } return 0; }
运行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 为 ../data
。
注:../data
即为文件 data 的文件名及其相对路径(是相对于可执行程序所在目录的相对路径)。
并在文件 data 中写入如下内容:
Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful fiery bird, he tells her, magical but untamed. "Daddy, shush, there is no such thing," she tells him, at the same time wanting him to tell her more. Shyly, she asks, "I mean, Daddy, is there?"
运行程序,程序执行结果如下所示:
// 运行结果 enter two words to search for, or q to quit: fiery bird enter third word: wind Executing Query for: ((fiery & bird) | wind) ((fiery & bird) | wind) occurs 3 times (line 2)Her Daddy says when the wind blows (line 4)like a fiery bird in flight. (line 5)A beautiful fiery bird, he tells her, enter two words to search for, or q to quit: q Process finished with exit code 0
下面是 andQueryTest.cpp 和 wordQueryTest.cpp 测试程序及其执行结果:
andQueryTest.cpp
#include "Query.h" #include "TextQuery.h" #include <string> using std::string; #include <iostream> using std::cout; using std::endl; #include <set> using std::set; int main(int argc, char **argv) { // gets file to read and builds map to support queries TextQuery file = get_file(argc, argv); do { string sought1, sought2; // stop if hit eof on input or a "q" is entered if (!get_words(sought1, sought2)) break; // find all the occurrences of the requested string Query andq = Query(sought1) & Query(sought2); cout << "\nExecuting query for: " << andq << endl; QueryResult results = andq.eval(file); // report matches print(cout, results); results = Query(sought1).eval(file); cout << "\nExecuted query: " << Query(sought1) << endl; // report matches print(cout, results); results = Query(sought2).eval(file); cout << "\nExecuted query: " << Query(sought2) << endl; // report matches print(cout, results); } while (true); return 0; }
// 运行结果 enter two words to search for, or q to quit: hair Alice Executing query for: (hair & Alice) (hair & Alice) occurs 1 time (line 1)Alice Emma has long flowing red hair. Executed query: hair hair occurs 2 times (line 1)Alice Emma has long flowing red hair. (line 3)through her hair, it looks almost alive, Executed query: Alice Alice occurs 1 time (line 1)Alice Emma has long flowing red hair. enter two words to search for, or q to quit: q Process finished with exit code 0
wordQueryTest.cpp
#include "Query.h" #include "TextQuery.h" #include <string> #include <vector> #include <map> #include <set> #include <iostream> #include <fstream> #include <cctype> #include <cstring> using std::set; using std::string; using std::map; using std::vector; using std::cerr; using std::cout; using std::cin; using std::ifstream; using std::endl; int main(int argc, char **argv) { TextQuery file = get_file(argc, argv); // iterate with the user: prompt for a word to find and print results do { string sought; if (!get_word(sought)) break; // find all the occurrences of the requested string // define synonym for the line_no set Query name(sought); const QueryResult results = name.eval(file); cout << "\nExecuting Query for: " << name << endl; // report no matches print(cout, results) << endl; } while (true); // loop indefinitely; the exit is inside the loop return 0; }
// 运行结果 enter a word to search for, or q to quit: Daddy Executing Query for: Daddy Daddy occurs 3 times (line 2)Her Daddy says when the wind blows (line 7)"Daddy, shush, there is no such thing," (line 10)Shyly, she asks, "I mean, Daddy, is there?" enter a word to search for, or q to quit: q Process finished with exit code 0
15.40
【出题思路】
理解并集的计算过程。
【解答】
OrQuery 的 eval 从 lhs 和 rhs 获取范围来构造 set(或向其插入),而 set 的构造和插入操作可以正确处理空范围,因此无论 lhs 和 rhs 的结果是否为空集,eval 都能得到正确结果。
15.41
【出题思路】
练习在复杂程序中,自己实现资源管理。
【解答】
关键是在没有了 shared_ptr 的帮助后,要为 Query 类设计拷贝控制成员,管理内存。
除了将成员 q 的类型改为 Query_base *
外,还需增加引用计数成员 int *uc
,并增加拷贝构造函数、拷贝赋值运算符和析构函数。具体方法参考第 13 章的练习即可。当然,其它用到 q 的地方也需要进行修改。
其实只需修改 Query.h 文件中 Query 类的实现部分即可。代码如下所示:
Query.h
#ifndef TEST_QUERY_H #define TEST_QUERY_H #include "TextQuery.h" #include <string> #include <set> #include <iostream> #include <fstream> #include <sstream> #include <memory> // abstract class acts as a base class for concrete query types; all members are private class Query_base { friend class Query; protected: typedef TextQuery::line_no line_no; // used in the eval functions virtual ~Query_base() {} private: // eval returns the QueryResult that matches this Query virtual QueryResult eval(const TextQuery &) const = 0; // rep is a string representation of the query virtual std::string rep() const = 0; }; // interface class to manage the Query_base inheritance hierarchy class Query { // these operators need access to the shared_ptr constructor friend Query operator~(const Query &); friend Query operator|(const Query &, const Query &); friend Query operator&(const Query &, const Query &); public: Query(const std::string &); // builds a new WordQuery // interface functions: call the corresponding Query_base operations QueryResult eval(const TextQuery &t) const { return q->eval(t); } std::string rep() const { return q->rep(); } // 练习 15.41 // 拷贝构造函数 Query(const Query & query) : q(query.q), uc(query.uc) { ++*uc; } // 拷贝赋值运算符 Query &operator=(const Query &); // 析构函数 ~Query(); private: // Query(std::shared_ptr<Query_base> query) : q(query) {} // std::shared_ptr<Query_base> q; Query(Query_base *query) : q(query), uc(new int(1)) {} Query_base *q; int *uc; }; inline std::ostream & operator<<(std::ostream &os, const Query &query) { // Query::rep makes a virtual call through its Query_base pointer to rep() return os << query.rep(); } inline Query& Query::operator=(const Query &query) { ++*query.uc; if (--*uc == 0) { delete q; delete uc; } q = query.q; uc = query.uc; return *this; } inline Query::~Query() { if (--*uc == 0) { delete q; delete uc; } } class WordQuery : public Query_base { friend class Query; // Query uses the WordQuery constructor WordQuery(const std::string &s) : query_word(s) {} // concrete class: WordQuery defines all inherited pure virtual functions QueryResult eval(const TextQuery &t) const { return t.query(query_word); } std::string rep() const { return query_word; } std::string query_word; // word for which to search }; inline Query::Query(const std::string &s) : q(new WordQuery(s)), uc(new int(1)) {} class NotQuery : public Query_base { friend Query operator~(const Query &); NotQuery(const Query &q) : query(q) {} // concrete class: NotQuery defines all inherited pure virtual functions std::string rep() const { return "~(" + query.rep() + ")"; } QueryResult eval(const TextQuery &) const; Query query; }; class BinaryQuery : public Query_base { protected: BinaryQuery(const Query &l, const Query &r, std::string s) : lhs(l), rhs(r), opSym(s) {} // abstract class: BinaryQuery doesn't define eval std::string rep() const { return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; } Query lhs, rhs; // right- and left-hand operands std::string opSym; // name of the operator }; class AndQuery : public BinaryQuery { friend Query operator&(const Query &, const Query &); AndQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "&") {} // concrete class: AndQuery inherits rep and defines the remaining pure virtual QueryResult eval(const TextQuery &) const; }; class OrQuery : public BinaryQuery { friend Query operator|(const Query &, const Query &); OrQuery(const Query &left, const Query &right) : BinaryQuery(left, right, "|") {} QueryResult eval(const TextQuery &) const; }; inline Query operator&(const Query &lhs, const Query &rhs) { return new AndQuery(lhs, rhs); } inline Query operator|(const Query &lhs, const Query &rhs) { return new OrQuery(lhs, rhs); } inline Query operator~(const Query &operand) { return new NotQuery(operand); } std::ifstream &open_file(std::ifstream &, const std::string &); TextQuery get_file(int, char **); bool get_word(std::string &); bool get_words(std::string &, std::string &); std::ostream &print(std::ostream &, const QueryResult &); #endif //TEST_QUERY_H
我们拿 and_orQueryTest.cpp 做测试,如下所示:
#include "Query.h" #include "TextQuery.h" #include <string> #include <set> #include <iostream> using std::set; using std::string; using std::cin; using std::cout; using std::cerr; using std::endl; int main(int argc, char **argv) { // gets file to read and builds map to support queries TextQuery file = get_file(argc, argv); // iterate with the user: prompt for a word to find and print results while (true) { string sought1, sought2, sought3; if (!get_words(sought1, sought2)) break; cout << "\nenter third word: " ; cin >> sought3; // find all the occurrences of the requested string Query q = Query(sought1) & Query(sought2) | Query(sought3); cout << "\nExecuting Query for: " << q << endl; const QueryResult results = q.eval(file); // report matches print(cout, results); } return 0; }
// 运行结果 enter two words to search for, or q to quit: fiery bird enter third word: wind Executing Query for: ((fiery & bird) | wind) ((fiery & bird) | wind) occurs 3 times (line 2)Her Daddy says when the wind blows (line 4)like a fiery bird in flight. (line 5)A beautiful fiery bird, he tells her, enter two words to search for, or q to quit: q Process finished with exit code 0
15.42
【出题思路】
本题练习复杂程序的设计和实现。
【解答】
在 练习 12.27 的代码基础上进行如下修改:
(a)要支持基于同一句子而不是同一行计算单词,只需将文本按句子而不是按文本行存储到 vector 容器。
在 TextQuery.cpp 中将 TextQuery 类的构造函数修改如下:
TextQuery.cpp
#include "TextQuery.h" #include "make_plural.h" #include <cstddef> using std::size_t; #include <memory> using std::shared_ptr; #include <sstream> using std::istringstream; #include <string> using std::string; using std::getline; #include <vector> using std::vector; #include <map> using std::map; #include <set> using std::set; #include <iostream> using std::cerr; using std::cout; using std::cin; using std::endl; using std::ostream; #include <fstream> using std::ifstream; #include <cctype> using std::ispunct; using std::tolower; #include <cstring> using std::strlen; #include <utility> using std::pair; // because we can't use auto, we'll define typedefs // to simplify our code // type of the lookup map in a TextQuery object typedef map<string, shared_ptr<set<TextQuery::line_no>>> wmType; typedef wmType::mapped_type lineType; // the iterator type for the map typedef wmType::const_iterator wmIter; // type for the set that holds the line numbers typedef set<TextQuery::line_no>::const_iterator lineIter; // 读输入文件,将每个句子存储为 vector 的一个元素 TextQuery::TextQuery(ifstream &is) : file(new vector<string>) { char ws[] = {'\t', '\r', '\v', '\f', '\n'}; char eos[] = {'?', '.', '!'}; set<char> whiteSpace(ws, ws + 5); // 空白符 set<char> endOfSentence(eos, eos + 3); // 句子结束符 string sentence; char ch; while (is.get(ch)) { // 未遇到文件结束符 if (!whiteSpace.count(ch)) // 非空白符 sentence += ch; if (endOfSentence.count(ch)) { // 读完了一个句子 file->push_back(sentence); int n = file->size() - 1; istringstream iss(sentence); string word; while (iss >> word) { word = cleanup_str(word); lineType &lines = wm[word]; if (!lines) lines.reset(new set<line_no>); lines->insert(n); } sentence.assign(""); // 将 sentence 清空,准备读下一个句子 } } } // not covered in the book -- cleanup_str removes // punctuation and converts all text to lowercase so that // the queries operate in a case insensitive manner string TextQuery::cleanup_str(const string &word) { string ret; for (string::const_iterator it = word.begin(); it != word.end(); ++it) { if (!ispunct(*it)) ret += tolower(*it); } return ret; } QueryResult TextQuery::query(const string &sought) const { // we'll return a pointer to this set if we don't find sought static lineType nodata(new set<line_no>); // use find and not a subscript to avoid adding words to wm! // cleanup_str removes punctuation and convert sought to lowercase wmIter loc = wm.find(cleanup_str(sought)); if (loc == wm.end()) return QueryResult(sought, nodata, file); // not found else return QueryResult(sought, loc->second, file); } ostream &print(ostream &os, const QueryResult &qr) { // if the word was found, print the count and all occurrences os << qr.sought << " occurs " << qr.lines->size() << " " << make_plural(qr.lines->size(), "time", "s") << endl; // print each line in which the word appeared // for every element in the set for (lineIter num = qr.lines->begin(); num != qr.lines->end(); ++num) // don't confound the user with text lines starting at 0 os << "\t(sentence " << *num + 1 << ")" << *(qr.file->begin() + *num) << endl; return os; } // debugging routine, not covered in the book void TextQuery::display_map() { wmIter iter = wm.begin(), iter_end = wm.end(); // for each word in the map for ( ; iter != iter_end; ++iter) { cout << "word: " << iter->first << "{"; // fetch location vector as a const reference to avoid copying it lineType text_locs = iter->second; lineIter loc_iter = text_locs->begin(), loc_iter_end = text_locs->end(); // print all line numbers for this word while (loc_iter != loc_iter_end) { cout << *loc_iter; if (++loc_iter != loc_iter_end) cout << ", "; } cout << "}\n"; // end list of output this word } cout << endl; // finished printing entire map }
此外,将 print 函数(print 函数在 TextQuery.cpp 中定义)中输出提示的 “line” 改为 “sentence”。
其它部分与 练习 12.27 相同。
运行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 为 ../data
。
注:../data
即为文件 data 的文件名及其相对路径(是相对于可执行程序所在目录的相对路径)。
并在文件 data 中写入如下内容:
Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful fiery bird, he tells her, magical but untamed. "Daddy, shush, there is no such thing," she tells him, at the same time wanting him to tell her more. Shyly, she asks, "I mean, Daddy, is there?"
运行程序,程序执行结果如下所示:
// 运行结果 enter word to look for, or q to quit: Daddy Daddy occurs 3 times (sentence 2)Her Daddy says when the wind blowsthrough her hair, it looks almost alive,like a fiery bird in flight. (sentence 4)"Daddy, shush, there is no such thing,"she tells him, at the same time wantinghim to tell her more. (sentence 5)Shyly, she asks, "I mean, Daddy, is there? enter word to look for, or q to quit: dadd,y. dadd,y. occurs 3 times (sentence 2)Her Daddy says when the wind blowsthrough her hair, it looks almost alive,like a fiery bird in flight. (sentence 4)"Daddy, shush, there is no such thing,"she tells him, at the same time wantinghim to tell her more. (sentence 5)Shyly, she asks, "I mean, Daddy, is there? enter word to look for, or q to quit: q Process finished with exit code 0
未修改前,程序按行存储。程序执行结果如下:
// 运行结果 enter word to look for, or q to quit: Daddy Daddy occurs 3 times (line 2)Her Daddy says when the wind blows (line 7)"Daddy, shush, there is no such thing," (line 10)Shyly, she asks, "I mean, Daddy, is there?" enter word to look for, or q to quit: .?dad,dy .?dad,dy occurs 3 times (line 2)Her Daddy says when the wind blows (line 7)"Daddy, shush, there is no such thing," (line 10)Shyly, she asks, "I mean, Daddy, is there?" enter word to look for, or q to quit: q Process finished with exit code 0
(b)(c)的实现可参考 C++ Primer 习题集(第 5 版)