目录
1、对多态的理解
2、实现多态的例子
3、多态的意义
4、静态联编与动态联编
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):确定具体要调用多个同名函数中的哪一个
静态联编:在编译时就确定了要调用的是哪个函数(根据多个重载函数的参数列表确)
动态联编:直到运行时才知道实际调用的是哪个函数(根据指针指向对象的实际身份)
至此,我们已经介绍完了面向对象的四个特征:封装、派生、继承、多态。