虚继承、虚函数、纯虚函数

虚继承解决同源继承二义性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A{
public:
char* name;
};

class A1 :public A{

};

class A2 :public A{

};

class B :public A1, public A2{

};

void main(){
B b;
// 报错,存在二意性
b.name = "jack";

// 显式指定父类进行调用
b.A1::name = "rose";
b.A2::name = "jack";
}

通过虚继承,可以确保同源继承,只有一份相同的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A1 :virtual public A{

};

class A2 :virtual public A{

};

void main(){
B b;
// 报错,存在二意性
// 当继承父类为virtual继承时,能够确保不同路径继承来的同名成员只有一份拷贝,解决二义性
b.name = "jack";
}

虚函数实现多态

动态多态:在程序运行期间,决定哪个函数被调用(类似于动态代理)
1,继承
2,父类引用或指针接收子类的对象实例
3,函数重写

静态多态:子类进行方法重载

在Java中,默认情况下,使用父类变量来接收子类实例就能够实现动态多态,但是,C++中,要实现
动态多态,我们必须显式的将父类函数声明为虚函数;

plane.h

1
2
3
4
5
6
7
8
9
#pragma once

class Plane{
public:
// 为了实现动态多态,必须声明为virtual类型
virtual void fly();

virtual void land();
};

plane.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include "plane.h"

#include <iostream>
using namespace std;

void Plane::fly(){
cout << "simple fly" << endl;
}

void Plane::land(){
cout << "simple land" << endl;
}

jet.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "plane.h"
#include <iostream>

using namespace std;

class Jet :public Plane{

public:
void fly();

void land();

};

jet.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include "jet.h"

#include <iostream>
using namespace std;

void Jet::fly(){
cout << "jet fly" << endl;
}

void Jet::land(){
cout << "jet land" << endl;
}

dragon.h

1
2
3
4
5
6
7
8
9
#pragma once
#include "plane.h"

class Dragon :public Plane{
public:
void fly();

void land();
};

dragon.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include "dragon.h"

#include <iostream>
using namespace std;

void Dragon::fly(){
cout << "Dragon fly" << endl;
}

void Dragon::land(){
cout << "Dragon land" << endl;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "plane.h"
#include "jet.h"
#include "dragon.h"
// 如果在Java中,则可以通过父类接收子类,并且执行时也是调用实际的子类的内容(也就是多态)
void bizPlay(Plane& p){
p.fly();

p.land();
}

void main(){

Plane p;
bizPlay(p);

// 如果想要子类的函数能够被调用(实现多态),则父类声明函数时,必须声明为虚函数
Jet j;
bizPlay(j);

Dragon d;
bizPlay(d);

system("pause");
}

纯虚函数

1,有纯虚函数的类即抽象类
2,抽象类不能被实例化
3,子类继承抽象类,必须实现父类的纯虚函数,否则子类也是抽象类
作用:为了继承约束,对于子类具体实现是未知的。

纯虚函数在函数声明后面必须增加”=0”,这是与虚函数的区别:

拥有纯虚函数的类是抽象类,是不允许进行实例化的:

1
2
3
4
5
6
7
8
9
class Tet{
public:
virtual void myFun()=0;
};

void main(){
// 实例化将在编译期就报错
Tet t;
}

拥有虚函数的类和普通类并没有太大区别(除了实现多态),不管函数只是头文件声明,还是已经实现了
函数体,都能够被直接实例化。

1
2
3
4
5
6
7
8
9
class Tet{
public:
virtual void myFun();
};

void main(){
// 实例化在编译期没有问题,但是,并没有实现函数体,所以运行期将报错;
Tet t;
}

拥有纯虚函数的类即为抽象类,抽象类是允许有非纯虚函数的;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Shape{
public:
// 纯虚函数
// 必须等于0,否则会被解释为函数的头文件函数声明
virtual void sayArea() = 0;

// 和Java一样,抽象类中可以有已经实现的方法
void print(){
cout << "I am print" << endl;
}
};

class Circle :public Shape{
public:
Circle(int r){
this->r = r;
}

void sayArea(){
cout << "Circle Area" << endl;
}
private:
int r;
};

void main(){
// 纯虚函数不能被实例化
//Shape s;
//s.sayArea();

// 子类必须实现抽象父类的方法,才能够被实例化
Circle c(2);
c.sayArea();

system("pause");
}

只包含纯虚函数的类即接口

接口与抽象类,在语法上区别并不是很大,只是逻辑上的划分(C++),可以认为,接口就是只存在
纯虚函数的抽象类。

1
2
3
4
// 可以认为是接口
class Drawable{
virtual void draw() = 0;
};