昨天由于时间的关系剩下一个小尾巴,今天忙里偷闲来把这个洞洞填上昨天学习了“面向对象编程”的部分,详细讨论了复制控制与类作用域需要注意的问题。这里有一个新的问题,如何实现一个类似“购物车”的数据结构呢?用过淘宝的同学们一定都晓得“购物车”应用,可以记录不同的商品,并且相同的商品可以显示次数,最后计算出总额。如果用C++来实现的话,当然是首选容器对象了。由于是统计可以重复的对象,所以可以使用multiset关联容器。现在你有两个选择:一是将商品对象放入容器中成为容器对象;二是容器中存储指向商品对象的指针或引用。第一种方法明显是不合适的,因为商品之间可能存在着继承关系,那么我们的容器类型时基类还是派生类呢?派生类型的容器元素的派生部分就会是未定义状态,而基类型的容器元素则会失去派生类部分成员。如果使用指针或者引用呢?可以避免上面的问题,但是用户必须负责管理指针或引用,尤其是防止“悬垂指针”的出现,即要确保指针在,对象在;对象失,指针无。这无疑会加重用户的开发负担。那么有没有更好的解决办法呢?有的,就是我们今天要讨论的句柄类。
一、指针型句柄
其实说白了句柄类一点都不神秘,前面我们接触过“计数类”用来管理指针,这里的句柄类类似,但是比简单的计数类增加了一些其他的功能,因为我们除了利用它管理指针,还希望便捷的使用其指向的对象。这里的主要idea就是将指针的管理工作封装到一个类中,类中起码具有两个数据成员,一个指向对象的指针和一个计数指针,计数归零时意味着要释放对象和句柄类。这里我们使用的例子还是上一节中的图书类,这里简单再次给出:
-
//图书基类
-
class Item_base
-
{
-
public:
-
Item_base(const std::string &book = "",
-
double sales_price = 0.0):
-
book_no(book), price(sales_price) {}
-
std::string book() const {return book_no;}
-
virtual double net_price(std::size_t n) const
-
{return n*price;}
-
virtual ~Item_base() {}
-
private:
-
std::string book_no;
-
protected:
-
double price;
-
-
} ;
-
//含有折扣的图书类,必须满足最小的min_qyt数量要求才可以享有折扣
-
class Bulk_item : public Item_base
-
{
-
public:
-
double net_price(std::size_t) const;
-
private:
-
std::size_t min_qyt;
-
double discount;
-
}
-
double Bulk_item::net_price(std::size_t cnt) const
-
{
-
if (cnt >= min_qty)
-
return cnt * (1 - discount ) * price;
-
else
-
return cont * price;
-
}
我们即将定义的句柄类,除了封装指针以外,还希望用户能够像使用一个指针一样去使用它,如:
-
Handle_item item(Bulk_item("0-201-42778-2", 42, 2, 201));
-
item->net_price() //对net_price函数的虚调用
接下来我们来定义句柄类Handle_item:
-
//定义一个句柄类
-
class Handle_item
-
{
-
public:
-
Handle_item():p(0), cnt(new std::size_t(1)) {} //默认构造函数
-
Handle_item(const Item_base&); //自定义的接受基类引用为参数,进行绑定的构造函数
-
Handle_item(const Handle_item &i): //复制构造函数
-
p(i.p), cnt(i.cnt) {++*cnt;}
-
~Handle_item() {des_use();} //析构函数
-
Handle_item& operator=(const Handle_item); //定义赋值操作
-
-
const Handle_item *oprator->()const { //定义->运算符
-
if (p) return p;
-
else
-
throw std::logic_error("未绑定Handle_item"));
-
)
-
const Handle_item &operator*() const { //定义解引用运算符
-
if (p) return p;
-
else
-
throw std::logic_error("未绑定Handle_item");
-
}
-
private:
-
Item_base *p;
-
std::size_t *cnt;
-
void des_use()
-
{
-
if (--*cnt == 0)
-
{
-
delete p;
-
delete cnt;
-
}
-
}
-
-
-
}
从上面的定义中,可以看到Handle_item类具有两个数据成员p和cnt,分别指向基类对象和计数对象,并且定义了构造函数和复制控制函数,为了能够使得我们的句柄类对象像指针那样使用,我们还单独重载了->和*运算符。下面是复制构造函数的实现:
-
Handle_item&
-
Handle_item::oprator=(const Handle_item &rhs)
-
{
-
++*rhs.use;
-
des_use();
-
p = rhs.p;
-
use = rhs.use;
-
return *this;
-
}
当我们实现类的复制构造函数时,必须要考虑用户调用函数为自身赋值的情况。因为赋值操作时析构和复制构造二者的结合,因此必须小心实现的操作步骤。为了使得为自身赋值时程序依旧能够正确运行,在des_use()之间首先将计数器加一,避免为自身赋值时首先调用des_use()导致自身对象被析构,从而引发程序错误。
实现构造函数的时候,特别说明接受Item_base引用绑定一个句柄类的构造函数,因为这里存在的问题是Item_base引用的实际对象类型只有在运行时才能够确定,那如何为其分配空间呢?解决的思路是:既然参数是动态绑定,那么我们的行为也实现动态绑定。实现动态绑定的方法自身是使用虚函数,这就需要我们定义一个新的虚函数clone(),从而能够实际的对象类型开辟合适的空间。为了使用虚函数clone(),我们为基类和派生类添加函数:
-
Item_base::
-
virtual Item_base* clone() const {return new Item_base(*this);}
-
-
Bulk_item::
-
Bulk_item* clone() const {return new Bulk_item(*this);}
这里也许有细心的童鞋会奇怪,不是说虚函数的原型必须一致才能动态绑定吗?其实这里是一个例外,如果虚函数的基类实例返回类类型的引用或指针,那么该虚函数的派生类实例可以返回基类实例返回的类型的派生类(或指针|引用)。有了clone函数,我们就可以给出Handle_item类构造函数的定义了:
-
Handle_item::Handle_item(const Item_base &item):
-
p(item.clone()), cnt(new std::size_t(1)) {}
二、句柄的使用
定义了handle_item,接下来使用的时候就要将句柄类对象放入multiset容器中成为容器元素,从而实现利用指针访问商品对象的最终目的。这里有一个小问题需要解决,放入multiset中的元素必须能够比价大小从而确定排列顺序,我们不会重载<运算符,那样太费事了,我们选择的方法是直接提供一个比较函数,就比较图书的book号码就可以。
-
inline bool
-
compare(const Handle_item &lhs, const Handle_item &rhs)
-
{
-
return lhs->book() < rhs->book();
-
}
经过了这么多,现在我们终于可以来定义我们的“购物车”啦:
-
class Basket {
-
typedef bool (*comp)(const Handle_item&, const Handle_item&);
-
public:
-
typedef std::multiset<Handle_item, comp> set_type;
-
typedef set_type::size_type size_type;
-
typedef set_type::const_iterator const const_iter;
-
Basket():items(compare) {}
-
void add(const Handle_item &item)
-
{
-
items.insert(item);
-
}
-
size_type size(cosnt Handle_item &i)const
-
{
-
return items.count(i);
-
}
-
double total() const;
-
private:
-
std::multiset<Handle_item, comp> items;
-
}
-
-
double Basket::total() const
-
{
-
double sum = 0.0;
-
for (const_iter iter = items.begin();
-
iter != items.end();
-
iter = items.upper_bound(*iter))
-
{
-
sum += (*iter)->net-price(items.count(*iter));
-
}
-
}
在定义Basket类时为了书写方便,我们使用typedef进行了简化,typedef、对象和函数可以成为类的成员。计算总价的total函数比较有意思,首先是for循环的递增式使用了upper.bound(),目的是使得跳过相邻的相同的商品直接到达下一个不同类的商品的迭代器位置,而最后的sum式子中的count()则用来计算同类商品的数目,因为我们的折扣是在同种商品达到最低的数目之后才会享有的。
PS:
最后谈谈自己对于句柄类的感觉。句柄类听名字就让自己想到了windows中的句柄概念,记得当时看的时候也只是知道那是一个数据结构,里面肯定封装了对象的指针,但是还有更多的对象属性在里面,比如访问权限控制等等。现在想来原型结构上系统的“句柄”也许与我们变成使用的句柄类有些类似吧。
阅读(274) | 评论(0) | 转发(0) |