C++ Primer(第五版)|练习题答案与解析(第十五章:面向对象程序设计)
本博客主要记录C++ Primer(第五版)中的练习题答案与解析。
参考:C++ Primer
C++ Primer
C++Primer
练习题15.1
什么是虚成员?
P526-52。.虚成员是基类希望派生类进行覆盖的函数,在其成员前加关键字virtual,使得该成员可以实现动态绑定。
练习题15.2
protected访问说明符与private有何区别?
P529。
- private成员:即使是基类的派生类也无法直接访问。
- protected成员:基类的派生类可以访问,但禁止其它用户访问。
练习题15.3
定义你自己的Quote类和print_total函数
quote.h
#include <string>
class Quote
{
public:
Quote() = default;
Quote(const std::string &b, double p) :
bookNo(b), price(p) { }
std::string isbn() const { return bookNo; }
virtual double net_price(std::size_t n) const { return n * price; }
virtual ~Quote() = default;
private:
std::string bookNo;
protected:
double price = 0.0;
};
main.c
#include <iostream>
#include <string>
#include <map>
#include <functional>
#include "quote.h"
double print_total (std::ostream& os, const Quote& item, size_t n);
int main()
{
return 0;
}
double print_total(std::ostream &os, const Quote &item, size_t n)
{
double ret = item.net_price(n);
os << "ISBN:" << item.isbn()
<< "# sold: " << n << " total due: " << ret << std::endl;
return ret;
}
练习题15.4
下面哪条声明语句是不正确的?请解释原因。
class Base { ... };
(a ) class Derived : public Derived { ... };
不能自己继承自己,造成类重复定义。
(b ) class Derived : private Base { ... };
这是类的定义,不是声明。
(c ) class Derived : public Base;
类的声明不包含类派生列表。
练习题15.5
定义你自己的Bulk_quote类。
bulk_quote.h
#include <quote.h>
class Bulk_quote : public Quote
{
public:
Bulk_quote() = default;
Bulk_quote(const std::string& b, double p, std::size_t q, double disc) :
Quote(b, p), min_qty(q), discount(disc) { }
double net_price(std::size_t n) const override;
private:
std::size_t min_qty = 0;
double discount = 0.0;
};
bulk_quote.cpp
#include "bulk_quote.h"
double Bulk_quote::net_price(std::size_t n) const
{
return n * price * ( n >= min_qty ? 1 - discount : 1);
}
练习题15.6
将Quote和Bulk_quote的对象传给15.2.1节练习中的print_total函数,检查该函数是否正确。
使用15.3和15.6中定义的类:
main.cpp
#include <iostream>
#include <string>
#include "quote.h"
#include "bulk_quote.h"
double print_total (std::ostream& os, const Quote& item, size_t n);
int main()
{
// ex15.6
Quote q("MLBook", 10.50);
Bulk_quote bq("MLBook", 10.50, 10, 0.3);//超过10本有折扣
print_total(std::cout, q, 10);
print_total(std::cout, bq, 8);//没有折扣
print_total(std::cout, bq, 10);//有折扣
return 0;
}
double print_total(std::ostream &os, const Quote &item, size_t n)
{
double ret = item.net_price(n);
os << "ISBN:" << item.isbn()
<< "# sold: " << n << " total due: " << ret << std::endl;
return ret;
}
测试:
ISBN:MLBook# sold: 10 total due: 105
ISBN:MLBook# sold: 8 total due: 84
ISBN:MLBook# sold: 10 total due: 73.5
练习题15.7
定义一个类使其实现一种数量首先的折扣策略,具体策略是:当购买书籍的数量不超过一个给定的限量时享受折扣,如果购买量一旦超过了限量,则超出的部分将以原价销售。
新添:
limit_quote.h
#include "quote.h"
class Limit_quote : public Quote
{
public:
Limit_quote();
Limit_quote(const std::string& b, double p, std::size_t max, double disc):
Quote(b, p), max_qty(max), discount(disc) { }
double net_price(std::size_t n) const override;
private:
std::size_t max_qty = 0;
double discount = 0.0;
};
limit_quote.cpp
#include "limit_quote.h"
double Limit_quote::net_price(std::size_t n) const
{
if (n > max_qty)
return max_qty * price * discount + (n - max_qty) * price;
else
return n * discount *price;
}
main.cpp
#include <iostream>
#include <string>
#include "quote.h"
#include "bulk_quote.h"
#include "limit_quote.h"
double print_total (std::ostream& os, const Quote& item, size_t n);
int main()
{
Quote q("MLBook", 10);
Bulk_quote bq("MLBook", 10, 10, 0.3);
Limit_quote lq("DLBook", 10, 10 , 0.3);
print_total(std::cout, q, 5);
print_total(std::cout, bq, 5);
print_total(std::cout , lq, 5);
return 0;
}
double print_total(std::ostream &os, const Quote &item, size_t n)
{
double ret = item.net_price(n);
os << "ISBN:" << item.isbn()
<< "# sold: " << n << " total due: " << ret << std::endl;
return ret;
}
测试:
ISBN:MLBook# sold: 5 total due: 50
ISBN:MLBook# sold: 5 total due: 50
ISBN:DLBook# sold: 5 total due: 15
练习题15.8
给出静态类型和动态类型的定义。
P534
- 静态类型:表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型。
- 动态类型:变量或表达式表示的内存中的对象的类型,动态类型直到运行时才可知。
练习题15.9
在什么情况下表达式的静态类型可能与动态类型不同?请给出三个静态类型与动态类型不同的例子。
P534
基类的指针或引用的静态类型可能与其动态类型不一致,如果表达式既不是指针也不是引用,则它的动态类型永远与静态类型一致。
如15.03中的函数print_total中,参数item的静态类型是其定义的类型Quote&,但如果我们传递一个Bulk_quote&进去,则它的动态类型是Bulk_quote&,此例中item的静态类型和动态类型不一致。
练习题15.10
回忆我们在8.1节(279页)进行的讨论,解释284页中将ifstream传递给Sales_data的read函数的程序是如何工作的。
read是std::istream下的函数,不过由于ifstream继承自istream,因此也可以使用read函数。
练习题15.11
为你的Quote类体系添加一个名为debug的虚函数,令其分别显示每个类的数据成员。
// Quote类
virtual void debug() const;
void Quote::debug() const
{
cout << "This is Quote class." << endl;
cout << "bookNo = " << bookNo << " price = " << price << endl;
}
// Bulk_quote类
void debug() const override;
void Bulk_quote::debug() const
{
cout << "This is Bulk_quote class." << endl;
cout << " price = " << price << endl;
cout << "min_qty = " << min_qty << " discount = " << discount << endl;
}
// Limit_quote类
void debug() const override;
void Limit_quote::debug() const
{
cout << "This is Limit_quote class." << endl;
cout << " price = " << price << endl;
cout << "max_qty = " << max_qty << " discount = " << discount << endl;
}
练习题15.12
有必要将一个成员函数同时声明成override和final吗?为什么?
可以这么做,override意味着重载父类中的虚函数,final意味着禁止子类重载该虚函数。两个用法并不冲突。
练习题15.13
给定下面的类,解释每个print函数的机理,且中存在问题吗?如果有,你该如何修改它?:
class base {
public:
string name() { return basename; }
virtual void print (ostream& os) { os << basename; }
private:
string basename = "home";
};
class derived : public base {
public:
void print(ostream& os) { print(os); os << " " << i; }
// 这里意为重写base类的print函数,并且在其中调用base的print(os),但是由于没有加::范围限定符,导致
// 其调用的还是derived的print函数,造成无限递归。现象是打印完一行home之后就卡住了。
// 改为:void print(ostream& os) override{ base::print(os); os << " " << i; }
// 加override说明是覆盖基类的虚函数。
private:
int i = 2;
};
练习题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()
P527:当使用基类的引用(或指针)调用一个虚函数时将发生动态绑定。
练习题15.15
定义你自己的Disc_quote和Bulk_quote。
disc_quote.h
#include "quote.h"
class Disc_quote : public Quote
{
public:
Disc_quote();
Disc_quote(const std::string& b, double p, std::size_t q, double d) :
Quote(b, p), quantity(q), discount(d) { }
virtual double net_price(std::size_t n) const override = 0;
protected:
std::size_t quantity;
double discount;
};
bulk_quote.h
#include "disc_quote.h"
class Bulk_quote : public Disc_quote
{
public:
Bulk_quote() = default;
Bulk_quote(const std::string& b, double p, std::size_t q, double disc) :
Disc_quote(b, p, q, disc) { }
double net_price(std::size_t n) const override;
void debug() const override;
};
bulk_quote.cpp
#include "bulk_quote.h"
double Bulk_quote::net_price(std::size_t n) const
{
return n * price * ( n >= quantity ? 1 - discount : 1);
}
void Bulk_quote::debug() const
{
Quote::debug();
std::cout //<< "data members of this class:\n"
<< "min_qty= " << quantity << " "
<< "discount= " << discount<< " ";
}
练习题15.16
改写你在15.2.2节(第533页)练习中编写的数量受限的折扣策略,令其继承Disc_quote。
#include "disc_quote.h"
class Limit_quote : public Disc_quote
{
public:
Limit_quote() = default;
Limit_quote(const std::string& b, double p, std::size_t max, double disc):
Disc_quote(b, p, max, disc) { }
double net_price(std::size_t n) const override
{ return n * price * (n < quantity ? 1 - discount : 1 ); }
void debug() const override;
};
练习题15.17
尝试定义一个Disc_quote的对象,看看编译器给出的错误信息是什么?
error: cannot declare variable ‘dq’ to be of abstract type ‘Disc_quote’
练习题15.18
假设给定了第543页和第544页的类,同时已知每个对象的类型如注释所示,判断下面的哪些赋值语句是合法的。解释那些不合法的语句为什么不被允许:
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
假设543页和544页的每个类都有如下形式的成员函数:
void memfcn(Base &b) { b = *this; }
对于每个类,分别判断上面的函数是否合法。
P544
- 无论D以什么方式继承B,其成员函数和友元都能使用派生类到基类的转换。因此,Pub_Derv, Pro_Derv和Priv_Derv类中都合法。
- 如果D继承B的方式是共有的或受保护的,则D的派生类成员和友元可以使用D向B的类型转换,反之,如果D继承B是私有的,则不能使用。
因此,Derived_from_Public合法,Derived_from_Private和Derived_from_Protected都不合法。
练习题15.20
编写代码检验你对前面两题的回答是否正确。
#include <iostream>
#include <string>
#include "quote.h"
#include "bulk_quote.h"
#include "limit_quote.h"
#include "disc_quote.h"
class Base
{
public:
void pub_mem(); // public member
protected:
int prot_mem; // protected member
private:
char priv_mem; // private member
};
struct Pub_Derv : public Base
{
void memfcn(Base &b) { b = *this; }
};
struct Priv_Derv : private Base
{
void memfcn(Base &b) { b = *this; }
};
struct Prot_Derv : protected Base
{
void memfcn(Base &b) { b = *this; }
};
struct Derived_from_Public : public Pub_Derv
{
void memfcn(Base &b) { b = *this; }
};
struct Derived_from_Private : public Priv_Derv
{
//void memfcn(Base &b) { b = *this; }
};
struct Derived_from_Protected : public Prot_Derv
{
void memfcn(Base &b) { b = *this; }
};
int main()
{
Pub_Derv d1;
Priv_Derv d2;
Prot_Derv d3;
Derived_from_Public dd1;
Derived_from_Protected dd2;
Derived_from_Private dd3;
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,非法
return 0;
}
练习题15.21
从下面这些一般性抽象概念中任选一个(或者选一个你自己的),将其对应的一组类型组织成一个继承体系:
(a ) 图形文件格式(如gif、tiff、jpeg、bmp)
(b ) 图形基元(如方格、圆、球、圆锥)
(c ) C++语言中的类型(如类、函数、成员函数)
练习题15.22
对于你在上一题中选择的类,为其添加合适的虚函数及共有成员和受保护的成员。
class Shape
{
public:
typedef std::pair<double, double> Coordinate;
Shape() = default;
Shape(const std::string& n) :
name(n) { }
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual ~Shape() = default;
private:
std::string name;
};
class Rectangle : public Shape
{
public:
Rectangle() = default;
Rectangle(const std::string& n,
const Coordinate& a,
const Coordinate& b,
const Coordinate& c,
const Coordinate& d) :
Shape(n), a(a), b(b), c(c), d(d) { }
~Rectangle() = default;
protected:
Coordinate a;
Coordinate b;
Coordinate c;
Coordinate d;
};
class Square : public Rectangle
{
public:
Square() = default;
Square(const std::string& n,
const Coordinate& a,
const Coordinate& b,
const Coordinate& c,
const Coordinate& d) :
Rectangle(n, a, b, c, d) { }
~Square() = default;
};
练习题15.23
假设第520页的D1类需要覆盖它继承而来的fcn函数,你应该如何对其进行修改?如果你修改了之后fcn匹配了Base中的定义,则该节的那些调用语句应如何解析?
#include <iostream>
using namespace std;
class Base
{
public:
virtual int fcn() { cout << "Base::fcn()" << endl; }
};
class D1 : public Base
{
public:
int fcn(int);
virtual int fcn() override{ cout << "d1::fcn()" << endl; }
virtual void f2(){ cout << "d1::f2()" << endl; }
};
class D2 : public D1
{
public:
int fcn(int);
int fcn() override { cout << "d2::fcn()" << endl; }
void f2() override { cout << "d2::f2()" << endl; }
};
int main()
{
Base bobj;
D1 d1obj;
D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
bp1->fcn(); // 虚调用,运行时调用Base::fcn
bp2->fcn(); // 虚调用,运行时调用D1::fcn
bp3->fcn(); // 虚调用,运行时调用D2::fcn
D1 *d1p = &d1obj; D2 *d2p = &d2obj;
// bp2->f2(); // 错误,Base中没有f2成员。
d1p->f2(); // 虚调用,运行时调用D1::f2()
d2p->f2(); // 虚调用,运行时调用D2::f2()
return 0;
}
测试:
Base::fcn()
d1::fcn()
d2::fcn()
d1::f2()
d2::f2()
练习题15.24
哪种类需要虚析构函数?虚析构函数必须执行什么样的操作?
作为基类,被其它类继承的类需要有虚析构函数,基类的析构函数定义为虚函数,可以允许子类中的对象动态销毁。
练习题15.25
我们为什么为Disc_quote定义一个默认构造函数?如果去除掉该构造函数的话会对Bulk_quote的行为产生什么影响?
P554,因为Disc_quote有自定义的构造函数,如果不显示声明,编译器不会再生成默认构造函数,这将阻止其子类生成默认构造函数,因此基类的默认构造函数应该显式声明,以便子类在执行默认构造函数的时候调用。
练习题15.26
定义Quote和Bulk_quote的拷贝控制成员,令其与合成的版本行为一致。为这些成员以及其他构造函数添加打印状态的语句,使得我们能够知道正在运行哪个程序。使用这些类编写程序,预测程序将创建和销毁哪些对象。重复实验,不断比较你的预测和实际输出结果是否相同,直到预测完全准确再结束。
#include <string>
#include <iostream>
class Quote
{
friend bool operator !=(const Quote& lhs, const Quote& rhs);
public:
Quote() { std::cout << "默认构造 Quote\n"; }
Quote(const std::string &b, double p) :
bookNo(b), price(p) { std::cout << "Quote : 构造函数有两个参数\n"; }
// copy constructor
Quote(const Quote& q) : bookNo(q.bookNo), price(q.price)
{ std::cout << "Quote: 拷贝构造\n"; }
// move constructor
Quote(Quote&& q) noexcept : bookNo(std::move(q.bookNo)), price(std::move(q.price))
{ std::cout << "Quote: 移动构造\n"; }
// copy =
Quote& operator =(const Quote& rhs)
{
if(*this != rhs)
{
bookNo = rhs.bookNo;
price = rhs.price;
}
std::cout << "Quote: copy =() \n";
return *this;
}
// move =
Quote& operator =(Quote&& rhs) noexcept
{
if(*this != rhs)
{
bookNo = std::move(rhs.bookNo);
price = std::move(rhs.price);
}
std::cout << "Quote: move =!!!!!!!!! \n";
return *this;
}
std::string isbn() const { return bookNo; }
virtual double net_price(std::size_t n) const { return n * price; }
virtual void debug() const;
virtual ~Quote()
{
std::cout << "销毁 Quote\n";
}
private:
std::string bookNo;
protected:
double price = 10.0;
};
bool inline
operator !=(const Quote& lhs, const Quote& rhs)
{
return lhs.bookNo != rhs.bookNo
&&
lhs.price != rhs.price;
}
#include "quote.h"
class Disc_quote : public Quote
{
friend bool operator !=(const Disc_quote& lhs, const Disc_quote& rhs);
public:
Disc_quote() { std::cout << "默认构造 Disc_quote\n"; }
Disc_quote(const std::string& b, double p, std::size_t q, double d) :
Quote(b, p), quantity(q), discount(d)
{
std::cout << "Disc_quote : 构造函数有4个参数.\n";
}
// copy constructor
Disc_quote(const Disc_quote& dq) :
Quote(dq), quantity(dq.quantity), discount(dq.discount)
{
std::cout << "Disc_quote : 拷贝构造.\n";
}
// move constructor
Disc_quote(Disc_quote&& dq) noexcept :
Quote(std::move(dq)), quantity(std::move(dq.quantity)), discount(std::move(dq.discount))
{
std::cout << "Disc_quote : 移动构造.\n";
}
// copy =()
Disc_quote& operator =(const Disc_quote& rhs)
{
Quote::operator =(rhs);
this->quantity = rhs.quantity;
this->discount = rhs.discount;
std::cout << "Disc_quote : copy =()\n";
return *this;
}
// move =()
Disc_quote& operator =(Disc_quote&& rhs) noexcept
{
if (*this != rhs)//调用operator !=(const Disc_quote& lhs, const Disc_quote& rhs)
{
Quote::operator =(std::move(rhs));
this->quantity = std::move(rhs.quantity);
this->discount = std::move(rhs.discount);
std::cout << "*this != rhs\n";
}
std::cout << "Disc_quote : move =()\n";
return *this;
}
virtual double net_price(std::size_t n) const override = 0;
~Disc_quote()
{
std::cout << "销毁 Dis_quote\n";
}
protected:
std::size_t quantity = 3;
double discount = 0.0;
};
bool inline
operator !=(const Disc_quote& lhs, const Disc_quote& rhs)
{
return Quote(lhs) != Quote(rhs)
&&
lhs.quantity != rhs.quantity
&&
lhs.discount != rhs.discount;
}
#include "disc_quote.h"
class Bulk_quote : public Disc_quote
{
public:
Bulk_quote() { std::cout << "默认构造 Bulk_quote\n"; }
Bulk_quote(const std::string& b, double p, std::size_t q, double disc) :
Disc_quote(b, p, q, disc) { std::cout << "Bulk_quote : 构造函数有4个参数\n"; }
// copy constructor
Bulk_quote(const Bulk_quote& bq) : Disc_quote(bq)
{ std::cout << "Bulk_quote : 拷贝构造\n"; }
// move constructor
//page 535, " In a constructor, noexcept appears between the parameter list and the : that begins the constructor initializer list"
Bulk_quote(Bulk_quote&& bq) noexcept : Disc_quote(std::move(bq))
{
std::cout << "Bulk_quote : 移动构造\n";
}
// copy =()
Bulk_quote& operator =(const Bulk_quote& rhs)
{
Disc_quote::operator =(rhs);
std::cout << "Bulk_quote : copy =()\n";
return *this;
}
// move =()
Bulk_quote& operator =(Bulk_quote&& rhs) noexcept
{
Disc_quote::operator =(std::move(rhs));
std::cout << "Bulk_quote : move =()\n";
return *this;
}
double net_price(std::size_t n) const override;
void debug() const override;
~Bulk_quote() override
{
std::cout << "销毁 Bulk_quote\n";
}
};
测试文件:
#include <iostream>
#include <string>
#include "quote.h"
#include "bulk_quote.h"
#include "limit_quote.h"
#include "disc_quote.h"
int main()
{
std::cout << "---------测试1------------" << std::endl;
Bulk_quote bq1;
std::cout << "---------测试2------------" << std::endl;
Bulk_quote bq2("ss", 2.05, 12, 0.3);
std::cout << "---------测试3------------" << std::endl;
bq2 = std::move(bq2);
return 0;
}
测试:
---------测试1------------
默认构造 Quote
默认构造 Disc_quote
默认构造 Bulk_quote
---------测试2------------
Quote : 构造函数有两个参数
Disc_quote : 构造函数有4个参数.
Bulk_quote : 构造函数有4个参数
---------测试3------------
Quote: 拷贝构造
Quote: 拷贝构造
销毁 Quote
销毁 Quote
Disc_quote : move =()
Bulk_quote : move =()
销毁 Bulk_quote
销毁 Dis_quote
销毁 Quote
销毁 Bulk_quote
销毁 Dis_quote
销毁 Quote
解释一下测试3:“Quote: 拷贝构造”两次,是std::move(bq2);
中进行判断使用了operator !=(const Disc_quote& lhs, const Disc_quote& rhs)
,会临时拷贝构造两个Quote类进行比较。最后会销毁bq1和bq2,从派生类到基类依次调用析构函数。
练习题15.27
重新定义你的Bulk_quote类,令其继承构造函数。
class Bulk_quote : public Disc_quote
{
public:
Bulk_quote()
{
cout << "Bulk_quote default construction." << endl;
}
using Disc_quote::Disc_quote;
Bulk_quote(const std::string& b, double p, std::size_t q, double disc) :
Disc_quote(b, p, q, disc){ }
Bulk_quote (Bulk_quote& bq) : Disc_quote(bq)
{
cout << "Bulk_quote copy construction." << endl;
}
Bulk_quote& operator=(Bulk_quote& rhs)
{
Disc_quote::operator=(rhs);
cout << "Bulk_quote assigned construction." << endl;
return *this;
}
Bulk_quote(Bulk_quote&& bq) : Disc_quote(std::move(bq))
{
cout << "Bulk_quote move construction." << endl;
}
double net_price(std::size_t n) const override {}
};
练习题15.28
定义一个存放Quote对象的vector,将Bulk_quote对象传入其中。计算vector中所有元素总的net_price。
练习题15.29
再运行一次你的程序,这次传入Quote对象的shared_ptr。如果这次计算出的总额与之前的程序不一致,解释为什么;如果一致,也请说明原因。
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include "quote.h"
#include "bulk_quote.h"
#include "limit_quote.h"
#include "disc_quote.h"
int main()
{
/**
* 练习题15.28
*/
std::vector<Quote> v;
for(unsigned i =1; i != 2; ++i)
v.push_back(Bulk_quote("sss", i * 10.1, 10, 0.3));
double total = 0;
for (const auto& b : v)
{
total += b.net_price(20);
}
std::cout << total << std::endl;
std::cout << "======================\n\n";
/**
* 练习题15.29
*/
std::vector<std::shared_ptr<Quote>> pv;
for(unsigned i =1; i != 2; ++i)
pv.push_back(std::make_shared<Bulk_quote>(Bulk_quote("sss", i * 10.1, 10, 0.3)));
double total_p = 0;
for (auto p : pv)
{
total_p += p->net_price(20);
}
std::cout << total_p << std::endl;
return 0;
}
测试:
Quote : 构造函数有2个参数
Disc_quote : 构造函数有4个参数.
Quote: 移动构造
销毁 Bulk_quote
destructing Dis_quote
销毁 Quote
202
======================
Quote : 构造函数有2个参数
Disc_quote : 构造函数有4个参数.
Quote: 移动构造
Disc_quote : 移动构造.
Bulk_quote : 移动构造
销毁 Bulk_quote
destructing Dis_quote
销毁 Quote
141.4
销毁 Bulk_quote
destructing Dis_quote
销毁 Quote
销毁 Quote
练习题15.30
编写你自己的Basket类,用它计算上一个练习中交易记录的总价格。
basket.h
#include "quote.h"
#include <set>
#include <memory>
// a basket of objects from Quote hierachy, using smart pointers.
class Basket
{
public:
// copy verison
void add_item(const Quote& sale)
{ items.insert(std::shared_ptr<Quote>(sale.clone())); }
// move version
void add_item(Quote&& sale)
{ items.insert(std::shared_ptr<Quote>(std::move(sale).clone())); }
double total_receipt(std::ostream& os) const;
private:
// function to compare needed by the multiset member
static bool compare(const std::shared_ptr<Quote>& lhs,
const std::shared_ptr<Quote>& rhs)
{ return lhs->isbn() < rhs->isbn(); }
// hold multiple quotes, ordered by the compare member
std::multiset<std::shared_ptr<Quote>, decltype(compare)*>
items{ compare };
};
#include "basket.h"
double Basket::total_receipt(std::ostream &os) const
{
double sum = 0.0;
for(auto iter = items.cbegin(); iter != items.cend();
iter = items.upper_bound(*iter))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// @note this increment moves iter to the first element with key
// greater than *iter.
{
sum += print_total(os, **iter, items.count(*iter));
} // ^^^^^^^^^^^^^ using count to fetch
// the number of the same book.
os << "Total Sale: " << sum << std::endl;
return sum;
}
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <fstream>
#include "quote.h"
#include "bulk_quote.h"
#include "limit_quote.h"
#include "disc_quote.h"
#include "basket.h"
int main()
{
std::cout << "-------------1-----------" << std::endl;
Basket basket;
std::cout << "-------------2-----------" << std::endl;
for (unsigned i = 0; i != 2; ++i)
basket.add_item(Bulk_quote("CNN", 15, 20, 0.3));
std::cout << "-------------3-----------" << std::endl;
for (unsigned i = 0; i != 2; ++i)
basket.add_item(Bulk_quote("Test", 18, 5, 0.4));
std::cout << "-------------4-----------" << std::endl;
for (unsigned i = 0; i != 2; ++i)
basket.add_item(Quote("Train", 20));
std::cout << "-------------5-----------" << std::endl;
std::ofstream log("log.txt", std::ios_base::app|std::ios_base::out);
std::cout << "-------------6-----------" << std::endl;
basket.total_receipt(log);
std::cout << "-------------7-----------" << std::endl;
return 0;
}
测试:
-------------1-----------
-------------2-----------
Quote : constructor taking 2 parameters
Disc_quote : constructor taking 4 parameters.
Quote: move constructing
Disc_quote : move constructor.
Bulk_quote : move constructor
destructing Bulk_quote
destructing Dis_quote
destructing Quote
Quote : constructor taking 2 parameters
Disc_quote : constructor taking 4 parameters.
Quote: move constructing
Disc_quote : move constructor.
Bulk_quote : move constructor
destructing Bulk_quote
destructing Dis_quote
destructing Quote
-------------3-----------
Quote : constructor taking 2 parameters
Disc_quote : constructor taking 4 parameters.
Quote: move constructing
Disc_quote : move constructor.
Bulk_quote : move constructor
destructing Bulk_quote
destructing Dis_quote
destructing Quote
Quote : constructor taking 2 parameters
Disc_quote : constructor taking 4 parameters.
Quote: move constructing
Disc_quote : move constructor.
Bulk_quote : move constructor
destructing Bulk_quote
destructing Dis_quote
destructing Quote
-------------4-----------
Quote : constructor taking 2 parameters
Quote: move constructing
destructing Quote
Quote : constructor taking 2 parameters
Quote: move constructing
destructing Quote
-------------5-----------
-------------6-----------
-------------7-----------
destructing Quote
destructing Quote
destructing Bulk_quote
destructing Dis_quote
destructing Quote
destructing Bulk_quote
destructing Dis_quote
destructing Quote
destructing Bulk_quote
destructing Dis_quote
destructing Quote
destructing Bulk_quote
destructing Dis_quote
destructing Quote
练习题15.31
已知s1、s2、s3和s4都是string,判断下面的表达式分别创建了什么样的对象。
(a)Query(s1) | Query(s2) & ~ Query(s3);
创建:WordQuery, NotQuery, AndQuery, OrQuery, Query
(b)Query(s1) | (Query(s2) & ~ Query(s3));
创建: WordQuery, NotQuery, AndQuery, OrQuery, Query
(c)(Query(s1) | (Query(s2)) | (Query(s3) & Query(s4));
创建:WordQuery, AndQuery, OrQuery, Query
练习题15.32
当一个Query类型的对象被拷贝、移动、赋值或销毁时,将分别发生什么?
- 拷贝:当Query对象被拷贝时,会调用合成的拷贝构造函数,拷贝Query的数据成员,成员q由于是shared_ptr,其引用计数会加1.
- 移动:当Query对象被移动时,会调用合成移动构造函数,会移动数据成员到新的对象。在这个例子中,新对象中的shared_ptr会指向原对象的shared_ptr所指向的地址,新对象的shared_ptr的引用计数加1,原对象的shared_ptr的引用计数减1。
- 赋值:调用合成的赋值函数,结果与拷贝操作相同。
- 销毁:调用合成的析构函数,shared_ptr的引用计数减1,如果其引用计数减至0,则会释放它指向的内存。
练习题15.33
当一个Query_base类型的对象被拷贝、移动、赋值或销毁时,将分别发生什么?
由于Query_base类中没有需要分配内存的数据成员,所以发生拷贝、移动、赋值或销毁,合成的版本就可以用,Query_base是抽象类,所以发生拷贝、移动、赋值或销毁时,操作的其实是它对应类型的子类。
练习题15.34
当一个Query_base类型的对象被拷贝、移动、赋值或销毁时,将分别发生什么?
(a ) 列举出处理表达式的过程中执行的所有构造函数。
(b ) 列举出cout << q所调用的rep。
(c ) 列举出q.eval() 所调用的eval。
(a)Query q = Query("firey") & Query("bird") | Query("wind");
Query::Query(std::string& s)
,s分别是"firey", “bird”, “wind”WordQuery::WordQuery(const std::string& s)
,s分别是"firey", “bird”, “wind”AndQuery::AndQuery(const Query& left, const Query& right)
BinaryQuery::BinaryQuery(const Query &l, const Query& r, std::string s)
Query::Query (std::shared_ptr<Query_base> query)
当调用 q->eval, q->repOrQuery::OrQuery(const Query& left, const Query& right)
BinaryQuery::BinaryQuery(const Query &l, const Query& r, std::string s)
Query::Query (std::shared_ptr<Query_base> query)
(b)cout << q
- 在
operator<<
函数中调用的是Query的rep() ; - Query中的rep调用OrQuery中继承Query_base的rep,因为生成对象q调用的是”|“运算返回的Query。但OrQuery中没有定义rep,因此调用的是Binary_Query中的rep;
- Binary_Query中的rep由lhs和rhs调用。lhs调用的是AndQuery中的rep,rhs调用的是Word_Query中的rep;
- AndQuery中调用的是Binary_Query中的rep;
- Binary_Query中的rep调用的是Word_Query中的rep。
(c)q.eval()
所调用的eval
- Query中的eval调用的是Query_base中的eval。
- 但这里Query_base指向的是OrQuery,所以调用的是OrQuery中的eval。
练习题15.35
实现Query类和Query_base类,其中需要定义rep而无须定义eval。
书上:
#ifndef QUERY_H
#define QUERY_H
#include <string>
#include <vector>
#include <set>
#include <map>
using namespace std;
class TextQuery;
//QueryResult保存查询的结果
class QueryResult
{
public:
typedef vector<string>::size_type line_no;//保存出现的行号,使用类型别名
friend ostream& operator<<(ostream&, const QueryResult&);//输出查询结果
public:
QueryResult(const string& s, shared_ptr<std::set<line_no>> set,
shared_ptr<vector<string>> v)
: word(s), nos(set), input(v)
{
}
private:
string word;//查询的单词
shared_ptr<std::set<line_no>> nos;//用set保存出现的行号,自动排序
shared_ptr<vector<string>> input;//输入文件vector的指针
};
//TextQuery接受输入文件,并保存,生成map
class TextQuery
{
public:
/*using line_no = vector<string>::size_type;//C++11新标准,可以使用typedef代替*/
typedef vector<string>::size_type line_no;
TextQuery(ifstream&);//接受输入文件的构造函数
QueryResult query(const string&) const;//具体的查询函数
private:
shared_ptr<vector<string>> input;//保存输入的vector的智能指针
map<string, shared_ptr<set<line_no>>> result;//map保存单词出现的行和列
};
//抽象基类Query_base,用于派生
class Query_base
{
friend class Query;
private:
//纯虚函数,返回与当前Query匹配的QueryResult
/*virtual QueryResult eval(const TextQuery&) const = 0;*/
//纯虚函数,保存用于查询的string
virtual string rep() const = 0;
protected:
/*using line_no = vector<string>::size_type;//C++11新标准,可以使用typedef代替*/
typedef vector<string>::size_type line_no;
virtual ~Query_base() /*= default*/;//C++11新标准,不加
};
//查询基类 Query
class Query
{
//定义运算符函数
friend Query operator~(const Query&);
friend Query operator|(const Query&,const Query&);
friend Query operator&(const Query&,const Query&);
public:
Query(const string&s);//新的构造函数,查询单词
//接口函数
// QueryResult eval(const TextQuery &t) const
// {
// return q->eval(t);//查询结果
// }//事先无需定义
string rep() const
{
return q->rep();//查询单词
}
private:
Query(shared_ptr<Query_base> query):q(query){}//构造函数,接受一个Query_base的指针
shared_ptr<Query_base> q;//保存一个指向基类的指针,私有成员
};
std::ostream &operator<<(std::ostream &os, const Query query)
{
return os << query.rep();
}
//基础查询单词类
class WordQuery:public Query_base
{
//所有成员皆为private,
friend class Query;//Query需要使用其构造函数
WordQuery(const string &s):Query_word(s){cout<<"WordQuery"<<endl;}//初始化
QueryResult eval(const TextQuery& t) const
{
return t.query(Query_word);
}//纯虚函数,必须重写
string rep() const
{
return Query_word;
}//纯虚函数,必须重写
string Query_word;
};
inline Query::Query(const string &s):q(new WordQuery(s)){cout<<"Query"<<endl;}//程序顺序执行,所用之对象必须进行声明
//NotQuery类,取反的query
class NotQuery:public Query_base
{
friend Query operator~(const Query&);
NotQuery(const Query&q):query(q){cout<<"NotQuery"<<endl;}//构造函数
string rep()
{
return "~("+query.rep()+")";//表示不需要查询的单词
}
QueryResult eval(const TextQuery&);
Query query;
};
// inline Query operator~(const Query&operand)
// {
// return shared_ptr<Query_base>(new NotQuery(operand));
// }
//BinaryQuery类,一个抽象基类,因为继承了基类的纯虚函数eval()
class BinaryQuery:public Query_base
{
protected:
BinaryQuery(const Query&l, const Query&r,string s):lhs(l),rhs(r),opSym(s){cout<<"BinaryQuery"<<endl;}//构造函数
string rep() const
{ cout<<"Binary_rep"<<endl;//36题
return "("+lhs.rep()+" "+opSym+" "+rhs.rep()+")";
}
Query lhs,rhs;
string opSym;//运算符的名字
};
//AndQuery类,继承自抽象基类BinaryQuery
class AndQuery:public BinaryQuery
{
protected:
friend Query operator&(const Query&,const Query&);//之前定义的运算符
AndQuery(const Query&left, const Query&right):BinaryQuery(left,right,"&"){cout<<"AndQuery"<<endl;}//构造函数
//返回查询结果的函数
QueryResult eval(const TextQuery&);
};
// inline Query operator&(const Query&lhs, const Query&rhs)
// {
// return shared_ptr<Query_base>(new AndQuery(lhs,rhs));//在未定义纯虚函数eval()时,为抽象基类,不可实例化
// }
//OrQuery类,继承自抽象基类BinaryQuery
class OrQuery:public BinaryQuery
{
protected:
friend Query operator|(const Query&,const Query&);//之前定义的运算符
OrQuery(const Query&left, const Query&right):BinaryQuery(left,right,"|"){cout<<"OrQuery"<<endl;}//构造函数
//返回查询结果的函数
QueryResult eval(const TextQuery&);
};
// inline Query operator|(const Query&lhs, const Query&rhs)
// {
// return shared_ptr<Query_base>(new OrQuery(lhs,rhs));
// }
#endif QUERY_H
练习题15.37
如果在派生类中含有shared_ptr类型的成员而非Query类型的成员,则你的类需要做出怎样的改变。
不需要做出改变。若要含有base类型的成员,应当在派生类中声明 friend class Query_base。
练习题15.38
下面的声明合法吗?如果不合法,请解释原因:如果合法,请指出该声明的含有。
(a)BinaryQuery a = Query("firey") & Query("bird");
不合法,因为BinaryQuery是一个抽象类。
(b)AndQuery b = Query("firry") & Query("bird");
不合法,因为&操作返回的是Query操作,不能转换为AndQuery。
(c)OrQuery c = Query("firey") & Query("bird");
不合法,因为&操作返回的是Query操作,不能转换为OrQuery 。
练习题15.39
实现Query类和Query_base类,求图15.3中表达式的值并打印相关信息,验证你的程序是否正确。
// Query.cpp的实现。 主要要使用strBlob版本的textQuery
#include "Query.h"
#include <iostream>
#include <algorithm>
using namespace std;
QueryResult OrQuery::eval(const TextQuery& text) const
{
// call Query::eval() --> Query_base::eval() --> QueryResult::eval()
QueryResult right = rhs.eval(text), left = lhs.eval(text);
auto ret_line = make_shared<set<line_no>>(left.begin(), left.end());
ret_line->insert(right.begin(), right.end());
return QueryResult(rep(), ret_line, left.get_file());
}
QueryResult AndQuery::eval(const TextQuery& text) const
{
QueryResult right = rhs.eval(text), left = lhs.eval(text);
// auto ret_line = make_shared<set<line_no>> (left.begin(), left.end());
shared_ptr<std::set<line_no>> ret_lines =
std::make_shared<std::set<line_no>>(left.begin(), left.end());
set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(*ret_lines, ret_lines->begin()));
return QueryResult(rep(), ret_lines, left.get_file());
}
QueryResult NotQuery::eval(const TextQuery& text) const
{
auto result = query.eval(text);
auto ret_lines = make_shared<set<line_no>>();
auto beg = result.begin(), end = result.end();
auto sz = result.get_file().size();
for (size_t i = 0; i < sz; ++ i)
{
if (beg == end || *beg != i)
{
ret_lines->insert(i);
}
else if (beg != end)
{
++ beg;
}
}
return QueryResult(rep(), ret_lines, result.get_file());
}
int main()
{
Query q = Query("firey") & Query("bird") | Query("wind");
cout << q;
}
// 打印的结果 ((firey & bird) | wind)
练习题15.40
在OrQuery的eval函数中,如果rhs成员返回的是空集将发生什么?如果lhs是空集呢?如果lhs和rhs都是空集又将发生什么?
由于:auto ret_line = make_shared<set<line_no>>(left.begin(), left.end());
,这行代码,make_shared 将会动态分配set,如果返回的是空集,set中就不会插入值。所以不会发生什么。