B站视频学习C++ 多态 135-

it2023-03-07  79

(135) 多态

多态分为两类

静态多态:函数重载 和 运算符重载 ,复用函数名动态多态:子类和 虚函数 实现运行时多态

静态多态 和 动态多态 区别:

静态多态的地址早绑定-- 编译阶段确定函数地址动态多态的地址晚绑定-- 运行阶段确定函数地址 #include <iostream> using namespace std; class Animal { public: void speak() { cout << "动物在说话" << endl; } }; class Cat :public Animal { public: void speak() { cout << "小猫在说话" << endl; } }; // 执行说话的函数 // void doSpeak(Animal &animal) // Animal &animal = cat; { animal.speak(); } void test01() { Cat cat; doSpeak(cat); // 走动物在说话还是猫在说话? } int main() { test01(); return 0; }

问题: 结果是动物在说话还是猫在说话?

结果是动物在说话。????

2、如果想执行猫在说话,怎么做? 答:animal 类中的 speak函数 前加 virtual

#include <iostream> using namespace std; class Animal { public: virtual void speak() { cout << "动物在说话" << endl; } }; class Cat :public Animal { public: void speak() { cout << "小猫在说话" << endl; } }; // 执行说话的函数 // void doSpeak(Animal &animal) // Animal &animal = cat; { animal.speak(); } void test01() { Cat cat; doSpeak(cat); // 走动物在说话还是猫在说话? } int main() { test01(); return 0; }

运行结果: 小猫在说话

3、加了狗类

class Dog :public Animal { public: void speak() { cout << "小狗在说话" << endl; } }; // 执行说话的函数 // 地址早绑定,在编译阶段确定函数地址 // 如果想执行猫在说话,那么这个函数地址不能提前绑定,需要在运行阶段进行绑定,地址晚绑定 void doSpeak(Animal &animal) // Animal &animal = cat; { animal.speak(); } void test01() { Cat cat; doSpeak(cat); // 走动物在说话还是猫在说话? Dog dog; doSpeak(dog); }

运行结果: 小猫在说话 小狗在说话

动态多态满足条件:

有继承关系子类重写父类虚函数 (本题speak函数) 重写:函数返回值类型 函数名 参数列表完全相同

动态多态使用

父类的 指针或引用 指向 子类对象,本题就是

多态的目的: 封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了“接口重用”。也即,不论传递过来的究竟是类的哪个对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

多态:不同对象接收相同消息时产生不同的动作 虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。

(136) 多态原理分析

#include <iostream> using namespace std; class Animal { public: void speak() { cout << "动物在说话" << endl; } }; class Cat :public Animal { public: void speak() { cout << "小猫在说话" << endl; } }; void doSpeak(Animal &animal) // Animal &animal = cat; { animal.speak(); } void test02() { cout << "sizeof Animal = " << sizeof(Animal) << endl; } int main() { test02(); return 0; }

运行结果: sizeof Animal = 1 为什么?

加了关键字 virtual后,即

class Animal { public: virtual void speak() { cout << "动物在说话" << endl; } };

运行结果: sizeof Animal = 4 为什么?

这4个字节是指针 当子类重写父类的虚函数后,子类中的虚函数表 内部 会替换成 子类的虚函数地址 在父类 Animal 的内部结构中,虚函数指针(vfptr)指向 虚函数表(vftable),在虚函数表中存储 父类虚函数的地址(&Animal::speak())

当发生继承时,子类(Cat)虚函数表中存储 父类虚函数的地址(&Animal::speak())

当发生多态时,子类的虚函数表中的 父类虚函数的地址 将被替换成 子类自己的虚函数地址(&Cat::speak())

注: 当父类的指针 或 引用 指向 子类的对象时,就发生了多态

替换后 子类替换自己的 虚函数表,即 &Animal::speak 替换成 &Cat:: speak ,不会替换父类的 虚函数表

`

当父类指针或引用 指向 子类对象时,发生多态`。即 Animal & animal = cat; animal.speak();

