C++ Primer(第五版)|练习题答案与解析(第十五章:面向对象程序设计)

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->rep
  • OrQuery::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中就不会插入值。所以不会发生什么。

发布了76 篇原创文章 · 获赞 44 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_24739717/article/details/104524523