1.多态是什么
1.多态的概念:通俗来讲,就是多种形态。具体来讲,就是在做同一种事情的时候,不同的对象去做会产生不同的状态。
2.举个例子:就如同旅游去景点买票,如果是成人,那就要全票购买,如果是儿童或者学生,那就有优惠。
2.多态的定义
1.多态的构成条件:
多态是在不同继承关系的类对象,去调用同一函数,产生不同的行为。
那么,构成如此情况需要两个条件:
①:必须通过基类指针或者引用调用该函数。
②:必须调用的是虚函数,且派生类必须对基类的该函数进行重写。
如下图:
使用不同的对象去调用,得到了不同的结果。
2.虚函数:
如上图,被virtual修饰的函数,就是虚函数,同样也是构成多态的不可缺少的功臣,有了它的修饰,才能构成多态。
3.虚函数的重写:
①:虚函数的重写(覆盖,在多态的原理下面会将覆盖),意思是在派生类中要有一个和基类一模一样的虚函数(返回值,参数,函数名都一样),称派生类重写了基类的虚函数。
②:还有一点要注意的是,在派生类重写基类虚函数时,虚函数前可加可不加virtual,不管加不加,此时这个函数都是虚函数。
3.多态的原理
1.虚函数表
①:大家一定很疑惑,多态到底是怎么实现的,为什么虚函数的重写就可以成为构成多态,其实在内部,给虚函数制定了一个特殊的位置,那就是虚函数表。如图:
从图中我们可以看到,在类对象p中除了我们所定义的私有成员外,发现一个__vfptr的成员,其实这就是虚函数表,里面只放了虚函数表的地址,为什么这样说呢,如下图:
通过查看类对象的所占用的字节大小,可以发现,在32位操作系统下,私有成员占有8个字节,那么多出的4个字节应该就是__vfptr所占有的大小。
②:经过修改代码,如下:
class Person
{
public:
virtual void func()
{
cout << "Person::func()" << endl;
}
virtual void func1()
{
cout << "Person::func1()" << endl;
}
void func2()
{
cout << "Person::func2()" << endl;
}
private:
char a = 'A';
int b = 1;
};
class Student : public Person
{
public:
virtual void func()
{
cout << "Student::func()" << endl;
}
private:
int c = 2;
};
int main()
{
Person p;
Student s;
}
运行后,结果如图:
由图可以看见:
1.派生类有两部分成员,一部分是继承下来的,虚拟表就在这部分中,另一部分就是自己的成员。
2.派生类和基类的虚拟表有不一样的地方,首先,地址不一样,其次,func函数发生了重写,所以s中的存在的就是重写后的,虚函数重写就是覆盖,覆盖是指虚拟表中虚拟函数的覆盖,重写是语法的叫法,覆盖是原理层的叫法。
3.只有虚函数才能放进需表里,func2就没放入虚表里面。
4.虚函数表的本质就是一个存放虚函数的数组,最后一个虚函数后存的是nullptr。
5.派生类虚函数表生成顺序:
首先,将基类的虚函数表拷贝到自己的虚函数表中;其次,将派生类重写的基类的函数覆盖基类的该函数;最后,将自己新加的虚函数按照声明顺序排在已有的虚函数表后面。
2. 多态的原理
1.如下代码:
class Person
{
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
return 0;
}
如图,运行可以得下图:
如图,蓝线和红线分别代表不同的路线,分别在基类和派生类的虚函数表中找到所对应的虚函数,这样就产生了不同对象调用相同函数产生了不同的形态。
2.出现这样的情况,是因为满足多态以后的函数调用,不是在编译时确定的,而是在运行时到对象去找的。而不满足多态的函数调用,是在编译时就已经确定好的。
3.静态绑定与动态绑定
①:静态绑定:是在程序编译的时候就已经确定了程序的行为,所以也叫前期绑定,也称静态多态。(重载就是一种静态绑定)
②:动态绑定:是在程序运行期间,根据拿到的具体类型确定程序的行为,所以也叫后期绑定,也称动态多态。(就如重写)