C++中多态由浅到深理解


  简单一句话就是,操作接口表现出多种形态。严格的多态分为编译时多态(静态多态)和运行时多态(多态多态)。我们一般讲的多态都是运行时多态。本文讲的多态就是运行时多态。
  言归正传,讲我们的多态。


多态

多态通过继承和虚函数实现。运行时才绑定函数地址(晚绑定)。最常见的用法就是什么一个父类指针,让父类型的指针指向任意一个子类对象,然后通过父类的指针动态地调用实际子类的成员函数。根据指向的子类的不同而实现不同的功能。(或者让父类型的引用绑定子类对象(不用指针用引用的话))

怎么写多态

通过虚函数
目的:**接口重用。**一个接口,多种形态。
比方,我们有个动物类,吃饭函数。一个接口:吃饭。继承的类有狗、羊、猫,它们也要吃饭,但是狗要吃骨头、羊要吃草、猫要吃鱼。我们就可以把父类动物类的吃饭函数加个virtual定义为虚函数。然后子类去重写(即重新定义)吃饭函数。用的时候,用父类的指针,指向任意一个子类对象,调用的时候去调用实际子类的成员函数,动物类指向猫则调用吃鱼,指向羊则调用吃草,指向狗则调用吃骨头。一个吃饭的接口,表现出了多种形态,吃鱼吃骨头吃草。

Base 动物{virtual 吃函数 };
Derived 猫{重写吃饭为吃鱼};
Derived 狗{重写吃饭为吃骨头};
Derived 羊{重写吃饭为吃草};
猫类 cat;
狗类 dog;
羊类 hseep; 

动物类 *a= &dog;
a.吃();//调用的是吃骨头

动物类 *a1= &cat;
a1.吃();//调用的是吃鱼

注意:所有类的吃饭函数都是虚函数了,不仅仅是父类。

多态的实现:by虚函数

c++成员函数有两种:一种是子类直接继承而不需要改变的函数,一种是允许子类重写/覆盖(override)的函数。把后者定义为了虚函数。加virtual关键字。

什么时候要声明为虚函数?

即,如果子类需要修改父类的行为,即要重写,就应该在父类中将相应的函数声明为虚函数。

虚函数的目的?

加关键字后,virtual告诉编译器不应当完成早绑定(绑定现在这个函数(虚函数)的地址),应当晚绑定,运行时动态绑定。

虚函数virtual function的实现机制——虚函数表Virtual table

V-table好比一张地图,指明了实际所应该调用的函数。
V-table也是虚函数的一个代价:产生系统开销。(弊)
编译器对每一个包含虚函数的类创建一个表。
当一个包含虚函数的类的对象被创建,会有一个虚表指针vptr(vpointer),指向该类的虚表,虚表里放的是一个类的虚函数的地址。是顺序存放虚函数地址的。

eg.

  1. class Base有: virtual void f(), virtual void g(), virtual void h()
    可以通过Base的实例b来得到虚函数表:
    在这里插入图片描述
    地址:虚函数表地址、虚函数表中第一个虚函数地址、表中第二个虚函数地址…
  2. 没有虚函数重写/覆盖(实际上毫无意义,为了便于理解)
    class Derive:public Base{
    vir void f1(), vir void g1(), vir void h1()}
    对于实例Derive1 d虚函数表如下:
    在这里插入图片描述
    看到:虚函数按照其声明顺序放于表中。父类的虚函数在子类的虚函数前面。
  3. 子类覆盖/重写了父类的虚函数f
    class Derive:public Base{
    vir void f(), vir void g1(), vir void h1()}
    对于子类的实例,其虚函数表如下:
    在这里插入图片描述
    看到:覆盖的f()函数被放到了虚表中原来父类虚函数f()的位置。没有被覆盖的虚函数依旧。

于是,就有了,我们一般写的程序父类指针指向子类对象Base *b= new Derive(); b->f(); delete b;,实际调用的时候调用的是子类重写的函数Derive::f(),实现了多态。因为b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代。
  Derive d;(栈上面开辟空间) *Base b= &d;
  new是在堆上面。

为什么虚函数效率低?

因为虚函数需要一次间接寻址,而一般的函数可以在编译时定为到函数的地址,虚函数(动态类型调用)要根据某个指针定位到函数的地址。多了一个过程,效率低一些,但带来了运行时的多态。

虚函数的入口地址和普通函数有什么区别?

虚函数表:
在这里插入图片描述
当一个包含虚函数的对象被创建的时候,
它在头部附加一个指针vptr,执行vtable
中相应位置。调用虚函数的时候,不管
你是用什么指针调用的,它先要去vtable
里找到入口地址在再执行
,从而实现了
“动态联编”。不像普通函数那样简单地
跳转到一个固定地址。

发布了137 篇原创文章 · 获赞 74 · 访问量 11万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章