什么是多态?

多态一词最初来源于希腊语,意思是具有多种形式或形态的情形,当然这只是字面意思,它在C++语言中多态有着更广泛的含义。

这要先从对象的类型说起!对象的类型有两种:

实例:Derived1类和Derived2类继承Base类

class Base
{}; class Derived1 : public Base
{}; class Derived2 : public Base
{}; int main()
{
Derived1 pd1 = new Derived1; //pd1的静态类型为Derived1,动态类型为Derived1
Base *pb = pd1; //pb的静态类型为Base,动态类型现在为Derived1
Derived2 pd2 = new Derived2; //pd2的静态类型为Derived2,动态类型现在为Derived2
pb = pd2; //pb的静态类型为Base,动态类型现在为Derived2 return 0;
}

  对象有静态类型,也有动态类型,这就是一种类型的多态。

多态分类

多态有静态多态,也有动态多态。 静态多态,比如函数重载,能够在编译器确定应该调用哪个函数;动态多态,比如继承加虚函数的方式(与对象的动态类型紧密联系,后面详解),通过对象调用的虚函数是哪个是在运行时才能确定的。

【静态多态】

实例:函数重载 ( 指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数 )

long long Add(int left, int right)
{
return left + right;
} double Add(float left, float right)
{
return left + right;
} int main()
{
cout<<Add(10, 20)<<endl; //语句一
cout<<Add(12.34f, 43.12f)<<endl; //语句二 return 0;
}

  编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),即可推断出要调用哪个函数,如果有对应的函数就调用该函数,否则出现编译错误。

【动态多态】

进入动态多态前,先看一个普通继承的例子:

class Person
{
public:
void GoToWashRoom()
{
cout<<"Person-->?"<<endl;
}
}; class Man : public Person
{
public:
void GoToWashRoom()
{
cout<<"Man-->Please Left"<<endl;
}
}; class Woman : public Person
{
public:
void GoToWashRoom()
{
cout<<"Woman-->Please Right"<<endl;
}
}; int main()
{
Person per, *pp;
Man man, *pm;
Woman woman, *pw; pp = &per;
pm = &man;
pw = &woman; //第一组 //这些都是毫无疑问的
per.GoToWashRoom(); //调用基类Person类的函数
pp->GoToWashRoom(); //调用基类Person类的函数
man.GoToWashRoom(); //调用派生类Man类的函数
pm->GoToWashRoom(); //调用派生类Man类的函数
woman.GoToWashRoom(); //调用派生类Woman类的函数
pw->GoToWashRoom(); //调用派生类Woman类的函数 //第二组
pp = &man;
pp->GoToWashRoom(); //调用基类Person类的函数
pp = &woman;
pp->GoToWashRoom(); //调用基类Person类的函数 return 0;
}

  

运行结果:

第一组毫无疑问,通过本类对象和本类对象的指针就是调用本类的函数;

第二组中先让基类指针指向子类对象,然后调用该函数,调用的是基类的,后让基类指针指向另一个子类对象,调用的是还是基类的函数。

这是因为p的类型是一个基类的指针类型,那么在p看来,它指向的就是一个基类对象,所以调用了基类函数。就像一个int型的指针,不论它指向哪,读出来的都是一个整型(在没有崩溃的前提下),即使将它指向一个float。

再来对比着看下一个例子。

实例:继承+虚函数

class Person
{
public:
virtual void GoToWashRoom() = 0;
}; class Man : public Person
{
public:
virtual void GoToWashRoom()
{
cout<<"Man-->Please Left"<<endl;
}
}; class Woman : public Person
{
public:
virtual void GoToWashRoom()
{
cout<<"Woman-->Please Right"<<endl;
}
}; int main()
{
for (int i = 0; i < 10; i++)
{
Person *p;
if (i&0x01)
p = new Man;
else
p = new Woman; p->GoToWashRoom();
delete p;
sleep(1);
} return 0;
}

  

运行结果:

就像上边这个例子所演示的那样,通过重写虚函数(不再是普通的成员函数,是虚函数!),实现了动态绑定,即在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。

使用virtual关键字修饰函数时,指明该函数为虚函数(在例子中为纯虚函数),派生类需要重新实现,编译器将实现动态绑定。在上边例子中,当指针p指向Man类的对象时,调用了Man类自己的函数,p指向Woman类对象时,调用了Woman类自己的函数。

【动态绑定条件】

  1. 必须是虚函数
  2. 通过基类类型的引用或者指针调用

总结

  • 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
  • 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
  • 只有类的成员函数才能定义为虚函数,静态成员函数不能定义为虚函数
  • 如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加
  • 构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容 易混淆
  • 不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会 出现未定义的行为
  • 最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构 函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
  • 虚表是所有类对象实例共用的

tips:协变:在C++中,只要原来的返回类型是基类类型的指针或引用,新的返回值类型是派生类的指针或引用,覆盖的方法就可以改变返回类型,这样的返回类型称为协变返回类型。

//协变,也可以构成重写(覆盖),但返回值是该类类型的指针或引用
class Base
{
virtual Base * FunTest()
{
//do something
}
};
class Derived : public Base
{
Derived * FunTest()
{
//do something
}
};

  

容易混淆的点:

