多态性

多态性是面向对象程序设计的关键技术之一。若程序设计语言不支持多态性,不能称为面向对象的语言。利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能。

多态性(polymorphism)多态性是考虑在不同层次的类中,以及在同一类中,同名的成员函数之间的关系问题。函数的重载,运算符的重载,属于编译时的多态性。以虚基类为基础的运行时的多态性是面向对象程序设计的标志性特征。体现了类推和比喻的思想方法。

虚函数

虚函数是一个类的成员函数,定义格式如下:
virtual 返回类型  函数名(参数表)

关键字virtual指明该成员函数为虚函数。virtual仅用于类定义中,如虚函数在类外定义,不可加virtual(但是类内部声明时要加virtual)。

当某一个类的一个类成员函数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特征。

最后一句话如何理解?

#include<iostream>
using namespace std; class Base
{
public:
virtual void show()
{
cout << "This is Base show" << endl;
}
}; class D :public Base
{
public:
void show()
{
cout << "This is D show" << endl;
}
}; class C :public D
{
public:
void show()
{
cout << "This is C show" << endl;
}
}; void main()
{
C c;
D *pd = &c;
pd->show();
}

倘若D的show不是虚函数,那么pd->show();访问的就是类D的show方法。但结果却是C的show方法,所以类D的show方法也是虚的。

在整个虚函数继承体系中,父类 子类的虚函数需要做到3同。函数名相同,函数返回值相同,函数参数相同(包括参数个数,参数类型)

当在派生类中重新定义虚函数(overriding a virtual function,亦译作覆盖)时,不必加关键字virtual。但重新定义时不仅要同名,而且它的参数表和返回类型全部与基类中的虚函数一样,否则链接时出错。

多态演示代码

#include<iostream>
using namespace std; class Animal
{
public:
void Eat()
{
cout << "Animal Eat" << endl;
}
void Sleep()
{
cout << "Animal Sleep" << endl;
}
}; class People :public Animal
{
public:
void Eat()
{
cout << "People Eat" << endl;
}
void Sleep()
{
cout << "People Sleep" << endl;
}
}; class Dog :public Animal
{
public:
void Eat()
{
cout << "Dog Eat" << endl;
}
void Sleep()
{
cout << "Dog Sleep" << endl;
}
}; class Pig :public Animal
{
public:
void Eat()
{
cout << "Pig Eat" << endl;
}
void Sleep()
{
cout << "Pig Sleep" << endl;
}
}; void fun(Animal *a)
{
a->Eat();
a->Sleep();
}
void main()
{
People p;
Dog dog;
Pig pig;
fun(&p);
fun(&dog);
fun(&pig);
}

由于赋值兼容规则,导致都是Animal的Eat和Sleep

#include<iostream>
using namespace std; class Animal
{
public:
virtual void Eat()
{
cout << "Animal Eat" << endl;
}
virtual void Sleep()
{
cout << "Animal Sleep" << endl;
}
}; class People :public Animal
{
public:
void Eat()
{
cout << "People Eat" << endl;
}
void Sleep()
{
cout << "People Sleep" << endl;
}
}; class Dog :public Animal
{
public:
void Eat()
{
cout << "Dog Eat" << endl;
}
void Sleep()
{
cout << "Dog Sleep" << endl;
}
}; class Pig :public Animal
{
public:
void Eat()
{
cout << "Pig Eat" << endl;
}
void Sleep()
{
cout << "Pig Sleep" << endl;
}
}; void fun(Animal *a)
{
a->Eat();
a->Sleep();
}
void main()
{
People p;
Dog dog;
Pig pig;
fun(&p);
fun(&dog);
fun(&pig);
}

虚函数注意事项

成员函数应尽可能地设置为虚函数,但必须注意以下几条:

1.派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是同名隐藏,而不是虚函数。如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个例外。

#include<iostream>
using namespace std; class Base
{
public:
virtual Base* Show()
{
cout << "This is Base Show()" << endl;
return this;
}
}; class Son :public Base
{
public:
virtual Son* Show()
{
cout << "This is Son Show()" << endl;
return this;
}
}; void main()
{
Son son;
Base *pb = &son;
pb->Show();
}

