首页 > 编程语言 >C++基础/C++中的多态(关于虚...)

C++基础/C++中的多态(关于虚...)

时间:2024-08-28 17:37:51浏览次数:8  
标签:... 函数 子类 绑定 多态 C++ 父类 指针

C++中的多态(关于虚...)

1.前置基础知识

1.1对象是如何存储在内存中的

#include <iostream>
#include <string>

class Animal {
private:
    string name;
    int age;
    
public:
    Animal(std::string name, int age) : name(name), age(age) {};
    ~Animal();
    virtual void eat() {
        std::cout << "Animal在吃东西" << std::endl;
    }
};

class Cat : public Animal {
private:
    int lives = 0;

public:
    Cat(int lives) : Animal("cat", 18), lives(lives) {}
    virtual void eat() {
        std::cout << "cat在吃鱼" << std::endl;
    }
};
 
int main() {
    Animal a("dog", 18);   // 属性会单独的存储在一个连续的内存空间中,但是函数大家都是一样的,所以不会重复拷贝一份函数和属性存储在一起,是放在另一快内存空间,供大家一起调用
    Animal b("cat", 12);   
    
    Cat c(8);  // 会申请一片内存空间,从父类继承过来的属性以及虚函数表指针存储在子类的属性和虚函数表指针前,是连续的
    
    return 0;
}

1.2虚函数

#include <iostream>
#include <string>

class Animal {
public:
    std::string name;
    int age;

public:
    Animal(std::string name, int age) : name(name), age(age) {};
    ~Animal();
    virtual void eat() {
        std::cout << "Animal在吃东西" << std::endl;
    }
};

class Cat : public Animal {
private:
    int lives = 0;

public:
    Cat(int lives) : Animal("cat", 18), lives(lives) {}
    virtual void eat() {
        std::cout << "cat在吃鱼" << std::endl;
    }
};



int main() {
    Animal* c = new Cat(8);
    c->eat();

    return 0;
}

在这里插入图片描述

  • 因为是Animal类型,所以调用的eat方法也就是Animal的eat方法

在这里插入图片描述

  • 给函数加上virtual关键字就代表是虚函数,这个时候调用会根据子类的虚函数表指针去查,根据对象去判断使用父类的方法还是子类重写后的方法

1.3虚函数表

在这里插入图片描述

  • 加上virtual关键字变为虚函数的函数都会存在虚函数表中,父类子类都有,一开始父类子类的虚函数表都是一样的,子类重写后,就会覆盖子类修函数表对应的位置上的内容

1.4虚函数表指针

在这里插入图片描述

  • 虚函数表指针顾名思义就是一个指针,指向于虚函数表,与对象的属性存储在一起

  • 虚函数 虚函数表 虚函数表指针关系

    在这里插入图片描述

1.5动态绑定vs静态绑定

  • Animal* c = new Cat("helloKitty", 18);上述这一行代码,为什么必须是指针或者引用?

    • 静态绑定+对象切片

      • 因为如果Animal c = new Cat("helloKitty", 18);这样写,就声明了c是Animal类型的,但是Cat构造法方法中多了一个属性,且重写了,会直接截取对象,使得c对象缺少lives这个属性和重写后的方法·。此时也为静态绑定,在编译的时候就根据对象类型已经确定了对应方法和属性,、。

        对象切片:当你将子类对象赋值给父类对象时,只有父类部分被复制,子类特有的部分被"切掉"了。 - 这意味着你失去了子类的特定信息和行为。

        静态绑定: - 使用对象(而不是指针或引用)调用方法时,编译器会在编译时决定调用哪个函数版本。 - 这种绑定是基于变量的声明类型,而不是实际对象的类型。

    • 动态绑定

      • 使用父类对象的指针或引用指向子类对象,并不会发生对象切片。因为只是把子类对象的地址赋值给父类指针(把地址传给指向父类指针,由于是虚函数,此时编译器不知道是什么类型),后在运行的时候确定类型,并根据虚函数指针查找虚函数表确认函数地址。
  • 总结

  1. 静态绑定(Static Binding):
    • 发生时间:编译时
    • 决定因素:变量的声明类型
    • 工作原理:编译器根据变量的声明类型直接决定调用哪个函数
    • 适用情况:非虚函数调用,或通过对象(非指针/引用)调用函数
    • 优点:运行效率高,没有额外开销
    • 缺点:不支持多态
  2. 动态绑定(Dynamic Binding):
    • 发生时间:运行时
    • 决定因素:指针或引用所指对象的实际类型
    • 工作原理:通过虚函数表查找并调用正确的函数
    • 适用情况:通过基类的指针或引用调用虚函数
    • 优点:支持多态,提高代码的灵活性和可扩展性
    • 缺点:相比静态绑定有轻微的性能开销

