多重继承是一个类的父类也有父类,一层一层的继承父类。
多重继承的对象初始化方式最高的父类到子类。
A()–>B–>C()
销毁时正好是初始化的反顺序。
~C–>~B–>A()
class A{};
class B:public A{};
class C:public B{};
用子类初始化父类,为避免内存泄露,父类的构析函数最好用virtual 修饰。
多继承:一个子类拥有很多父类 ,一般指一个类有2个以上父类。
多继承的定义:派生类的基类大于一个。多
多继承的对象初始化方式是父类依次初始化。
A()–>B–>AB()
销毁时正好是初始化的反顺序。
~AB–>~B()–>A~()
class A{};
class B{};
class AB:public A,public B{};
多继承语法:
class 派生类名:继承方式1 基类名1,继承方式2 基类名2...
{
<派生类新定义成员>
};
多继承与构造函数的关系:
多继承时构造函数的作用:
1)初始化派生类(自己)
2)调用该派生类的所有基类构造函数,并且为所有基类传参(参数个数必须包含所有基类所需参数)
构造函数语法:
派生类构造函数名(总参数表列): 基类1构造函数(参数表列), 基类2构造函数(参数表列), 基类3构造函数(参数表列)
{
//派生类中新增数成员据成员初始化语句
}
说明:派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。
具体点来说:初始化列表中要包括对 直接基类 + 虚基类 进行调用。
构造函数的执行次序(不含虚基类):
(1)基类:依派生的次序决定,与构造函数中书写顺序无关
(2)子对象的构造函数
(3)派生类的构造函数
析构函数的执行次序:和上述执行顺序相反
注意:
1)析构函数能继承;
2)派生类中要定义自己的析构函数释放在派生中新增的成员;
3)从基类中继承的成员释放,可以通过基类的析构函数实现;
4)析构函数的调用顺序与构造函数调用顺序相反。
#include
using namespace std;
class A
{
public:
A()
{
cout<<"调用A的构造函数"<
运行结果:
调用A的构造函数--C的基类
调用B的构造函数--C的基类
调用A的构造函数--C的对象成员(C的成员变量)
调用C的构造函数--C自己的构造函数
虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数,这时会产生二义性。为了解决二义性,同时为了节约内存,B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类,这样D中就只有一份A中的变量和函数。
#include
using namespace std;
class A
{
public:
A(int a)
{
m_a = a;
cout<<"调用A的构造函数"<
说明:
1)C是D的虚基类,故先调用C的构造函数
2)在同层次的多个虚基类中,从左到右调用,先B到C
3)基类构造函数调用完后,在调用D的构造函数
使用虚基类和不使用虚基类的说明:
使用虚基类后的层次图:
#include
using namespace std;
class A
{
protected:
int a;
public:
A(int a)
{
this->a=a;
}
};
class B:virtual public A
{
public:
B(int a):A(a)
{
}
};
class C:virtual public A
{
public:
C(int a):A(a)
{
this->a=a;
}
};
class D:public B,public C
{
public:
D(int a):B(a),C(a),A(a)//使用虚基类,声明时是 D的直接基类B和C + 直接基类的共同基类A
{
}
void display()
{
cout<<"a="<
不使用虚基类后的层次图:
虚继承就是为了节约内存的,他是多重继承中的特有的概念。适用与菱形继承形式。
如:类B、C都继承类A,D继承类B和C。为了节省内存空间,可以将B、C对A的继承定义为虚拟继承,此时A就成了虚拟基类。
class A;
class B:vitual public A;
class C:vitual public A;
class D:public B,public C;
虚函数继承就是覆盖。即基类中的虚函数被派生类中的同名函数所覆盖。
class parent
{
public:
vitual void foo(){cout < <"foo from parent";};
void foo1(){cout < <"foo1 from parent";};
};
class son:public parent
{
void foo(){cout < <"foo from son";};
void foo1(){cout < <"foo1 from son";};
};
int main()
{
parent *p=new son();
p->foo();
p->foo1();
return 0;
}
其输出结果是:
foo from son,foo1 from parent
虚继承主要用于菱形形式的继承形式。
1、真正意义上的虚函数调用,是运行时绑定的;
2、什么是真正意义上的虚函数调用?通过指针或者引用执行虚函数;
3、通过对象执行虚函数会不会是动态绑定的?不会。
4、一个类是否有虚函数,就看它是否包含一个指向虚函数表的指针;
5、如果类本身含有virtual 声明的函数,或者继承了virtual 函数,那么它肯定会包含一个指向虚函数表的指针;
6、从纯抽象类或者非抽象类继承了virutal,意义上是一样的,效率上是一样的,并不因为你是纯抽象类的继承而效率变高;
7、虚函数调用比普通函数调用慢多少?假设这个函数仅执行 return i > j,大概慢 15%左右(3000万 * 100次规模测试),如果是个真正有意义上的函数,效率影响可以忽略不计;
8、因此说虚函数慢的基本上是放屁,担心虚函数影响效率的基本上是杞人忧天;
9、虚函数会慢,但是那是对内联函数而言的,虚函数会忽略 inline前缀,请注意这一点;
10、继承层次不影响虚函数效率,如果你这个类是原始类的第10层继承,那么虚函数调用效率和第1层继承的类没有差别,当然如果你要在该函数中调用上一层的虚函数那就另当别论了;
11、每个类应该只有一个virtual table,而不是每个对象有一个(对象只含有指向虚表的指针),那些说虚函数增大空间开销的可以自宫了;
12、如果一个类含有虚函数,在构造时,使用memset(this, 0, sizeof(*this))是找死的行为;
13、虚函数是运行时多态,模板是编译时多态,一个动,一个是静。
14、子类覆盖父类的虚函数的时候,实际上是在构造函数中修改了虚表中的函数指针;因此使得 FatherClass* p = new ChildClass();的情况下,p->VirtualFunc()始终执行的是子类的虚函数;