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