面向对象编程实例——句柄类的使用

1.句柄类存储和管理指针是C++一个通用的技术。指针所指对象的类型可以变化,它既可以指向基类类型对象又可以指向派生类类型对象。用户通过句柄类访问继承层次的操作。句柄的用户可以获得动态行为而不需操心指针的管理。

2.句柄类经常需要在不知道对象的确切类型时分配已知对象的副本,为了支持句柄,需要从基类开始,在继承层次的每个类型中增加clone,基类必须将该函数定义为虚函数,以获得动态行为。

3. clone函数

clone函数很重要,要使用句柄类该是不可避免的需要定义clone函数


    回想为什么要使用句柄类,一开始,想用multiset来存储某个类的一些对象,这些对象可能存在父子关系,所以不能直接存放对象(子对象转化为父对象将丢失派生部分),那么只能存放指针,然而指针的管理麻烦(这点我倒是不清楚到底有多难)。这样就发明了句柄类,建一个类,该类包含两个指针,一个指针p指向一个希望存储的对象,另一个指针指向一个size_type类型的use整数存放该对象的引用数(引用数的作用即管理指针何时释放)。


回到我们的问题,clone函数干什么用,是这样的,对于一个我们希望存储的对象,我们要把这个对象跟句柄类关联起来才能存储。

那么有两种办法,一,直接把这个对象的地址赋值给句柄类里的p指针(即指向对对象的那个),二,用这个对象复制一个对象,再把这个复制出来的对象拿来跟p关联,

现在我来告诉你,第一种方法不可行:这个原始对象从哪来的我们不确定,如果这个对象的空间管理是由系统管理,那么我们不知道它何时就被释放了,也许能挺到main函数结束,也许在某个函数调用结束,它被释放了我们还能指向它么。或者这个对象用户还希望使用在很多地方,却由于use为0而被我们手工的释放了,这也是灾难。再说,这个对象要是再被拿去初始化另一个句柄类,那么造成的情况是我们有两群,是两群,看清楚了,这两群句柄对象,他们关联到同一个需要存储的对象,却各自有一个计数,相互独立,带来的问题是,该对象的释放如何统一?

    所以,我们只能用第二种方法,我是说,我们的需要存储的对象与句柄类对象关联起来,我们用的方法是,用这个对象复制出一个新对象,而把这个新对象与句柄对象关联(重复一下:关联的方法就是把新建对象的地址赋值给句柄对象的p指针)。好了,对于某个我们希望关联到句柄类对象的对象,我们首先要复制它!耐心点,接下来便要讲到clone函数的意义了。在代码中,我们是如何关联的:在我们的句柄对象的构造函数里,传过来的的是需要存储的那群对象的基类引用,我们的构造函数要实现的功能就是根据这个引用复制一个与引用指向的对象相同的对象,问题是,引用他可能指向那一群对象中的任何一个,而接受引用的只是基类对象引用,如何根据基类引用接受的参数去复制派生类对象,你发现没有,在句柄类中是实现不了的!基类引用或指针根本无法调用派生类的方法,再重复,根据一个基类对象引用无法复制一个派生类对象出来。怎么办呢?我们复制不出来,让它自己复制一个给我们不就行了么,所以,关于复制对象的实现,不是在句柄类里,而是在需要被复制的对象所属的类里,那群对象各自有各自所属的类,在它们各自的类里都定义一个clone函数,当然,是虚函数,然后用基类引用接受了那群对象里的某一个,让该对象调用clone,根据多态的特性,它会调用自身所属的类得clone函数,用这个函数,当然可以复制出跟自身一摸一样对象,复制对象的目的就达到了,这就是clone函数的由来。


a) Item_base类是真正用于记录的类

#ifndef ITEM_H
#define ITEM_H
#include<string>

class Item_base{
public:
	Item_base(const std::string &book=" ",double sales_price=0.0):
		isbn(book),price(sales_price){}

	std::string book() const{
		return isbn;
	}
	
