C++多态性

2023-11-10

本文参考浅谈C++多态性

多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphism),字面意思多种形状。

C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性

重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。

多态与非多态的本质区别就是函数地址的早绑定还是晚绑定,如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,也就是说地址是早绑定的。而如果函数调用的地址不能在编译期间确定,需要在运行时确定,这就属于晚绑定。

多态的作用是什么呢?

  • 封装可以使代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用;
  • 多态的目的是为了接口重用,也就是说,无论传递过来的究竟是哪个类的对象,函数都能通过同一接口调用到适应各自对象的实现方法;

最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。

如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。


#include<iostream>
using namespace std;

class A
{
public:
    void foo()
    {
        printf("1\n");
    }
    virtual void fun()
    {
        printf("2\n");
    }
};
class B : public A
{
public:
    void foo()
    {
        printf("3\n");
    }
    void fun()
    {
        printf("4\n");
    }
};
int main(void)
{
    A a;
    B b;
    A *p = &a;
    p->foo();
    p->fun();
    p = &b;
    p->foo();
    p->fun();
    return 0;
}

第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。
 
第二个输出结果就是1、4。p->foo()和p->fun()则是基类指针指向子类对象,正式体现多态的用法,p->foo()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的foo()函数的代码了,因此输出的结果还是1。而p->fun()指针是基类指针,指向的fun是一个虚函数,由于每个虚函数都有一个虚函数列表,此时p调用fun()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fun()函数的地址,因此输出的结果也会是子类的结果4。

笔试的题目中还有一个另类测试方法。即:
B *ptr = (B *)&a; ptr->foo(); ptr->fun();

问这两调用的输出结果。这是一个用子类的指针去指向一个强制转换为子类地址的基类对象。结果,这两句调用的输出结果是3,2。

  并不是很理解这种用法,从原理上来解释,由于B是子类指针,虽然被赋予了基类对象地址,但是ptr->foo()在调用的时候,由于地址偏移量固定,偏移量是子类对象的偏移量,于是即使在指向了一个基类对象的情况下,还是调用到了子类的函数,虽然可能从始到终都没有子类对象的实例化出现。
  而ptr->fun()的调用,可能还是因为C++多态性的原因,由于指向的是一个基类对象,通过虚函数列表的引用,找到了基类中fun()函数的地址,因此调用了基类的函数。由此可见多态性的强大,可以适应各种变化,不论指针是基类的还是子类的,都能找到正确的实现方法。


//小结:1、有virtual才可能发生多态现象
// 2、不发生多态(无virtual)调用就按原类型调用
#include<iostream>
using namespace std;

class Base
{
public:
    virtual void f(float x)
    {
        cout<<"Base::f(float)"<< x <<endl;
    }
    void g(float x)
    {
        cout<<"Base::g(float)"<< x <<endl;
    }
    void h(float x)
    {
        cout<<"Base::h(float)"<< x <<endl;
    }
};
class Derived : public Base
{
public:
    virtual void f(float x)
    {
        cout<<"Derived::f(float)"<< x <<endl;   //多态、覆盖
    }
    void g(int x)
    {
        cout<<"Derived::g(int)"<< x <<endl;     //隐藏
    }
    void h(float x)
    {
        cout<<"Derived::h(float)"<< x <<endl;   //隐藏
    }
};
int main(void)
{
    Derived d;
    Base *pb = &d;
    Derived *pd = &d;
    // Good : behavior depends solely on type of the object
    pb->f(3.14f);   // Derived::f(float) 3.14
    pd->f(3.14f);   // Derived::f(float) 3.14

    // Bad : behavior depends on type of the pointer
    pb->g(3.14f);   // Base::g(float)  3.14
    pd->g(3.14f);   // Derived::g(int) 3 

    // Bad : behavior depends on type of the pointer
    pb->h(3.14f);   // Base::h(float) 3.14
    pd->h(3.14f);   // Derived::h(float) 3.14
    return 0;
}

