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中就不會插入值。所以不會發生什麼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章