关键区别:

  1. 确定时机:
    • 静态绑定在编译时确定调用哪个函数。
    • 动态绑定在运行时确定调用哪个函数。
  2. 类型判断:
    • 静态绑定基于编译时已知的类型信息。
    • 动态绑定基于运行时对象的实际类型。
  3. 灵活性:
    • 静态绑定对于编译器来说更直接,但缺乏运行时的灵活性。
    • 动态绑定允许在运行时根据实际情况选择正确的函数版本。
  • 一句话总结区别
  • 静态绑定在编译的时候就确定了,效率较高、内存占用少(编译器默认都是静态绑定)。动态绑定是只有是虚函数才会发生,不然就根据声明类型编译时就确定了。

2.实现多态

2.1何为多态(分为静态多态和动态多态)

  • 动态多态

    • 动态多态:它允许我们通过基类的引用或指针来调用派生类的重写方法,这样就能在不修改代码的情况下实现多态行为,提高代码的复用性和扩展性。

      我理解的多态是,在编写任何相关代码时,如果声明传入的对象是父类的,可以根据虚函数,传入子类从而调用子类重写的方法,达到极高的代码复用性。

  • 静态多态

    • 静态多态:在编译时就确定使用的方法,可以通过重载和或者模板

2.2动态多态必须具备的条件

  1. 首先必须具备一个具有虚函数的父类和重写虚函数的子类
  2. 父类指针或引用指向(绑定)子类对象
  3. 通过动态绑定实现多态
#include <iostream>
#include <string>

class Animal {
public:
    std::string name;
    int age;

public:
    Animal(std::string name, int age) : name(name), age(age) {};
    virtual void eat() {           // 1.父类虚函数
        std::cout << "Animal在吃东西" << std::endl;
    }
    virtual ~Animal() {
        delete this;
    }
};

class Cat : public Animal {
private:
    int lives = 0;

public:
    Cat(int lives) : Animal("cat", 18), lives(lives) {}
    virtual void eat() {          // 1.子类重写父类虚函数
        std::cout << "cat在吃鱼" << std::endl;
    }
};

void sleep(Animal& a) {           // 2.父类引用指向子类对象
    a.eat();                      // 3.进行动态绑定实现多态
}

int main() {
    Animal* c = new Cat(8);
    c->eat();
    sleep(*c);
    Cat* ca = new Cat(9);         // 2.父类指针指向子类对象,这个例子可能要难理解些,因为嵌套了一层,显示一个指针再传为引用,引用依靠着这里的指针,如果指针是不满足条件的那么第二次引用也会出问题,类似于高楼大厦特别依靠地基
    sleep(*ca);

    return 0;
}

2.3静态多态

  • 这里不过多介绍,仅介绍部分概念
  • 静态多态通过函数重载和模板,函数重载常见的有运算符重载。模板可以自定义数据类型。

3.多态的注意点

3.1纯虚函数

  • 虚函数=0

    virtual ~Base() = 0

3.2虚析构函数

  • 概念

    • 虚基类: 指的是一个类由被其它类虚继承,那么该类就为虚基类。

    • 虚基类派生的子类的子类只能接受一份关于虚基类的属性与方法

  • 当父类指针或引用指向(绑定)子类对象,若虚基类中没有析构函数不是虚函数,那么只会调用父类的析构,不会调用子类的析构函数,可能会造成内存泄漏

    #include <iostream>
    
    class Base {
    public:
        Base() { std::cout << "Base constructor\n"; }
        virtual ~Base() { std::cout << "Base destructor\n"; }  // 虚析构函数
    };
    
    class Derived : public Base {
    public:
        Derived() { std::cout << "Derived constructor\n"; }
        ~Derived() { std::cout << "Derived destructor\n"; }
    };
    
    int main() {
        Base* obj = new Derived();
        delete obj; // 正确:会调用Derived的析构函数,然后再调用Base的析构函数
        return 0;
    }
    
    //输出结果
    //Base constructor
    //Derived constructor
    //Derived destructor
    //Base destructor
    

3.3抽象类和纯析构函数

  • 抽象类就是虚函数=0,抽象类必须被继承

    #include <iostream>
    
    class Base {
    public:
        Base() { std::cout << "Base constructor\n"; }
        virtual ~Base() = 0 // 虚析构函数
    };
    
    Base::virtual ~Base() {
        std::cout << "Base析构执行了" << std::endl;
    }
    
    class Derived : public Base {
    public:
        Derived() { std::cout << "Derived constructor\n"; }
        ~Derived() { std::cout << "Derived destructor\n"; }
    };
    
    int main() {
        Derived* obj = new Derived();
        delete obj;
        return 0;
    }
    
  • 纯析构是实现抽象类的一个方法,但是要注意也要实现一下纯析构函数,原因是纯虚析构函数确保基类的析构过程能被正确调用,以便释放派生类对象的资源。没有实现,析构过程会出现未定义行为。这个时候有人会问可是我加了个函数函数体但是是空的却不会有问题,因为纯虚析构和函数体为空是不同的

