IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++多态 -> 正文阅读

[C++知识库]c++多态


多态感觉真挺难的,这篇就水一水了

引言

多态:多种形态
静态的多态:函数重载,同一个函数看起来调用同一个函数有不同的行为
动态的多态:
静态:是在编译的时候实现的

动态的多态

一个父类的引用或指针去调用同一个函数,如果传递不同的对象,会调用不同的函数
动态:运行时实现的

举个例子:
买票这个行为,对于不同的人价格不同,普通人去买价格低,而有钱人买价格高

扫红包:也是一种多态行为,根据行为去分类,鼓励新用户用,给的红包就多,

虚函数

子类中满足三同
函数名,参数,返回值相同的虚函数,就叫做重写(覆盖),只有虚函数
本质就是不同的人去做同一件事情,结果不同
virtual(只能是类的非静态的成员函数才能是虚函数):其他函数不能称为虚函数
:在最前面加一个virtual

静态成员函数不能加virtual

否则就是隐藏的关系

多态的构成条件(少一个都不行)

  1. 必须通过基类指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

重写要求返回值相同,有一个例外:协变:要求返回值是父子关系的指针或者引用(基本不会见到)

class A
class B



void func(Person p)//我们没有用父类的指针和引用,而是用对象,就不能实现多态
{
    p.BuyTicket();
}
func(st);//正常来说因为我们是父类的引用,所以的对父类使用,一定是没有问题的,但是这个对子类也是可以使用的
    func(ps);//

析构函数的重写(析构函数)

析构函数也可以是虚函数,构成重写(函数名,返回值,参数,virtual)都相同
把析构函数都被特殊处理成destructor,
完成重写,构成多态,那么才能正确调用析构函数
只有动态申请的对象给了父类指针管理才可以,那么就需要定义成虚函数


class Person
{
public:
    virtual ~Person()//析构的名字都被特殊处理过了,差个虚函数的条件,这样就能够实现多态了
    {
        cout << "person" << endl;
    }
};
class Student : public Person
{
public:
    virtual ~Student() //

    {
        cout << "student" << endl;
    }
};

//析构函数是否是虚函数都正常调用了
int main()
{
    /*
    Person p;
    Student s;//person也会被析构,但是没加virtual也没有关系,
    */
   //
    Person *p1 = new Person; // new 是开空间+构造函数
    Person *p2 = new Student;//这里是基类的指针,这里是父类的指针,就需要多态了
    Student *p3 = new Student;//这里是基类的指针,z这里不需要多态


    //p1->destructor()
    //p2->destructor()
    


    //多态申请的对象如果给了父类指针管理,那么需要析构函数是虚函数(我们直接全部都)
    delete p1;//析构函数+释放空间
    //我期望p1这里调用父类的析构函数
    delete p2;//指向student就掉student,又会掉person
    delete p3;
    //这里调用子类的析构函数
    //这里不构成多态,都指向person,因为p2也是person
    //构成多态才可以
    return 0;
}

虚函数的重写允许
:两个都是虚函数,或者父类是虚函数,再满足三同就构成重写
虽然子类没有写virtual
是因为他继承了父类的virtual的属性,也完成了重写(是因为析构函数,父类析构函数加上virtual,就不存在不构成多态的了,没钓到子类析构函数,内存泄漏的)
:在public:private也是可以的

一般来说父类是别人写的,子类是我们写的,只要父类加了virtual,子类就不用加virtual
我们建议都写上

C++11 override 和final

final

设计一个不能被继承的类
c++ 98的方式

//设计一个无法被继承的基类,只能让基类的构造函数变成私有,因为初始化子类对象的时候要去调用其父类的构造函数,但是没法使用,间接限制
//c++ 98的方式
class A
{
private:
    A(int a = 0)
        : _a(a)
    {
    }

public:
    //因为如果是一个成员函数,返回A 他必须是一个对象,但是他没有对象,加了一个静态就可以使用了
    static A createobj(int a = 0) //调用这个函数就可以使用了,获得父类的构造函数,如果是成员函数
    //静态的成员函数,没有this指针,只能访问静态的成员变量,和成员函数,不能访问_a 这些成员变量
    {

        return A(a);
    }

protected:
    int _a;
};
class B:public A
{

};
//子类继承了父类,但是构造函数被变成私有的了
int main()
{
    //因为我们把A 类型的构造函数变成一个私有的构造函数,所以他没有办法被使用,但是我们写了一个静态的成员函数就可以获得这个东西
    A aa=A::createobj(10);//就可以这样把对象创建出来了

    return 0;
}

c++ 11

  1. 使用final 这个类就无法被继承
class A final//不希望A 被继承

{
    protected:
    int _a;
};

class B:public A
{

};

int main()
{
    return 0;
}
  1. 使用final这个函数就无法被重写,放在父类中

class C 
{
    public:
    virtual void f() final// 我不想他被重写
    {
        cout<<"hello "<<endl;
    }
};
class D :public C
{
    public:
    virtual void f()//重写
    {
        cout<<"world"<<endl;
    }
};

int main()
{
    return 0;
}

override

同样也是放在类的后面
放在子类重写的虚函数的后面,检查是否完成重写,如果没有完成重写就会报错

class car 
{
   public:
   virtual void drive()
   {

   }
};


class benz: public car
{
   public:
   virtual void drive()  override
   {

   }
};

int main()
{
   return 0;
}

