当通过基类(父类)的指针,指向多个子类,达到多态时,父类指针可以使用子类的成员变量和成员函数,做到“一个接口,多种实现”
C++的多态,可以理解为,用一个父类 ( 基类 ) 指针来操控多种子类,倘若这些子类分别用指针来操控,就很容易乱,例如有 100000 个子类,至少需要 100000个指针来操控(仅限于指针操作,实例另外),指针很容易出错,尤其是在析构的时候,要记得释放内存。但多态可以做到一个指针,操控所有子类。
增强理解:一个电脑 usb 接口可以连手机,U盘,移动硬盘等,不需要那么多 usb 接口,一个可以操作了 。
虚函数和析构函数
析构函数不可以是纯虚函数
基类指针,指向子类对象,在程序最后,要根据析构函数,释放内存。析构函数可能调用多个,也可能调用一个。
错误的写法
#include <bits/stdc++.h> using namespace std ; class Gragh { public: // 通过基类指针销毁子类对象 // 如果声明了 virtual, 就会先调用子类, 再调用基类的析构函数 // 也就是先销毁子类独有的内存, 再析构子类中含有的基类的内存部分 // 如果析构函数不声明 virtual, 就只会调用子类的 int parent_data ; ~Gragh () { cout << "ans = 1 " << endl << endl ; } } ; class Point : public Gragh { public: int child_data ; ~Point () { cout << "ans = 2" << endl << endl ; } } ; int main () { cout << "用父类指针指向子类内存, 析构不声明 virtual" << endl << endl ; Point *One = new Point ; Gragh *ptr = One ; // ptr = new Point 是一样的, 用父类指针指向子类内存 delete ptr ; // 只会调用父类析构函数 ptr = NULL ; cout << "用子类指针指向子类内存, 析构不声明 virtual" << endl << endl ; Point *Two = new Point ; delete Two ; // 因为声明本来就是 Point 指针, 析构调用 Point 的析构函数 Two = NULL ; return 0 ; }
程序运行结果
这里错在用基类指针销毁子类内存时,不声明虚析构函数 virtual ,调用的是基类的析构函数,而不是子类的析构函数,子类独自分配的那一块内存空间没被释放掉,内存容易泄漏。
正确的写法
#include <bits/stdc++.h> using namespace std ; // 虚表是每个含有虚函数的类中维护的一张保存着各个虚函数地址的表 // 每个含有虚函数的类中都保存着一个指向虚表的指针, // 而虚表中保存了该类各个虚函数的地址。 // 程序会找到子类的虚函数表,然后调用子类的函数 // 用父类指针实现多态时, 如果在类中没有static变量、enum常量等 // 且没有内存分配的情况下,析构函数可以不使用 virtual class Gragh { public: // 通过基类指针销毁子类对象, 如果析构函数不声明 virtual, 就只会调用子类的 // 如果声明了 virtual, 就会先调用子类, 再调用基类的析构函数 // 也就是先销毁子类独有的内存, 再析构子类中含有的基类的内存部分 int parent_data ; virtual ~Gragh () { cout << "基类析构被调用" << endl << endl ; } } ; class Point : public Gragh { public: int child_data ; ~Point () { cout << "子类析构被调用" << endl << endl ; } } ; int main () { cout << "用父类指针指向子类内存, 析构声明 virtual" << endl << endl ; Point *One = new Point ; Gragh *ptr = One ; // ptr = new Point 是一样的, 用父类指针指向子类内存 delete ptr ; ptr = NULL ; cout << "用子类指针指向子类内存, 析构声明 virtual" << endl << endl ; Point *Two = new Point ; delete Two ; Two = NULL ; return 0 ; } // 总结: // 1. Gragh *ptr = new Point // ptr 的静态类型是 Gragh, 动态类型是 Point // 当父类析构函数是虚函数, 是动态绑定, 运行时会根据动态类型 // 调用子类的析构函数, 由于继承关系, 也会调用父类的析构函数 // 当父类析构函数不是虚函数, 是静态绑定, 会调用静态类型, // 只会调用父类自己的析构函数 // 以上说的是父类指针指向子类, 实现多态的情况 // 但是, 如果 Point *ptr = new Point, 本来就是 Point, 先调用 // Point 的析构函数, 再调用 Gragh 的析构函数