	void set_price(double new_price){
		price=new_price;
	}

	//价格计算函数
	virtual double net_price(size_t n)const{
		return n*price;
	}

	virtual Item_base* clone() const{//
		return new Item_base(*this);
	}

	virtual ~Item_base(){}
private:
	std::string isbn;
protected:
	double price;
};

//抽象类
//保存折扣率和可实行折扣策略的数量,派生类将使用这些数据实现定价策略
class Disc_item: public Item_base{
public:
	//
	Disc_item(const std::string &book=" ",double sales_price=0.0,
		size_t qty=0,double disc_rate=0.0):
		Item_base(book,sales_price),quantity(qty),discount(disc_rate){}
	
	double net_price(size_t n) const =0;

	std::pair<size_t,double> discount_policy() const{
		return std::make_pair(quantity,discount);
	}
protected:
	size_t quantity;//可执行折扣策略的购买量
	double discount;//折扣率
};

//批量购买折扣类
class Bulk_item : public Disc_item {
public:
	Bulk_item (const std::string &book=" ",double sales_price=0.0, 
				size_t qty=0,double disc_rate=0.0):
				Disc_item(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;
	}

	virtual Item_base* clone() const{//供句柄类动态调用的构造函数
		return new Bulk_item(*this);
	}
};

//有限折扣类:购买量不超过阈值,则给折扣,超出部分原价购买
class Lds_item :public Disc_item{
public:
	Lds_item(const std::string &book=" ",double sales_price=0.0,
			size_t qty=0,double disc_rate=0.0):
			Disc_item(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-quantity*discount*price;
	}

	virtual Item_base* clone() const {
		return new Lds_item(*this);
	}
};
#endif

b) Sales_item类是句柄类,用于包装Item_base并管理Item_base的指针和内存释放

#ifndef SALES_ITEM_H
#define SALES_ITEM_H
#include "Item.h"

class Sales_item{
public:
	//默认构造函数,创建未绑定的句柄
	Sales_item(): p(0),use(new size_t (1) ) {}
	
	//将创建绑定到Item_base对象副本的句柄
	Sales_item(const Item_base &item): p(item.clone()), use(new size_t(1))  {  }
	
	//复制控制成员管理使用计数和指针
	Sales_item(const Sales_item &i):p(i.p),use(i.use){++*use;}

	~Sales_item(){decr_use();}

	Sales_item& operator=(const Sales_item &);
	
	//重载操作符
	const Item_base *operator->() const{
		if(p)
			return p;
	}

	const Item_base &operator*(){
		if(p)
			return *p;
	}
	
	void change_price(double new_price){
		p->set_price(new_price);
	}
private:
	Item_base *p;//指向共享Item_base对象的指针
	size_t *use;//指向共享的使用计数的指针

	//为析构函数和赋值操作符使用的辅助函数
	void decr_use(){
		if(--*use==0)
			delete p;
			delete use;
	}
};

Sales_item& Sales_item::operator=(const Sales_item& rhs){
	++*rhs.use;
	decr_use();
	p=rhs.p;
	use=rhs.use;
	return *this;
}
#endif


c) Basket类是进行实际计算的类,它将通过句柄类计算书的总价,而不关心实际类Item_base的内存释放

注意这里multiset容器的使用方法:首先传递给容器一个比较函数,用于对键值进行顺序存储(保证相同ISBN的书放在一起);然后使用upper_bound(*iter)和items.count(*iter)进行了价格的计算。

#ifndef BASKET_H
#define BASKET_H


#include "Sales_item.h"
#include <set>


//compare 函数用于比较对象,以确定Basket的multiset成员中各元素的排列次序
inline bool compare(const Sales_item &lhs,const Sales_item &rhs){
	return lhs->book() < rhs->book();//按照ISBN进行排序
}

