C++ Primer 15.8容器与继承Basket,Quote,Bulk

C++ Primer 15.8容器与继承Basket,Quote,Bulk,第1张


class Quote
{
public:
	Quote() :price(0.0) {}
	Quote(const string& s, double p) :bookNo(s), price(p) {}
	virtual ~Quote() = default;

	virtual double net_price(size_t n)const
	{
		return n * price;
	}
	const string& isbn()const { return bookNo; }

	// 申请一份当前对象的拷贝
	// 返回当前对象的一份动态分配的拷贝
	virtual Quote* clone()const &  // 左值类型
	{
		return new Quote(*this); // 将它自己拷贝给新分配的对象
	}
	virtual Quote* clone()&&
	{
		return new Quote(std::move(*this));  //将自己移动到新数据中 //*this是一个Quote类对象,move返回一个Quote类对象的右值引用版本,用于移动构造创建的Quote新对象
	}
protected:
	double price;
private:
	string bookNo;
};

class Disc_Quote:public Quote
{
public:
	Disc_Quote(const string& s, double p, size_t q, double dis)
		:Quote(s, p), quantity(q), discount(dis) {}
	virtual double net_price(size_t cnt)const = 0;   // 纯虚函数
protected:
	size_t quantity;
	double discount;
};

class Bulk_Quote :public Disc_Quote
{
public:
	Bulk_Quote(string s, double p, size_t q, double dis)
		:Disc_Quote(s, p, q, dis) {}

	virtual Bulk_Quote* clone()const& override
	{
		return new Bulk_Quote(*this);  
	}
	virtual Bulk_Quote* clone()&& override
	{
		return new Bulk_Quote(std::move(*this));
	}
	double net_price(size_t cnt)const override
	{
		if (cnt > quantity)
			return cnt * (1 - discount) * price;
		else
			return cnt * price;
	}
};

Quote是基类,表示一种最普通的书籍,这种书籍没有打折策略,仅保存ISBN和价格,虚函数net_price的参数是购买数量,返回总价格。

Disc_Quote类是一个抽象基类,它继承Quote的价格和ISBN,自己的数据成员是,达到折扣的边界购买数量,以及折扣值,他表示一种抽象的折扣策略,而不是某种具体的销售折扣策略。

Bulk_Quote继承Disc_Quote,他表示,如果购买数量大于quantity,则多出的数量采取打折销售。而net_price覆盖了基类的函数,返回cnt个书籍的总价格。

print_total函数

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;
}

这个函数,用于对传来的书籍实参进行计算总价格,书籍的个数有n传递。函数内部调用net_price成员函数,因为item是基类的引用,所以调用虚函数时,发生动态绑定,调用的实际版本由引用指向的动态对象的类型决定。

Basket类


class Basket
{
public:
	void add_item(const Quote&);   // 拷贝给定对象
	void add_item(Quote&&);    // 移动给定的对象
	double total_receipt(ostream& os)const;
private:
	static bool compare(const shared_ptr& lhs, const shared_ptr& rhs) { return lhs->isbn() < rhs->isbn(); }
	multiset, decltype(compare)* > items{ compare };
};

void Basket::add_item(const Quote& sale)  // 拷贝给定的对象
{
	items.insert(shared_ptr(sale.clone()));  // sale是一个左值,调用const &版本的clone 而到底是基类的还是派生类的,取决于sale绑定的动态类型
}

void Basket::add_item(Quote&& sale)  // 移动给定的对象
{
	items.insert(shared_ptr(std::move(sale).clone()));
}

double Basket::total_receipt(ostream& os)const
{
	double sum = 0;
	for (auto i = items.cbegin(); i != items.cend(); i = items.upper_bound(*i))
	{
		sum += print_total(os, **i, items.count(*i));   // 这里的upper_bound和count函数传的都是元素。
	}
	// 这里**i可能是一个派生类Bulk_Quote对象,传过去之后,第三个参数就是net_price的cnt参数,和对应的Bulk_Quote类的quantity比较,看是否满足打折条件
	// 而Basket就是一个购物篮,里面的multiset存储的是一本本书。根本不存在一个指针指向多本书的情况。
	os << "Total Sale: " << sum << endl;
	return sum;
}

这个类表示一个购物篮,也就是说,可以把书籍放入这个购物篮中,是通过参数add_item进行的。起初,add_item的参数类型是const shared_ptr&类型,但是,这样的话,就需要类的用户进行动态申请内存

优化之后,函数将动态申请对象的工作交给了add_item函数本身,而类的用户只需要传递Quote类对象,或者Bulk_Quote_类对象,当然,允许这样传递的原因是参数是&类型(基类的引用可以绑定到派生类对象上)。

这样出现了一个问题,传递过来的类对象类型不确定,如果直接在add_item函数内部进行new申请空间,就会出现错误。所以,在Quote类和Bulk_Quote类内部创建虚函数,功能是申请一份当前对象的动态分配的拷贝。也就是clone函数,这样一来,通过参数Quote&,调用虚函数,就可以进行动态绑定,申请的对象类型就是引用所绑定的动态类型。

拿Bulk_Quote类的举例:

clone函数有两个版本,一个是const & 一个是&&,即左值调用第一个clone,右值调用第二个clone。

	virtual Bulk_Quote* clone()const& override
	{
		return new Bulk_Quote(*this);  
	}
	virtual Bulk_Quote* clone()&& override
	{
		return new Bulk_Quote(std::move(*this));
	}

左值引用版本 将它自己拷贝给新分配的对象,右值引用版本将自己移动到新数据中。两个函数都返回指向新开辟的对象的指针。

add_item函数有两个版本,一个是拷贝给定的对象,然后添加到items中,一个是移动给定的对象,添加到items中。左值匹配第一个add_item ,右值匹配第二个add_item。

void Basket::add_item(const Quote& sale)  // 拷贝给定的对象
{
	items.insert(shared_ptr(sale.clone()));  // sale是一个左值,调用const &版本的clone 而到底是基类的还是派生类的,取决于sale绑定的动态类型
}

void Basket::add_item(Quote&& sale)  // 移动给定的对象
{
	items.insert(shared_ptr(std::move(sale).clone()));
}

第一个很好理解,调用multiset的insert成员函数,调用sale的clone函数,这是一个左值,自然匹配const &版本的clone。然后将一个shared_ptr绑定到这个返回的指针上,添加到items中。

第二个右值版本中,虽然参数sale的类型是右值引用类型,但是,这里不能直接调用clone()。因为,实际上sale本身(和任何其他变量一样)是个左值。这里就是右值引用相关的知识点了。所以。我们调用move函数,move返回参数的右值引用类型,调用clone,就会匹配&&类型的clone函数。返回移动过去的新开辟的对象的指针。绑定到shared_ptr上。添加到items中。

补:突然意识到一个东西:继承类的虚函数覆盖基类的虚函数时,本身要求是函数名,参数列表,返回类型都相同,但是上方Quote 和 Bulk_Quote类的clone函数的返回类型并不相同,是因为这是一个例外,即:虚函数的返回类型如果是所属类型对象的指针时,是可以不同的。即Quote* Bulk_Quote*。 // 有疑问就看VS源码的注释

const & 代表,this指针指向左值,同时const限定了不能改变调用的对象,即不能对调用的对象执行写 *** 作,即this指向的对象。

&&代表  this指针指向右值,右值匹配这个,代表着调用的对象是一个临时对象,可以从调用的对象中窃取移动资源。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/1353801.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-14
下一篇2022-06-14

发表评论

登录后才能评论

评论列表(0条)

    保存