食用指南:本文在有C基础的情况下食用更佳
🍀本文前置知识: C++初识继承
♈️今日夜电波:No title —REOL
1:02 ━━━━━━️💟──────── 4:03
🔄 ◀️ ⏸ ▶️ ☰
💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍
目录
✅虚函数的概念
⭕虚函数的结构
定义
内层结构(重要)
☑如何查看类的内层结构
✔虚函数的应用案例
✅虚函数的概念
C++面向对象中的多态性是指同一种类型的对象在不同的情况下表现出不同的行为。这种多态性可以通过虚函数来实现。在C++中,虚函数是指在基类中声明的函数,在派生类中可以被重写,从而实现不同的行为。当使用基类指针或引用调用虚函数时,实际调用的是派生类中重写的函数,而不是基类中的函数。 通过使用虚函数,可以实现运行时多态性,即在程序运行时才确定调用哪个函数,而不是在编译时确定。这种多态性可以提高程序的灵活性和可扩展性,使得程序更加易于维护和扩展。
简而言之,就是用父类型别的指针指向其子类,然后通过父类的指针调用实际子类的成员函数。
🌰
class Animal{public:void sleep(void){cout << "animal 动物在睡觉" << endl;}};class Cat :public Animal{public:void sleep(void){cout << "Cat 猫在睡觉!! 喵喵" << endl;}};void test01(){//用基类(指针或引用) 保存 子类对象(向上转换)Animal * p = new Cat;p-> sleep();//调用的是基类的sleepCat cat; Animal & ob = cat;ob.sleep();//调用的是基类的sleep}
在这个例子中,我们想实现的功能是什么呢?我们用的是父类的指针指向了子类的空间,想要调用其中的成员函数,但是事实真的如我们所愿吗?请看以下运行结果:
可以看到我们实际上调用的都是父类中的成员函数,而这也是C++继承在作祟。对此,我们使用虚函数!(仅仅只是加了一个 virtual)
class Animal{public:virtual void sleep(void){cout << "animal 动物在睡觉" << endl;}};
运行结果又是如何呢?如下:
这是什么原因呢?这就要提到虚函数的结构了!
⭕虚函数的结构
定义
virtual 函数(包涵返回值、参数等等(就是在前面多加了个virtual))
内层结构(重要)
我们使用Develop Command Prompt for VS 2022 打开相应的类结构,如下为加了virtual的父类Animal:
vfptr是虚函数指针 指向的是虚函数表(vftable)
vftable表存放的是 vfptr做保存的函数入口地址
一图让你明白~
当函数前加了virtual后会自动生成一个vfptr指针以及一个vftable,虚函数指针会指向这个虚函数表,而函数表中通常储存着虚函数入口的地址。而这些地址的存储也是有一定的规则的,他们通过序号以及当前的虚指针相对于虚基类的首地址的偏移量。当其中有多个虚函数时,以便找到相应的函数。
重要的来了,那为啥当我们在父类指针中定义了虚函数,而在子类中就能实现用父类指针就可以访问子类的成员函数了呢?
这是因为:当虚函数涉及到继承的时候 子类 会继承 父类的(虚函数指针vfptr 虚函数表vftable),编译器会将虚函数表中的函数入口地址 更新 成子类的 同名(返回值、参数都同)的函数入口地址。如果基类指针、引用 访问虚函数的时候 就会 间接的调用 子类的虚函数。
二图让你明白~
看到这里,相信你的好奇心起来了,如何查找类的结构呢?别急~接下来就分享给你(*╹▽╹*)
☑如何查看类的内层结构
1、首先在你的任务栏中搜索 Develop Command Prompt for VS 2022 (2022是版本,你可以根据自己的版本变更)
2、打开后回到你的VS执行以下操作
3、回到 Develop Command Prompt for VS 2022,输入以下命令
如果你的文件是在E盘 你首先得先进入E盘 所以输入 E: 如果是C盘则C:以此类推
在进入所在盘后,进入到对应的路径 cd + 路径 如下:
4、输入对应的操作
cl /d1 reportSingleClassLayout+类名 类所在程序名.cpp
如下:
✔虚函数的应用案例
(基类指针、引用 作为函数的参数)
🌰
#include <iostream>using namespace std;class Base {public:virtual void sleep(void){cout << "父亲在睡觉" << endl;}};class Son1 :public Base{public:void sleep(void){cout << "Son1在安静的睡觉" << endl;}};class Son2 :public Base{public:virtual void sleep(void){cout << "Son2在轻度的睡觉" << endl;}};class Son3 :public Base{public:virtual void sleep(void){cout << "Son3在雨声般的睡觉" << endl;}};class Son4 :public Base{public:virtual void sleep(void){cout << "Son4在鼾声如雷" << endl;}}; //以基类指针作为函数的参数 函数可以操作该基类派生出的任意子类对象void sleepFun(Base & ob){ob.sleep();}int main(int argc, char* argv[]){Son1 ob1;Son2 ob2;Son3 ob3;Son4 ob4;sleepFun(ob1);sleepFun(ob2);sleepFun(ob3);sleepFun(ob4);return 0;}
运行结果如下:
感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o!
给个三连再走嘛~