什么是多态?
同一个方法在派生类和基类中的不同行为称为多中形态,简称多态。换句话来说,就是方法的行为应取决于调用该方法的对象。
如何实现多态?
1. 多态构成的条件:
- 必须在继承体系中;
- 调用函数的对象必须是基类的指针或者基类的引用;
- 被调函数必须是虚函数,并且派生类对完成了虚函数的重写;
下面为大家解释一下什么是虚函数,以及虚函数的重写;
简单来说,就是被virtual关键字修饰的类的成员函数;
在虚函数声明的后边加上“==0”,表示该虚函数为纯虚函数,派生类继承之后必须重写;
class Base
{
public:
//虚函数
virtual void Test(){};
};
派生类在继承基类虚函数的基础上,对虚函数做了一定修改而形成的虚函数的过程被称为重写。重写也被称为覆盖;
重写的虚函数和被重写的虚函数必须满足:函数名,参数,返回值都相同(有个例外:协变)。
//虚函数的重写
class Base
{
public:
virtual void Test()
{
cout<<"Base::Test()"<<endl;
};
};
class Derived:public Base
{
public:
virtual void Test()
{
cout<<"Derived::Test()"<<endl;
};
};
在派生类中重写的虚函数前可以不用加virtual关键字,这样也是构成重写的。
- 虚函数重写的例外:协变
协变和普通的虚函数的重写的区别就是:
协变重写的虚函数的返回值可以不同,但是必须分别是基类的指针和派生类指针(或者基类的引用和拍派生类引用)。
class A{};
class B:public A{};
class Base
{
public:
virtual A* func()
{
cout<<"Base::func()"<<endl;
};
};
class Derived:public Base
{
public:
virtual B* func()
{
cout<<"Derived::func()"<<endl;
}
};
重载,重写,重定义的对比:
2. 多态的原理:
要了解多态的原理,就需要了解虚函数表和虚函数表指针;
- 虚函数表
虚函数表顾名思义,就是保存虚函数地址的表,本质上就是一个保存虚函数指针的指针数组;这个指针数组最后面放了一个nullptr 。
基类中虚函数表和派生类中的虚函数表有什么不同呢?
#include<iostream>
using namespace std;
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
通过上边的例子可以得出:继承自含有虚函数的类的派生类中也有虚函数表;
派生类的虚函数表的生成过程:
a. 先将基类的虚表内容拷贝一份到派生类的虚表中;
b. 如果派生类重写了某个继承下来的虚函数,那么在派生类会将自己重写的虚函数覆盖掉自己虚表中被重写的虚函数;(虚函数的重写叫也做覆盖,覆盖是原理层的叫法)
c. 派生类自己增加的虚函数按照声明的次序增加到派生类虚表的后边;
总结:
- 虚表可以继承,如果子类的虚函数没有重写,那么,子类虚表中保存的该函数的地址仍然是父类的虚函数的地址;
- 派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。
注意:虚函数表中保存的是虚函数的指针,不是虚函数;而虚函数表保存到内存中的代码区;对象通过虚表指针访问虚表;
- 虚函数表指针
#include<iostream>
using namespace std;
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
int main()
{
Base b;
cout << sizeof(b) << endl;
return 0;
}
上面的例子证明了一个含有虚函数的类的对象不仅仅只包括成员变量,还会有一个叫做 _vfptr的指针。而这个指针就是虚函数表指针,也叫做虚表指针;所以这个指针指向的东西就叫做虚函数表;
类中存在虚函数时,每个对象中都有一个指向虚函数表的指针(_vfptr)。
下面这个例子完美体现了多态;
#include<iostream>
using namespace std;
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;
}
C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。