c++多态性详解(转)的更多相关文章

  1. Java多态性详解——父类引用子类对象

    来源:http://blog.csdn.net/hikvision_java_gyh/article/details/8957456 面向对象编程有三个特征,即封装.继承和多态. 封装隐藏了类的内部实 ...

  2. Java:@Override标签的多态性详解

    Override(重写)是子类与父类的一种多态性体现. Override允许子类改变父类的一些行为. 为什么需要Override:当父类不满足子类的一些要求时我们就需要子类对父类的一些行为进行重写.  ...

  3. Java多态性详解 (父类引用子类对象)

    面向对象编程有三个特征,即封装.继承和多态. 封装隐藏了类的内部实现机制,从而可以在不影响使用者的前提下改变类的内部结构,同时保护了数据. 继承是为了重用父类代码,同时为实现多态性作准备.那么什么是多 ...

  4. java多态性方法的重写Overriding和重载Overloading详解

    java多态性方法的重写Overriding和重载Overloading详解 方法的重写Overriding和重载Overloading是Java多态性的不同表现.重写Overriding是父类与子类 ...

  5. Java中堆内存和栈内存详解2

    Java中堆内存和栈内存详解   Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,ja ...

  6. C++:虚函数的详解

    5.4.2 虚函数详解 1.虚函数的定义 虚函数就是在基类中被关键字virtual说明,并在派生类重新定义的函数.虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问 ...

  7. Java面向对象详解

    Java面向对象详解 前言:接触项目开发也有很长一段时间了,最近开始萌发出想回过头来写写以前学 过的基础知识的想法.一是原来刚开始学习接触编程,一个人跌跌撞撞摸索着往前走,初学的时候很多东西理解的也懵 ...

  8. C# 中4个访问符和8个修饰符详解

    4个访问修饰符(是添加到类.结构或成员声明的关键字) Public:公有的,是类型和类型成员的访问修饰符.对其访问没有限制. Internal:内部的,是类型和类型成员的访问修饰符.同一个程序集中的所 ...

  9. javascript设计模式详解之命令模式

    每种设计模式的出现都是为了弥补语言在某方面的不足,解决特定环境下的问题.思想是相通的.只不过不同的设计语言有其特定的实现.对javascript这种动态语言来说,弱类型的特性,与生俱来的多态性,导致某 ...

随机推荐

  1. 前端之JavaScript(一)

    一.JavaScript前世今生 它最初由Netscape的Brendan Eich设计.JavaScript是甲骨文公司的注册商标.Ecma国际以JavaScript为基础制定了ECMAScript ...

  2. testng系列-ReportNG

    生成reportng报告操作步骤: 一.maven的pom.xml文件需要添加内容: <properties> <!-- maven 参数配置,这里引用不同的testng.xml - ...

  3. 亮眼的购物季数据,高涨的 Amazon Prime

    依照往年的惯例,亚马逊公布了 2013 购物季的销售数据.据 The Verge 的报道,今年,仅仅网购星期一(Cyber Monday)一天就在全球范围内销售出 3680 万件商品,而去年这一数字为 ...

  4. rest_framework之版本控制

    简介 API版本控制可以用来在不同的客户端使用不同的行为.REST框架提供了大量不同的版本设计. 版本控制是由传入的客户端请求决定的,并且可能基于请求URL,或者基于请求头. 有许多有效的方法达到版本 ...

  5. python中的os.walk

    原文出处:https://www.jianshu.com/p/bbad16822eab python中os.walk是一个简单易用的文件.目录遍历器,可以帮助我们高效的处理文件.目录方面的事情. 1. ...

  6. 用了这么多年的MCU,你知道哪些MCU原厂最牛?

    单片机诞生于1971年,经历了SCM.MCU.SoC三大阶段.单片机由以前的1位.4位.8位.16位,发展到现在的32位甚至64位. 90年代后随着消费电子产品大发展,单片机技术得到了巨大提高,相继诞 ...

  7. 软件工程android项目简介

    我们的程序名字叫做“有爱”APP,英文名“you i”.意味着you and i,是一款旨在两人聊天,生活日记,记账工具,和对方通知的小软件. 1.首先我们的创意解决了用户什么需求? 答:在当今信息爆 ...

  8. 团队开发——软件需求分析报告(Hello World 团队)

    一.   项目名称 超级迷宫 二.   设计背景 随着生活节奏加快,游戏更新速度的加快,游戏大同小异缺少新颖度,同时为了满足多游戏的结合,充实人们的生活,同时增加知识,有协作模式增进友谊和感情,在闲暇 ...

  9. 2018软工实践—Alpha冲刺(2)

    队名 火箭少男100 组长博客 林燊大哥 作业博客 Alpha 冲鸭鸭! 成员冲刺阶段情况 林燊(组长) 过去两天完成了哪些任务 协调各成员之间的工作 协助前端界面的开发 搭建测试用服务器的环境 完成 ...

  10. 《我是一只IT小小鸟》心得

    虽然读这本书是老师布置的作业,但是读了几页后就被书中的内容所吸引住了.或许是因为我也是学这个专业的,所以书中的一些内容让我觉得非常的有兴趣.作为一个学习软件工程的大一学生还没真正的认识到这个专业的深奥 ...