C++ 之多态总结
前言
最近为了完成数据库系统的实验,又复习起了《C++ Primer》,上一次看这本巨著也是大二下的六月份,那时看面向对象程序编程这一章还云里雾里的,没有领会多态的奥妙,学完 Java 之后回头再看这一章发现对多态有了更好的理解。好记性不如烂笔头,本篇博客将会对 C++ 的多态机制做一个不太详细的总结,希望下一次不需要从头再看一遍《C++ Primer》了 _(:з」∠)_。
多态
多态离不开继承,首先来定义一个基类 Animal,里面有一个虚函数 speak():
class Animal {
public:
Animal() = default;
Animal(string name) : m_name(name) {}
virtual ~Animal() = default;
virtual void speak() const { cout << "Animal speak" << endl; }
string name() const { return m_name; }
private:
string m_name;
};
接着定义子类 Dog,并重写虚函数,由于构造函数无法继承,所以使用 using 来 “继承” 父类的构造函数。和父类相比,Dog 还多了一个 bark() 方法。
class Dog : public Animal {
public:
using Animal::Animal;
// 可加上 override 声明要重写虚函数,函数签名必须和基类相同(除非返回类自身的指针或引用)
void speak() const override { cout << "Dog bark" << endl; }
void bark() const { cout << "lololo" << endl; }
};
向上转型
我们在堆上创建一个 Dog 对象,并将地址赋给一个 Animal 类型的指针。由于指针指向的是个 Dog 对象,调用 speak() 方法时,实际上调用的是底层狗狗重写之后的 speak() 方法,而不是基类 Animal 的 speak()。也就是说编译时不会直接确定要调用的是哪个 speak() ,要在运行时绑定。
Animal* pa = new Dog("二哈");
pa->speak(); // 调用的是 Dog::speak
pa->Animal::speak(); // 强制调用基类的 speak
利用运行时绑定这一特点,我们将基类的析构函数定义为虚函数,这样子类对象在析构的时候就能调用自己的虚函数了。
虽然 pa 指向的是一个 Dog 对象,但是不能使用 bark() 方法。因为 pa 是一个 Animal 类型的指针,在编译时编译器会跳过 Dog 而直接在 Animal 的作用域中寻找 bark 成员,结果发现并不存在此成员而报错。
要实现向上转型不止能用指针,引用同样可以实现。但是如果写成以下这种形式,实质上是调用了拷贝构造函数,会用 Dog 的基类部分来初始化 Animal 对象,和向上转型没有任何关系,之后调用的就是底层 Animal 对象的 speak() 方法:
Dog dog("二哈");
Animal animal = dog;
animal.speak(); // 调用的是 Animal::speak
向下转型
要想调用底层 Dog 对象的 bark() 方法,我们需要将 pa 强转为 Dog 类型的指针。一种方法是使用 static_cast 进行静态转换,另一种这是使用 dynamic_cast 进行运行时转换。相比于前者,dynamic_cast<type *> 转换失败的时候会返回空指针,而 dynamic_cast<type &> 则会报 bad_cast 错误,因此更加安全。
Dog* pd_ = static_cast<Dog *>(pa);
pd_->bark();
if (Dog* pd = dynamic_cast<Dog*>(pa)) {
pd->bark();
} else {
cout << "转换失败" << endl;
}
作用域
子类的作用域是嵌套在父类里面的,在子类的对象上查找一个成员时,会现在子类中查找,如果没找到才回去父类中寻找。由于作用域的嵌套,会导致子类隐藏掉父类中的同名成员。比如下述代码:
class Animal {
public:
virtual void speak() const
{
cout << "Animal speak" << endl;
}
};
class Dog : public Animal {
public:
// void speak() const override { cout << "Dog speak" << endl; }
void speak(string word) const
{
cout << "Dog bark: " + word << endl;
}
};
int main(int argc, char const* argv[])
{
Animal* pa = new Dog();
Dog* pd = new Dog();
// pd->speak(); 报错
pd->speak("666"); // Dog::speak 隐藏了 Animal::speak
return 0;
}
我们在父类中定义了一个虚函数 void speak(),子类中没有重写它,而是定义了另一个同名但是参数不同的函数 void speak(string word)。这时候子类中的同名函数会隐藏掉父类的虚函数,如果写成 pd->speak(),编译器会先在子类作用域中寻找名字为 speak 的成员,由于存在 speak(string word),它就不会接着去父类中寻找了,接着进行类型检查,发现参数列表对不上,会直接报错。如果用了 VSCode 的 C/C++ 插件,可以看到参数列表确实只有一个,没有提示有重载的同名函数。

要想通过调用基类的 speak() 方法,有两种方法:
- 向上转型,使用基类的指针
pa来调用pa->speak(),由于子类没有重写虚函数,所以在动态绑定时会调用父类的虚函数; - 使用作用域符强制调用父类的虚函数:
pd->Animal::speak()
《C++ Primer》对名字查找做了一个非常好的总结:

