C++编译时多态和运行时多态

作者:melonstreet
出处:https://www.cnblogs.com/QG-whz/p/5132745.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

前言

今日的C++不再是个单纯的“带类的C”语言,它已经发展成为一个多种次语言所组成的语言集合,其中泛型编程与基于它的STL是C++发展中最为出彩的那部分。在面向对象C++编程中,多态是OO三大特性之一,这种多态称为运行期多态,也称为动态多态;在泛型编程中,多态基于template(模板)的具现化与函数的重载解析,这种多态在编译期进行,因此称为编译期多态或静态多态。在本文中,我们将了解:

  1. 什么是运行期多态
  2. 什么是编译期多态
  3. 它们的优缺点在哪

运行期多态

运行期多态的设计思想要归结到类继承体系的设计上去。对于有相关功能的对象集合,我们总希望能够抽象出它们共有的功能集合,在基类中将这些功能声明为虚接口(虚函数),然后由子类继承基类去重写这些虚接口,以实现子类特有的具体功能。典型地我们会举下面这个例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8RIXVVw-1642570130701)(C++编译时多态和运行时多态.assets/poly0.png)]

class Animal
{public :virtual void shout() = 0;
};
class Dog :public Animal
{public:virtual void shout(){ cout << "汪汪!"<<endl; }
};
class Cat :public Animal
{public:virtual void shout(){ cout << "喵喵~"<<endl; }
};
class Bird : public Animal
{public:virtual void shout(){ cout << "叽喳!"<<endl; }
};int main()
{Animal * anim1 = new Dog;Animal * anim2 = new Cat;Animal * anim3 = new Bird;//藉由指针(或引用)调用的接口,在运行期确定指针(或引用)所指对象的真正类型,调用该类型对应的接口anim1->shout();anim2->shout();anim3->shout();//delete 对象...return 0;
}

运行期多态的实现依赖于虚函数机制。当某个类声明了虚函数时,编译器将为该类对象安插一个虚函数表指针,并为该类设置一张唯一的虚函数表,虚函数表中存放的是该类虚函数地址。运行期间通过虚函数表指针与虚函数表去确定该类虚函数的真正实现。

运行期多态的优势还在于它使处理异质对象集合称为可能:

//我们有个动物园,里面有一堆动物
int main()
{vector<Animal*>anims;Animal * anim1 = new Dog;Animal * anim2 = new Cat;Animal * anim3 = new Bird;Animal * anim4 = new Dog;Animal * anim5 = new Cat;Animal * anim6 = new Bird;//处理异质类集合anims.push_back(anim1);anims.push_back(anim2);anims.push_back(anim3);anims.push_back(anim4);anims.push_back(anim5);anims.push_back(anim6);for (auto & i : anims){i->shout();}//delete对象//...return 0;
}

总结:运行期多态通过虚函数发生于运行期

编译期多态

对模板参数而言,多态是通过模板具现化和函数重载解析实现的。以不同的模板参数具现化导致调用不同的函数,这就是所谓的编译期多态。
相比较于运行期多态,实现编译期多态的类之间并不需要成为一个继承体系,它们之间可以没有什么关系,但约束是它们都有相同的隐式接口。我们将上面的例子改写为:

class Animal
{public :void shout() { cout << "发出动物的叫声" << endl; };
};
class Dog
{public:void shout(){ cout << "汪汪!"<<endl; }
};
class Cat
{public:void shout(){ cout << "喵喵~"<<endl; }
};
class Bird
{public:void shout(){ cout << "叽喳!"<<endl; }
};
template <typename T>
void  animalShout(T & t)
{t.shout();
}
int main()
{Animal anim;Dog dog;Cat cat;Bird bird;animalShout(anim);animalShout(dog);animalShout(cat);animalShout(bird);getchar();
}

在编译之前,函数模板中t.shout()调用的是哪个接口并不确定。在编译期间,编译器推断出模板参数,因此确定调用的shout是哪个具体类型的接口。不同的推断结果调用不同的函数,这就是编译器多态。这类似于重载函数在编译器进行推导,以确定哪一个函数被调用。

运行期多态与编译期多态优缺点分析

运行期多态优点

  1. OO设计中重要的特性,对客观世界直觉认识。
  2. 能够处理同一个继承体系下的异质类集合。

运行期多态缺点

  1. 运行期间进行虚函数绑定,提高了程序运行开销。
  2. 庞大的类继承层次,对接口的修改易影响类继承层次。
  3. 由于虚函数在运行期在确定,所以编译器无法对虚函数进行优化。
  4. 虚表指针增大了对象体积,类也多了一张虚函数表,当然,这是理所应当值得付出的资源消耗,列为缺点有点勉强。

编译期多态优点

  1. 它带来了泛型编程的概念,使得C++拥有泛型编程与STL这样的强大武器。
  2. 在编译器完成多态,提高运行期效率。
  3. 具有很强的适配性与松耦合性,对于特殊类型可由模板偏特化、全特化来处理。

编译期多态缺点

  1. 程序可读性降低,代码调试带来困难。
  2. 无法实现模板的分离编译,当工程很大时,编译时间不可小觑。
  3. 无法处理异质对象集合。

关于显式接口与隐式接口

所谓的显式接口是指类继承层次中定义的接口或是某个具体类提供的接口,总而言之,我们能够在源代码中找到这个接口.显式接口以函数签名为中心,例如

void AnimalShot(Animal & anim)
{anim.shout();
}

