多态篇2019-11-29

什么是多态

多态是 C++ 编程时的一种特性,多态性即是对一个接口的多种实现,多态可以分为 静态多态和动态多态,所谓静态多态就是通过函数重载、模板、强制类型转换实现的, 静态多态是在函数编译阶段就决定调用的机制,即在编译链接截断将函数的入口地址给出,而 动态多态是在程序运行时刻才决定调用机制,而在 C++ 中动态多态是通过虚函数实现的

那么什么又是函数重载、模板、强制类型转换、虚函数呢

【函数重载】所谓函数重载就是一个同名函数可以完成不同的功能,编译系统在编译时期通过函数参数个数、参数类型不同来区分该调用哪一个函数,其实现静态多态

【模板】模板也是静多态的早绑定是因为模板生成代码的代码,模板特例化出函数之后不同的参数类型就形成了函数的重载,而函数的重载就是早绑定的静多态。因此 模板为静多态的实质就是函数重载为静多态

【虚函数】在类中用 virtual 关键字声明的函数叫虚函数

虚函数是如何实现多态的

classObject{public:virtualvoidfunc(intx =10) {        cout <<"print object x ="<< x << endl;    }};classTest:publicObject{private:voidfunc(inty =10) {        cout <<"print test y ="<< y << endl;    }};intmain(){    Test t1;    Object* p = &t1;    p->func();return0;}//print Test y = 10classObject{public:voidfunc(intx =10) {        cout <<"print object  ="<< x << endl;    }};classTest:publicObject{private:voidfunc(inty =20) {        cout <<"print test y ="<< y << endl;    }};intmain(){    Test t1;    Object* p = &t1;    p->func();return0;}print object x =10

通过上述代码和两种不同形式下的两种结果,我们能够知道当有 virtual 关键字修饰 函数时,函数是呈动态性的,p 是基类类型的指针,现在指向了派生类,但是在派生 类中有两部分,即派生类自己的部分和虚函数部分,此时的 p 是指向派生类中的基类部分, 所以程序在编译期间程序拿到的是基类中的 func 函数的形参 x = 10,但在运行期间由于 virtual 关键字 的存在形成了动多态,即动态绑定,在派生类中对基类的 func 函数进行了隐藏, 如果要调用Object中的fun()的加上作用域,即p->Object::fun() ),但是此时拿到的参数任然为在基类中 拿到的 10,所以打印的结果为:print Test y = 10。

在不加 virtual 关键字的情况下,我们构造 Test 类的对象时,首先要调用 Object 类 的构造函数去构造 Object 类的对象,然后才调用Test类的构造函数完成自身部分的构造, 从而拼接出一个完整的 Object 对象。当我们将 Test 类的对象转换为 Object 类 型时,该对象就被认为是原对象整个内存模型的上半部分,也就是下图中的 “animal 的对象所占 内存”。那么当我们利用类型转换后的对象指针去调用它的方法时,当然也就是调用它所在的内存中的 方法,由于 p 是基类 Object 的指针,所以他调用的是基类中的 fun() 函数。

在 virtual 背后究竟发生了什么

编- 译器在编译程序的时候,发现类中存在虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(即虚表是属于类的,一个类的 所有对象共享一个虚表),虚表是一个一维数组,在这个数组中存放着每个虚函数的地址,对于上述程序,Object 和 Test 类都包含了一个虚函数 func,因此 编译器会为这两个类创建虚表,(即使派生类没有 virtual 函数,但是其棋类里面有,所以派生类类也就有了)

那么如何定位虚表呢?编译器另外还为每个类的对象提供了一个虚表指针(一个对象包含一个),这个指针指向了对象所属类的虚表,在程序运行时, 根据对象的类型去初始化 vptr,从而让 vptr 正确的指向所属类的虚表,从而在调用虚函数时,就能找到正确的函数,对于上述程序,由于 p 实际指向的类型是 Test,因 此 vptr 指向的 Test 类的虚表,当调用 p->func() 时,根据虚表中函数地址找到的就是 Test 类的 func() 函数,而在虚表中存有的虚函数指针(即 vptr,每个虚 函数有一个),虚函数指针就指向对应的虚函数

正是由于每个对象调用的虚函数都是通过虚表指针来进行索引的,也就决定了虚表指针的正确初始化是非常重要的,换句话说,在虚表指针没有正 确初始化之前,我们不能够去调用虚函数,那么虚表指针在什么时候进行初始化呢,或者它在什么地方进行初始化

在构造函数中进行虚表的创建和虚指针的初始化,在构造子类对象时,要先调用基类的构造函数,此时编译器 只看到了基类,并不知道后面是否还有继承者,它初始化基类对象的虚表指针,该虚表指针指向基类的虚表,当执行 派生类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表,对于程序来说,当 Test 类的对象构造完毕之后,其内部的虚表指针也就被初始化为指向 Test 类的虚表, 在类型转换后,调用 p->func(),由于 p 实际指向的是 Test 类的对象,该对象内部的虚表指针指向的是 Test 类的虚表,因此最终调用的是 Test 类 的 func 函数

对于虚函数的调用来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表,所以在程序中,不管你的对象类型如何转换,但 该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是 C++ 多态性实现原理

类的多态性和函数的多态性

一般情况下,类的多态性是指用虚函数实现的,函数的多态性是用模板和函数重载实现的

析构函数声明为虚函数的好处

为了防止内存泄漏,基类的析构函数必须声明为虚函数

那么,为什么将基类的析构函数声明为虚函数就可以防止内存泄漏?

如果没有将基类的析构函数声明为虚函数,在释放指向派生类对象的基类指针的时候,只会调用基类的析构函数,而派生类的析构函数不会调用,导致属于 派生类新添加的数据不能释放,从而导致内存泄漏

如果将析构函数声明为虚函数,在释放指向派生类对象的基类指针的时候,会调用派生类的析构函数,而派生类的析构函数会自动调用基类的析构函数,从而使放 所有内存,避免内存泄漏

可是这里会有个问题,为什么将基类析构函数声明为虚函数,在释放指向派生类对象的基类指针时,会调用派生类的析构函数,难道派生类的析构函数重写了基类的析构函数吗?函数名不同啊

其实析构函数是一个特殊的函数,编译器在编译时,析构函数的名字同一命名为 destucter

所以只要将基类的析构函数声明为虚函数即可,不管派生类的析构函数前面是否有 virtual 关键字,都构成重写,这也就可以解释为什么将基类析构函数声明为虚函数,释放指向派生类对象的基类 指针时,会调用派生类的析构函数,因为虚表中的函数指针指向的是派生类的析构函数

构造函数为什么不能声明为虚函数

我们知道,在调用虚函数前,需要先访问虚表指针,得到虚表,然后在执行虚表中对应的虚函数,假设 现在将构造函数声明为虚函数,调用构造函数前,发现构造函数是一个虚函数,然后去访问虚表指针,可是虚表指针是在构造函数中初始化的,而目前构造函数 还未执行,也就是说,虚表指针还没有初始化,只是一个空值,理所当然,这便找不到构造函数的函数指针,因此无法完成构造任务,所以说,构造函数声明为虚函数是很愚蠢的

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容