总结
在 《C++ Primer》中对于上述内容有更加详细的讲解,这里不过是拾其牙慧罢了。看了这一章之后最大的感受就是:
你看 100 遍她的视频,她不是你的,你看 100 遍 《C++ Primer》,知识就是你的
以上~~
C++ 之多态总结的更多相关文章
- Java中的多态
1.多态:具有表现多种形态的能力的特征 父类: public abstract class Animal {public abstract void Say();} 子类: public class ...
- C# 工厂模式+虚方法(接口、抽象方法)实现多态
面向对象语言的三大特征之一就是多态,听起来多态比较抽象,简而言之就是同一行为针对不同对象得到不同的结果,同一对象,在不同的环境下得到不同的状态. 实例说明: 业务需求:实现一个打开文件的控制台程序的d ...
- C#非常重要基础之多态
前几天看了一位同志的博客,写的是关于他自己去支付宝面试的经历.过程大体是这样的:问答的时候,前面部分,作者都应答如流,说起自己经验如何之丰富,最后面试官问了作者一个问题:请简述多态的概念和作用.结果这 ...
- C++多态详解
多态是面向对象的程序设计的关键技术.多态:调用同一个函数名,可以根据需要但实现不同的功能.多态体现在两个方面,我们以前学过的编译时的多态性(函数重载)和现在我们这一章将要学习的运行时的多态性(虚函数) ...
- 【那些年关于java多态应用】
1.多态:具有表现多种形态的能力的特征 父类: public abstract class Animal { public abstract void Say();} 子类: public class ...
- JAVA多态
多态是指当系统A访问系统B的服务时,系统B可以通过多种方式来提供服务,而这一切对系统A是透明的.比如动物园的饲养员能够给各种各样的动物喂食.下图显示了饲养员Feeder,食物Food和动物Animal ...
- C#多态“说来也说”——逻辑层BLL中的多态使用
本文版权归博客园和作者吴双本人共同所有.欢迎转载,转载和爬虫请注明原文地址 http://www.cnblogs.com/tdws/p/5861842.html 昨天晚上,有个朋友说学了好久,依然没搞 ...
- java多态的理解
面向对象语言中的类有三个特征,封装.继承.多态.封装与继承很好理解,那什么是多态呢? 1.什么是多态? 多态的定义:指允许不同类的对象对同一消息做出响应.即同一消息可以根据发送对象的不同而采用多种不同 ...
- java中如何实现多态
复习基础知识 多态,就是重载和重写.重载发生在一个类中.重写发生在子类,意思就是子类重写父类相同名称的方法.刚学语言有的东西,不必搞得那么清楚,只有知道怎么用就行了,有的问题你要想真正把它搞得很懂,短 ...
- OC多态
要点: 1.多种形态,引用的多种形态对于一个引用变量,可以指向任何类的对象.对于一个父类的引用(类与类之间有一种继承关系),可以指向子类,也可以指向本类,指向的类型不同.当通过此引用向对象发送消息,调 ...
随机推荐
- Java实现单链表的逆序打印
思路1:可以将链表进行反转,然后进行数据的输出即可,单链表反转地址如下https://blog.csdn.net/Kevinnsm/article/details/113763272 这个思路1肯定有 ...
- Java学习day14
可变参数用作方法的形参,方法参数的个数就可变 格式:修饰符 返回值类型 方法名(数据类型...变量名){ } 方法内的形参只能有一个,这里的变量是一个数组 public static <T> ...
- APL 和 Web APL 的概述
APL APl ( Application ProgrammingInterface,应用程序编程接口) 是一些预先定义的函数,目的是提供应用程序 与开发人员基于某软件或硬件得以访问一组例程的能力,而 ...
- 【Vue3+Express实战】项目开发环境搭建
大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师-关注公众号:搞前端的半夏,了解更多前端知 ...
- delaycall.js 修改表单延迟自动提交的 jQuery / Zepto 插件
delaycall.js delaycall 是一个 jQuery / Zepto 插件,用于在用户完成某项操作后,延迟指定秒数后自动调动指定函数.如用户输入完内容后,延迟1秒,自动提交表单. Git ...
- Typora实现云同步
Typora很好用,但是不自带同步功能,最近找到个简便的实现Typora同步的方法. step1. 下载"坚果云": step2. 在本地创建一个"坚果云"的同 ...
- 常用的Linux 系统备份、恢复命令
公众号关注 「开源Linux」 回复「学习」,有我为您特别筛选的学习资料~ 删库跑路的事常常听说,不过,这只能是个调侃的话题,真正的工作中可不能这么干,否则,库是删了,路怕是跑不了了. 所以,备份很重 ...
- Intel CPU平台和架构介绍
点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 服务器主板上数据传输流依次为CPU .内存.硬盘和网卡, ...
- Docker系列教程01-使用Docker镜像
docker系列导读 一文带你读懂什么是docker Docker安装部署 10张图带你深入理解Docker容器和镜像 前言 学习Docker,我们需要掌握它的三大核心概念:镜像.容器和仓库. 今天先 ...
- 中间件漏洞之Nginx
中间件漏洞之Nginx 我们常见的中间件有IIS.Apache.Nginx,其中Nginx中间件有什么漏洞呢? Nginx 文件名逻辑漏洞(CVE-2013-4547): 漏洞版本: Nginx 0. ...