class Basket {
	typedef bool (*Comp)( const Sales_item &lhs, const Sales_item &rhs);
public:
	typedef std::multiset<Sales_item,Comp> set_type;//multiset中可以指定比较函数————1
	typedef set_type::size_type size_type;
	typedef set_type::const_iterator const_iter;
	
	Basket(): items(compare){ };//初始化比较器——————————————————————2

	void add_item(const Sales_item &item){
		items.insert(item);
	}

	size_type size(const Sales_item &i) const {
		return items.count(i);
	}

	double total() const;//
private:
	std::multiset< Sales_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)){//upper_bound寻找不等于*iter的最右边界

		sum+=(*iter)->net_price(items.count(*iter));	
	}
	return sum;
}

#endif

main.cpp

#include<iostream>
#include "Basket.h"
#include "Sales_item.h"
#include "Item.h"
using namespace std;

int main(){
	Basket basket;
	Sales_item item1(Bulk_item("7-115-14554-7", 99, 2, 0.2));
	Sales_item item2(Item_base("7-115-14554-8", 39));
	Sales_item item3(Lds_item ("7-115-14554-9", 59, 200, 0.2));
	Sales_item item4(Bulk_item("7-115-14554-7", 99, 2, 0.2));
	Sales_item item5(Bulk_item("7-115-14554-7", 10, 2, 0.2));
	
	basket.add_item(item1);
	cout<<basket.total()<<endl;

	basket.add_item(item2);
	cout<<basket.total()<<endl;

	basket.add_item(item3);
	cout<<basket.total()<<endl;

	basket.add_item(item4);
	cout<<basket.total()<<endl;
	
	basket.add_item(item5);
	cout<<basket.total()<<endl;

	return 0;
}


Makefile

objects=main.o

run:$(objects)
	g++ -o run $(objects)

main.o:		Sales_item.h Item.h Basket.h

.PHONY:
	clean
