程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++primer第15章習題解答

C++primer第15章習題解答

編輯:關於C++

練習15.1:什麼是虛成員?

在類中被聲明成virtual的成員,基類希望這種成員在派生類中重定義。除了構造函數外,任意非static成員都可以為虛成員。

 

練習15.2:protected訪問說明符與private有何區別?

protected為受保護的訪問標號,protected成員可以被該類的成員、友元和派生類成員(非友元)訪問,而不可以被該類型的普通用戶訪問。而private成員只能被基類的成員和友元訪問,派生類不能訪問。

 

練習15.3:定義你自己的Quote類和print_total函數。

 

class Quote
{
public:
	Quote() = default;
	Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) { }
	std::string isbn() const { return bookNo; }
	virtual double net_price(std::size_t n) const { return n * price; }
	virtual ~Quote() = default;
private:
	std::string bookNo;
protected:
	double price = 0.0;
};

double print_total(ostream &os, const Quote &item, size_t n)
{
	double ret = item.net_price(n);
	os << "ISBN: " << item.isbn() << "#sold: " << n << " total due: " << ret << endl;
	return ret;
}

練習15.4:下面哪條聲明語句時不正確的?請解釋原因。

 

class Base{. . .};

(a)class Derived : public Derived {. . .};

(b)class Derived : private Base {. . .};

(c)class Derived : public Base;

(a)錯誤,不能用類本身作為類的基類。(b)正確 (c)錯誤。聲明類時,不可以包含派生列表。

 

練習15.5:定義你自己的Bulk_quote類。

 

class Bulk_quote : public Quote
{
public:
	Bulk_quote() { }
	Bulk_quote(const std::string&, double, std::size_t, double);
	double net_price(size_t cnt) const override
	{
		if (cnt >= min_qty)
			return cnt * (1 - discount) * price;
		else
			return cnt * price;
	}
private:
	std::size_t min_qty = 0;
	double discount = 0.0
};

練習15.6:將Quote和Bulk_quote的對象傳給15.2.1節練習中的print_total函數,檢查改函數是否正確。

 

 

#include 
#include 
using std::ostream;
using std::cin;
using std::endl;
using std::cout;

class Quote
{
public:
	Quote() = default;
	Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) { }
	std::string isbn() const { return bookNo; }
	virtual double net_price(std::size_t n) const { return n * price; }
	virtual ~Quote() = default;
private:
	std::string bookNo;
protected:
	double price = 0.0;
};

double print_total(ostream &os, const Quote &item, size_t n)
{
	double ret = item.net_price(n);
	os << "ISBN: " << item.isbn() << " #sold: " << n << " total due: " << ret << endl;
	return ret;
}

class Bulk_quote : public Quote
{
public:
	Bulk_quote() { }
	Bulk_quote(const std::string &book, double p, std::size_t qty, double disc) : Quote(book, p), min_qty(qty), discount(disc) { }
	double net_price(size_t cnt) const override
	{
		if (cnt >= min_qty)
			return cnt * (1 - discount) * price;
		else
			return cnt * price;
	}
private:
	std::size_t min_qty = 0;
	double discount = 0.0;
};

int main()
{
    Quote b1("0-2016-988", 68.5);  
    Bulk_quote b2("0-2016-988", 68.5, 20, 0.75);  
    print_total(cout, b1, 20);   
    print_total(cout, b2, 20);
    return 0;
}

練習15.7:定義一個類使其實現一種數量受限的折扣策略,具體策略是:當購買書籍的數量不超過一個給定的限量時享受折扣,如果購買量一旦超過了限量,則超出的部分將以原價銷售。

 

 

class Limited_quote : public Quote
{
public:
	double net_price(size_t cnt) const override
	{
		if (cnt <= max_qty)
			return cnt * (1- discount) * price;
		else
			return max_qty * (1-discount) * price + (cnt - max_qty) * price;
	}
private:
	size_t max_qty;
	double discount;
};

練習15.8:給出靜態類型和動態類型的定義。

 

