当前位置: 首页 > 工具软件 > BasKet > 使用案例 >

面向对象示例——Basket类

苏畅
2023-12-01

目标

定义一个能存放继承关系的容器Basket类

难点

容器中不能保存不同类型的元素,那么该如何将具有继承关系的多种类型的对象存放在容器中呢?比如Bulk_quote和Quote,因为我们不能将Quote转换为Bulk_quote,所以容器肯定不能存放Bulk_quote类型。那保存Quote类型吗?也不行,虽然保存Quote类型可以让Bulk_quote放置进该容器,但是会丢失派生类自己的部分。

vector<Quote> basket;
basket.push_back(Quote("0-201-82470-1", 50));
basket.push_back(Bulk_quote("0-201-54848-8", 50, 10, 0.25));
//正确的,但是只把对象的Quote部分拷贝给了basket
cout << basket.back().net_price(15) << endl;
//这里调用的是Quote版本的net_price

实际上当我们希望在容器中存放具有继承关系的对象时,我们存放的通常是基类的指针(智能指针更好)。这些指针所指对象的动态类型可能是基类类型,也可能是派生类类型

vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("0-201-82470-1", 50));
basket.push_back(make_shared<Bulk_quote>("0-201-54848-8", 50, 10, 0.25));
//就像派生类的指针可以转换成基类指针
//这里派生类的智能指针转换成了基类的智能指针
//make_shared<Bulk_quote>返回指向Bulk_quote的智能指针
//调用push_back时将该智能指针转换成Quote的智能指针
cout << basket.back()->net_price(15) << endl;

接下来就利用本节面向对象和上面的容器保存继承知识来写此次的Basket类

实现

class Basket
{
public:
	void add_item(const shared_ptr<Quote> &sale) { items.insert(sale); }
	//打印每本书的总价和购物篮中所有书的总价
	double total_receipt(ostream&) const;
private:
	static bool compare(const shared_ptr<Quote> &lhs, const shared_ptr<Quote> &rhs)
	{ return lhs->isbn() < rhs->isbn(); }
	multiset<shared_ptr<Quote>, decltype(compare)*> items{compare};
	//multiset存放交易信息,可以保存同一本书的多条交易记录
	//shared_ptr没有定义小于运算符,所以定义上面的compare
};

可能multiset不太容易理解,从左向右读实际就是一个指向Quote对象的shared_ptr的multiset,因为shared_ptr没有自己的小于运算符,为了能够对元素进行排序,我们自己写了比较运算符compare。multiset的名字是items,我们初始化items并令其使用我们的compare函数

定义Basket成员

total_receipt

total_receipt函数负责将购物篮的内容逐项打印成清单,最后返回总价格

double Basket::total_receipt(ostream &os) const
{
	double sum = 0.0;
	for (auto iter = items.cbegin(); iter != items.end(); iter = items.upper_bound(*iter))
	//upper_bound返回一个迭代器,该迭代器指向这批元素的尾后位置
		sum += print_total(os, **iter, items.count(*iter));
		//*iter是指向准备打印对象的指针,**iter是准备打印的对象,可能是Quote也可能是派生类
		//count是multiset的函数,用来统计multiset有多少元素键值相同
	os << "Total Sale: " << sum << endl;
	return sum;
}

add_item

现在的add_item接受一个shared_ptr参数,也就是说购物篮的用户要这样编码:

Basket bsk;
bsk.add_item(make_shared<Quote>("123", 45));
bsk.add_item(make_shared<Bulk_Quote>("345", 45, 3, 0.5));

我们应该重新定义add_item,接受一个Quote对象而不是shared_ptr。新的add_item负责处理内存分配,这样用户就会省事了

//定义两个,一个拷贝,一个移动
void add_item(const Quote& sale);
void add_item(Quote&& sale);

还有个问题是add_item不知道要分配什么类型。item里面存的是指针,所以函数内肯定会有这样的表达式make_shared<Quote>(sale)。这里是分配Quote类型的对象,然后拷贝sale的Quote部分。显然,如果sale是Bulk_quote对象,派生类部分就被切掉了…两边都走不通,怎么办?

模拟虚拷贝

想传递Quote的引用,然后又能在不切掉派生类部分的情况下插入进multiset,到底该怎么做?我们可以给Quote添加一个虚函数,这个函数返回当前对象的动态内存

class Quote
{
public:
	//该虚函数返回当前对象的一份动态分配的拷贝
	virtual Quote* clone() const & {return new Quote(*this);}
	//const左值引用成员将自己拷贝到新分配的对象
	virtual Quote* clone() && {return new Quote(std::move(*this));}
	//右值引用成员将自己移动到新数据
};
class Bulk_quote
{
	Bulk_quote* clone() const & {return new Bulk_quote(*this);}
	Bulk_quote* clone() && {return new Bulk_quote(std::move(*this));}
};

然后重新写add_item

class Basket
{
public:
	void add_item(const Quote& sale)
		{ items.insert(shared_ptr<Quote>(sale.clone())); }
	void add_item(Quote&& sale)
		{ items.insert(shared_ptr<Quote>(std::move(sale).clone()));}
	//这样动态类型就能决定返回谁的动态内存
};
 类似资料: