【C++】多态概念(入门)

介绍:

        多态的概念:通俗来说,多态就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。比如扫红包操作,同样是扫码动作,不同的用户扫 得到的不一样的红包,这就是一种多态行为。

多态的构成条件

        首先,我们先来认识一下虚函数。在类中,被virtual修饰的类成员函数称为虚函数。如下;

class Person {
public:
    virtual void BuyTicket()  //虚函数

    {

        cout << "买票-全价" << endl;

    }
};

        多态的构成需要两个条件:1,必须通过基类的指针或者引用调用虚函数,这时传入的是父类调用的是父类虚函数,传入的是子类调用的是子类的虚函数。2,被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(重写:派生类中有一个跟基类完全相同的虚函数,即派生类虚函数与基类虚函数的 返回值类型、函数名字、参数列表完全相同,称子类的虚函数重写了基类的虚函数)。

        注意:多态中,派生类的virtual写不写都可以,但是基类的virtual必须写上。

#include <iostream>
using namespace std;
class Person 
{
public:
    virtual void BuyTicket() 
    {
        cout << "买票-全价" << endl; 
    }
};
class Student : public Person 
{
public:
    virtual void BuyTicket() 
    {
        cout << "买票-半价" << endl;
    }
    /*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用,如以下*/
   /*void BuyTicket() { cout << "买票-半价" << endl; }*/

   //注意:基类无论在什么情况下若不加virtual就无法构成重写,也就说基类必须写上virtual
};
void Func(Person& p)  //通过基类Person引用调用虚函数
{
    p.BuyTicket();
}
void Func2(Person p)  
{
    p.BuyTicket();
}
int main()
{
    Person ps;
    Student st;
    Func(ps);
    Func(st);
    cout << endl;
    //下面的调用将全部调用基类的函数
    Func2(ps);
    Func2(st); 
    return 0;
}

        总的来说在继承体系中存在两种调用:普通调用和多态调用

                普通调用:系统看的是指针或引用或对象本身的类型。   

                多态调用:系统看的是具体指向的对象。

虚函数重写的两个例外

1,协变:基类与派生类虚函数返回值类型不同,但这里返回值必须要求是父子类关系的指针或引用。(协变本身没有意义,这里只需了解即可)

#include <iostream>
using namespace std;
class A 
{    };
class B : public A
{    };
class Person {
public:
    virtual A* f() {
        cout << "Person A::f()" << endl;
        return new A; 
    }
    void fun()
    {
        cout << "class Person" << endl;
    }
};
class Student : public Person {
public:
    virtual B* f() {
        cout << "Student B::f()" << endl;
        return new B;
    }
    void fun()
    {
        cout << "class Student" << endl;
    }
};

int main()
{
    Person* p = new Student; 
    p->f(); //调用子类Student函数,即多态协变
    p->fun();  //调用父类Person函数,即继承赋值转换,不是隐藏
    Person p1;
    Student s;
    p1 = s;
    p1.fun();  //同理,调用父类Person函数
    return 0;
}

2. 析构函数的重写(基类与派生类析构函数的名字不同)

        继承体系中,基类与派生类的析构函数中存在问题。比如当我们使用基类指针指向派生类地址空间时,由于指针指向的是派生类中基类的地址,所以这里结束时默认调用基类析构函数,不会调用派生类析构函数,这将会导致派生类中部分空间没有释放,导致内存泄漏问题。因此,这里需使用虚函数,使其调用派生类的析构函数。

        如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字, 都与基类的析构函数构成重写,这里是特殊情况,这里可以理解为编译器对析构函数的名称做了特殊处理。

#include <iostream>
using namespace std;
class Person {
public:
    virtual ~Person() { 
        cout << "~Person()" << endl;
    }
};
class Student : public Person {
public:
     virtual ~Student() { 
        cout << "~Student()" << endl;
    }
};

int main()
{
    // 析构函数在没有加virtual关键字前,下面的两次清理空间都调用Person的析构函数。
    Person* p1 = new Person;
    delete p1;
    //在没有加上virtual前,开辟派生类Student空间中的析构函数没有调用,即派生类中部分空间没有清理,可能会导致内存泄漏
    //加上virtual后,将会调用指向对象的析构函数,即子类Student的析构函数,之后会调用基类Person的析构函数

    Person* p2 = new Student; 
    delete p2;
    return 0;
}

C++11标准的override和final

1,final:它即可修饰虚函数,表示该虚函数不能再被重写,也可修饰类,表示此类不能被继承

#include <iostream>
using namespace std;

class A final
{
public:
    A()
    {    }
};
class B : public A //直接报错,final修饰的类不可被继承
{
public:
    B()
    {    }
};

class A
{
public:
    virtual void Drive() final 
    {    }
};
class B : public A
{
public:
    virtual void Drive() { //基类Drive虚函数加上final,不能被重写
        cout << "B" << endl;
    }
};

        说到这里,要想父类不被子类继承,除了使用final关键字外,只需将父类的构造函数私有化,使子类实例不出对象即可。

class A
{
private:
    A()
    {    }
};
class B : public A
{
public:
    B()  //在子类B中不能调用父类A的构造函数,即不能被继承
    {    } 
};

2,override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。此关键字通常用来检查重写有没有问题。