我们称shout为一个显式接口。在运行期多态中的接口皆为显式接口。

而对模板参数而言,接口是隐式的,奠基于有效表达式。例如:

template <typename T>
void AnimalShot(T & anim)
{anim.shout();
}

对于anim来说,必须支持哪一种接口,要由模板参数执行于anim身上的操作来决定,在上面这个例子中,T必须支持shout()操作,那么shout就是T的一个隐式接口。

C++编译时多态和运行时多态相关推荐

  1. 编译时类型和运行时类型到底是什么?

    最近在看多态,里面有一句话困扰了我很久,就是 编译时类型和运行时类型 ps:多态定义:把子类对象直接赋给父类的引用时,当运行时调用该引用类型的方法时,其方法行为总是表现出子类的行为特征,而不是父类的行 ...

  2. 编译时异常和运行时异常的区别

    编译时异常和运行时异常的区别 参考文章: (1)编译时异常和运行时异常的区别 (2)https://www.cnblogs.com/lxl57610/p/6716252.html (3)https:/ ...

  3. 异常---编译时异常和运行时异常:IO异常、NullPointerException、ArrayIndexOutBoundsException、ClassCastExoeption

    一.概述 1.定义: 程序运行过程中的发生了不正常的情况 2.异常处理机制和作用----JVM虚拟机打印的 java语言时很完善的语言,提供了异常处理方式,出现异常,将异常信息打印到输出控制台,供程序 ...

  4. java 运行 对象_java实例对象的编译时类型和运行时类型

    为什么要区分编译时类型和运行时类型? 看这样一句代码:Person p=new Women()(Women类继承自Person类)那么,假如p的属性修饰符为public 访问属性时得到的是Person ...

  5. 编译时异常和运行时异常区别

    文章目录 1.编译时异常因为什么而得名 2.编译时异常和运行时异常区别 3.编译时异常还有其他名字 4.运行时异常还有其他名字 5.所有异常都发生在运行阶段的 1.编译时异常因为什么而得名 编译时异常 ...

  6. 浅谈Java异常及其编译时异常和运行时异常的区别

    异常是程序编码和运行时经常发生的事件,了解异常有助于我们提高代码质量,增强系统的健壮性,这里总结一下Java编程中的异常.以及Java编译时异常和运行时异常的区别,并列举几种常见的异常,以供参考学习. ...

  7. java中的编译时异常和运行时异常

    首先区分一下 编译时异常和运行时异常 运行时异常,也就是extends RuntimeException的异常编译时不用try{}catch(){}和throws 编译时异常,也就是extends E ...

  8. java面向对象三大特性之多态---编译时多态和运行时多态详解

    一.引言 1.什么是多态? 说到重载和重写,大家可能都知道.它们都是多态性的体现,那么说什么是多态呢?多态是指允许不同子类型的对象对同一行为作出不同的响应.例如在生活中,比如跑的动作,小猫.小狗和大象 ...

  9. java编译时多态和运行时多态_运行时多态、编译时多态和重载、重写的关系(不区分Java和C#,保证能看懂!)...

    以前在大学学习OOP的时候,知道了重载和重写的区别,但如果要把他们和多态联系起来,我想很多新手朋友和我当初一样是死记的,可是时间长了,自然而然就忘记了,最近在写测试的时候,终于"开窍&quo ...

最新文章

  1. c语言获取指针分配的字节数,c语言指针知识点总结(共6篇).docx
  2. python输入星期几_Python练习实例31 | 输入首字母,判断是星期几
  3. java地址值每个字母,Java--------在控制台输入一句英语, 获得每个字母出现的次数...
  4. 认证服务器协议,基于口令的客户端/服务器认证协议
  5. python文件合并_用Python 将两个文件的内容合并成一个新的文件.
  6. 人工智障学习笔记——深度学习(1)神经网络
  7. ftp端口号_ftp端口号,完成ftp更改端口号只需5步
  8. 超星考试浏览器_超星浏览器官方下载
  9. 基于DBSCAN聚类算法的超像素实时分割
  10. 读书笔记 大前研一 《M型社会》
  11. iOS 13 苹果登录实践 Sign In with Apple
  12. 计算机专业毕业论文格式,2016年计算机专业毕业论文内容及格式要求
  13. 客服整理的聊天话术怎么导入到新电脑上面?
  14. cpu设计和实现(pc跳转和延迟槽)
  15. python爬虫个人学习笔记
  16. 糖果车站的街外小雪初晴
  17. python忽略警告
  18. ARP渗透与攻防(八)之ARP攻击防御
  19. Vijos 雷曼兔(csapc)
  20. 浙工大计算机研究生学制,计算机考研2018年浙江工业大学硕土研究生招生章程...

热门文章

  1. 怎样把连续的多个commit整理成1个?
  2. (vue基础试炼_03)使用vue.js实现TodoList
  3. powerdesigner-建立数据库模型及全局脚本
  4. VBA GetOpenFilename 方法
  5. mui的back重写
  6. 在php中调用java接口吗,php 调用 java 接口
  7. mysql中的锁的指令_mysql中的锁
  8. 萤石网络摄像头服务器稳定吗,萤石摄像头画面稳定性如何?
  9. java 获取ip地址_老杜带你学Java【第二课】
  10. oracle11管理员连接数据库,1.运行 cmd.exe;2.输入 sqlplus / as sysdba,以系统管理员(sysdba)身份连接数据库,进行数据库管理操作。3.连接成功后执...