C++教程正在更新中,具体请查看教程目录
C++的多态
多态
多态在[维基百科](https://zh.wikipedia.org/wiki/%E5%A4%9A%E5%9E%8B_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)中的定义如下:
在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。 [1]多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。[2]
多态按照字面意思来理解,就是多种形态,当我们定义的类之间存在层次的结构时,并且类之间是通过继承来关联的时候,就会用到多态,多态意味着调用成员函数的时候,会根据函数的调用的对象的类型来执行不同的函数。
试想下面的情景,在一个全球性聊天软件中,对于不同国籍的人来说,系统检测到当前用户的国籍即输出当前用户所说的语言。我们设计出如下代码:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Person {
public:
Person() {
}
void speak() {
printf("Speak\n");
}
};
class Chinese : public Person {
public:
Chinese() {
}
void speak() {
printf("Speak Chinese\n");
}
};
class English : public Person {
public:
English() {
}
void speak() {
printf("Speak English\n");
}
};
int main() {
Chinese Person_Chinese;
English Person_English;
Person_Chinese.speak();
Person_English.speak();
return 0;
}
运行上面的代码,输出的结果为:
Speak Chinese
Speak English
我们换一个思路,如果这三个人都是人那么我需要在不同国籍的人后面输出他们所说的语言:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Person {
public:
int Type;
public:
Person() {
Type = 0;
}
void speak() {
printf("Speak\n");
}
};
class Chinese : public Person {
public:
Chinese() {
Type = 1;
}
void speak() {
printf("Speak Chinese\n");
}
};
class English : public Person {
public:
English() {
Type = 2;
}
void speak() {
printf("Speak English\n");
}
};
int main() {
Chinese Chinese_Person_1;
Chinese Chinese_Person_2;
English English_Person_1;
Person* people[3];
people[0] = &Chinese_Person_1;
people[1] = &Chinese_Person_2;
people[2] = &English_Person_1;
for (R int i = 0; i < 3; ++i) {
printf("Person%d:", i + 1);
if (people[i]->Type == 1) {
Chinese* person = (Chinese*)people[i];
person->speak();
}
else if (people[i]->Type == 2) {
English* person = (English*)people[i];
person->speak();
}
}
return 0;
}
尝试编译这段代码输出了同样的结果,但是如果这样写代码,写出来的代码将会非常冗余,我们可以尝试在父类的函数前加上virtual关键字,从而实现相同的功能。
考虑下为什么不加关键字,程序的输出结果为:
Person1:Speak
Person2:Speak
Person3:Speak
导致程序错误输出的原因是,调用函数speak被编译器设置为基类中的版本,这就是所谓的静态多态,/静态链接,也就是说函数调用在程序执行前就已经准备好了,这种情况被称之为早绑定,因为函数在程序编译的时候就已经被设置好了,当我们使用virtual关键字的时候,编译器看的是指针的内容,而不是指针的类型,因此将子类的地址提取出来会调用各自的speak函数。
正如上所示,每一个子类都有一个函数speak独立实现,这就是多态的一般的使用方式,有了多态就会有多个不同的类,并且可以实现同一个名称但是不同作用的函数,甚至函数的参数可以完全相同。改进后的程序如下所示:
#include<map>
#include<list>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<iomanip>
#include<cstring>
#include<iterator>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
#define pi 3.141
#define INF 1400000000
using namespace std;
class Person {
public:
int Type;
public:
Person() {
Type = 0;
}
void speak() {
printf("Speak\n");
}
};
class Chinese : public Person {
public:
Chinese() {
Type = 1;
}
void speak() {
printf("Speak Chinese\n");
}
};
class English : public Person {
public:
English() {
Type = 2;
}
void speak() {
printf("Speak English\n");
}
};
int main() {
Chinese Chinese_Person_1;
Chinese Chinese_Person_2;
English English_Person_1;
Person* people[3];
people[0] = &Chinese_Person_1;
people[1] = &Chinese_Person_2;
people[2] = &English_Person_1;
for (R int i = 0; i < 3; ++i) {
printf("Person%d:", i + 1);
people[i]->speak();
}
return 0;
}
虚函数
虚函数是指在父类中定义的使用关键字virtual声明的函数,在子类中需要重新定义父类中定义的虚函数的时候程序会告诉编译器不要静态链接到这个函数。
有些时候,我们编写程序,需要的是在程序中的任意点可以根据所调用的对象的类型来选择我们需要调用的函数,这种操作被称为动态链接或者后期绑定。
纯虚函数
如果我们在进行程序设计的时候,可能在父类中无法或者不需要对虚函数给出一个有意义的实现,那么此时就需要用到纯虚函数,我们可以将父类中的虚函数改为如下形式:
class Person {
public:
int Type;
public:
Person() {
Type = 0;
}
virtual void speak() = 0;
};
通过上面的方法,我们告诉编译器这个函数没有主体,就是一个纯虚函数。
动态多态的条件
1.父类中必须包含虚函数,并且子类中一定要对父类中的虚函数进行重写。
2.通过基类对象的指针或者引用进行调用虚函数
重写
1.基类中将要被重写的函数必须是虚函数
2.基类的派生类中的虚函数的原型必须保持一致,协变函数和析构函数(父类和子类的析构哈桑农户不同)除外
3.访问限定符不同
协变函数:父类(子类)的虚函数返回父类(子类)的指针或者引用
不能被定义为虚函数的子类
1.友元函数,不是类的成员函数
2.全局函数
3.静态成员函数,没有this指针
4.构造函数,拷贝构造函数,赋值运算符重载(赋值运算符可以作为虚函数但是不建议作为虚函数)
总结一些要点
包含纯虚函数的类被称为抽象类(也成为接口类),抽象类不能实例化出对象,纯虚函数在子类中重新定义之后,子类才能实例化出对象,纯虚函数一定要被继承,否则纯虚函数的存在没有任何意义,纯虚函数一定没有定义,纯虚函数的存在就是用来规范子类的行为。
对于虚函数来说,父类和子类都有各自的版本,由多态方式调用的时候动态绑定。
实现了纯虚函数的子类,这个纯虚函数在子类中就变成了虚函数,子类的子类可以覆盖整个虚函数,由多态的方式调用的时候进行动态绑定。
在有动态分配的堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚函数。
友元函数不等于成员函数,只有成员函数才可以是虚函数,因此友元函数不能是虚函数,但是可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
析构函数应该是虚函数,将调用相应的对象类型的析构函数,因此如果指针指向的是子类的对象,将调用子类的洗后函数,然后再自动调用父类的析构函数。
查看上一篇:C++继承,一篇文章一小时带你理解清C++的继承
查看下一篇:C++重载运算符和重载函数
查看目录:C++教程目录