当子类重写虚函数时,子类会将 自身虚函数表中 父类的虚函数地址 替换成 子类的虚函数地址

调用cat后,到子类的虚函数表,确定函数入口,

(137) 多态案例-- 计算器类

分别利用 普通写法和多态 技术,实现两个操作数进行运算的 计算器类 多态的优点:

代码组织结构清晰可读性强利于前期和后期扩展以及维护

普通写法:

#include <iostream> #include<string> using namespace std; // 普通写法 class Calculaator { public: int getResult(string oper) { if (oper == "+") { return m_num1 + m_num2; } else if (oper=="-") { return m_num1 - m_num2; } else if (oper == "*") { return m_num1 * m_num2; } } int m_num1; int m_num2; }; void test01() { Calculaator c; c.m_num1 = 10; c.m_num2 = 10; cout << c.m_num1 << " + " << c.m_num2 << " = " << c.getResult("+") << endl; cout << c.m_num1 << " - " << c.m_num2 << " = " << c.getResult("-") << endl; } int main() { test01(); return 0; }

运行结果: 10 + 10 = 20 10 - 10 = 0 10 * 10 = 100

存在的问题: 如果想扩展新的功能(比如增加除法),需要修改源码(本题改getResult函数) 在真实开发中,提倡 开闭原则,即 对扩展进行开放,对修改进行关闭

用多态改写:

#include <iostream> #include<string> using namespace std; // 多态实现 class AbstractCalculator // 类名后没有括号 { public: virtual int getResult() { return 0; } int m_Num1; int m_Num2; }; // 加法计算机类 class AddCalculator :public AbstractCalculator { public: int getResult() { return m_Num1 + m_Num2; } }; // 减法计算器类 class SubCalculator :public AbstractCalculator { public: int getResult() { return m_Num1 - m_Num2; } }; void test02() { AbstractCalculator* abc = new AddCalculator; abc->m_Num1 = 10; abc->m_Num2 = 10; cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl; // 用完后记得销毁 delete abc; abc = new SubCalculator; abc->m_Num1 = 10; abc->m_Num2 = 10; cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl; delete abc; } int main() { test02(); return 0; }

运行结果: 10 + 10 = 20 10 - 10 = 0

多态实现的好处: 1、组织结构清晰;都是一个个 独立出的小块,便于改写; 2、对于前期和后期 扩展和维护性高

138 纯虚函数 和 抽象类

在多态中,通常父类中 虚函数的实现是毫无意义的,主要是 调用子类重写的内容 因此可以将虚函数 改为纯虚函数。

纯虚函数语法: virtual 返回值类型 函数名 (参数列表)=0; 当类中有纯虚函数,这个类称为 抽象类。

抽象类特点:

无法实例化对象子类必须重写抽象类中的虚函数,否则属于抽象类 // 138 纯虚函数 和 抽象类 #include <iostream> using namespace std; class Base { public: // 纯虚函数 // 抽象类特点 // 1、无法实例化对象 virtual void func() = 0; }; int main() { Base b; // 在栈上 报错 new Base; // 在堆上 报错 return 0; }

2、子类不重写父类的纯虚函数,也是抽象类,无法实例化

class Base { public: // 纯虚函数 // 抽象类特点 // 1、无法实例化对象 // 2、抽象类的子类 必须重写父类的纯虚函数,否则也属于 抽象类 virtual void func() = 0; }; class Son :public Base { public: }; int main() { Son a; return 0; }

3、 抽象类的子类 必须重写父类的纯虚函数,否则也属于 抽象类

#include <iostream> using namespace std; class Base { public: // 纯虚函数 // 抽象类特点 // 1、无法实例化对象 // 2、抽象类的子类 必须重写父类的纯虚函数,否则也属于 抽象类 virtual void func() = 0; }; class Son :public Base { public: virtual void func() { cout << "func函数调用" << endl; } }; void test01() { Base* base = new Son; base->func(); } int main() { test01(); return 0; }

运行结果: func函数调用

最新回复(0)