4多继承和菱形继承

  • 多继承:

    就是一个子类继承了多个类,注意要避免二义性,就是多个父类含有相同的命名的属性,这个时候需要通过作用域运算符进行访问"::"

  • 菱形继承

    在这里插入图片描述

    可与看出子类齐天大圣的两个父类都继承了同一个父类,那么齐天大圣就会有两个关于地球的属性,这就重复了,所以就有了虚继承

  • 虚继承: 如果多个基类共享一个公共基类,可以使用虚继承来避免菱形继承问题,确保公共基类只存在一个实例。

  • 虚基类: 指的是一个类由被其它类虚继承,那么该类就为虚基类。

  • 虚基类派生的子类的子类只能接受一份关于虚基类的属性与方法

  • 如何使用

    class Base {
    public:
        int base;
        Base() : base(0) {}
    };
    
    class Derived1 : virtual public Base {
        // 通过虚继承继承 Base
    };
    
    class Derived2 : virtual public Base {
        // 通过虚继承继承 Base
    };
    
    class Final : public Derived1, public Derived2 {
    public:
        void show() {
            std::cout << "Base::base = " << base << std::endl; // 这里访问 Base::base
        }
    };
    
    int main() {
        Final f;
        f.show(); // 正常输出 Base::base
        return 0;
    }
    

标签:...,函数,子类,绑定,多态,C++,父类,指针
From: https://www.cnblogs.com/cha9/p/18385205

相关文章

  • C++基础/C++中的多态(关于虚...)
    C++中的多态(关于虚…)1.前置基础知识1.1对象是如何存储在内存中的#include<iostream>#include<string>classAnimal{private:stringname;intage;public:Animal(std::stringname,intage):name(name),age(age){};~Animal();......
  • C++基础
    指针#include<iostream>usingnamespacestd;intmain(){//实际变量的声明intvar=20;//指针变量的声明int*addr;//在指针变量中存储var的地址addr=&var;cout<<"var="<<var<<endl;//输出在指针变量中存......
  • 你可能想知道如何下载DEV C++
    DevC++是一款适用于Windows平台的C/C++集成开发环境(IDE),它由Bloodshed公司开发,但自2011年后由独立开发者Orwelldevcpp继续更新维护。它支持C和C++编程语言,提供了编写、编译、调试和执行程序所需的基本功能。以下是下载DEVC++的步骤:官方下载渠道官方网站:访问SourceForgehttps......
  • C/C++实现JSON格式数据解析
    参考文章推荐以下几篇,针对Cjson的应用写的很详细,感谢!!!https://blog.csdn.net/xiaopengX6/article/details/104629606https://liang.blog.csdn.net/article/details/86605234运用场景在做C的项目时,对方通过TCP套接字将内容按照帧头+帧体的格式发送过来,其中帧体的内容是JSON格式......
  • 高斯坐标转WGS84 GPS坐标 C#版本 python版本和C++版本 3度带进行投影 三个版本的代码
    找了很久,都没有很靠谱的版本,这个是自己从C#版本转换的另外两个版本完整代码可以用经过了对比核对计算,确保3个版本之间的计算结果是一致的C#版本:GPSPointGSXYToGPS(doubleX,doubleY,doubleL0){//X=571879.3482847388;//Y=2770741.66......
  • C++学习随笔——lock_guard和mutex的使用方法
    std::mutex和std::lock_guard是C++标准库中用于多线程同步的工具,主要用于防止多个线程同时访问共享资源,导致数据竞争问题。 std::mutex是一个用于互斥锁的类,提供了锁定(lock)和解锁(unlock)的功能。使用方法:#include<iostream>#include<thread>#include<mutex>std::......
  • C++中常用宏
    C++中会常使用到宏来进行条件编译,或设置变量1、__cplusplus:区分当前代码为C++或C//定义只在C++中才进行extern"C"的操作#ifdef__cplusplusextern"C"{#endifvoidfun(inta,intb);#ifdef__cplusplus}#endif2、区分操作系统:_WIN32:Windows操作系统,不区......
  • c++算法3-广度优先搜索算法dfs
    搜索算法众所周知,搜索算法分为常见的两种深度优先搜索算法(dfs)广度优先搜索算法(bfs)深度优先搜索算法深度优先搜索算法就是一条道走到黑,如迷宫问题,重复不断地向前探索如果碰到死胡同就说明前面已经没有路了,这时候就可以想其他方向搜索,最终走到终点。回溯回溯是一种搜索算法......
  • 适用于多语言的VScode配置教程:同一文件夹内支持C++, JAVA, Python
    前言VScode作为一款强大的文本编辑器,只要配置恰当,便可以同时在一个环境内编译多种语言的文件。本文简要给出一种同时支持C++,Python,Java的配置方式(windows平台)。配置格式1.创建工作区并建立如图的文件夹及文件结构其中包括vscode的配置文件夹.vscode,以及其他三个代码文件......
  • C++学习随笔——算法题:全排列问题
    算法题:输入一个不存在重复字符的字符串,打印出字符串中字符的全部排列组合。代码实现:#include<iostream>#include<string>#include<vector>#include<algorithm>//std::swapvoidpermute(std::stringstr,intleft,intright){if(left==right){st......