C++ Pirmer第十五章⑤
C++ Primer
面向对象程序设计
容器与继承
我们不能把具有继承关系的多种类型的对象直接存放在容器中:
vector<Quote> basket;
basket.push_back(Bulk_quote("a", 50, 10, 0.25));
basket的元素是Quote对象,因此当我们向其中添加Bulk_quote对象时,派生部分会被忽略,那我们应该怎么做呢?
在容器中放置(智能)指针而非对象
我们在容器中存放具有继承关系的对象时,最好是指针(更好是智能指针),和之前一样,这些指针所指对象的动态类型可能是基类类型,也可能是派生类类型:
vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("a", 50));
basket.push_back(make_shared<Quote>("b", 50, 10, 0.25));
cout << basket.back()->net_price(15) << endl; //打印折扣后的价格,这回派生类对象是完整的
注意:我们以前能把派生类的指针转换成基类指针;那我们也能把派生类的智能指针转换为基类的智能指针(原来指针派生,现在指个更简单的基类当然可以)
当我们在调用push_back时,shared_ptr<Bulk_quote>被转换为1基类shared_ptr<Quote>
编写Basket类
对于C++面向对象的编程来说,我们并不是直接用对象的,我们用的是指针和引用。。。
因为指针会增加程序的复杂性,我们就会定义一些辅助类
在这里,我们先定义一个表示购物篮的类:
class Basket
{
public:
//使用合成的那些操作
void add_item(const shared_ptr<Quote> &sale) {item.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保存多个报价,按照compare成员排序
multiset<shared_ptr<Quote>, decltype(compare)*> items(compare);
//我们定义了一个名为compare的私有静态成员作为multiset的比较运算符
};
那个multiset的声明可能有点长,我来解释一下:
它定义了一个multiset,里面的元素是智能指针,智能指针指向的是Quote对象,这个变量的名字是items,我们使用了compare作为multiset键比较的依据
定义Basket的成员
一个成员函数是上面已经写好的add_item函数,还有一个是打印购物篮并返回总价:
double Basket::total_receipt(ostream &os) const
{
double sum = 0;
for(auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound(*iter))
{
sum += print_total(os, **iter, items.count(*iter)); //调用以前的打印函数,不用管
//*item是迭代器解引用成为智能指针,**item是智能指针解引用为对象
}
os << "总价为:" << sum << endl;
return sum;
}
upper_bound函数返回一个迭代器,指向所有与iter关键字相等的元素中最后一个元素的下一个位置
隐藏指针
我们来看看按照现在的类怎么写代码,来试着调用一下:
Basket bsk;
bsk.add_item(make_shared<Quote>("123", 45));
bsk.add_item(make_shared<Bulk_quote>("345", 45, 3, 0.15));
我们下一步的目标是重新定义add_item,使得它接受一个Quote对象而不是指针shared_ptr,新版本的add_item将负责内存分配,这样我们就不用管内存了,我们要拷贝和移动都行:
void add_item(const Quote& sale); //拷贝
void add_item(Quote&& sale); //移动
这里有一个小问题,我们不知道sale是什么类型的,sale可能指向Quote对象,也可能是Bulk_quote对象,如果我们只分配Quote类型的对象,那对于Bulk_quote就损失了派生类那部分对象,这其实又回到了开头的问题-在有继承体系的对象中我们为什么要存指针或引用而不是直接存对象
不过对于这部分的小问题,我们可以用虚函数解决,你不是不知道要分配多大吗?我就让你知道知道,我们给类添加一个虚函数,该函数的功能是申请一份当前对象的拷贝:
class Quote
{
public:
virtual Quote* clone() const & {return new Quote(*this);} //只能用于左值
virtual Quote* clone() const && {return new Quote(std::move(*this);} //只能用于右值
};
class Bulk_Quote : public Quote
{
Bulk_quote* clone() const & {return new Bulk_quote(*this);} //只能用于左值
Bulk_quote* clone() const && {return new Bulk_quote(std::move(*this);} //只能用于右值
};
继承体系Quote和Bulk_quote有了这些函数,我们就可以在类Basket中写出新版本的add_item:
class Basket
{
public:
void add_item(const Quote& sale) //拷贝给定对象,接受的参数是对象引用而不是指针
{
item.insert( shared_ptr<Quote>(sale.clone()) );
}
void add_item(Quote&& sale) //移动给定对象,接受的参数是对象引用而不是指针
{
item.insert( shared_ptr<Quote>(std::move(sale).clone()) );
}
};
这样,我们通过类的优良设计,让类的用户再也不用管内存这回事了,他们甚至不知道有内存这回事,这就厉害了