多态的实质:父类的引用或指针指向子类对象
怎么理解?
假设我们现在有如下的类,其中Animal为基类,Cat和Dog都是其派生类。
class Animal{
public:
virtual void speak(){
cout << "动物在说话" << endl;
}
};
class Cat :public Animal{
public:
void speak(){
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal{
public:
void speak(){
cout << "小狗在说话" << endl;
}
};
1.父类指针指向子类对象
调用时,可以定义父类的指针,然后指向子类的对象,指向哪个对象,便执行其对象的虚函数实现
int main() {
方法1:
Dog B;
Animal *A=&B;
A->speak();
如果你不想给Dog类取名:
Animal *A=new Dog;
A->speak();
方法2:
A=new Cat;//如果小猫有名字A=new Cat("名字");
A->speak();
return 0;
}
2.父类引用指向子类对象
这种实现,我们需要额外定义一个“实现函数”,其参数是基类的引用(否则不能实现多态)。这样在调用时就实现了父类引用指向子类对象。
void DoSpeak(Animal & X){ //
X.speak();
}
int main() {
Cat A;
DoSpeak(A);
Dog B;
DoSpeak(B);
Animal C;
DoSpeak(C);
return 0;
}
.
.
.
一.静态联编与动态联编
多态分为:
-
静态联编:如函数重载
- 地址早绑定,编译阶段就确定好了地址(在写完这条语句就被绑定了)
-
动态联编:如虚函数,继承关系
-地址晚绑定,运行的时候才绑定好地址
#define _CRT_SECURE_NO_WARNINGS #include#include using namespace std; class Animal { public: virtual void speak()//加了virtual关键字,变成了虚函数 { cout << "动物在说话" << endl; } private: }; class Cat:public Animal { public: void speak()//子类的virtual可写可不写 { cout << "小猫在说话" << endl; } private: }; //当调用DoSpeak时,speak函数的地址早就绑定好了(静态联编),也就是绑定在class Animal中 //如果我们想要调用猫的DoSpeak函数,就不能提前绑定好speak函数的地址,而是在运行的时候再去确定(改成动态联编) //动态联编的写法:把DoSpeak中的函数改成虚函数(+virtual关键字) void DoSpeak(Animal& animal) 多态的实质:父类的引用或指针指向子类对象 { animal.speak(); } //如果发生了继承的关系,编译器允许进行类型转换 void test01() { Cat cat; DoSpeak(cat);//此行不会报错!因为Cat和Animal发生了继承的关系 } int main() { test01(); system("pause"); return 0; }
.
.
.
二.多态原理解析
-
当父类中有了虚函数(virtual)后,内部结构发生了改变
-
内部多了一个virptf指针
- virtual function pointer 虚函数表指针
- 指向vftable虚函数表
-
父类中的结构有:virptf虚函数表指针 vtfable虚函数表
-
子类中进行继承,会继承父类的virptf虚函数表指针 vtfable虚函数表
-
在子类的构造函数中,会自动将自己继承下来的virptf虚函数表指针指向自己的vtfable虚函数表
-
如果子函数中发生了重写,会替换掉vtfable虚函数表中原有的speak,变成Cat::speak
- 重写:子类写speak父类的虚函数,重写必须返回值,参数个数,类型,顺序都相同
.
.
.
三.抽象基类和纯虚函数
如果父类中有一些函数,它不需要任何的实现,只想让子类通过多态做新的实现(如计算机案例),此时可以用抽象类,也就是在类中加入至少一个纯虚函数,使得基类被称为抽象类
- 纯虚函数使用关键字virtual,并在其后面加上=0,如果试图去实例化一个抽象类,编译器会制止(报错)
- 当Calculator是一个抽象基类时Calculator cal ;和Calculator * cal = new Add_Calculator都会报错!(也就是Calculator里面含有纯虚函数)
- 当继承一个抽象类时,必须实现所有的纯虚函数(也就是通过多态进行改写),否则由抽象类派生的类也是一个抽象类
.
.
.
四.虚析构和纯虚析构
虚析构:
- 调用形式:virtual~类名(){}
- 解决问题:通过父类指针指向子类对象释放时释放不干净的问题(普通的析构是不会调用子类的析构的,也就是子类的析构函数无法被调用,所以可能会释放不干净)
纯虚析构函数:
- 调用形式:virtual~类名()=0 类内声明,类外实现类名::~类名(){}
- 如果出现了纯虚构函数,这个类也算是抽象类,不能进行实例化
以给小动物取名为例
#define _CRT_SECURE_NO_WARNINGS #include#include using namespace std; class Animal { public: virtual void speak() { cout << "动物在说话" << endl; } ~Animal() { cout << "Animal的析构函数调用" << endl; } private: }; class Cat :public Animal { public: //给小猫起个名字(拷贝构造函数) Cat(const char* name) { //由于是在堆上开辟的空间,所以要考虑深浅拷贝的问题 this->m_name = new char[strlen(name) + 1]; strcpy(this->m_name, name); } void speak() { cout << this->m_name << "在说话" << endl; } virtual ~Cat() { cout << "Cat的析构函数调用" << endl; } private: char* m_name; }; void test01() { //多态的实质:父类的引用或指针指向子类对象 Animal* animal = new Cat("TOM"); animal->speak(); delete animal; } int main() { test01(); system("pause"); return 0; }
如果不调用虚析构函数,那么Cat类的析构不会被调用
调用了虚析构函数,才会把子类的数据给清理掉!
纯析构函数的写法
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
virtual~Animal() = 0;
private:
};
Animal::~Animal()
{
//纯虚析构函数实现
cout << "纯虚析构函数调用" << endl;
}
文章转自布尔博客
欢迎关注技术公众号,获取更多硬件学习干货!
我们能为你提供什么?
技术辅导:C++、Java、嵌入式软件/硬件
项目辅导:软件/硬件项目、大厂实训项目
就业辅导:就业全流程辅导、技术创业支持
对接企业HR:培养输送优质性人才



