本篇文章,将带大家了解什么是多态,多态使用的注意事项和抽象类。
多态
1.多态的概念
2.多态的调用
3.多态的原理
4.多态的两个关键字
5.抽象类
多态的概念
多态的前提是继承,只有在继承的条件下才能形成多态。下面我们来一起分析。
多态的构成条件 :
虚函数重写
父类的指针或引用调用虚函数
父子继承关系的两个虚函数要保证函数名相同、参数类型相同、返回值相同
首先我们先介绍什么是虚函数。
那么什么叫虚函数的重写呢?
虚函数重写,也可称为覆盖。
派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的,参数类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
class Person
{
public:
virtual void BuyTicket()
{
std::cout << "全价" << std::endl;
}
};
class Student :public Person
{
public:
void BuyTicket()
{
std::cout << "半价" << std::endl;
}
};
上述代码便完成了对函数BuyTicket的重写。
多态的调用
下面我们上代码分析
上图代码中,Person类与Student类中的BuyTicket 构成多态的关系。
1.Person与Student构成继承关系
2.父类中构成多态的函数为虚函数并且在子类中进行了虚函数重写
3.重写的虚函数保证了三同
4.引用是父类的指针调用虚函数
我们进行结果的输出
如果子类中重写的虚函数没有加virtual能不能运行成功呢。代码说明一切。
我们可以看到,即使子类中重写的函数不加虚函数标志virtual也是可以的。
那么如果父类不写virtual行不行呢?上代码!
我们可以看到,当父类不加virtual时,系统只会调用父类中的函数,因此我们可以的到如下总结。
在实现多态时,父类中的virtual必须加,子类中的virtual可加可不加 。
下面我们用一道经典题目来深入了解多态的特性。
大家思考看看这道题输出结果是多少呢。
首先我们可以看到B和A间构成的继承关系,并且func函数构成了多态。
这里的指针p调用text(),p是B类型指针,所以很多同学觉得,在text()中()是B*this。
实际上,()仍然为A*this,这里调用的func调用的是B的func。所以第一个输出是B->.
那么输出的是1还是0呢。
希望大家将这句话牢记:虚函数重写继承的是父类的接口,重写的实现。
也就是说,这里的val用的是父类中的接口,为1.所以输出为B->1.
多态的原理
虽然我们知道了多态如何使用,但作为对一门底层语言的学习,更重要的是知道它原理。
如果大家觉得大小为4的话那可就掉入陷阱咯!
我们先上代码分析
事实上在32位机器下,这里的结果是8,在64位机器下,这里的结果是16!
这是因为它除了有一个变量外,还有
一个指针,此指针指向一个虚函数表
为什么大小会是8呢,我们来看看虚函数的内存。
大家有没有发现,内存里多出的指针?
这里的指针_vfptr并不是实际意义上的指针,而是一个虚数表,用于存放此对象中所有虚函数的地址。
我们用图像来进行理解。
需要注意的是,只有存在虚函数才会有虚数表。
既然有虚函数就有虚数表,那父类和子类的虚函数有什么关联的地方吗?
我们来看看他们各自的内存。通过以下代码观察内存情况
class A
{
public:
virtual void func1()
cout << "父类func1";
virtual void func2()
cout << "父类func2";
private:
int _a;
};
class B : public A
{
public:
virtual void func1()
cout << "子类func1";
private:
int _b;
};
int main()
{
A a;
B b;
return 0;
}
我们调用窗口进行观察。
从窗口我们可以获取以下信息:
1.子类和父类的虚函数表是不同的,也就是子类父类的虚函数表占取的地址不同。
2.子类中重写的虚函数,如本例中的func1 ,所存在的地址是不同的。
3.子类中没有进行重写的虚函数,如本例中的func2,占有的地址是相同的。
因此我们可以得到如下结论:
子类父类函数表不同
重写的虚函数地址不同
未重写的虚函数地址不同
多态的两个关键字
1.final:修饰虚函数,被修饰的虚函数不能进行重写。
上述代码中我们对父类中的func1进行了final修饰,可以看到编译器提示,无法重写。
2.override:检查子类类虚函数是否重写了基类虚函数,如果没有重写编译报错
抽象类
抽象类的概念: 包含有纯虚函数的类
什么是纯虚函数?
在虚函数后面写上等于0就构成了纯虚函数。如下列代码就构成了纯虚函数
virtu void func1()=0
关于虚函数的几个小结论:
- 析构函数最好定义为虚函数
- 构造函数不能定义为虚函数
- 静态成员函数不能是虚函数
- 内联函数(inline)不能是虚函数