注:上述代码需要在VC6.0以上版本允许,VC6.0对C++语法支持不完善

2.只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象。

3.静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。

4.一个类对象的静态和动态类型是相对的,实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性。

5.内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。还有一个,全局函数也不能是虚函数

6.析构函数可定义为虚函数,构造函数不能定义虚函数,因为在调用构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性。

#include<iostream>
using namespace std; class Base
{
public:
virtual Base* Show()
{
cout << "This is Base Show()" << endl;
return this;
}
Base()
{
cout << "Create Base" << endl;
}
~Base()
{
cout << "Free Base" << endl;
}
}; class Son :public Base
{
public:
virtual Son* Show()
{
cout << "This is Son Show()" << endl;
return this;
}
Son()
{
cout << "Create Son" << endl;
}
~Son()
{
cout << "Free Son" << endl;
}
}; void main()
{
Base *pb = new Son;
delete pb;
}

构造的时候父类、子类都构造了。析构的时候只析构了父类,导致内存泄漏。改进代码,父类析构函数变成虚函数,代码如下

#include<iostream>
using namespace std; class Base
{
public:
virtual Base* Show()
{
cout << "This is Base Show()" << endl;
return this;
}
Base()
{
cout << "Create Base" << endl;
}
virtual ~Base()
{
cout << "Free Base" << endl;
}
}; class Son :public Base
{
public:
virtual Son* Show()
{
cout << "This is Son Show()" << endl;
return this;
}
Son()
{
cout << "Create Son" << endl;
}
~Son()
{
cout << "Free Son" << endl;
}
}; void main()
{
Base *pb = new Son;
delete pb;
}

父类指针在指向子类对象的时候,之所以能够指向是因为在子类里面隐藏包含一个父类。pb指向的是整个空间的起始位置,而这个起始位置也恰恰是父类的起始位置。由于是Base类型,在delete的时候,编译器会认为你要删除的只是Base那么大的内存,而不会释放子类对象内存,导致内存泄漏。一旦把父类的析构函数声明为析构函数之后,这种释放就会达到一个多态的释放。即先检查一下我是否是因为创建子类而创建的父类,如果是则先释放子类对象,然后释放父类对象,达到级联式删除。

7.虚函数执行速度要稍慢一些。为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总是要付出一定代价,但通用性是一个更高的目标。

8.如果定义放在类外,virtual只能加在函数声明前面,不能(再)加在函数定义前面。正确的定义必须不包括virtual。

重载、隐藏、覆盖

重载是针对一个类内部情况而言的,作为重载的依据:参数列表不同(包括参数的个数不同、类型不同或顺序不同,仅仅参数名称不同是不可以的);函数返回值也不能作为重载的依据。

隐藏是针对继承体系而言,父类子类有相同名字的成员函数,这里只要求一同即函数名字。并且不能使用virtual关键字,子类方法会隐藏父类所有同名方法

覆盖也是针对继承体系而言,但是需要加关键字virtual

