c++中的多态

多态分为两类
1.静态多态: 函数重载和运算符重载属于静态多态,复用函数名
2.动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:
1.静态多态的函数地址早绑定 - 编译阶段确定函数地址
2.动态多态的函数地址晚绑定 - 运行阶段确定函数地址

实例:

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
37
38
39
40
41
42
43
44
45
46
47

#include<iostream>
using namespace std;
class Animal{
public:
virtual void speak(){//在成员函数前面加上virtual就变为了虚函数
//编译器就不能确定函数调用了
cout<<"animal speak"<<endl;
}
};
class Cat:public Animal{//继承了animal这个类
public:
void speak(){//子类重写了父类中的虚函数
cout<<"cat speak"<<endl;
}
};
class Dog:public Animal{
public:
void speak(){//也重写了父类中的虚函数
cout<<"dog speak"<<endl;
}
};

void dospeak(Animal &animal){//Animal &animal=cat
animal.speak();//父类的引用指向了子类对象,
//首先要知道c++中运行父子之间的类型转换,不需要强制类型转换
//父类的引用或指针可以直接指向子类对象

}

void test01(){
Cat cat;
dospeak(cat);
Dog dog;
dospeak(dog);
}
int main(){
test01();
system("pause");
return 0;
}
// 总结:
// 多态满足条件:
// 有继承关系, 子类重写父类中的虚函数
// 多态使用条件:父类指针或引用指向子类对象

// 重写:函数返回值类型 函数名 参数列表 完全一致称为重写

上面的代码输出为:
cat speak
dog speak

我们再来看一下本质:
Animal的内部结构在变为虚函数后是这样的:

1
2
3
4
5
6
7
8
9
class     Animal       size(4)
+---
0 |{vfptr}
+---

Animal::$vftable@:
|&Animal_meta
|0
0 |&Animal::speak

变为虚函数后,由vfptr这个指针指向vftable中,
Cat类重写过后内部是这样的:

1
2
3
4
5
6
7
8
9
10
class     Cat         size(4)
+---
0 | +---base class Animal
0 | | {vfptr}
| +---
+---
Cat::$vftable@:
| &Cat_meta
| 0
0 | &Cat::speak

由vfptr指针指向vftable,其中因为被重写过了,已经由上面的&Animal::speak变为&Cat::speak,
变为虚函数,编译器就不能确定函数调用了,又经过这样的重写,指向了Cat,这样就真正实现了多态。

======
下面再介绍纯虚函数的概念:
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:
无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类
实例:

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
#include<iostream>
using namespace std;
#include<string>

class Base{
public:
virtual void func()=0;//这样就是一个纯虚函数
};//此时Base被称为抽象类

class Son:public Base{//这里Son继承了Base类
public:
virtual void func(){
cout<<"func diaoyong"<<endl;
}//这里就是对父类(抽象类)进行了重写
};
void test01(){
Base *base=NULL;
//base=new Base; 错误,抽象类无法实例化对象
base = new Son;//这个是可以实例化对象的,因为Son里面重写了(能用父类指向子类)
base->func();
delete base;//销毁
}
int main(){
test01();
system("pasue");
return 0;
}

======
在多态的使用,如果子类中的属性有开辟到对去了,那么父类指针在释放时调用不到子类的析构函数,所以c++中将父类的析构函数改为虚析构函数或者纯虚析构函数来解决此问题。

两者的共性:
1.都可以解决父类指针释放子类对象的问题
2.但是都需要具体的函数实现,也就是说是不能自动生成两类函数的

两者的区别:
1.如果是纯虚析构函数,该类属于抽象类,无法实例化
实例:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<iostream>
using namespace std;
#include<string>

class Animal{
public:
Animal(){//构造函数
cout<<"Animal gouzao func"<<endl;
}
virtual void speak()=0;//虚函数
// virtual ~Animal(){//虚析构函数
// cout<<"Animal virtual xigou func"<<endl;
// }
virtual ~Animal()=0;//纯虚析构函数
};

Animal::~Animal(){//纯虚析构必须要有一个函数的具体实现,所以,
//这里必须要对上面的纯虚析构进行重写
cout<<"Animal pure virtual xigou func"<<endl;//对纯虚析构函数的重写
}

class Cat:public Animal{
public:
Cat(string name){//构造函数
cout<<"cat gouzao func have name"<<endl;
m_name=new string(name);
}
// Cat(){
// cout<<"Cat gouzao func"<<endl;
// }

virtual void speak(){//虚函数
cout<<*m_name<<"cat speak"<<endl;
}
~Cat(){
cout<<"Cat xigou func"<<endl;
if(this->m_name!=NULL){
delete m_name;
m_name=NULL;
}
}
public:
string *m_name;
};

void test01(){
Animal *animal=new Cat("tom");//
animal->speak();
delete animal;
}

int main(){
test01();
system("pause");
return 0;
}

总结:

  1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  3. 拥有纯虚析构函数的类也属于抽象类