令人迷惑的隐藏规则
本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual 关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

上面的程序中:
(1)函数Derived::f(float)覆盖了Base::f(float)。
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。

C++纯虚函数

一、定义

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
virtual void funtion()=0;

二、引入原因

1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。

2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

三、相似概念

1、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a、编译时多态性:通过重载函数实现
b、运行时多态性:通过虚函数实现。

2、虚函数
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态覆盖(Override)

3、抽象类
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++多态性 的相关文章

  • C++_生成随机字符串

    include
  • C#中unsafe的使用

    1 unsafe在C 程序中的使用场合 实时应用 采用指针来提高性能 引用非 net DLL提供的如C 编写的外部函数 需要指针来传递该函数 调试 用以检测程序在运行过程中的内存使用状况 2 使用unsafe的利弊 好处是 性能和灵活性提高
  • 每天都在谈SOA和微服务,但你真的理解什么是服务吗?

    近几年来 我一直从事着和面向服务相关的底层软件研发工作 逐渐的形成了一些自己的看法 其中我觉得比较重要的看法就是服务需要一个更准确细致的定义 简单来说 服务的本质就是行为 业务活动 的抽象 为了更好的阐述新服务的概念 并方便与传统的SOA中
  • 五. python面向对象(多态 和metaclass=abc.ABCMeta)

    一 多态 多肽 一种事物的多种形态 叫多肽 例如 动物 animal 猫 狗 animal py 文件 动物类 都有名字这个属性 和吃这个方法 class Animal object def init self name self name
  • 解决“17: 错误:程序中有游离的‘\240’,\302’

    参考链接 https blog csdn net asuphy article details 54602426 执行如下命令即可 sed i s o240 o302 g dy haikang test cpp
  • C语言pcre库的使用及验证IP地址的合法性

    PCRE是一个用C语言编写的正则表达式函数库 它十分易用 同时功能也很强大 性能超过了POSIX正则表达式库和一些经典的正则表达式库 在使用PCRE库时 首先肯定是需要安装pcre的 不过一般的系统都会有自带的PCRE库 不过如果想使用最新
  • c++得到窗口句柄

    include
  • C/C++中浮点数格式学习——以IEEE75432位单精度为例

    这是浮点数的通常表示形式 在IEEE754中 单精度浮点数有如下形式 32位单精度 单精度二进制小数 使用32个比特存储 1 8 23位长 S Exp Fraction 31 30至23偏正值 实际的指数大小 127 22至0位编号 从右边
  • 无法打开源文件<sys/time.h>,但是用time.h编译就会出错,缺少gettimeofday()

    因为sys time h是uinx系统下的库文件 而现在使用的平台是在windows 由于未指明程序运行的系统 导致找不到对应的头文件 需要重新实现gettimeofday 函数 define WIN32 include
  • 使用QZXing生成并解析二维码

    QZxing 是对 zxing 的一个封装 用于在 Qt 程序中加入条形码和二维码识别的功能 这里就讲讲如何编译和使用这个库 前几年 QZXing 的代码是放到 sourceforge net 上的 现在迁移到了 github com 所以
  • 经典面试题之new和malloc的区别

    new和malloc的区别是C C 一道经典的面试题 我也遇到过几次 回答的都不是很好 今天特意整理了一下 0 属性 new delete是C 关键字 需要编译器支持 malloc free是库函数 需要头文件支持 1 参数 使用new操作
  • BP学习算法-构建三层神经网络

    引 人工神经网络 Artificial Neural Networks 简写为ANNs 也简称为神经网络 NNs 或称作连接模型 Connection Model 是一种模仿动物神经网络行为特征 进行分布式并行信息处理的算法数学模型 这种网
  • vector,list,deque区别

    http blog csdn net renkaihao article details 6803866 vector和built in数组类似 它拥有一段连续的内存空间 并且起始地址不变 因此它能非常好的支持随即存取 即 操作符 但由于它
  • floor(),ceil()函数

    地板 天花板函数 均包含在math h中 意思分别为 返回不大于形参的最小整数和不小于形参的最大整数 include
  • lua和测试(一)

    lua做为一门高级语言 在游戏产业运用到机会越来越多了 测试掌握几门脚本语言也有一定的重要性 以下对于lua组合输入做出一些引导 测试需要掌握的关于返回数值 主要用到布尔类 前言的指引 lua的语法比较简单和清晰 学过c语言的可以很好的掌握
  • Trace Function Enter, Exit and Leave

    http developer nokia com community wiki Trace Function Enter Exit and Leave
  • 模板的完全特例化和部分特例化

    介绍 完全特例化就是类型完全明确的版本 而部分特例化指的是 只知道是几个参数的函数而不知道参数的类型 或者是只知道是引用或者是指针类型 而不知道具体是char 还是 int 模板特例化实例1 template
  • C++ 字符串比较------strcmp函数和strncmp函数

    strcmp 函数原型 int strcmp const char str1 const char str2 功能 strcmp函数会按照字典顺序逐个比较两个字符串的字符 直到遇到不同的字符或者遇到字符串结束符 0 返回值 该函数返回值如下
  • C++实现函数重载的原理

    一 函数重载的概念 C 中允许存在同名函数 但要求函数参数的类型 个数不同 这些同名函数就称为函数的重载 void func int a int b cout lt lt func int a int b lt lt endl void f
  • C中的内存使用问题

    请帮忙 操作系统 Linux 其中 sleep 1000 中 此时 top 显示Linux任务 给我写了7 7 MEM使用 valgrind 未发现内存泄漏 我明白 写得正确 所有 malloc 结果都是 NULL 但是为什么这次 睡眠 我