clean:
	rm run $(objects)



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书作者根据自己学习C++的亲身体会及多年教学经验,用简单的例子和简练的叙述讲解C++编程,别具特色。 全书共分十八章,内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、 动态对象创建、继承和组合、多态和虚函数、模板和包容器、多重继承、异常处理和运行时型识别。 本书作为正式教材和自学用书均非常优秀,作为程序设计者的参考用书亦极为合适。 目 录 译者序 前言 第1章 对象的演化 1 1.1 基本概念 1 1.1.1 对象:特性+行为 1 1.1.2 继承:型关系 1 1.1.3 多态性 2 1.1.4 操作概念:OOP程序像什么 3 1.2 为什么C++会成功 3 1.2.1 较好的C 3 1.2.2 采用渐进的学习方式 4 1.2.3 运行效率 4 1.2.4 系统更容易表达和理解 4 1.2.5 “库”使你事半功倍 4 1.2.6 错误处理 5 1.2.7 大程序设计 5 1.3 方法学介绍 5 1.3.1 复杂性 5 1.3.2 内部原则 6 1.3.3 外部原则 7 1.3.4 对象设计的五个阶段 9 1.3.5 方法承诺什么 10 1.3.6 方法应当提供什么 10 1.4 起草:最小的方法 12 1.4.1 前提 13 1.4.2 高概念 14 1.4.3 论述(treatment) 14 1.4.4 结构化 14 1.4.5 开发 16 1.4.6 重写 17 1.4.7 逻辑 17 1.5 其他方法 17 1.5.1 Booch 18 1.5.2 责任驱动的设计(RDD) 19 1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 什么是非正常 29 2.5 基本对象 30 2.6 什么是对象 34 2.7 抽象数据型 35 2.8 对象细节 35 2.9 头文件形式 36 2.10 嵌套结构 37 2.11 小结 41 2.12 练习 41 第3章 隐藏实现 42 3.1 设置限制 42 3.2 C++的存取控制 42 3.3 友元 44 3.3.1 嵌套友元 45 3.3.2 它是纯的吗 48 3.4 对象布局 48 3.5 48 3.5.1 用存取控制来修改stash 50 3.5.2 用存取控制来修改stack 51 3.6 句柄(handle classes) 51 3.6.1 可见的实现部分 51 3.6.2 减少重复编译 52 3.7 小结 54 3.8 练习 54 第4章 初始化与清除 55 4.1 用构造函数确保初始化 55 4.2 用析构函数确保清除 56 4.3 清除定义块 58 4.3.1 for循环 59 4.3.2 空间分配 60 4.4 含有构造函数和析构函数的stash 61 4.5 含有构造函数和析构函数的stack 63 4.6 集合初始化 65 4.7 缺省构造函数 67 4.8 小结 68 4.9 练习 68 第5章 函数重载与缺省参数 69 5.1 范围分解 69 5.1.1 用返回值重载 70 5.1.2 安全型连接 70 5.2 重载的例子 71 5.3 缺省参数 74 5.4 小结 81 5.5 练习 82 第6章 输入输出流介绍 83 6.1 为什么要用输入输出流 83 6.2 解决输入输出流问题 86 6.2.1 预先了解操作符重载 86 6.2.2 插入符与提取符 87 6.2.3 通常用法 88 6.2.4 面向行的输入 90 6.3 文件输入输出流 91 6.4 输入输出流缓冲 93 6.5 在输入输出流中查找 94 6.6 strstreams 96 6.6.1 为用户分配的存储 96 6.6.2 自动存储分配 98 6.7 输出流格式化 100 6.7.1 内部格式化数据 101 6.7.2 例子 102 6.8 格式化操纵算子 106 6.9 建立操纵算子 108 6.10 输入输出流实例 111 6.10.1 代码生成 111 6.10.2 一个简单的数据记录 117 6.11 小结 123 6.12 练习 123 第7章 常量 124 7.1 值替代 124 7.1.1 头文件里的const 124 7.1.2 const的安全性 125 7.1.3 集合 126 7.1.4 与C语言的区别 126 7.2 指针 127 7.2.1 指向const的指针 127 7.2.2 const指针 127 7.2.3 赋值和型检查 128 7.3 函数参数和返回值 128 7.3.1 传递const值 128 7.3.2 返回const值 129 7.3.3 传递和返回地址 131 7.4 133 7.4.1 里的const和enum 133 7.4.2 编译期间里的常量 134 7.4.3 const对象和成员函数 136 7.4.4 只读存储能力 139 7.5 可变的(volatile) 140 7.6 小结 141 7.7 练习 141 第8章 内联函数 142 8.1 预处理器的缺陷 142 8.2 内联函数 144 8.2.1 内部的内联函数 145 8.2.2 存取函数 146 8.3 内联函数和编译器 150 8.3.1 局限性 150 8.3.2 赋值顺序 150 8.3.3 在构造函数和析构函数里隐藏行为 151 8.4 减少混乱 152 8.5 预处理器的特点 153 8.6 改进的错误检查 154 8.7 小结 155 8.8 练习 155 第9章 命名控制 157 9.1 来自C语言中的静态成员 157 9.1.1 函数内部的静态变量 157 9.1.2 控制连接 160 9.1.3 其他的存储型指定符 161 9.2 名字空间 161 9.2.1 产生一个名字空间 162 9.2.2 使用名字空间 163 9.3 C++中的静态成员 166 9.3.1 定义静态数据成员的存储 166 9.3.2 嵌套和局部 168 9.3.3 静态成员函数 169 9.4 静态初始化的依赖因素 171 9.5 转换连接指定 174 9.6 小结 174 9.7 练习 174 第10章 引用和拷贝构造函数 176 10.1 C++中的指针 176 10.2 C++中的引用 176 10.2.1 函数中的引用 177 10.2.2 参数传递准则 178 10.3 拷贝构造函数 179 10.3.1 传值方式传递和返回 179 10.3.2 拷贝构造函数 182 10.3.3 缺省拷贝构造函数 187 10.3.4 拷贝构造函数方法的选择 188 10.4 指向成员的指针

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值