c++多态

什么是多态性?

多态:相同对象收到不同消息或不同对象收到相同消息时产生不同的动作。C++支持两种多态性:编译时多态性,运行时多态性。

  • 编译时多态性:通过重载函数实现
  • 运行时多态性:通过虚函数实现。

早绑定 vs 晚绑定

多态与非多态的实质区别就是函数地址是早绑定(静态多态)还是晚绑定(动态多态)。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。

当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。简单概括为“一个接口,多个方法”。
对于同一条命名,不同对象接到后所做出的动作是不同的,这就叫多态。

#include<iostream>
#include<stdlib.h>
#include "Circle.h"
#include "Rect.h"
using namespace std;

int main()
{
    Shape *shape1 = new Rect(3, 6);
    Shape *shape2 = new Circle(5);
    shape1->calcArea();
    shape2->calcArea();
    delete shape1;
    shape1 = NULL;
    delete shape2;
    shape2 = NULL;
    system("pause");
    return 0;
}

普通虚函数 vs 虚析构函数

普通虚函数:在类的成员函数前加virtual关键字,并在派生类中重新定义的成员函数,其作用是:当父类和子类定义有相同的成员函数,用父类的指针指向子类的对象时能够调用子类的该成员函数。因为若不加virtual关键字,那么调用的就是父类中与之同名的成员函数。
(与之区别的是隐藏,隐藏发生在子类的对象调用子类中的成员函数时而隐藏掉其父类中同名的成员函数。)
虚析构函数:在析构函数前加virtual关键字,其作用是:用父类的指针指向子类的对象并在子类中开辟一段内存时,在销毁内存时由于delete后面跟的是父类的对象,故只调用父类的析构函数而不调用子类的析构函数,从而使得子类中的内存无法释放。而虚析构函数就解决了这种内存泄漏问题。

#ifndef SHAPE_H
#define SHAPE_H
class Shape
{
public:
    Shape();
    virtual ~Shape();//虚析构函数
    virtual double calcArea();//加virtual关键字,虚函数
};
#endif 

多态中容易出现的一个问题就是:内存泄漏

若构造函数里没有从堆中申请内存,那么也就不需要在析构函数中进行释放内存操作,也就是说若构造函数什么也不做(只是打印刷存在感)的话,调用与不调用都一样

class Circle :public Shape
{
public:
    Circle(double r );
    ~Circle();
    virtual double calcArea();
protected:
    double m_dR;
    Coordinate m_pCenter;//在Circle类中定义一个指向圆心坐标的指针
};

在Circle类中定义一个指向圆心坐标的指针,则需要在Circle类的构造函数中申请内存,并且在析构函数中释放内存,从而保证内存不泄漏。
当用父类指针指向子类对象并对操作子类对象的虚函数时,是没问题的,但想借助父类指针去销毁子类对象的内存时就会出现问题,因为delete后面如果跟的是父类的指针,则只会调用父类的析构函数,若跟的是子类的指针,则既调用子类的析构函数也调用父类的析构函数。
而对于多态问题,就是用父类的指针去指向子类对象,并对子类对象进行操作,但释放的是父类的指针,此时并不调用子类的析构函数,也就无法释放子类中申请的内存,造成内存泄漏。

为解决此问题就要用到虚析构函数
即用virtual去修饰析构函数(注意是在父类的析构函数前加virtual,子类析构函数可加可不加,不加时系统会自动加上,但推荐都加上,以防子类被其他类继承变成新的父类)

class Shape
{
public:
    Shape();
    virtual ~Shape();//虚析构函数
    virtual double calcArea();
};
Circle::Circle(double r)
{
    m_pCenter = new Coordinate(x, y);
    m_dR = r;
    cout << "Circle()" << endl;
}
Circle::~Circle()
{
    delete m_pCenter;
    m_pCenter = NULL;
    cout << "~Circle()" << endl;
}

virtual在修饰函数时的一些限制:

  • 不能修饰普通函数(全局函数),也即该函数必须是某个类的成员函数,否则编译出错
  • 不能修饰静态函数,
  • 不能修饰内联函数,若virtual修饰inline,则系统会忽略掉inline关键字,使内联函数变成虚函数,
  • 不能修饰构造函数

