多态

同一消息根据发送对象的不同而产生不同的行为,多态是建立的在封装和继承的基础之上

一个小案例引发的问题

#include <iostream>

using namespace std;

class Person {
public:
Person(){};
void test() {cout << "Person test" << endl;}
virtual void vTest() {cout << "Person vTest" << endl;} //虚函数
}; class Worker: public Person {
public:
Worker(){};
void test() {cout << "Worker test" << endl;}
virtual void vTest() {cout << "Worker vTest" << endl;} //虚函数
}; class Farmer: public Person {
public:
Farmer(){};
void test() { cout << "Farmer test" << endl;} virtual void vTest() { cout << "Farmer vTest" << endl;}
}; int main(void) {
Person* person = dynamic_cast<Person*>(new Worker());
person->test();
person->vTest();
person = dynamic_cast<Person*> (new Farmer());
person->test();
person->vTest();
}

上面的代码输出的是

Person test
Worker vTest
Person test
Farmer vTest

首先我们先对代码进行分析一下,有三个类,一个是Person,一个是Worker。Worker,Farmer类继承于Person类。在主函数中我们创建了一个Worker和Farmer对象,并将其返回值转为Person,然后分别调用test()和vTest()。有上面的现象的我们提出以下几个问题:

  • 静态绑定与动态绑定是什么,和本案例有啥关系
  • 多态到底是什么,如何体现
  • 虚函数的原理是什么

下面就来回答这三个问题,这三个问题弄懂了,多态就理解了就算理解了一半

静态绑定与动态绑定

静态绑定

在编译链接阶段需要确定函数的调用地址,即将函数的调用和函数的实现(函数体)关联起来,如果关联不到函数体,怎会报下面的错误,这也是我们经常遇到的

undefined reference to 'xxx()'

编译阶段将函数的调用和函数体关联起来我们称作静态绑定

在小案例中

 person->test();

就是静态绑定,编译器会将test()和person中test()方法体关联起来,所以结果输出的总是

Person test
动态绑定

当我们为方法添加virtual关键字后,编译器将不会执行静态绑定,因为该关键字给编译器一个暗示,将绑定推迟至运行时,这就是所谓的动态绑定,也叫运行时绑定。这样好处时什么呢?这样的做的话就可以调用同一接口而产生不同效果。

person->vTest()

上面的代码被调用了两次,但是却产生了不同的效果,这和静态绑定最大的区别 。那么动态绑定也是绑定,也要将方法调用和方法体绑定起来,那么它是根据什么绑定的呢?根据上下文来绑定,查看当前的指针指向的真实对象是什么。person开始指向的是Worker对象,那么就绑定该对象拥有的方法体,后来person指向了Farmer对象,那么就绑定Farmer对象拥有的方法体,等绑定完成之后就是开始执行方法体了

小结

动态绑定使得程序更加的灵活,因为如果使用静态绑定,那么一个接口不管执行多少次,其结果都是一样,但是使用动态绑定,在接口不变的情况下,我们只要更改对象就可以获取不同的效果

多态的含义

在本文的导语就说了多态是不同对象对同一消息会产生不同的行为。我们分解一下这句话就可以理解多态了

  • 同一消息:即同一函数调用,比如小案例中person->vTest()
  • 不同对象:不同对象很好理解了,比如Worker和Farmer就是不同的对象,但是有一个特点就是他们必须有共同基类
  • 产生不同行为:小案例中person->vTest()被调用了两次从而产生不同的输出结果就是不同的行为

多态的实质就是动态绑定,因为在同一消息的情况下产生不同的效果只能使用动态绑定来实现

虚函数

上面我们看到要实现多态必须借助虚函数,关于虚函数的使用如下:

  • 虚函数可以被子类继承
  • 如果要复写父类的虚函数则需要返回值,函数名和参数列表与父类保持一致
  • virtual不能修饰类成员方法(static)
  • 不能修饰内联函数

虚函数的原理是使用虚函数表和函数指针来实现的,当类中如果有虚函数时,类加载到内存会为其生成张虚函数表,虚函数表中记录了类中虚函数的位置,下面通过一个案例来说明:

Shape类及其虚函数表
class Shape {
public:
virtual double calcArea();
protected:
int m_iEege;
};