靜態類型在編譯時就已經確定了,它是變量聲明時的類型或表達式生成的類型;而動態類型則是變量或表達式表示的內存中的對象的類型,動態類型直到運行時才能知道。如:Quote *pQuote = new Bulk_quote; ,指針pQuote的靜態類型是Quote,在編譯時就已經確定了。但是它的動態類型是Bulk_quote,知道運行時才能知道它指向的是基類還是派生類。如果一個變量非指針也非引用,則它的靜態類型和動態類型永遠一致。但基類的指針或引用的靜態類型可能與其動態類型不一致。

 

練習15.9:在什麼情況下表達式的靜態類型可能與動態類型不同?請給出三個靜態類型與動態類型不同的例子。

基類的指針或引用的動態類型可能與靜態類型不一致。

Bulk_quote bulk;

Quote *pQuote = &bulk;

Quote &rQuote = bulk;

double print_total(ostream &os, const Quote &item, size_t n);

 

練習15.10:回憶我們在8.1節進行的討論,解釋第284頁中將ifstream傳遞給Sales_data的read函數的程序是如何工作的。

在要求使用基類的地方,可以使用派生類型的對象來代替,是靜態類型和動態類型不同的典型例子。

 

練習15.11:為你的Quote類體系添加一個名為debug的虛函數,令其分別顯示每個類的數據成員。

 

class Quote
{
public:
	Quote()=default;
	Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price){ }
	std::string isbn() const {return bookNo;}
	virtual double net_price(std::size_t n) const {return n * price;}
	virtual void debug()
	{
		cout << "bookNo: " << bookNo << " price: " << price << endl;
	}
	virtual ~Quote() = default;
private:
	std::string bookNo;
protected:
	double price = 0.0;
};

class Bulk_quote : public Quote
{
	Bulk_quote(const string &book = "", double sales_price = 0.0, size_t qty = 0, double disc_rate = 0) : Quote(book, sales_price), min_qty(qty), discount(disc_rate) { }
	double net_price(size_t cnt) const
	{
		if (cnt > min_qty)
			return cnt * (1 - discount) * price;
		else
			return cnt * price;
	}
	virtual void debug()
	{
		Quote::debug();
		cout << "min_qty: " << qty << " discount: " << discount << endl;
	}
private:
	size_t min_qty;
	double discount;
};

練習15.12:有必要將一個成員函數同時聲明成override和final嗎?為什麼?

 

有必要。

override:在C+=11新標准中我們可以使用override關鍵字來說明派生類中的虛函數。這麼做的好處是在使得我們的意圖更加清晰明確地告訴編譯器我們想要覆蓋掉已存在的虛函數。如果定義了一個虛函數與基類中的名字相同但是形參列表不同,在不使用override關鍵字的時候這種函數定義是合法的,在使用了override關鍵字之後這種行為是非法的,編譯器會提示出錯。

final:如果我們將某個函數定義成final,則不允許後續的派生類來覆蓋這個函數,否則會報錯。

因此同時將一個成員函數聲明成override和final能夠使我們的意圖更加清晰。

 

練習15.14:給定上一題中的類以及下面這些對象,說明在運行時調用哪個函數:

base bobj; base *bp1 = &bobj; base &br1 = bobj;

derived dobj; base *bp2 = &dobj; base &br2 = dobj;

(a)bobj.print(); (b)dobj.print(); (c)bp1->name();

(d)bp2->name(); (e)br1.print(); (f)br2.print();

(a)用的是基類的print函數。

(b)用的是派生類的print函數。

(c)用的是基類的name函數。

(d)用的是基類的name函數。

(e)用的是基類的print函數。

(f)用的是派生類的print函數。

 

練習15.15:定義你自己的Disc_quote和Bulk_quote。

 

class Disc_quote : public Quote
{
public:
	Disc_quote(const string &book = "", double sales_price = 0.0, size_t qty = 0, double disc = 0.0) : Quote(book, sales_price), quantity(qty), discount(disc) { }
	double net_price(size_t cnt) = const = 0;
private:
	size_t quantity;
	double discount;
};

class Bulk_quote :public Disc_quote
{
public:
	Bulk_quote(const string &book = "". double sales_price = 0.0, size_t qty = 0, double disc_rate = 0) : Disc_quote(book, sales_price, qty,disc_rate) {  }
	double net_price(size_t cnt) const
	{
		if(cnt > quantity)
			return cnt * (1-discount) * price;
		else
			return cnt * price;
	}
};