函数指针

函数的本质就是一段二进制的代码写进内存中。可以通过一个指针指向这段代码的开头,计算机就会从开头一直执行下去,直到结尾。
先定义一个Shape类

class Shape
{
public:
    virtual double calcArea()
    {
        return  0;
    }
protected:
    int m_iEdge;
};

再定义一个Circle子类,其中并没有定义计算面积的函数,而是利用Shape中的虚函数来计算面积。那么此时虚函数是如何来实现的呢?为了弄懂这个问题,先看一个概念:虚函数表

class Circle :public Shape
{
public:
    Circle(double r);
private :
    double m_dR;
};

虚函数表

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术等。

覆盖 vs 隐藏 vs 重载

  • 隐藏:当父类与子类出现同名的函数,实例化子类对象,并去调用该函数时只会调用子类中定义的函数而不会去调用父类中的同名函数,这就叫函数的隐藏(即指派生类的函数屏蔽了与其同名的基类函数)。当然,若一定要调用父类中的同名函数只需要在函数名前加上父类名就可以了。
  • 覆盖:C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。重写的话可以有两种,直接重写成员函数(比如隐藏就是这种情况)和重写虚函数(虚函数一般定义在父类中,子类中virtual关键字可加可不加,一般加上为好,以防子类被其他类继承),只有重写了虚函数的才能算作是体现了C++多态性。

如何区分覆盖和隐藏:
如果基类中的函数和派生类中的两个名字一样的函数f
满足下面的两个条件
(a)在基类中函数声明的时候有virtual关键字
(b)基类中的函数和派生类中的函数一模一样,函数名,参数,返回类型都一样。
那么这就是叫做覆盖(override),这也就是虚函数,多态的性质
那么其他的情况呢??只要名字一样,不满足上面覆盖的条件,就是隐藏了。

  • 重载:重载则是同一个域中允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。

所以,相同的函数名的函数,在基类和派生类中的关系只能是覆盖或者隐藏。

RTTI

运行时类型识别(Run-Time Type Identification),它提供了运行时确定对象类型的方法。
其中有两个重要的运算符:typeiddynamic_cast

  • typeid的name函数用于查看对象类型
    typeid(*pHuman) == typeid(Japanese)
    typeid注意事项:
    1 返回一个type-info对象的引用
    2 若想通过基类指针获得派生类的数据类型,基类必须带有虚函数
    3 只能获取对象的实际类型
  • dynamic_cast 常用于从多态编程基类指针向派生类指针的向下类型转换。它有两个参数:一个是类型名;另一个是多态对象的指针或引用。其功能是在运行时将对象强制转换为目标类型并返回布尔型结果。(即编译器无法验证是否发生正确的转换)
    dynamic_cast<Japanese*>(pHuman)
    dynamic_cast注意事项:
    1 转换的类型只能是指针或引用,而不能是对象本身
    2 要转换的类型中必须包含虚函数
    3 转换成功返回子类地址,失败返回NULL。

纯虚函数

纯虚函数:没有函数体,且在虚函数名后面加=0
抽象类:包含纯虚函数的类,由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
接口类:仅包含纯虚函数的类。即无数据成员只有成员函数且成员函数均为纯虚函数。

接口类更多的表达的是一直能力或协议。

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

推荐阅读更多精彩内容

  • 注意:本文中代码均使用 Qt 开发编译环境 虚函数是动态联编的基础。虚函数必须是基类的非静态成员函数,其访问权限可...
    赵者也阅读 1,892评论 0 2
  • 多态是面向对象的程序设计的关键技术。多态:调用同一个函数名,可以根据需要但实现不同的功能。多态体现在两个方面,我们...
    随波逐流007阅读 554评论 0 0
  • 本文转载自:(做了一些改动)http://www.cnblogs.com/feixiang927/p/504856...
    安然_fc00阅读 5,962评论 1 6
  • 继承和多态 1. 继承的优缺点 优点:(1)子类可以灵活地改变父类中的已有方法;(2)能够最大限度的实现代码重用。...
    MinoyJet阅读 620评论 0 0
  • 如何通俗易懂地解释「协方差」与「相关系数」的概念? - 知乎
    crazydane阅读 176评论 0 0