随机推荐

  • ==和equals的区别

    1 在八种基本类型中 比较的是值的本身 eg public class Damo2 public static void main String args int str 10 int str1 10 System out println
  • ROS navigation的学习和分析

    ROS navigation功能包简单来说就是输入传感器信息和机器人位姿 通过导航算法输出机器人的速度控制指令实现机器人的2D路径规划 贴出代码库 navigation github官方仓库 以下是ROS官方的文档 navigation官方
  • avue-crud 组件,form中实现树形下拉框联动输入框数据,省市区字典联动

    1 需要实现的功能是 当我选择一条数据的时候 后面几个输入框会自动带入 使用的是avue crud组件 参数配置
  • 数据结构--环形队列的介绍与实现

    数据结构 环形队列实现 一 环形队列实现原理 环形队列的几个判断条件 二 代码实现 1 环形队列类 CircleQueue 2 环形队列类测试类 3 程序运行结果 4 完整代码 环形队列可以用数组实现 也可以使用循环链表实现 在使用数组实现
  • 2023前端面试题总结(vue,react)

    Vue 1 MVC与MVVM的区别 MVC和MVVM的区别并不是VM完全取代了C ViewModel存在目的在于抽离Controller中展示的业务逻辑 而不是替代Controller 其它视图操作业务等还是应该放在Controller中实
  • Vue-条件渲染和循环渲染

    文章目录 条件渲染 循环渲染 条件渲染 条件渲染指令是用来辅助开发者控制DOM的显示与隐藏 条件渲染指令有如下两个 分别是 v show和v if v show和v if的区别 v show是通过动态的为元素添加或移除display non
  • 【计算机视觉】ViT:Vision Transformer 讲解

    有任何的书写错误 排版错误 概念错误等 希望大家包含指正 在阅读本篇之前建议先学习 自然语言处理 Attention 讲解 自然语言处理 Transformer 讲解 自然语言处理 BERT 讲解 ViT Vision Transforme
  • 眼光独到便能发现刷脸支付带来的商机

    2020年 属于扫码支付的时代已经过去 刷脸支付时代悄然而至 在5G和数字化时代的引领下 刷脸支付将充满无限可能 率先发现商机并加入刷脸支付的人 可以早日占据移动支付刷脸支付市场的一片天空 在现金支付还是主流的时代 人们对扫码支付的概念十分
  • 坦克大战JAVA程序(韩顺平老师)

    坦克大战java程序 目前程序完成的功能 1 打开游戏选择继续上一局游戏还是重新开始 2 页面打开后有一段背景音乐 增加体验感 3 页面显示击毁的敌方坦克数量 4 玩家坦克由wasd键控制方向 j键控制射击 敌方坦克被击中后 出现爆炸效果
  • Codeforces Round #752 (Div. 2) C. Di-visible Confusion (思维暴力))

    题解 根据题意如果2 i 1范围内没法整除就删掉嘛 所以这题其实最多暴力前100就行 证明 因为如果前100有不能整除的 那么这个数一定可以删掉 但是如果前100都能被整除 也就是前100每个数都是这个数的因子 那么这个数就太大了绝对超过了
  • 力扣简单算法题

    简单题 一分类 数组 1 两数之和 哈希表 给定一个整数数组 nums 和一个整数目标值 target 请你在该数组中找出 和为目标值 target 的那 两个 整数 并返回它们的数组下标 你可以假设每种输入只会对应一个答案 但是 数组中同
  • 数据结构作业:时间复杂度和二叉树

    计算时间复杂度 int x 0 i j 1 for i 1 i
  • 小程序可以通过以下几种方式下发消息

    1 模板消息 小程序可以通过模板消息向用户发送通知 例如订单状态更新 活动提醒等 开发者需要先在小程序后台设置好模板消息 并获取到模板消息的模板ID 然后在代码中调用相应的API 将模板消息的内容填充并发送给用户 以下是一个示例代码 用于发
  • CSS开发技巧(四):解决flex多行布局的行间距异常、子元素高度拉伸问题

    在使用flex布局时 若出现换行 有两种较为特殊的现象是值得我们研究的 子元素高度被拉伸 其实际高度大于它的内容高度 各行子元素之间的行间距过大 甚至我们根本没有给子元素设置margin 现在我们将要探究引发这两种现象的原因及解决方案 一
  • for循环三种跳出循环的方法(retrun、continue、break)Mark

    1 continue 指的是跳出当前循环 即不执行continue后的语句 直接进入下次循环 continue语句和break语句差不多 不同的是 它不是退出一个循环 而是跳出当前循环 进行下一轮循环 public static void
  • mac 下的 bash gradle command not found

    最近在用android studio 使用命令行打包的时候出现 bash gradle command not found这个问题 其实也就是因为自己唑gradle的环境变量给弄丢了 但是由于来自大山的孩子对于mac不是很熟 所以不知道咋去
  • Redis3.0.7 cluster/集群 安装配置教程

    原文地址 http blog csdn net ouchuquan article details 50884479 参考地址 http doc redisfans com topic cluster tutorial html http
  • ios 轻 App

    整体建议 使用在一些展示项目中 少量数据产生的 需要借助apple平台让用户了解到的 另外 该功能也可以作为品牌宣传 特别是一些景点之类 简单 页面的 轻 App 是 app 的一小部分 因此您可以使用 iOS SDK 在与完整 app 相
  • Rational rose 2007 下载和安装教程

    文章目录 Rational rose 简介 一 下载Rational rose 2007 二 安装Rational rose 2007 三 激活Rational Rose 2007 四 启动Rational Rose 2007 Ration
  • C++多态性

    本文参考浅谈C 多态性 多态性可以简单地概括为 一个接口 多种方法 程序在运行时才决定调用的函数 它是面向对象编程领域的核心概念 多态 polymorphism 字面意思多种形状 C 多态性是通过虚函数来实现的 虚函数允许子类重新定义成员函