練習15.16:改寫你在15.2.2節練習中編寫的數量受限的折扣策略,令其繼承Disc_quote。

 

 

class Limited_quote : public Disc_quote
{
	Limited_quote(const string &book = "", double sales_price = 0.0, size_t qty = 0, double disc_rate = 0.0) : Disc_quote(book, sales_price, qty, disc_rate) { }
	double net_price(size_t cnt) const override
	{
		if (cnt <= quantity)
			return cnt * (1 - discount) * price;
		else
			return quantity * (1 - discount) * price + (cnt - quantity) *price;
	}
};

練習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

只有d1和dd1才能夠賦值。這是因為:只有當派生類公有地繼承基類時,用戶代碼才能使用派生類向基類的轉換;也就是說,如果派生類繼承基類的方式是受保護的或者私有的,則用戶代碼不能使用該轉換。

在題中,只有d1和dd1類是公有地繼承基類,故只有它們才能完成向基類的轉換。

 

練習15.19:假設543頁和544頁的每個類都有如下形式的成員函數:

void memfcn(Base &b) { b = *this; }

對於每個類,分別判斷上面的函數是否合法。

Derived_from_Private: private Priv_Derv這個類的函數不合法。

原因如下:

1、無論派生類以什麼方式繼承基類,派生類的成員函數和友元都能使用派生類向基類的轉換;派生類向其直接基類的類型轉換對於派生類的成員和函數來說永遠是可訪問的。

2、如果派生類繼承基類的方式是公有的或者受保護的,則派生類的成員和友元可以使用派生類向基類的類型轉換;反之,如果派生類繼承基類的方式是私有的,則不能使用。

 

練習15.20:編寫代碼檢驗你對前面兩題的回答是否正確。

 

#include 
using namespace std;

class Base
{
public:
	void pub_mem();
protected:
	int prot_mem;
private:
	char priv_mem;
};
struct Pub_Derv : public Base
{
	int f() {return prot_mem;}
	void memfcn(Base &b)
	{
		b =*this;
		cout << "Pub_Derv" << endl;
	}
};
struct Priv_Derv : private Base
{
	int f1() const {return prot_mem;}
	void memfcn(Base &b)
	{
		b =*this;
		cout << "Priv_Derv" << endl;
	}
};
struct Prot_Derv : protected Base
{
	int f2() {return prot_mem;}
	void memfcn(Base &b)
	{
		b =*this;
		cout << "Prot_Derv" << endl;
	}
};
struct Derived_from_Public : public Pub_Derv
{
	int use_base() {return prot_mem;}
	void memfcn(Base &b)
	{
		b =*this;
		cout << "Derived_from_Public" << endl;
	}
};
struct Derived_from_Protected : protected Pub_Derv
{
	int use_base() {return prot_mem;}
	void memfcn(Base &b)
	{
		b =*this;
		cout << "Derived_from_Protected" << endl;
	}
};

int main(int argc, const char *argv[])
{
	Pub_Derv d1;
	Priv_Derv d2;
	Prot_Derv d3;
	Derived_from_Public dd1;
	//Derived_from_Private dd2;
	Derived_from_Protected dd3;
	Base base;
	Base *p= new Base;
	p = &d1;
	//p = &d2;
	//p = &d3;
	p = &dd1;
	//p = &dd2;
	//p = &dd3;

	d1.memfcn(base);
	d2.memfcn(base);
	d3.memfcn(base);
	dd1.memfcn(base);
	//d2.memfcn(bade);
	dd3.memfcn(base);
	return 0;
}

練習15.21:從下面這些一般性抽象概念中任選一個(或者選擇一個你自己的),將其對應的一組類型組織成一個繼承體系:

 

(a) 圖形文件格式(如gif、tiff、jpeg、bmp)

(b) 圖形基元(如方格、圓、球、圓錐)

(c) C++語言中的類型(如類、函數、成員函數)

對(b)中的幾何圖元組織成一個繼承層次:1)公共基類Figure,表示幾何圖元;2)類Rectangle、Circle、Sphere和Cone分別表示矩形、圓、球形和錐形等圖元,這些類可以定義為Figure類的派生類。

 


