多态
导航:
1.多态基本概念
2.多态小案例
3.纯虚函数
4.虚析构与纯虚析构
———————————————————————————————————
多态的基本概念
多态分为两类:
1.静态多态:函数重载以及运算符重载都属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行多态
如何区分:
静态多态的函数地址早绑定——编译阶段确定函数地址
动态多态的函数地址晚绑定——运行阶段确定函数地址
动态多态的满足条件:
1.具有继承关系。
2.子类重写父类的虚函数。(虚函数就是在成员函数前加上关键字virtual)
其中重写条件:类中函数的返回值类型,函数名,参数列表必须完全相同
动态多态的使用方法:父类的指针或者引用指向子类地址。
例子:
#include <iostream> #include <string> using namespace std; class cost //定义一个父类 { public: void shop() //仅仅只是一个成员函数,函数地址早绑定,也就是早确定函数地址 { cout<<"最高消费0元"<<endl; } }; class shoper1:public cost //定义一个子类 { public: void shop() { cout<<"最高消费1000元"<<endl; } }; class shoper2:public cost { public: void shop() { cout<<"最高消费1500元"<<endl; } }; void doshop(cost &cs) //父类的指针或者引用可以指向子类对象 { cs .shop(); //进行成员函数调用 } void test01() { shoper1 s1; doshop(s1); //进行引用传递 shoper2 s2; doshop(s2); } int main() { test01(); system("pause"); return 0; }
运行程序:
最高消费0元 最高消费0元
如何将其重写,然后输出该有结果
这时候就要在父类中的函数前加上virtual,此时便为虚函数,是动态多类,函数地址晚绑定,就可以就行重写。
例如:
public: virtual void shop() //加上virtual则叫做虚函数,这样函数地址是晚绑定,不加则就是早绑定,确定了函数地址 { cout<<"最高消费0元"<<endl; } };
这个时候再运行程序:
最高消费1000元 最高消费1500元
———————————————————————————————————
类中仅有一个函数,那么就是一个空类,占1个字节
若类中有一个虚函数,那么占4个字节
虚函数-vfptr (虚函数表指针)指向vftable-(虚函数表)
当子类重写父类的虚函数
子类的虚函数表内部会替换成子类虚函数地址
利用普通和多态写的案例:
#include <iostream> #include <string> using namespace std; //普通写法 class Calculator { public: int getresult(string oper) { if(oper == "+") { return num1+num2; } if(oper == "-") { return num1-num2; } if(oper == "*") { return num1*num2; } return 0; } int num1; int num2; }; //普通的进行测试 void test() { Calculator c1; c1.num1 = 100; c1.num2 = 100; cout<<c1.num1<<"+"<<c1.num2<<"="<<c1.getresult("+")<<endl; cout<<c1.num1<<"-"<<c1.num2<<"="<<c1.getresult("-")<<endl; cout<<c1.num1<<"*"<<c1.num2<<"="<<c1.getresult("*")<<endl; } //利用多态进行写 class AbstractCalculator { public: virtual int getresult() { return 0; } int num1; int num2; }; //加法 class AddCalculator:public AbstractCalculator { public: int getresult() { return num1 + num2; } }; //减法 class SubCalculator:public AbstractCalculator { public: int getresult() { return num1 - num2; } }; //乘法 class MulCalculator:public AbstractCalculator { public: int getresult() { return num1 * num2; } }; void test02() { //调用加法 AbstractCalculator *abs = new AddCalculator;//父类指针或者引用指向子类对象 abs->num1 = 100; abs->num2 = 100; cout<<abs->num1<<"+"<<abs->num2<<"="<<abs->getresult()<<endl; delete abs; //手动进行释放 //调用减法 abs = new SubCalculator; abs->num1 = 100; abs->num2 = 100; cout<<abs->num1<<"-"<<abs->num2<<"="<<abs->getresult()<<endl; delete abs; //调用乘法 abs = new MulCalculator; abs->num1 = 100; abs->num2 = 100; cout<<abs->num1<<"-"<<abs->num2<<"="<<abs->getresult()<<endl; delete abs; } int main() { test02(); system("pause"); return 0; }
用多态写的好处是:有利于扩展,结构清晰,可读性强
———————————————————————————————————
纯虚函数
语法:virtual 返回值类型 函数名 (参数列表) = 0
若父类中含有一个纯虚函数,那么称为抽象类,无法进行实例化
同时子类中也无法实例化对象,也被称为抽象类
例子:
#include <iostream> #include <string> using namespace std; class Base { public: //纯虚函数 virtual void func() = 0; }; class son:public Base { public: virtual void func() { cout<<"子类中func的调用"<<endl; } }; void test() { //Base b1; //其中有纯虚函数,无法实例化 //new Base; //也无法实例化 Base *b = new son; b->func(); delete b; //手动释放 } int main() { test(); system("pause"); return 0; }
———————————————————————————————————虚析构与纯虚析构
共同点:
1.可以解决父类指针释放子类对象
2.都需要有具体的函数实现
区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构:virtual ~类名() {}
纯虚析构: virtual ~类名() = 0 (需要声明,也要有代码的实现)
能用到纯析构,是因为要在堆区开辟新数据,父类指针释放子类对象
例子:
#include <iostream> #include <string> using namespace std; //纯析构和纯虚函数 class Animal { public: Animal() { cout<<"Aniaml构造函数调用"<<endl; } //纯虚函数 virtual void speak() = 0; //利用虚析构可以解决 父类指针释放子类对象时不干净问题 //纯虚析构 必须要有代码实现 需要声明也需要实现 //有纯虚析构 也无法进行实例化对象 virtual ~Animal() = 0; //虚析构 /*virtual ~Animal() { cout<<"Aniaml析构函数调用"<<endl; }*/ }; //类外进行代码实现 Animal::~Animal() { cout<<"Aniaml纯析构函数调用"<<endl; } class Cat:public Animal { public: Cat(string name) { cout<<"Cat构造函数调用"<<endl; m_Name = new string(name); } virtual void speak() { cout<<*m_Name<<"小猫在说话"<<endl; } ~Cat() { if(m_Name != NULL) { cout<<"Cat虚构函数调用"<<endl; delete m_Name; //释放内存 m_Name = NULL; } } string *m_Name; //指针 }; void test01() { Animal *animal = new Cat("Tom"); //父类指针在析构时不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏存在 animal->speak(); delete animal; } int main() { test01(); system("pause"); return 0; }
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放类对象
2.如果子类中没有堆区数据,可以不写为虚析构式或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类,无法进行实例化