Shape类中有个虚函数calcArea,当Shape类被加载到内存中后,加载器会为其创建一张虚函数表(一个类对应一张虚函数表,如果类中没有虚函数则不会创建虚函数表)。当我们在程序中创建Shape对象后,该对象中将会有一个隐藏的指针指向虚函数表,如下图所示:

在图中虚函数表的地址时0xCCFF,那么创建的Shape对象其虚函数表指针vftable_ptr将会指向0xCCFF。在虚函数表中有个函数指针为calcArea_ptr指向0x3355内存区域,该区域其实存储了calcArea()函数体。

Circle类及其虚函数表
class Circle:public Shape {
protected:
int m_dR;
}

Circle继承于Shape类,那么也会继承Shape类的虚函数。同时Circle类被加载到内存后也会为其创建虚函数表,但是有一点稍微不同的地方。

在上图中Circle对象中也会有个指针指向自己的虚函数表,在虚函数表中有个指针会指向calcArea()函数,我们看到这个函数的内存地址没有发生变化,仍然是0x3355。当我们复写了calcArea()后,这个指针才会执行新的函数的位置。

注意:如果类中有虚函数,则创建的对象将会多出四个字节用来保持虚函数表指针

通过上面的分析,我们就可以理解如何根据上下文来进行动态绑定了。

Person* person = dynamic_cast<Person*>(new Worker());
person->vTest();

person实际指向的是Worker对象,那么在调用vTest()的时候首先会从Worker对象查找虚函数表的位置,在虚函数表中查找vTest()在内存中的位置,找到之后调用vTest()方法

纯虚函数

纯虚函数的格式如下,纯虚函数是不能有函数体的,拥有纯虚函数的类被称为抽象类。由于纯虚函数没有函数体,那么在虚函数表中指向纯虚函数的指针将会被赋值为0

class Person {

virtual void work() = 0;

};

含有纯虚函数的类的不能被实例化

RTTI

RTTI被称为运行时类型信息,用来检查一个指针指向对象的具体类型。由于在设计中为了解耦,往往存在向上转型(把一个子类对象赋给父类指针)。但是有时我们需要通过指针检查该指针指向对象的具体类型,那么可以通过typeid查看。下面是一个简单的例子:

#include <iostream>
#include <typeinfo>
using namespace std;
class Person {
virtual void test(){};
}; class Worker:public Person {
}; int main(void) {
Person* person = dynamic_cast<Person*>(new Worker());
cout << typeid(*person).name() << endl;
if (typeid(*person) == typeid(Worker))
cout << "match successful!" << endl;
else
cout << "match failed!" << endl;
}

输出结果为

Worker
match successful!

上面的代码中我们查看了person指向的对象到底是哪个类实例化出来的,这样就能进行类型的检查了。

注意:typeid传入的值一般都是对象而不是指向对象的指针,这样才能检测该对象的实际类型;typeid传入的对象其父类必须有虚函数,如果没有虚函数则会返回父类类型。如果把上面的代码中Person的类中的virtual去掉,则输出的结果为:

Person
match failed

这是因为typeid主要用于多态,但是Person中没有虚函数表示我们对于该类我们放弃的多态的特性,所有typeid并不承担该责任。这在编程中需要注意。

typeid其实是个函数,其返回值是指向type_info对象的引用,关于type_info的东东可以百度或谷歌

异常

通过一个小案例说明异常

#include <iostream>
using namespace std;
class Exception {
public:
Exception(){};
virtual void printException() = 0;
virtual ~Exception(){};
}; class IndexException : public Exception {
public:
virtual void printException() {cout << "IndexException" << endl;}
IndexException(){};
virtual ~IndexException(){};
}; void test() {
throw IndexException();
} int main(void) {
try {
test();
} catch (Exception& e) {
e.printException();
}
}

总结

多态的实质就是动态绑定,而动态绑定与虚函数是息息相关的。所以要有多态的特性必须在父类中出现虚函数或这纯虚函数。在java中是天然支持多态的,所以java中所有的成员方法在C++的角度看来都是虚函数,当然静态的成员方法除外。

c++之旅:多态的更多相关文章

  1. JAVA之旅(八)——多态的体现,前提,好处,应用,转型,instanceof,多态中成员变量的特点,多态的案例

    JAVA之旅(八)--多态的体现,前提,好处,应用,转型,instanceof,多态中成员变量的特点,多态的案例 学习是不能停止的 一.多态 我们今天又要学习一个新的概念了,就是多态,它是面向对象的第 ...

  2. 【C++模版之旅】静态多态的讨论

    说到面向对象特性之一“多态”,以我的水平已经说不出太多新意了.相信很多程序员代码K多了,做梦都在“多态中”运行着.常规的多态是C++语义内置支持的一种特性,通过虚函数可以实现这个特性,为了后面以示区别 ...

  3. Java学习之旅基础知识篇:面向对象之封装、继承及多态

    Java是一种面向对象设计的高级语言,支持继承.封装和多态三大基本特征,首先我们从面向对象两大概念:类和对象(也称为实例)谈起.来看看最基本的类定义语法: /*命名规则: *类名(首字母大写,多个单词 ...

  4. Java之旅_面向对象_多态

    参考并摘自:http://www.runoob.com/java/java-polymorphism.html 多态 多态是一个行为具有多个不同表现形式的能力. 多态就是同一个接口,使用不同的实例而执 ...

  5. python之旅:面向对象之多态、多态性

    一 多态 多态指的是一类事物有多种形态 eg:动物有多种形态:猫,狗,猪 class Animal: #动物类 def eat(self): #吃 pass def drink(self): #喝 p ...

  6. Java探索之旅(8)——继承与多态

    1父类和子类: ❶父类又称基类和超类(super class)子类又称次类和扩展类.同一个package的子类可以直接(不通过对象)访问父类中的(public,缺省,protected)数据和方法. ...

  7. 逆袭之旅.DAY08东软实训.多态~

    2018年7月4日

  8. 180分钟的python学习之旅

    最近在很多地方都可以看到Python的身影,尤其在人工智能等科学领域,其丰富的科学计算等方面类库无比强大.很多身边的哥们也提到Python非常的简洁方便,比如用Django搭建一个见得网站只需要半天时 ...

  9. 【转】《我的WCF之旅》博文系列汇总

    转自:http://www.cnblogs.com/artech/archive/2007/09/15/893838.html WCF是构建和运行互联系统的一系列技术的总称,它是建立在Web Serv ...

随机推荐

  1. 全新的membership框架Asp.net Identity——绕不过的Claims

    http://www.cnblogs.com/JustRun1983/p/4708176.html?utm_source=tuicool&utm_medium=referral

  2. studio导入Eclipse 项目要改的文件

    添加下面文件即可,一个不能少 1. project 2.project.properties 3.classpath 4.AndroidManifest.xml 以上目录都有可以正常导入studio中

  3. Maven创建一个Web项目

    我们可以通过命令行或者直接使用Eclipse创建一个maven webapp项目:通过命令行创建在命令行中输入如下格式的命令将会创建一个新的maven webapp项目:mvn archetype:g ...

  4. Excel 一个工作表进行按行数拆分

    1. 如下Excel表,总共有120多行数据,如何将以50行数据为一个工作表进行拆分 Sub ZheFenSheet() Dim r, c, i, WJhangshu, WJshu, bt As Lo ...

  5. python3 + selenium + (chrome and firefox)使用

    目录 瞎扯一句 简介 最后放模板 瞎扯一句 最近在做一个关于 selenium 相关的项目,在选择浏览器方面,一般有3种方案: chrome phantomJs firefox(推荐) 网上有很多教程 ...

  6. 利用CSS3制作淡入淡出动画效果

    CSS3新增动画属性“@-webkit-keyframes”,从字面就可以看出其含义——关键帧,这与Flash中的含义一致. 利用CSS3制作动画效果其原理与Flash一样,我们需要定义关键帧处的状态 ...

  7. Delphi编写下载程序:UrlDownloadToFile的进度提示

    urlmon.dll中有一个用于下载的API,MSDN中的定义如下: HRESULT URLDownloadToFile(             LPUNKNOWN pCaller,       L ...

  8. ObjectId

    BSON Types — MongoDB Manual https://docs.mongodb.com/manual/reference/bson-types/#objectid ObjectId ...

  9. HTTP Transaction Delays

    w客户端.服务器超载 HTTP The Definitive Guide 与建立TCP连接以及传输请求和相应报文的时间相比,事务处理的时间是很短的.除非客户端或服务器超载或正在处理复杂的动态资源,否则 ...

  10. 20190401-记录一次bug ConstraintViolationException

    org.hibernate.exception.ConstraintViolationException 违反唯一约束条件 导致这个问题的原因有很多种. 在查询数据库时发生了这样的错误,一般这样的问题 ...