什么是多态?

多态一词最初来源于希腊语,意思是具有多种形式或形态的情形,当然这只是字面意思,它在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. Web性能测试篇:AB 压力测试

    1. 压力测试的概念\定义 1.这段话是给刚接触\学习性能测试知识的初学者,在实际工作中都会接触到性能测试.压力测试.负载测试等专业名词也容易混淆,下面带大家熟悉下这到底是怎么定义: 1.1.性能测试 ...

  2. Angular7运行机制--根据腾讯课堂米斯特吴 《Angular4从入门到实战》学习笔记分析完成

  3. 131. 分割回文串 javascript实现

    给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串. 返回 s 所有可能的分割方案. 示例: 输入: "aab" 输出: [ ["aa",&quo ...

  4. v-model 双向数据绑定

    通过v-model指令可以实现双向数据绑定 HTML部分: <div id="app"> <input type="text" v-model ...

  5. CsvHelper文档-6类型转换

    CsvHelper文档-6类型转换 CsvHelper使用类型转换器来转换string到对象,或者对象到string: ITypeConverter 类型转换器的结构,必须实现: public int ...

  6. TCP系列51—拥塞控制—14、TLP、ER与拥塞控制

    一.概述 这里的重点是介绍TLP.ER与拥塞控制并不是介绍TLP和ER本身,因此TLP和ER的详细内容请翻前文. 在TLP与拥塞控制的交互中有几个点需要注意 1.TLP触发的重传后,TCP仍然处于Op ...

  7. springMVC 流程

    springMVC流程控制 SpringMVC流程 web.xml 中配置 org.springframework.web.servlet.DispatcherServlet 这一步其实和spring ...

  8. 图文详解 IntelliJ IDEA 15 创建 Maven 构建的 Java Web 项目(使用 Jetty 容器)

    图文详解 IntelliJ IDEA 15 创建 maven 的 Web 项目 搭建 maven 项目结构 1.使用 IntelliJ IDEA 15 新建一个项目.  2.设置 GAV 坐标  3. ...

  9. Linux的计划任务

    1. 语法格式:Minute Hour DayOfMonth Month DayOfWeek User Command Minute, 每个小时的第几分钟执行该任务Hour,每天的第几个小时执行该任务 ...

  10. [翻译]API Guides - Layouts

    官方文档地址:http://developer.android.com/guide/topics/ui/declaring-layout.html PS:API Guides里面的内容不免都简单些,翻 ...