目录
派生类:
- 派生类由两部分构成:
①基类构成的子对象(由基类的非静态数据成员(如果有)组成。
②派生类的部分(派生类的非静态数据成员)组成。 - 定义一个派生类,需要这么定义,比如:
#include"num_sequence.h"//基类的类定义头文件先行包含
class Fibonnacci:public num_sequence{
public:
//...
};
此为派生类类定义的格式:class 派生类类名:public 基类类名{//...};
注意这是采用公有继承(继承基类)方式的派生类类定义,其他方式的继承比如私有继承和保护继承(protected继承)、多重继承、虚继承则再议。
- 派生类必须为从基类继承而来的每个纯虚函数提供对应的实现。
- 派生类必须声明自己的成员。
如:
//节选部分基类的类主体定义
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;
//。。。有所省略(具体完整的基类类定义查学习记录34不带继承的多态,定义一个抽象基类)
protected:
virtual void gen_elems( int pos ) const = 0;
bool check_integrity( int pos, int size ) const;
};
这是基类部分类定义,你看有很多的纯虚函数,比如what_am_i()、length()、 beg_pos()等。因为派生类必须为从基类继承而来的每个纯虚函数提供对应的实现。,所以派生类的定义请看(例子):
class Fibonacci: public num_sequence{
public:
Fibonnacci(int len=1,int beg_pos=1):_length(len),_beg_pos(beg_pos){}
//派生类F类的成员逐一初始化的类构造函数(提供参数默认值)
virtual int elem(int pos)const;//虚函数elem的声明,为了让基类指针能
//调用派生类的elem,所以把elem()在基类和派生类都设置成虚函数让基类指针能调用派生类同名函数elem。
//基类指针调用派生类其他的同名函数也是如此做法。
virtual const char* what_am_i()const{return "Fibonacci";}
//这里直接给出了what_am_i()虚函数的定义
virtual ostream& print(ostream &os=cout)const;
int length()const{return _length;}
int beg_pos() const {return _beg_pos;}
//这两个成员函数则是派生类Fibonacci专属的成员
//声明在public:里的成员函数都可以在派生类外(包括基类)还可以被调用,这个派生类的派生类还可以继承这个成员函数。
protected:
virtual void gen_elems(int pos)const;
//这个成员函数声明在protected里,那么这个派生类的派生类还可以继承这个成员函数而派生类外(不包括基类和他的派生类)
//是不可调用这个成员函数的。
int _length;
int _beg_pos;
static vector<int> _elems;
//这三个成员是派生类专属成员
一些机制的说明
注意代码要看关键点,不要纠结函数的一些定义细节(如参表)
纯虚函数和虚函数的补充
- 你问我为什么length()和beg_pos()这两个函数不声明成虚函数,是因为他们没有基类所提供的实体(定义)可供覆盖(基类没有定义length()和beg_pos()而只是声明这两个成员函数是纯虚函数,纯虚函数是不可以有定义的只可以有声明的)
- 基类声明的纯虚函数(基类没给这些函数提供定义)可认为并非基类提供接口(public和protected里)的一员,所以通过基类指针/引用进行操作时,不可访问非基类接口的一员(纯虚函数),如:
num_sequence *ps=new Fibonacci(12,8);//打个比方有这么行语句,ps基类(num_sequence)指针指向派生类Fibonacci
ps->what_am_i();//语法正确,通过虚函数机制指针ps调用派生类的what_am_i()成员函数-->Fibonacci::what_am_i()
ps->max_elems();//语法正确,通过指针ps调用继承基类而来的成员函数num_sequence::max_elems();(派生类并没有给这个函数声明/定义)
ps->length();//语法错误,length()在基类是纯虚函数(非基类接口中的一员)
delete ps;//语法正确,通过虚函数机制调用派生类Fibonacci的析构函数。
这里注意一下,派生类没有定义/声明的成员函数,而在派生类利用基类指针去调用基类的成员函数,这可以称作是派生类调用继承而来的基类的成员函数
- 分析那个语法错误,可知我们通过基类的接口无法访问length()和beg_pos()。怎么办啊?
解决方法:在基类num_sequence里加上俩纯虚函数length()和beg_pos()。
virtual int length() const = 0;
virtual int beg_pos() const = 0;
这样派生类的length()和beg_pos()就自动变成虚函数。然后我们通过基类的接口就可以访问length()和beg_pos() 。而且派生类的同名成员函数虚函数不需要再在函数声明/定义前加上virtual
再加上的话你就要面临麻烦问题。不如咱记住这项规定即可。
另一解决方法:把数据成员_length和_beg_pos抽离出派生类扔到基类里,然后派生类的length()和beg_pos()函数成为了继承而来的内联非虚拟函数。
- 派生类的虚函数必须精确吻合基类中的函数原型
- 在类之外对虚函数进行定义时,不必指明关键字virtual
比如:
class Fibonacci: public num_sequence{
public:
Fibonnacci(int len=1,int beg_pos=1):_length(len),_beg_pos(beg_pos){}
//派生类F类的成员逐一初始化的类构造函数(提供参数默认值)
virtual int elem(int pos)const;
//。。。
//👇并未在开头打上virtual空格
int Fibonacci::elem(int pos)const
{
if(!check_integrity(pos,_elems.size() ))//!!!原封不动地就写上去了这个函数调用
//如果传入位置不合理,退出该函数。
{
return 0;
}
if(pos>_elems.size())//传入位置大于数列长度值,充数入数列
{
Fibonacci::gen_elems(pos);//启动充数类成员函数
}
return _elems[pos-1];//返回存储数列的静态类成员(vector)
}
类继承的一些原则和注意(对于继承过来的数据成员/成员函数)
- 你可能会质疑一点:这个check_integrity()函数,不是基类的protected里的成员吗?怎么直接就原封不动地写进了派生类Fibonacci的成员函数里了呢?
//。。。节选自基类num_sequence
protected:
virtual void gen_elems( int pos ) const = 0;
bool check_integrity( int pos, int size ) const;
//....
从基类继承而来的public成员和protected成员,都可被视为派生类自身拥有的成员。
基类的public/protected成员继承到派生类里,其在派生类里也属于public/protected,并同样遵循他该访问层级的原则👇
注意:基类的private成员无法让派生类使用。
以上的说明仅限于公有继承的情况,其他继承情况有变,参考李普曼98(C++ primer?)的18.3节
跳过虚函数机制
- 如果你清楚在某类成员函数内想调用哪个类的成员函数,就不必再在运行时解析某成员函数,而是直接在编译时解析即可,即跳过虚函数机制
怎么办到呢?就是用类作用域运算符,这是指明调用哪个类对象的某成员函数,比如上一个例子的
int Fibonacci::elem(int pos)const
{
//。。。
if(pos>_elems.size())//传入位置大于数列长度值,充数入数列
{
Fibonacci::gen_elems(pos);//启动充数类成员函数
}
//。。。
}
这里指明了调用哪个类对象的gen_elems()成员函数,因为我们清楚地知道想要调用哪个类对象的gen_elems()。这样的话编译时解析这个gen_elems()函数是不是就可以了?原本gen_elems()在派生类Fibonacci是虚函数,在基类num_sequence是纯虚函数👇
class Fibonacci: public num_sequence{
//。。。
protected:
virtual void gen_elems(int pos)const;
};
class num_sequence {
//。。。
protected:
virtual void gen_elems( int pos ) const = 0;
};
我们指明了哪个类对象的gen_elems()成员函数Fibonacci::gen_elems(pos);
所以可得出结论:如果清楚知道要调用哪个类的成员函数,就采用类名::该成员函数名(形参表);
的形式,这样在程序运行时虚拟机制就被作用域解析运算符(::)机制给掩盖了,从而这个类成员函数在编译时就给解析了不必等到程序运行的时候再解析。
派生类同名成员优先被调用机制
- 每当派生类的某个成员和其基类的成员同名,在调用该成员时,调用的是派生类的成员(该成员被解析为派生类的成员而非基类成员),比如:
int Fibonacci::elem(int pos)const
{
//调用的是Fibonacci派生类的的check_integrity()
if(!check_integrity(pos))
{
return 0;
}
//...
}
前提是你得在派生类里至少声明了和基类同名的成员,这个派生类同名成员优先被调用机制才会发生
class Fibonacci: public num_sequence{
//。。。
protected:
bool check_integrity(int pos)const;
//...
};
如何在派生类调用基类的成员函数(该成员函数与派生类的一个成员函数同名)
如果你执意在派生类内使用从基类继承而来的哪个成员函数,则请利用类作用域解析运算符加以限定那个成员函数。如:
//看关键点,不要纠结函数的一些定义细节(如参表)
inline bool Fibonacci::check_integrity(int pos)const
{
if(!num_sequence::check_integrity(pos))
{
return false;
//如果在派生类中调用基类的成员函数,且这个基类的成员函数在派生类中有同名的成员函数,
//则必须在这个调用的成员函数前加上“基类名::”才可以在派生类中调用基类的成员函数
}
if(pos>_elems.size())
{
Fibonacci:gen_elems(pos);
//作用域解析运算符掩盖虚拟机制(强制在编译时解析这个gen_elems()成员函数)
}
return true;
}
缺陷所在
不过这样的方法有个问题,就是基类中的成员函数(check_integrity())并未视为虚函数(在基类的类定义中,这个函数没有被声明成虚函数)
- 而如果你要用基类指针/引用调用这个成员函数(check_integrity()),因为这个成员函数前加了
基类名::
,所以解析出来的都是基类的成员函数,而基类指针/引用指的如果是派生类的这个成员函数,那就调用不了派生类的这个成员函数了,强制调用基类的这个成员函数。
解决方法
重新定义基类的这个成员函数(check_integrity()).,比如把形参扩成两个参数。(int pos,int size),派生类的这个成员函数别改变。
这样基类指针/引用就可以选择调用基类的这个成员函数还是其派生类的这个成员函数了。
调试程序小提示:
逐步测试自己的实现代码(写一块代码了,加个cout输出一下看看输出什么)比整个程序都写完了后再测试要好多了。