#include <iostream>
using namespace std;
class A {
public:
    virtual void Drive() 
    {    }
    void test()
    {    }
};
class B : public A {
public:
    virtual void Drive() override { 
        cout << "B" << endl;
    }
    virtual void test() override { //没有重写基类虚函数,报错

    }
};
int main()
{
    A a;
    B b;
    return 0;
}

        最后,我们总结重载、重写和隐藏。三者关系非常紧密,如下图:

        在多态中,这里还有很多注意点,我们先以下面代码的典型例子来分析多态的运用。

#include <iostream>
using namespace std;
class A
{
public:
    virtual void func(int val = 1) { 
        cout << "A->" << val << endl;
    }
    virtual void test() { 
        func();  //这里通过this指针调用func,但这里属于多态调用,指向是对象是子类,所以会调用子类func
        //注意:val的数值为1是因为这里可看成基类声明func,子类定义func

    }
};
class B : public A
{
public:
    void func(int val = 0) {
        cout << "B->" << val << endl; 
    }
};
int main()
{
    B* p = new B;
    B b;
    //下面两次都输出B->1
    b.test(); 
    p->test();
    return 0;
}

         我们知道,在重写中,只要派生类写上virtual,基类可不用写virtual,这是因为virtual关键字的主要目的是在基类中标记一个函数,以便在派生类中可以重写它以实现多态性。在以上代码逻辑中,这里可看成基类实现虚函数的声明,派生类实现虚函数的定义。因此val的数值为1。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/491313.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【卷积神经网络中用1*1 卷积有什么作用或者好处呢?】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;深度学习 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 1*1 卷积有什么作用或者好处呢 作用降维和增加非线性特征组合和交互网络的宽度和深度调整全连接替代增强…

Rust升级慢,使用国内镜像进行加速

背景 rustup 是 Rust 官方的跨平台 Rust 安装工具&#xff0c;国内用户使用rustup update的时候&#xff0c;网速非常慢&#xff0c;可以使用国内的阿里云镜像源来进行加速 0x01 配置方法 1. Linux与Mac OS用户配置环境变量 修改~/.bash_profile文件添加如下内容&#xff1…

Vue26 内置标签 v-text v-html

实例 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>v-text指令</title><!-- 引入Vue --><script type"text/javascript" src"../js/vue.js"></script></head><…

苍穹外卖项目微信支付(没有商户号)的解决方法,超详细!!!

今天在写苍穹外卖项目时&#xff0c;写到微信支付时发现个人无法获取商户号&#xff0c;那么今天我就在这里分享一个方法&#xff0c;可以绕过微信支付实现订单支付的功能。本方法仅仅是绕过微信支付&#xff0c;没有进行真正的微信支付&#xff0c;如果想要体验真正的微信支付…

【并发】CAS原子操作

1. 定义 CAS是Compare And Swap的缩写&#xff0c;直译就是比较并交换。CAS是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令&#xff0c;这个指令会对内存中的共享数据做原子的读写操作。其作用是让CPU比较内存中某个值是否和预期的值相同&#xff0c;如果相…

“点击查看显示全文”遇到的超链接默认访问的问题

今天在做一个例子&#xff0c;就是很常见的点击展开全文。 我觉得这是一个很简单的效果&#xff0c;也就几行代码的事&#xff0c;结果点击了以后立刻隐藏不见&#xff0c;控制台代码也不报错&#xff0c;耽误了我很长时间&#xff0c;最后才发现问题出在超链接身上。 “展开全…

Escalate_Linux-环境变量劫持提权(5)

环境变量劫持提权 在Shll输入命令时&#xff0c;Shel会按PAH环境变量中的路径依次搜索命令&#xff0c;若是存在同名的命令&#xff0c;则执行最先找到的&#xff0c;若是PATH中加入了当前目录&#xff0c;也就是“”这个符号&#xff0c;则可能会被黑客利用&#xff0c;例如在…

flink源码分析 - 获取调用位置信息

flink版本: flink-1.11.2 调用位置: org.apache.flink.streaming.api.datastream.DataStream#flatMap(org.apache.flink.api.common.functions.FlatMapFunction<T,R>) 代码核心位置: org.apache.flink.api.java.Utils#getCallLocationName() flink算子flatmap中调用了一…

[力扣 Hot100]Day35 LRU 缓存

题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否…

python_pyecharts绘制漏斗图

python-pyecharts绘制漏斗图 from pyecharts.charts import Funnel from pyecharts import options as opts# 数据 data [("访问", 100), ("咨询", 80), ("订单", 60), ("点击", 40), ("展现", 20)]# 创建漏斗图 funnel …

【论文解读】transformer小目标检测综述

目录 一、简要介绍 二、研究背景 三、用于小目标检测的transformer 3.1 Object Representation 3.2 Fast Attention for High-Resolution or Multi-Scale Feature Maps 3.3 Fully Transformer-Based Detectors 3.4 Architecture and Block Modifications 3.6 Improved …

如何利用EXCEL批量插入图片

目录 1.excel打开目标表格&#xff1b; 2.点开视图-宏-录制宏&#xff0c;可以改宏的名字或者选择默认&#xff1b; 3.然后点开视图-宏-查看宏 4.点编辑进去 5.修改代码&#xff1a; &#xff08;1&#xff09;打开之后会显示有一堆代码 &#xff08;2&#xff09;将这个…