Essential C++学习记录&笔记整理34(不带继承的多态,定义一个抽象基类)

一步步深入进面向对象编程风格去。

不带继承的多态

5.3节我看了三遍,也没看出他讲了什么东西,反正diss一遍自己写的代码。
我矮子里拔将军,找出了这些知识点来给大家讲述。
static_cast是一个特殊的转换记号可将整型变量转换为对应的枚举型变量。(如可用于检验某整数是不是代表某一有效数列)
用法举例:

class num_sequence{
public:
	//...
	static ns_type nstype(int num)
	{
		return num<=0 || num>=num_seq ? unset : static_cast<ns_type>(num);//三目运算符(用于判断的三目运算符)别看错了!
	}

代码说明:①ns_type是enum型变量(enum ns_type{//...};),②ns_unset是无效值(整型变量)③static_cast<枚举类型变量>(整型变量);这个用法记住!

定义一个抽象基类

为什么要定义一个抽象基类?

  • 因为我想着为一些类设计出共享的抽象基类,这是一个需求

分三个步骤:

  • 第一,找出所有子类(一些类)共通的操作行为
  • 第二,找出哪些操作行为(基类成员函数)必须根据不同的派生类而有不同的实现方式
  • 第三,找出每个操作行为(基类成员函数)的访问层级

定义抽象基类第一步

所有子类共通的操作行为,这些行为代表的是基类的公有接口。举个例子:

class num_sequence{//基类num_sequence
public:
	int elem(int pos);//返回存储数列的vector的pos位置上的元素
	void gen_elems(int pos);//存储数列的vector后产生直到pos位置的所有元素
	const char* what_am_i()const;//返回确切的数列类型(字符型数组/字符串)
	ostream& print(ostream &os=cout)const;//将某数列的所有元素写进
	//os,准备输出数列所有的元素
	bool check_integrity(int pos);//检查pos是否在数列中为有效位置
	//即输入的pos是不是合法的输入位置
	static int max_elems();//返回存储数列的vector支持的最大位置值

不难看出,这些函数在派生类(数列类,比如Fibonacci类,square类,triangular类)会用到,这些函数(行为)就是派生类(子类)共通的操作行为。

定义抽象基类第二步

找出哪些操作行为(基类成员函数)必须根据不同的派生类而有不同的实现方式。
如果某操作行为(基类成员函数)必须根据不同的派生类而有不同的实现方式,这个操作行为就要成为整个类继承体系的虚函数即该成员函数(至少在基类中)的声明前加virtual。

  • 比如上面例子中的gen_elems()成员函数,他的作用是让存储某个数列的vector后产生直到pos位置的所有元素。每个数列类(派生类)都要提供自己的gen_elems()的实现。所以gem_elems()在基类主体的public里必须声明成虚函数。
  • check_integrity()和max_elems()成员函数不用在基类主体的public里必须声明成虚函数。为什么?因为这两个基类成员函数的作用分别是判断pos位置是否输入的合法和求人为规定存放最多多少个数列元素。这些作用对于哪个数列类(派生类)都是一个作用,不会因为数列类主体不同而有所不同。 那就不用给他们在基类主体里声明成虚函数。
  • 注意,C++语法规定静态成员函数无法被声明为虚函数

定义抽象基类第三步

  • 找出每个操作行为的访问层级。
    某个操作行为(类成员函数)能让一般程序(在该类主体和该类的派生类如果有派生类的话外)访问这个成员函数,那么我们将这个成员函数的声明/定义放进基类的public:里。
    某个操作行为(类成员函数)在基类之外不需要被用到,自然不用在基类之外访问这个成员函数。我们就把这个成员函数的声明/定义放进基类的private:里
    某个操作行为(类成员函数)在该类的派生类(如果有的话)主体/对象可以被访问,却不许一般程序访问。 那么我们就把这个成员函数的声明/定义放进基类的protected:里。
    给一个例子,结合着上面的例子(我搬下来给你看)和我说的这三条来看,明朗不少能让你。
class num_sequence{//基类num_sequence重构前
public:
	int elem(int pos);//返回存储数列的vector的pos位置上的元素
	void gen_elems(int pos);//存储数列的vector后产生直到pos位置的所有元素
	const char* what_am_i()const;//返回确切的数列类型(字符型数组/字符串)
	ostream& print(ostream &os=cout)const;//将某数列的所有元素写进
	//os,准备输出数列所有的元素
	bool check_integrity(int pos);//检查pos是否在数列中为有效位置
	//即输入的pos是不是合法的输入位置
	static int max_elems();//返回存储数列的vector支持的最大位置值
//重构基类num_sequence的定义
clss num_sequence{
public: 
	virtual ~num_sequence(){};
	//正是因为
	virtual int elem(int pos)const=0;
	//返回存储数列的vector的pos位置上的元素
	//这个成员函数在派生类的类主体里或许会用到(访问),在基类和其派生类类主体外也可能用到,
	//而且每个派生类主体内的同名成员函数定义(实现)也不一样,
	//就把这个基类成员函数声明开头打上虚函数并放进public里
	virtual const char* what_am_i()const=0;
	//返回确切的数列类型(字符型数组/字符串)
	//这个成员函数在派生类的类主体里或许会用到(访问),在基类和其派生类类主体外也可能用到,
	//而且每个派生类主体内的同名成员函数定义(实现)也不一样,
	//就把这个基类成员函数声明开头打上虚函数并放进public里
	static int max_elems(){return _max_elems;}
	//返回存储数列的vector支持的最大位置值,
	//这个静态成员函数在派生类的类主体里或许会用到(访问),在基类和其派生类类主体外也可能用到,
	//但每个派生类主体内的同名成员函数定义(实现)一样,
	//就把这个基类静态成员函数声明开头放进public里(C++语法规定静态成员函数无法被声明为虚函数)
	virtual ostream& print(ostream &os=cout)const=0;
	//将某数列的所有元素写进os,准备输出数列所有的元素
	//这个成员函数在派生类的类主体里或许会用到(访问),在基类和其派生类类主体外也可能用到,
	//而且每个派生类主体内的同名成员函数定义(实现)也不一样,
	//就把这个基类成员函数声明开头打上虚函数并放进public里
protected:
	virtual void gen_elems(int pos)const=0;
	//存储数列的vector后产生直到pos位置的所有元素,只会在该类(基类)的派生类类主体/类对象中用到,一般程序不会用到这个。
	//而且每个派生类主体内的同名成员函数定义(实现)也不一样,
	//就把这个基类成员函数声明开头打上虚函数并放进protected里
	bool check_integrity(int pos)const;
	//检查pos是否在数列中为有效位置(输入的pos是不是合法的输入位置),
	//只会在该类(基类)的派生类类主体/类对象中用到,一般程序不会用到这个。
	//但每个派生类主体内的同名成员函数定义(实现)一样,
	//就把这个基类成员函数声明放进protected里
	const static int _max_elems=1024;
	//派生类和基类都用到这个数据成员了。一般程序不会用,所以把该数据成员放进protected里。

这里你可能会疑惑声明为virtual的类成员函数,最后缀了一个=0,怎么回事?
这样的类成员函数就是纯虚函数

纯虚函数

  • 纯虚函数没有定义,代表这个类成员函数(纯虚函数)在声明它为纯虚函数的类中无实质意义。这也是纯虚函数存在的作用,为什么要用到纯虚函数,的解释(不想定义只想声明基类的虚函数,但要定义其派生类同名(虚)成员函数,zou这么个意思,基类的纯虚函数只是做一个“统一知晓这个成员函数的作用(顾名思义)”的角色罢了。
  • 将虚函数赋值为0,就是让这个虚函数变成了纯虚函数。
    例如:virtual void gen_elems(int pos)=0;
  • 任何类如果声明有一个(或多个)纯虚函数,声明纯虚函数所在的接口(public/private/protected)就不完整了。也就不能定义/声明这个类的类对象。这种类只能作为该类的派生类的子对象(subobject)使用(把该类(基类)当其派生类的子对象使用就是一般用作声明/定义派生类的成员函数时有基类名 形参名,这样的形式。这就是把声明有纯虚函数的基类当其派生类的子对象用了。,但前提是这些派生类必须为所有虚函数提供确切的定义

其他说明

本例的num_sequence类(基类)不声明任何数据成员(数列),因为这个基类只是为“数列继承体系”提供一个接口。这个基类的派生类必须有自身的数据成员。自然,这个基类没有任何的非静态数据成员进行初始化操作,也就不需要给这个基类设计构造函数。
但是还要给这个基类设计析构函数。为什么呢?

class num_sequence{
public:
	virtual ~num_sequence(){};
	//...

再举个例子,如果(注意,如果,就是可能怎么怎么着,所以咱们写代码才要有顾及如果的情况发生)有以下代码块:

num_sequence *ps=new Fibonnacci(12);
//利用指针使用Fibonacci数列
delete ps;
  • ps是基类num_sequence的指针,指向派生类Fibonacci的类对象。delete表达式作用时,指针所指的类对象的类析构函数应用于指针所指的类对象身上。
  • 于是指针将此类对象占用的内存空间归还给程序的空闲空间。
  • 我们不难发现,通过ps类指针调用的析构函数肯定是它指向的类对象(Fibonacci这个派生类的类对象)的析构函数,而非基类num_sequence的析构函数。
  • 所以,根据实际类对象的所属类选择调用哪个所属类的析构函数。这是个解析操作。在程序运行时进行,所以为了这样正确的行为发生,我们必须将基类的析构函数声明为虚函数。就是这样:
class num_sequence{
public:
	virtual ~num_sequence(){};
	//...

然而不建议把这个基类的析构函数声明为纯虚函数(为什么请看LIPPMAN96A的第五章引言部分),所以给这个析构函数(虚函数)提供空白定义即可。如下

inline num_sequence::~num_sequence(){}

至此抽象基类就定义完了。

完整抽象基类的定义

给出完整的抽象基类的定义(有的东西没学,放到下几节来讲)

class num_sequence {
public:
	typedef vector<unsigned int>::iterator iterator;

    virtual ~num_sequence(){};
	virtual num_sequence *clone() const = 0;
			 
	virtual unsigned int elem( int pos ) const = 0;
	virtual bool         is_elem(  unsigned int ) const=0;
	virtual int          pos_elem( unsigned int ) const=0;

    virtual const char*  what_am_i() const = 0;
	static  int          max_elems(){ return _max_elems; }
	virtual ostream&     print( ostream &os = cout ) const = 0;
	
	virtual bool operator ==( const num_sequence& ) const=0;
    virtual bool operator !=( const num_sequence& ) const=0;
	
	//想要让工作num_sequence operator+( const num_sequence& )函数,
	//则需要移除所有的纯虚函数。
    // virtual num_sequence operator+( const num_sequence& ) const=0;

	virtual iterator begin() const = 0;
	virtual iterator end() const = 0;

	virtual int length()  const = 0;
    virtual int beg_pos() const = 0; 

	virtual void set_position( int pos ) = 0;
	virtual void set_length( int pos ) = 0;

	virtual	const vector<unsigned int>* sequence() const=0;
  
protected:
	// static const int    _max_elems = 1024;	
	//编译器不支持const static类型(已测试确实如此,codeblocks17.12)
	enum { _max_elems = 1024 };

    virtual void gen_elems( int pos ) const = 0;
	bool check_integrity( int pos, int size ) const; 
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值