在这里插入图片描述
没加之后的报错

重载,覆盖,隐藏的对比

重载:

  1. 两个函数在同一个作用域
  2. 函数名相同,参数不同,返回值没有要求

覆盖:
3. 两个函数分别在基类和派生类中的作用域中
4. 函数名/参数/返回值,都相同(协变除外)
5. 两个函数必须是虚函数

重定义(隐藏)
6. 一个在基类一个在派生类
7. 函数名相同
8. 不构成重写就是隐藏

抽象类

在虚函数的后面加上=0,这个函数就叫做纯虚函数,
包含纯虚函数的类就叫做抽象类,不能实例化出对象,
派生类继承以后,也不能实例化出对象,只有子类重写这个虚函数,才能实例化出对象

底层剖析

在这里插入图片描述
我们会发现子类和父类是不一样的虚表,下面继承的是重写的虚函数(覆盖)
父类对象是父类对象的虚函数,子类对象是子类对象的虚函数
重写是语法层面,覆盖是原理上
覆盖成我重写的虚函数,

多态为什么必须是指针和引用呢?
指针和引用,虚表就是各自的,而用对象,那么虚表都是父类的,无法调用子类的,切片没有办法把虚表指针拷过去

多态的原理:
基类的指针/引用,指向谁就去谁的虚函数表中找到对应的虚函数进行调用,在父类里面找到父类的虚函数,在子类里面找到子类的虚函数,

同类型的对象,他的虚表指针是一样的,指向同一张虚表,都是指向它的
在这里插入图片描述
普通函数和虚函数都存储在代码段,只不过虚函数要把地址存一份到虚表,方便实现多态,

多态就算是私有的也可以调用,因为它是从虚表里面去找的,所以没有影响,有了虚函数这一个,私有也都是可以调用的,

总结:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员
  2. 基类b对象,和派生类d对象虚表是不一样的,这里我们发现func完成了重写,覆盖是指虚表中虚函数的覆盖,重写是语法上的 叫法,覆盖是原理层的叫法
  3. 子类继承下来没有重写的一部分,地址还是和父类的虚函数一样
  4. 子类也写了虚函数,这个虚函数也会在需表里面,只不过我们不好看

虚函数表在代码端(OS)/常量区(c语言)里面
虚表里面存的地址不是函数真正的地址,而是要跳几次才可以跳到目的函数

多继承中的虚表

打印虚表

class Base
{
public:
    virtual void func1() { cout << "base1::func1" << endl; }
    virtual void func2() { cout << "base1::func2" << endl; }

private:
    int a;
};


class driver : public Base
{
public:
    virtual void func1()
    {
        cout << "driver func1" << endl;
    }
    virtual void func3()
    {
        cout << "deriver func3" << endl;
    }
    void func4()
    {
        cout << "derive func4" << endl;
    }

private:
    int b;
};


typedef void(*vfptr)() ;//函数指针的typedef方式不一样,把名字定义在中间,重命名为vfptr
//对函数指针进行重命名


void printvftable(vfptr table[])//函数指针数组
{
    for(int i=0;table[i]!=nullptr;i++)//在windows下虚表最后一个元素是空
    {   
        printf("vft[%d] :%p\n",i,table[i]);
        //我们有了一个函数地址,那么就可以调用它
        vfptr f=table[i];//f指向了函数指针的地址
        f();//调用了这个函数
        //我们发现这个是可以调用的
        //就算是私有的我们也可以调用到,因为我们这里不是通过直接调用,而是通过取出来了虚函数的地址,
        //这里不是通过对象调的
        //有了虚表就会有一些安全隐患

    }
    cout<<endl;
}
int main()
{
    person mike;
    student jason;
    func(mike);  //到mike里面找到虚函数指针,在虚函数指针里面找到对应的虚函数,然后调用它
    func(jason); //如果是子类,就会发生切割(切片),
    person &r1 = jason;
    person r2 = jason;
    student s1;
    student s2;

    derive d; //多继承之后,就有两个虚表,
    //子类里面有两张虚表,它自己的虚函数只放一个虚表,放在第一个继承的虚表里面
    // base1和base2里面都有func1,但是发现他们的地址不一样,只是jump指令,最后会jump到一个地方去了

    Base b;//虚表的指针在初始化列表(构造函数)里面处理,内置类型不处理,自定义类型调用自己的构造函数处理
    //vfptr(virtual function table pointer )

    printvftable((vfptr*)(*(void**)&b));//取虚表数组的首元素地址,在这个地方的头4个字节, 先转化成int* 再解引用就变成int了可以获得头4个字节了,但是类型不匹配, 所以还得再强转一下
    //取一个对象里面的虚表(虚函数指针数组)
    //这样就弄出来了虚表里面的两个函数
    //不能直接转化的,不是所有类型都可以强制类型转化的(不相关的类型是不可以直接转化的,相关类型之间才可以直接转),要先取出来对应的地址
    driver s;
    printvftable((vfptr*)(*(void**)&s));//取虚表数组的首元素地址,在这个地方的头4个字节, 先转化成int* 再解引用就变成int了可以获得头4个字节了,但是类型不匹配, 所以还得再强转一下

    //重写的func1,拷贝下来的func2,还有自己的func3
    
    return 0;
}

在这里插入图片描述
打印成功
在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-21 18:44:48  更:2022-05-21 18:46:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:10:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码