传统的C通过预定义、预编译的函数库(如strlen(),rand())等,提供了代码重用的功能。代码重用很重要,方便,且容易维护。
然而,这具有局限性,函数一般不提供源代码,这样我们无法根据具体需求修改函数。而C++提供了类库,以源代码形式提供,并且继承这种方式可以更好修改和扩展类,只要搞清楚继承的相关知识,继承派生通常比设计一个新类方便。
1,基类和派生类
//基类
class T
{
private:
string firstname;
string lastname;
bool hash;
public:
T(const string & fn='none',const string &ln='none', bool ht=fasle); //默认构造函数
voif func(...);
}
T(const string & fn,const string &ln,bool ht): firstname(fn),lastname(ln),hash(ht){}//构造函数
}
这里有一个插曲,介绍一下类的初始化列表和构造函数初始化的区别。
T(const string & fn,const string &ln,bool ht): firstname(fn),lastname(ln),hash(ht){}//构造函数是初始化列表。也可以在函数里面赋值初始化。
区别是,初始化列表可以提高效率。因为函数体内初始化首先会调用默认构造函数构造类成员,再通过赋值运算符给成员赋值,两步操作;列表初始化直接调用拷贝构造函数,一次就够了。
另外,如果没有默认构造函数 ,必须使用列表初始化,或者const类型的成员必须使用列表初始化,因为const变量不能修改。
class D :public T
{
private:
int rating;
public:
D(int r=0,const string & fn='none',const string &ln='none', bool ht=fasle)
D(int r,conast T& t): T(t),rating(r) {}
其他函数;
}
公有继承,基类的公有成员也会成为派生类的公有成员,基类的私有成员也成为派生类的一部分,但只能通过基类的公有和保护方法访问。
派生类的初始化需要先调用基类的构造函数,再初始化自己增加的成员。析构时先执行派生类的析构函数,再执行基类的。
二者之间的特殊关系
1、派生类对象可以使用基类的方法,只要方法不是私有的。
2、基类指针或引用可以不在显式类型转换的情况下指向派生类对象,但是只能用于调用基类方法。反过来不行。
2、多态公有继承
如果我们希望同一个方法在基类和派生类中行为不同,有两种机制可以实现:
在派生类中重新定义基类的方法,对象调用。
使用虚函数,对于指针引用来说;
如果不使用虚函数,程序将根据指针或引用类型来选择方法;如果使用虚函数,将根据指向对象的类型来选择方法;举个例子:
T & t1_ref=T t(1,1,1);
T & t2_ref=D d(2,2,2);
t1_ref.func();
t2_ref.func();
如果func不是虚函数,那么调用的都是基类T的func方法,如果func是虚函数,那么t2_ref调用的就是派生类D定义的func方法。这就实现了多态。
注意虚函数需要虚析构函数,原因类似。虚析构函数可以根据指针指向对象类型来调用,而不是指针类型调用。比如t2_ref对派生类进行操作,那么就应该调用派生类的析构函数(再调用基类),而不是调用基类T的构造函数。
另外,基类中定义一个函数是虚函数 ,那么在其派生类中,他也是虚函数,即使不用virtual声明,不过最好还是声明一下。
接下来说一下虚函数的工作原理;
为每个对象添加一个隐藏成员虚函数表(vtbl)的指针(vptr).虚函数表就是一个存储很多虚函数地址的数组,派生类没有重新定义虚函数时,用的还是基类的虚函数。调用虚函数时,先查看存储在对象中vtbl的地址vptr,然后转向这个表,看看是第几个虚函数,获得地址,前往这个地址的虚函数并执行。
所以多态代价是:对象增大、虚函数表的空间、查找地址的时间花费。
虚函数这么好,但是构造函数不能是虚函数:原因是,派生类不继承基类的构造函数,而是调用它,所以没有意义,并且,虚函数实现需要对象中的vptr,而对象创建在构造函数完成之后,矛盾了。
关于protected
保护和private的区别只在基类派生的类中表现出来,派生类可以直接访问基类的保护成员,而基类的私有成员必须通过基类的方法访问。而总是用方法访问很麻烦,所以保护的作用是让派生类可以访问,但是外部不能直接访问,外部只能直接访问类的公有成员。
抽象基类ABC
有时二者继承比较麻烦,比如圆和椭圆,椭圆是基类,圆是派生类,但是圆只需要半径就够了,不需要长半轴短半轴等。所以,可以定义一个抽象基类存放二者共性,比如中心坐标,再派生圆和椭圆,这样,只需要抽象基类的指针数组就可以同时管理圆和椭圆(通过多态)
抽象基类至少有一个纯虚函数(在圆和椭圆中实现不同),纯虚函数就是虚函数加上=0, 如virtual func()=0,纯虚函数甚至可以不用定义。ABC不能实例化。使用ABC迫使派生类遵循ABC制定的接口规范(纯虚函数)。