練習15.22:對於你在上一題中選擇的類,為其添加合適的虛函數及公有成員和受保護的成員。

 

class Figure
{
public:
	Figure(double, double);
protected:
	double xSize, ySize;
};

class Figure_2D : public Figure{
public:
	Figure_2D(double, double);
	virtual double area() = 0;
	virtual double pcrimeter() = 0;
};

class Figure_3D : public Figure
{
public:
	Figure_3D(double, double, double);
	virtual double cubage() = 0;
protected:
	double zSize;
};

class Rectangle : public Figure_2D
{
public:
	Rectangle(double, double);
	virtual double area();
	virtual double pcrimeter();
};

class Circle : public Figure_2D
{
public:
	Circle(double, double);
	virtual double area();
	virtual double pcrimeter();
};

class Sphere : public Figure_3D
{
public:
	Sphere(double, double, double);
	virtual double cubage();
};

class Cone : public Figure_3D
{
public:
	Cone(double, double, double);
	virtual double cubage();
};

練習15.23:假設第550頁的D1類需要覆蓋它繼承而來的fcn函數,你應該如何對其進行修改?如果你修改之後fcn匹配了Base中的定義,則該節點的那些調用語句將如何解析?

1.將D1類的fcn函數更改為int fcn()。

2.p2->fcn(42),這一條調用語句將會出錯。

 

練習15.24:哪種類需要析構函數?虛析構函數必須執行什麼樣的操作?

作為基類使用的類應該具有虛析構函數,以保證在刪除指向動態分配的對象的基類指針時根據指針實際指向的對象所屬的類型運行適當的析構函數。

虛析構函數可以為空,即不執行任何操作。一般而言,析構函數的主要作用是清除本類中定義的數據成員。如果該類沒有定義指針類成員,則使用合成版本即可;如果該類定義了指針成員,則一般需要自定義析構函數以對指針成員進行適當的清除。因此,如果有虛析構函數必須執行的操作,則就是清除本類中定義的數據成員的操作。

 

練習15.25:我們為什麼為Disc_quote定義一個默認構造函數?如果去除掉該構造函數的話會對Bulk_quote的行為產生什麼影響?

因為Disc_quote的默認構造函數會運行Quote的默認構造函數,而Quote默認構造函數會完成成員的初始化工作。

如果去掉該函數的話,Bulk_quote的默認構造函數無法完成Disc_quote的初始化工作。

 

練習15.26:定義Quote和Bulk_quote的拷貝控制成員,令其與合成的版本行為一致。為這些成員以及其他的構造函數添加打印狀態的語句,使得我們能夠知道正在運行哪個程序。使用這些類編寫程序,預測程序將創建和銷毀哪些對象。重復實驗,不斷比較你的預測和實際輸出結果是否相同,直到預測完全准確再結束。

 

#include 
#include 
#include 
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::ostream;

class Quote
{
public:
	Quote() = default;
	Quote(const string &book = "", double sales_price = 0.0) : 
	bookNo(book), price(sales_price)
	{
		cout << "Quote constructor is running." << endl;
	}
	string isbn() const
	{
		return bookNo;
	}
	virtual double net_price(std::size_t n) const
	{
		return n * price;
	}
	virtual void debug()
	{
		cout << "bookNo = " << bookNo << "price = " << price << endl;
	}
	friend ostream &operator <<(ostream&, Quote&);

private:
	string bookNo;
protected:
	double price = 0.0;
};

ostream & operator <<(ostream &os, Quote &)
{
	os << "\tUsing operator << (ostream &, Quote &):" << endl;
	return os;
}

class Bulk_quote :public Quote
{
public:
	Bulk_quote(const string &book = "", double sales_price = 0.0, size_t qty = 0, double disc = 0.0) :
	Quote(book, sales_price), min_qty(qty), discount(disc)
	{
		cout << "Bulk_constructor is running" < min_qty)
			return cnt * (1 - discount) * price;
		else
			return cnt * price;
	}
	~Bulk_quote()
	{
		cout << "Bulk_quote destructor is running" << endl;
	}
private:
	size_t min_qty;
	double discount;
};

ostream &operator <<(ostream &os, Bulk_quote &bq)
{
	os << "\tUsing operator <<(ostream &, Bulk_quote &)" << endl;
	return os;
}

