多态
多态的基本概念
多态分为两类
🥇静态多态:函数重载和运算符重载 (复用函数名)
🥈动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别
-
静态多态的函数地址早绑定 -----编译阶段确定函数地址
-
也称作 静态绑定或前期绑定(早绑定):函数重载和函数模板实例化出多个函数(本质也是函数重载)。静态多态也称为编译期间的多态,编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
-
-
动态多态的函数地址晚绑定 -----运行阶段确定函数地址
-
也称为动态绑定或后期绑定(晚绑定):在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,即运行时的多态。在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
-
父类指针或引用指向父类,调用的就是父类的虚函数
-
父类指针或引用指向子类,调用的就是子类的虚函数
-
在c++中允许 允许父子之间类型转换 不用强制转换 可以直接有父类的引用或者指针 直接指向 子类对象
Animal &animal = Cat cat
虚函数
虚函数:即被virtual修饰的类成员函数称为虚函数。
作用: 虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
virtual void speak() { cout << "动物在说话" << endl; }
例子
class Animal { public: //函数前面加上virtual 关键字 变成 虚函数 //编译器在编译阶段 就不能够确定函数调用 即函数地址晚绑定 在运行阶段确定函数地址 virtual void speak() { cout << "动物在说话" << endl; } void sit() { cout << "动物坐下" << endl; } }; class Cat :public Animal { public: //继承下来的函数 默认也就是虚函数 virtual void speak()//虚函数 { cout << "小猫在说话" << endl; } /*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因 为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议 这样使用*/ void sit() { cout << "小猫坐下" << endl; } }; //如果不用virtual 的话 结果是 Animal的输出 属于静态绑定 在编译阶段 确定函数地址 void DoSpeak(Animal& animal)//Animal &animal = cat c++中 允许父子之间类型转换 不用强制转换 可以直接有父类的引用或者指针 直接指向 子类对象 { animal.speak();地址早绑定在编译阶段就确定了函数的地址 //构成多态,传的哪个类型的对象,调用的就是这个类型的虚函数 --- 跟对象有关 地址早绑定在编译阶段就确定了函数的地址 animal.sit();// 编译时确定函数地址 } void test() { Cat cat; DoSpeak(cat); } int main() { test(); return 0; }
构成多态的条件
-
有继承关系
-
子类 重写 父类中的 虚函数
重写vs重载
重写 返回值 函数名 形参列表都相同 内部代码不同
重载 函数名 相同 形参列表不同(个数,顺序)
多态的使用条件
父类指针或引用 指向子类对象
多态的原理解析
class Animal { public: virtual void speak() { cout << "动物在说话" << endl; } };
在不加virtual之前 sizeof Animal 占用 1个字节
在加上virtual之后 sizeof Animal 占用 4个字节 -----指针
vfptr 虚函数指针
vfptr指向vftable 虚函数表
vftable 中记录 虚函数的地址
当Cat类中没有重写虚函数时
Cat类只是将父类中的继承下来
当Cat中重写了父类的虚函数时
子类中的虚函数表内部 会替换为 子类的虚函数地址
多态案例1---计算机类
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class Calculator { public: Calculator() { num1 = 10; num2 = 20; } int getResult(string oper) { if (oper == "+") return num1 + num2; else if (oper == "-") return num1 - num2; else if (oper == "*") return num1 * num2; } public: int num1, num2; }; class Calutor { public: Calutor() { num1 = 20; num2 = 40; } virtual int getResult() { return 0; } int num1, num2; }; class son1 :public Calutor { public: virtual int getResult() { return num1 + num2; } }; void test1() { Calculator c; cout<<c.getResult("+"); cout << endl; cout << c.getResult("-"); cout << endl; cout << c.getResult("*"); cout << endl; Calutor *a = new son1; cout << a->getResult() << endl; } int main() { test1(); return 0; }
纯虚函数和抽象类
在多态中 父类中的虚函数的实现毫无意义,主要都是通过调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法
virtual 返回值类型 函数名 (形参列表) = 0
当一个类中有纯虚函数,则该类被称为 抽象类
抽象类特点
-
无法实例化对象
-
子类必须重写抽象类中的纯虚函数,否则也为抽象类(即无法实例化对象)
class base { //纯虚函数 //类中只要有一个纯虚函数 该类 就称为抽象类 //抽象类无法实例化对象 //子类必须重写父类中的纯虚函数 否则也为抽象类 public: virtual void func() = 0; }; class son :public base { public: virtual void func() { cout << "子类中的func()" << endl; } }; void dowork(base& b) { b.func(); } void test() { base* a = NULL; //a = new base; 抽象类 无法实例化对象 a = new son; a->func(); delete a; son s; dowork(s); } int main() { test(); return 0; }
多态案例2 -----制作饮品
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class ProcessDrinking { public: virtual void Boil() = 0; virtual void Brew() = 0; virtual void PourIncup() = 0; virtual void PutSomething() = 0; void making() { Boil(); Brew(); PourIncup(); PutSomething(); } }; class coffee :public ProcessDrinking { public: virtual void Boil() { cout << "加入农夫山泉" << endl; } virtual void Brew() { cout << "冲泡咖啡" << endl; } virtual void PourIncup() { cout << "将咖啡倒入杯中" << endl; } virtual void PutSomething() { cout << "加入牛奶" << endl; } }; void dowork(ProcessDrinking* d) { d->making(); delete d; } void test() { ProcessDrinking* make = new coffee; make->making(); delete make; } int main() { test(); dowork(new coffee); return 0; }
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决办法:将父类中的析构函数改为虚析构或者纯虚析构
如果子类中没有堆区数据,可以不写为纯虚析构或者虚析构
虚析构语法
virtual ~类名(){}
纯虚析构语法
virtual ~类名() = 0
类名 :: ~类名(){}
注意:
1.纯虚函数可以有实现,但必须在类的定义之外(cpp文件)实现。 2、纯虚析构函数必须有实现。因为父类中也可能有数据开辟到堆区
虚析构vs纯虚析构
-
可以解决父类指针释放子类对象问题
-
都需要具体的实现
-
如果是纯虚析构函数,则该类属于抽象类,无法实例化对象
class base { public: base() { cout << "base类的构造函数被调用" << endl; } virtual void func() = 0; virtual ~base() { cout << "base类的析构函数调用" << endl; } //virtual ~base() = 0; }; //base::~base() //{ // cout << "base类的析构函数调用" << endl; //} 纯虚函数也可以有实现 void base::func() { cout << "base的func函数被调用" << endl; } class son :public base { public: son(string name) { cout << "son的构造函数调用" << endl; m_name = new string(name); } virtual void func() { base::func();//静态调用 cout << *m_name << endl; } ~son() { //如果父类析构函数 不加 virtual 则父类指针 不会调用 子类的析构函数 cout << "son的析构函数在调用" << endl; if (m_name != NULL) { delete m_name; m_name = NULL; } } string *m_name; }; void test() { base* s = new son("tom"); s->func(); delete s; } int main() { test(); }
多态案例3---组装计算机
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class CPU { public: virtual void calculate() = 0; }; class VideoCard { public: virtual void display() = 0; }; class Memory { public: virtual void storage() = 0; }; class Computer { public: Computer(CPU* cpu, Memory* men, VideoCard* vc) { this->cpu = cpu; this->men = men; this->vc = vc; } void work() { cpu->calculate(); men->storage(); vc->display(); } ~Computer() { if (cpu != NULL) { delete cpu; cpu = NULL; } if (men != NULL) { delete men; men = NULL; } if (vc != NULL) { delete vc; vc = NULL; } } private: CPU* cpu; Memory *men; VideoCard *vc; }; class InterCpu :public CPU { public: virtual void calculate() { cout << "使用了inter的cpu" << endl; } }; class InterMen :public Memory { public: virtual void storage() { cout << "使用了inter的内存条" << endl; } }; class InterVc :public VideoCard { public: virtual void display() { cout << "使用了inter的显卡" << endl; } }; class LenovoCpu :public CPU { public: virtual void calculate() { cout << "使用了Lenovo的cpu" << endl; } }; class LenovoMen :public Memory { public: virtual void storage() { cout << "使用了Lenovo的内存条" << endl; } }; class LenovoVc :public VideoCard { public: virtual void display() { cout << "使用了Lenovo的显卡" << endl; } }; void test() { CPU* intercup = new InterCpu; VideoCard* intervc = new InterVc; Memory* intermen = new InterMen; Computer* c = new Computer(intercup, intermen, intervc); c->work(); delete c; Computer cf = Computer(new InterCpu, new LenovoMen, new InterVc); cf.work(); } int main() { test(); return 0; }