首页 > 程序开发 > 软件开发 > C++ >

C++primer第15章习题解答

2016-06-07

练习15 1:什么是虚成员? 在类中被声明成virtual的成员,基类希望这种成员在派生类中重定义。除了构造函数外,任意非static成员都可以为虚成员。

练习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[] = {&#39;\t&#39;, &#39;\r&#39;, &#39;\v&#39;, &#39;\f&#39;, &#39;\n&#39;};
	char eos[] = {&#39;?&#39;, &#39;.&#39;, &#39;!&#39;};
	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;
}
相关文章
最新文章
热点推荐