int main(int argc, char **argv)
{
	Quote base("C++ primer", 128.0);
	Bulk_quote bulk("Core Python Programming", 89, 5, 0.19);
	cout << base << endl;
	cout << bulk << endl;

	return 0;
}

練習15.27:重新定義你的Bulk_quote類,令其繼承構造函數。

 

 

class Disc_quote : public Quote
{
public:
	Disc_quote(const string bookNo = "", double sales_price = 0.0, size_t qty = 0, double disc = 0.0) :
	Quote(book, sales_prices), quantity(qty), discount(disc) { }
	double net_price(size_t cnt) const = 0;
protected:
	size_t quantity;
	double discount;
};

class Bulk_uote : public Disc_quote
{
public:
	using Disc_quote::Disc_quote;
	double net_price(size_t cnt) const
	{
		if (cnt > quantity)
			return cnt * (1 - discount) * price;
		else
			return cnt * price;
	}
};

練習15.28:定義一個存放Quote對象的vector,將Bulk_quote對象傳入其中,計算vector中所有元素總的net_price。

 

#include 
#include 
#include 
using std::cout;
using std::endl;
using std::string;
using std::vecor;

int main(int argc, char *argv[])
{
	vector itemVec;
	for (size_t i = 0; i != 10; ++i)
	{
		Bulk_quote item("C++ Primer", 6, 5, 0.5);
		itemVec.push_back(item);
	}

	double sum = 0;
	for (vector::iterator iter = itemVec.begin(); iter != itemVec.end(); ++iter)
	{
		sum += iter->net_price(10);
	}

	cout << sum <

練習15.29:再運行一次你的程序,這次傳入Quote對象的shared_ptr。如果這次計算出的總額與之前的程序不一致,解釋為什麼;如果一致,也請說明原因。

 

vector> itemVec;

程序產生的結果會存在差異。因為當通過Quote類型的對象調用虛函數net_price時,不實行動態綁定,調用的是Quote類中定義的版本;而通過Quote類型的指針調用虛函數net_price,實行動態綁定,而該指針實際指向Bulk_quote類中定義的版本。

 

練習15.30:編寫你自己的Basket類,用它計算上一個練習中交易記錄的總價格。

 

class Basket
{
public:
	void add_item(const std::shared_ptr &sale) { items.insert(sale); }
	double total_receipt(std::ostream&) const;
private:
	static bool compare(const std::shared_ptr &lhs, const std::shared_ptr &rhs)
	{
		return lhs->isbn() < rhs->isbn();
	}
	std::multiset, decltype(compare)*> items{compare};
};

double Basket::total_receipt(ostream &os) const
{
	double sum = 0.0;
	for(auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound(*iter))
	{
		sum += print_total(os, **iter, items.count(*iter));
	}
	os << "Total Sale: " << sum << endl;
	return sum;
}

練習15.31:已知s1、s2、s3和s4都是string,判斷下面的表達式分別創建了什麼樣的對象:

 

(a)Query(s1) | Query(s2) & ~Query(s3);

(b)Query(s1) | (Query(s2) & ~Query(s3));

(c)(Query(s1) & (Query(s2)) | (Query(s3) & Query(s4)));

 

(a)共創建12個對象:6個Query_base對象以及其相關聯的句柄。6個Query_base對象分別是3個WordQuery對象,1個NotQuery對象,1個AndQuery對象,1個OrQuery對象。

(b)與(a)相同。

(c)共創建14個對象:7個Query_base對象以及其相關聯的句柄。7個Query_base對象分別是4個WordQuery對象,2個AndQuery對象,1個OrQuery對象。

 

練習15.32:當一個Query類型的對象被拷貝、移動、賦值或銷毀時,將分別發生什麼?

Query類未定義自己的拷貝/移動控制成員,當進行這些操作時,執行默認語義。而其唯一的數據成員是Query_base的shared_ptr,因此,當拷貝、移動、賦值或銷毀一個Query對象時,會調用shared_ptr的對應控制成員,從而實現多個Query對象正確共享一個Query_base。而shared_ptr的控制成員調用Query_base的控制成員時,由於指向的可能是Query_base的派生類對象,因此可能在類層次中進行相應的拷貝移動操作,調用Query_base的派生類的相應控制成員。

 

練習15.33:當一個Query_base類型的對象被拷貝、移動、賦值或銷毀時,將分別發生什麼?

Query_base是一個虛基類,不允許直接聲明其對象。

當其派生類對象進行這些操作時,會調用Query_base的相應控制成員。而Query_base沒有定義自己的拷貝移動控制成員,實際上它沒有任何數據成員,無須定義這些操作,因此進行這些操作時,執行默認語義,什麼也不會發生。

 

練習15.34:針對圖15.3構建的表達式:

(a)列舉出在處理表達式的過程中執行的所有構造函數。

(b)列舉出cout<

(c)列舉出q.eval()所調用的eval.

(a)處理表達式Query("fiery")&Query("bird")|Query("wind")所執行的構造函數如下:

WordQuery(std::string&)

Query(const std::string&)

 

WordQuery(std::string&)

Query(const std::string&)

 

WordQuery(std::string&)

Query(const std::string&)

BinaryQuery(Query, Query, std::string)

AndQuery(Query, Query)

BinaryQuery(Query, Query, std::string)

Query(std:;shared_ptr query)

BinaryQuery(Query, Query, std::string)

OrQuery(Query, Query)

Query(std::shared_ptr query)

(b)執行cout<

BinaryQuery、Query、WordQuery、Query、BinaryQuery、Query、WordQuery、Query、WordQuery、BinaryQuery、Query、WordQuery、Query、WordQuery、BinaryQuery、Query、WordQuery、Query、BinaryQuery、Query、WordQuery、Query、WordQuery、Query、BinaryQuery、Query、WordQuery、Query、BinaryQuery、Query、WordQuery、Query、WordQuery

(c)計算q.eval時所調用的eval函數如下:

Query類的eval,OrQuery類的eval,AndQuery類的eval,WordQuery類的eval

 

練習15.35:實現Query類和Query_base類,其中需要定義rep而無需定義eval.

 

class Query
{
	friend Query operator~(const Query &);
	friend Query operator|(const Query&, const Query&);
	friend Query operator&(const Query&, const Query&);
public:
	Query(const std::string&);
	QueryResult eval(const TexQuery &t) const { return q->eval(); }
	std::string rep() const { return q->rep(); }
private:
	Query(std::shared_ptr query) : q(query) { }
	std::shared_ptr q;
};

class Query_base
{
	friend class Query;
protected:
	using line_no = TextQuery::line_no;
	virtual ~Query_base() = default;
private:
	virtual QueryResult eval(const TexQuery&) const = 0;
	virtual std::string rep() const = 0;
};

練習15.37:如果在派生類中含有shared_ptr類型的成員而非Query類型的成員,則你的淚需要作出怎樣的改變?

 

書中的實現方式是用Query類封裝了Query_base指針,管理實際查詢處理用到的不同Query類型對象。如果不使用Query類,則涉及使用Query類型的地方,都要改成Query_base指針。如創建單個詞查詢時,就必須創建WordQuery類而不是Query對象。幾個重載的布爾運算符也不能再針對Query對象,而需針對Query_base指針,從而復雜的查詢請求無法寫成目前的簡單形式,而需逐個運算完成,將結果賦予Query_base指針,然後再進行下一步運算。資源管理方面也需要重新設計。因此,當前的設計仍是最佳方式。

 

練習15.38:下面的聲明合法嗎?如果不合法,請解釋原因;如果合法,請指出該聲明的含義。

BinaryQuery a = Query("fiery") & Query("bird");

AndQuery b = Query("fiery") &Query("bird");

OrQuery c =Query("fiery") &Query("bird");

第一條聲明不合法,因為BinaryQuery中的eval是純虛函數。

第二條聲明不合法,不能將Query轉換為AndQuery。

第三條聲明不合法,不能將Query轉換為OrQuery。

 

練習15.40:在OrQuery的eval函數中,如果rhs成員返回的是空集將發生什麼?如果lhs是空集呢?如果rhs和lhs都是空集又將發生什麼?

OrQuery的eval從lhs和rhs獲取范圍來構造set,而set的構造和插入操作可以正確處理空范圍,因此無論lhs和rhs的結果是否為空集,eval都能得到正確結果。

 

練習15.41:重新實現你的類,這次使用指向Query_base的內置指針而非shared_ptr。請注意,作出上述改動後你的類將不能再使用合成的拷貝控制成員。

 

class Query
{
public:
	Query(const std::string&);
	Query(const Query& query) : q(query.q), uc(query.uc) {++*uc;}
	Query& operator=(const Query& query);
	~Query();
private:
	Query(Query_base* query) : q(query), uc(new int(l)) { }
	Query_base* q;
	int* uc;

};
inline Query::Query(const std::string &s) : q(new WordQuery(s)), uc(new int(l)) { }

inline Query::~Query()
{
	if(--*us == 0)
	{
		delete q;
		delete uc;
	}
}
inline 
Query& Query::operator=(const Query& query)
{
	++*query.uc;
	if (--*us == 0)
	{
		delete q;
		delete uc;
	}
	q = query.q;
	uc = query.uc;
	return *this;
}

inline Query operator&(const Query &lhs, const Query &rhs)
{
	return new AndQuery(lhs, rhs);
}

inline Query operator|(const Query &lhs, const Query &rhs)
{
	return new OrQuery(lhs, rhs);
}

inline Query operator~(const Query &operand)
{
	return new NotQuery(operand);
}

練習15.42:從下面的幾種改進中選擇一種,設計並實現它:

 

(a)按句子查詢並打印單詞,而不再是按行打印。

(b)引入一個歷史系統,用戶可以按編號查詢之前的某個查詢,並可以在其中增加內容或者將其與其他查詢組合。

(c)允許用戶對結果作出限制。比如從給定范圍的行中挑出匹配的進行顯示。

 

//a
TextQuery::TextQuery(ifstream &is) : file(new vector<string>)
{
	char ws[] = {'\t', '\r', '\v', '\f', '\n'};
	char eos[] = {'?', '.', '!'};
	set<char> whitespace(ws, ws+5);
	set<char> endOfSentence(eos, eos+3);
	string sentence;
	char ch;

	while(is.get(ch))
	{
		if(!whitespace.count(ch))
			sentence+=ch;
		if(endOfSentence.count(ch))
		{
			file->push_back(sentence);
			int n = file->size() -1;
			istringstream is(sentence);
			string word;
			while(is >> word)
			{
				auto &lines = wm[word];
				if (!lines)
					lines.reset(new set<line_no>);
				lines->insert(n);
			}
			sentence.assign("");
		}
	}
}

//b
bool get_word(string &s1)
{
	cout << "enter a word to search for, or q to quit, or h to history: ";
	cin >> s1;
	if (!cin || s1 == "q") return false;
	else return true;
}

int main(int argc, char **argv)
{
	TextQuery file = get_file(argc, argv);
	vector<array<string, 3>> h;

	while(true)
	{
		string sought1, sought2, sought3;
		if(!get_word(sought)) break;
		if(sought1 != "n")
		{
			cout << "\nenter second and third words: ";
			cin >> sought2 >> sought3;
			Query q = Query(sought1) &Query(sought2) | Query(sought3);
			h.push_back({sought1, sought2, sought3});
			cout << "\nRxcuting Query for: " << q <<endl;
			const auto results = q.eval;
			print(cout, results);
		}
		else
		{
			cout << "\nenter Query no.: ";
			int i;
			cin >> i;
			if (i<1 ||i > h.size())
				cout<< "\nBad Query no." << endl;
			else
			{
				Query q = Query(h[i-1][0]) & Query(h[i-1][1]) | Query(h[i-1][2]);
				cout << "\nExcuting Query for: " << q <<endl;
				const auto results = q.eval(file);
				print(cout, results);
			}
		}
	}
	return 0;
}

//c
ostream &print(ostream &os, const QueryResult &qr, int beg, int end)
{
	os << qr.sought<< " occurs "<<qr.lines->size() << " " << make_plural(qr.lines->size(), "time", "s") <<endl;

	for(auto num : *qr.lines)
		if(num +1 >=beg && num + 1 <= end)
			os << "\t(line " << num + 1 << ")" << *(qr.file->begin() + num) <<endl;

		return os;
}
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved