继承的主要作用:使代码可以复用,它会在原有类的基础上进行功能扩展。
有继承关系的两个类叫做基类(父类)和派生类(子类)。派生类继承子类的功能特性,并且会增加自己的功能特性。
继承的定义:class 派生类:继承类型基类,例如: class Derived:public Base。
1.继承的分类:
如图所示,有三种继承方式,我们使用例子说明基类和派生类之间的关系:
公有继承public:
#include<iostream>
using namespace std;
class Base
{
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
class Derived:public Base
{
public:
Derived()
{
_pub=1;
}
};
int main()
{
Derived d;
d._pub=1;
}
上述代码编译成功,说明当派生类公有继承基类时,派生类对象在类中和类外都可以访问基类的public成员。
#include<iostream>
using namespace std;
class Base
{
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
class Derived:public Base
{
public:
Derived()
{
_pro=1;
}
};
int main()
{
Derived d;
d._pro=2;//编译出错
}
上述代码编译出现错误,说明公有继承时,派生类对象只能在类中访问基类的protected成员,而不能在类外访问。
#include<iostream>
using namespace std;
class Base
{
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
class Derived:public Base
{
public:
Derived()
{
_pri=3;//编译出错
}
};
int main()
{
Derived d;
d._pri=3;//编译出错
}
上述代码有两处编译出错,说明公有继承时,派生类对象在类中和类外都不能访问基类的private成员。
用上述三种代码分别测试保护继承和私有继承后,可以总结出:
(1)三种继承的共同点是,派生类对象可以在类中访问基类的public,protected成员,而不能访问private成员
(2)不同点,公有继承时,派生类对象在类外只能访问基类的public成员,保护继承和私有继承时,派生类对象在类外不能访问基类的三种成员。
(3)三种继承的优先级,public>protected>private .
(4)公有继承时,如果基类的成员不想被派生类对象在类外访问,但需要在派生类中访问,可以将此成员定义为protected成员。
(5)使用class时,默认为私有继承,使用struct时,默认为公有继承。
2.同名隐藏:
当基类和派生类中有同名成员时,派生类对象会直接访问派生类的成员,隐藏基类成员。
当需要访问基类成员时,可以使用 派生类对.象基类::同名成员 进行访问,但是注意一般在继承体系中最好不要使用同名定义。
3.派生类的对象模型:
4.赋值规则(公有继承public)
(1)子类对象可以赋值给父类对象,但父类对象不能赋值给子类对象。
(2)同样,父类的指针/引用可以指向子类对象 ,子类的指针/引用不能指向父类对象。
5.
友元关系不能继承;
静态成员变量可以继承。
6.继承中构造函数和析构函数的调用:
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
cout<<"Base()"<<endl;
}
~Base()
{
cout<<"~Base()"<<endl;
}
int _b;
};
class Derived:public Base
{
public:
Derived()
{
cout<<"Derived()"<<endl;
}
~Derived()
{
cout<<"~Derived()"<<endl;
}
int _d;
};
void Funtest()
{
Derived d;
d._d=1;
}
int main()
{
Funtest();
return 0;
}
程序执行结果如下:
由此可知, 当在类外定义派生类对象时,函数体执行顺序顺序为:基类构造函数——>派生类构造函数——>派生类析构函数——>基类析构函数。
但是,当我们使用反汇编及断点进行调试时,发现与上述结果并不相同。
总结:当构造函数和析构函数在类中显式定义时,基类和派生类的执行顺序为:
注意:
(1)当基类中有非缺省构造函数时,派生类必须有构造函数且要在初始化列表处调用基类的构造函数;
(2)基类定义了带参的构造函数时,派生类必须定义构造函数;
(3)当派生类中未显式定义构造函数、拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载、const修饰的取地址操作符重载六个成员函数时,系统会默认合成这六个成员函数;
7.三种继承
(1)单继承:一个子类只有一个直接父类,上述例子都为单继承;
(2)多继承:一个子类有多个直接父类;
(3)菱形继承:
注意:当D类对象访问B类成员时,无法分清楚访问的是C1,C2类中哪一个的成员,产生二义性,并且在D类中会有两个A类成员,造成数据冗余。
8.虚继承(virtual)
目的:解决菱形继承中的二义性问题及数据冗余问题
#include<iostream>
using namespace std;
class B
{
public:
int _b;
};
class C1:virtual public B
{
public:
int _c1;
};
class C2:virtual public B
{
public:
int _c2;
};
class D:public C1,public C2
{
public:
int _d;
};
void FunTest()
{
D d;
d._b=1;
d._c1=2;
d._c2=3;
d._d=4;
}
int main()
{
FunTest();
cout<<sizeof(D)<<endl;//结果为24(4+8+8+4)
return 0;
}
结果:由上述分析可知,D类的对象模型已经改变,D类对象在访问B类成员时,根据偏移量访问对应基类成员,解决了二义性问题。
注意:(1)判断虚拟继承时,虚拟继承的汇编语句中会比普通继承多出一条语句push 1;
(2)当分别给出B,C1,C2,D的构造函数和析构函数时,D类的大小依旧为24;
(3)当误将菱形虚拟继承写为D虚拟继承C1,C2时,无法解决二义性问题。
总结:
1.继承的作用就是代码复用,子类继承父类的功能特性并增加自己的功能特性;
2.继承可以分为公有继承、保护继承、私有继承,在此基础上又可以分为单继承、多继承、菱形继承;
3.一般实际情况中,都会写为公有继承public;
4.class关键字默认的继承方式为私有继承,struct关键字默认的继承方式为公有继承;
5.继承体系中,派生类和基类是不同的作用域,最好不要定义为同名成员;
6.继承体系中的构造函数、析构函数调用顺序为:派生类构造函数->基类构造函数->派生类析构函数->基类析构函数;
7.菱形继承中会产生二义性和数据冗余问题,可以使用虚拟继承解决,但要注意虚拟继承的位置;