C++——多态性 与 虚函数的更多相关文章

  1. sdut 6-2 多态性与虚函数

    6-2 多态性与虚函数 nid=24#time" title="C.C++.go.haskell.lua.pascal Time Limit1000ms Memory Limit ...

  2. C++学习之路—多态性与虚函数(二)纯虚函数与抽象类

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 1    纯虚函数 在前面的博客中已经提到:有时 ...

  3. C++学习之路—多态性与虚函数(一)利用虚函数实现动态多态性

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 多态性是面向对象程序设计的一个重要特征.顾名思义 ...

  4. C++之多态性与虚函数

    面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为.在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体.也可以这样说就是实现了&quo ...

  5. C++学习7-面向对象编程基础(多态性与虚函数、 IO文件流操作)

    多态 多态性是指对不同类的对象发出相同的消息将返回不同的行为,消息主要是指类的成员函数的调用,不同的行为是指不同的实现: 函数重载 函数重载是多态性的一种简单形式,它是指允许在相同的作用域内,相同的函 ...

  6. C++ 多态性和虚函数

    2017-06-27 19:17:52 C++面向对象编程的一个重要的特性就是多态性,而多态性的实现需要依赖虚函数的帮助. 一.多态的作用: 隐藏实现细节,使得代码能够模块化: 接口重用,实现“一个接 ...

  7. [转]C++之多态性与虚函数

    面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为.在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体.也可以这样说就是实现了“一个接 ...

  8. 2013级C++第14周(春)项目——多态性、虚函数和抽象类

    课程首页在:http://blog.csdn.net/sxhelijian/article/details/11890759,内有完整教学方案及资源链接 第一部分 阅读程序1.阅读.改动和执行关于交通 ...

  9. c++特别要点:多态性与虚函数

    本来是准备在工厂模式中顺便整理.但粗略浏览了,内容还是很多,需要单独开一篇. 一.什么是多态性? 多态性可以概括为“一个接口,多种方法”. 多态与非多态的区别在于“成员函数调用地址的早绑定和晚绑定”. ...

  10. C++多态性:虚函数的调用原理

    多态性给我们带来了好处:多态使得我们可以通过基类的引用或指针来指明一个对象(包含其派生类的对象),当调用函数时可以自动判断调用的是哪个对象的函数. 一个函数说明为虚函数,表明在继承的类中重载这个函数时 ...

随机推荐

  1. 编译安装hls协议切片工具 m3u8-segmenter

    操作系统:Ubuntu16.04.4 amd64 安装http://m3u8-segmenter.inodes.org/方式安装m3u8-segmenter报错,于是有了这篇文章 apt instal ...

  2. chrome devTool

    在console中访问节点 使用document.querySelectAll()访问元素 使用$0快速访问选中的元素,光标选中的元素,早console中输入$0获取选中元素的dom信息 拷贝 > ...

  3. java-socket-demo的实现

    目录 前言 IO通讯模型 IO通讯模型简介 1. 阻塞式同步IO 2. 非阻塞式同步IO 3. 多路复用IO(阻塞+非阻塞) 4. 异步IO Java对IO模型的支持 注意点及实现方案 TCP粘包/拆 ...

  4. 微服务Consul系列之服务部署、搭建、使用

    使用Consul解决了哪些问题 是否在为不同环境来维护不同项目配置而发愁 是否有因为配置的更改导致代码还要进行修改.发布因为客流量大了还要规避开高峰期等到半夜来发布 微服务架构下应用的分解业务系统与服 ...

  5. Vue + ElementUI的电商管理系统实例02 主页

    1.打开Element网站,找到主页需要的布局格式 修改Home.vue: <template> <el-container class="home-container&q ...

  6. Ubuntu开发环境配置

    主要是: 源的更新 安装vim编辑器 远程登录xrdp相关配置 synergy symless键鼠共享配置 对新买的硬盘进行格式化和分区 vsftp环境搭建 gcc开发环境配置 qt5开发环境配置 m ...

  7. linux c基础技巧

    C语言:向文件末尾进行追加数据https://blog.csdn.net/qq_31243065/article/details/82354557 https://zhidao.baidu.com/q ...

  8. 014 Android 自定义组合控件

    1.需求介绍 将已经编写好的布局文件,抽取到一个类中去做管理,下次还需要使用类似布局时,直接使用该组合控件的对象. 优点:可复用. 例如要重复利用以下布局: <RelativeLayout an ...

  9. [转帖]使用Nginx转发TCP/UDP数据

    使用Nginx转发TCP/UDP数据 https://www.cnblogs.com/guigujun/p/8075620.html 编译安装Nginx 从1.9.0开始,nginx就支持对TCP的转 ...

  10. centos7 为使用su命令的用户添加pam授权认证

    # 查看用户所属哪个组 groups 用户名 #查看当前有哪些用户运行程序 ps -aux|awk '{print $1}'|sort -rn|uniq -c|sort -rn # 清理不再使用的用户 ...