1、对多态的理解
同一对象可以有多重层级递进身份
在不同的场合中,被外界所关注的是不同的身份,但本质和应有的行为并不会因外界眼光而改变。
比如说我自己 kali-Myon(一个实体)
生物学家会认为我(该实体)是人类 ;
教育局认为我是一名学生;
西南科技大学认为我是一名大学生。
但我目前在做什么呢?上大学对吧,我做着我自己最本质身份(大学生)的行为。
一个对象就是内存中的一个实体,它只能属于一个确定的类:最精确的子类
它可能在不同处被视为不同身份,但它本质行为方式与外界如何看待它是无关的。
为了保证一个对象执行其最本质身份的行为
我们可以利用虚函数重写和指向子类对象的父类指针来实现
2、实现多态的例子
eg1:
#include <iostream> using namespace std; class Human { //定义了一个父类 public: virtual void say() { cout << "I'm human\n"; } }; class Student : public Human { //子类1公有继承父类 public: virtual void say() { cout << "I'm a student\n"; } }; class CollegeStudent : public Student { //子类2公有继承子类1(对于子类2来说,子类1就相当于它的父类) public: void say() { cout << "I'm a college student\n"; } }; int main() { CollegeStudent a; //a是子类2的一个实例化对象 Human* p1 = (Human*)&a; //父类指针,指向子类对象 Student* p2 = (Student*)&a; //父类指针,指向子类对象 CollegeStudent* p3 = &a; p1->say(); //“->”是类成员访问运算符,可以被重载,它被定义用于为一个类赋予"指针"行为,常与指针引用运算符“*”结合使用 p2->say(); p3->say(); }
由运行结果可以看出:通过指针调用的是对象本质子类的方法
(即collegestudent这个类中的say()函数)
eg2:
有很多的人(human),男的(man)应该去男厕所,女的(women)该去女厕所
但是有太多对象man1,man2,women1,man3,women2...
如何用func()函数让他们都能去到正确的厕所
我们只需定义一个上厕所的函数toilet()
男的还是女的都是人,都具有上厕所这个行为
human可能指向两种不同的实际对象
事实上func()并不关心实际是什么,反正都当成Human,能toilet就行
#include <iostream> using namespace std; class Human { public: virtual void toilet() = 0; }; class Man : public Human { public: void toilet() { cout << "我去男厕所"; } }; class Woman : public Human { public: void toilet() { cout << "我去女厕所"; } }; void func(Human * human) { human->toilet(); } int main() { Man man1; Woman woman2; func(&man1); cout << endl; func(&woman2); }
运行结果:
可以看到,man1去了男厕所,woman2去了女厕所
3、多态的意义
实现代码复用
通过“虚函数+指向子类对象的父类指针”,无需针对不同的子类写相同逻辑,统一视为其共同父类,利用指针操作即可,本质是虚函数将能做什么和怎么做分离,父类指定要做什么,子类来实现具体做法。
eg3:
比如我们要设计一个函数来求图形的面积,但是我们并不知道具体是什么图形
如果没有多态,我们就需要对每种图形都实现一个函数
但是有多态,我们只需要实现一个函数
并不关心图形具体是什么,只要能求面积就行(满足父类,是个图形,就能求面积)
#include <iostream> using namespace std; class Shape { //定义了一个类shape public: virtual float getS() = 0; //在父类声明了一个纯虚函数 }; class Circle : public Shape { //圆公有继承父类shape private: float radius; public: Circle(float radius) { this->radius = radius; } float getS() { return 3.14 * radius * radius; } //在子类重写虚函数 }; class Rectangle : public Shape { //矩形公有继承父类shape private: float a; float b; public: Rectangle(float a, float b) { this->a = a; this->b = b; } float getS() { return a * b; } //在子类重写虚函数 }; void display(Shape* ptr) { //此处实现了多态:通过父类指针调用子类重写的虚函数 cout << ptr->getS() << endl; } int main() { Circle c(1.3); Rectangle r(1.5, 2.3); display(&c); display(&r); return 0; }
运行结果:
4、静态联编与动态联编
上述利用虚函数重写+指针实现的多态特指运行时多态,与之相对的是编译时多态
静态联编=编译时多态=函数重载=overload
动态联编=运行时多态=虚函数重写=override
联编(bind):确定具体要调用多个同名函数中的哪一个
静态联编:在编译时就确定了要调用的是哪个函数(根据多个重载函数的参数列表确)
动态联编:直到运行时才知道实际调用的是哪个函数(根据指针指向对象的实际身份)
至此,我们已经介绍完了面向对象的四个特征:封装、派生、继承、多态。