首页 > 编程语言 >C++之虚函数

C++之虚函数

时间:2022-10-21 11:24:04浏览次数:74  
标签:函数 -- 派生类 C++ fun 基类 Fun 之虚

最近在看侯捷的深入浅出MFC时,了解到C++的相关知识,比如this指针到底是怎么出现的?虚函数是如何做到准确调用某个函数的,明明大家都长的一样?普通的成员函数是怎么被调用的?覆盖和隐藏又是什么?。。。好多问题,以前都没考虑过,花了点时间研究了一下,特此记录;

侯捷:深入浅出MFC:第二章C++ (额,其实有些部分的内容顺序乱了,第二章没什么问题)


 CShape:基类,CRect:派生类;

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class CShape // 形状
 6 {
 7 private:
 8     int m_color;
 9 public:
10     void setcolor(int color) { m_color = color; }
11 };
12 
13 class CRect : public CShape
14 {                             
15 };
16 
17 int main()
18 {
19     CRect rect1,rect2;
20     rect1.setcolor(10);            // rect1.m_color = 10;
21     rect2.setcolor(1);            // rect2.m_color = 1;
22     return 0;
23 }

首先看this指针:派生类调用基类的函数(两个派生类对象调用同一个基类函数setcolor),结果分别成功设置改变了自己对象本身的数据?既然调用同一个函数,又是怎么实现分别改变两个对象的?(请注意,setcolor函数中只有一个参数color,他是无法去找到对象的地址的,也就是说根本做不到改变对象的成员变量的值)由此可以发现,必定有其他因素指定了对象的位置,否则无法实现;

原因在于隐藏的this指针,实际上的setcolor函数应该是这样的:void setcolor((CShape*)this,int color){ this->m_color = color; }     ,这样就可以理解了,this指针指向调用函数的那个对象,通过this指针操作就可以改变对象的成员数据了,rect1.setcolor(10)中this指针指向了rect1,所以才能定向改变rect1的成员变量;

所以实际上,成员函数还是拿了对象指针做参数。


 接下来又发现了一个问题:rect1.setcolor(10)这一句话就能调用函数setcolor,那么rect1是如何调用函数setcolor?最简单的想法,应该是对象中记录了每个函数的地址,当出现函数调用的时候,就转到这个函数中。对象中保存太浪费,类中保存就好了,然而实际上是这样,也不是这样。函数地址肯定是要保存的,但是并非我所想的对象调用函数。

rect1.setcolor(10);会被编译器进行转换:CShape::setcolor((CRect*)&rect1, 10);这实际上就等于使用了一个写在CShape类中的setcolor函数,所谓的对象调用函数,实际上是函数使用对象的指针作为参数。

至此:对象调用函数的问题解决!


 覆盖和隐藏到底是什么东西:覆盖/重载/隐藏,还是得分清楚的,需要搞明白到底哪个是哪个;

C++ 重载、重写(覆盖)、隐藏各自要求与异同总结: 简单来说,覆盖就是多态那一套,基类指针指向基类对象,就去基类找,基类指针指向派生类对象去派生类找,没找到再去基类找;;;隐藏,额,不知道怎么说,算了算了,搞明白再来;区分重载(overload),覆盖(Override)和隐藏(hide)


 看虚函数吧:当使用基类指针指向派生类对象时,怎么才能知道它到底是使用基类的函数,还是派生类的函数呢?而不是全部流回至基类函数?

在不加虚函数时,基类指针指向派生类,指向了一个派生类的对象,当其调用函数时,自然而然的使用了基类函数(隐藏情况)。而虚函数解决这个问题。在有虚函数的情况下,每个类维护一个虚函数表,如果派生类没有改写基类的虚函数,那就存基类中的函数地址,如果改写了,那就存派生类中改写后的函数地址;

有了虚函数表之后问题就很好解决了,每个对象都存一个指向虚函数表的指针vptr,当调用虚函数时,通过vptr指针找到虚函数表,然后再通过虚函数表查找对应的函数,继而找到对应的函数地址,即可实现函数调用,只要虚函数表正确找到函数。(当基类指针指向派生类对象时,虽然指针类型改变,但是传进去的指针找到的虚函数表而是属于派生类的,所以能够正确调用函数)


 本来是想用C模拟出来一个C++类的,就是实现C++的this指针/虚函数机制,emmmmmm,果然是我天真了。。草草做了一个盗版(盗版都算不上。。。),手动给成员赋值,手动模拟虚函数表,就是抄的网上别人写的C模拟C++多态。。。

--------------请特别注意104行隐藏系列函数存在的问题!!!!!!!(理论上(C++中)要调用基类函数)

  1 #include <stdio.h>
  2 
  3 // -------------------------------------------------------------
  4 
  5 // 函数指针
  6 typedef void(*Virtual_Fun) ();
  7 typedef void(*Derived_Fun) ();
  8 typedef void(*Inherit_Fun) ();
  9 typedef void(*Hide_Fun)       ();
 10 
 11 // -------------------------------------------------------------
 12 
 13 // 函数实现
 14 void Virtual_Fun_Base() {
 15     printf("Virtual_Fun_Base--我是基类的虚函数!\n");
 16 }
 17 
 18 void Virtual_Fun_Derived() {
 19     printf("Virtual_Fun_Derived--我是派生类的虚函数!\n");
 20 }
 21 
 22 void Derived_Fun_Derived() {
 23     printf("Derived_Fun_Derived--我是派生类独有的函数!\n");
 24 }
 25 
 26 void Inherit_Fun_Base() {
 27     printf("Inherit_Fun_Base--我是从基类继承下来的函数!\n");
 28 }
 29 
 30 void Hide_Fun_Base() {
 31     printf("Hide_Fun_Base--我是基类函数--隐藏系列!\n");
 32 }
 33 
 34 void Hide_Fun_Derived() {
 35     printf("Hide_Fun_Derived--我是派生类函数--隐藏系列!\n");
 36 }
 37 
 38 // -------------------------------------------------------------
 39 
 40 // 基类+派生类
 41 typedef struct MY_VIRTUAL{        // 拿My_Virtual充当虚函数表
 42     Virtual_Fun virtual_fun;    // 虚函数virtual_fun()
 43 }My_Virtual;
 44 
 45 typedef struct MY_BASE{
 46     int base_number;            // 基类成员
 47     Inherit_Fun inherit_fun;    // 普通函数--直接继承系列
 48     Hide_Fun hide_fun;            // 普通函数--隐藏系列
 49     My_Virtual virtual;            // 虚函数表--仅一个虚函数;
 50 }MyBase;
 51 
 52 typedef struct MY_DERIVED{
 53     int base_number;            // 基类成员
 54     Inherit_Fun inherit_fun;    // 普通函数--直接继承系列
 55     Hide_Fun hide_fun;            // 普通函数--隐藏系列
 56     My_Virtual virtual;            // 虚函数表--仅一个虚函数;
 57     // 上面部分为继承MyBase,也可直接嵌套结构体MyBase
 58     Derived_Fun derived_fun;    // 派生类函数--独有
 59 }MyDerived;
 60 
 61 // -------------------------------------------------------------
 62 
 63 int main09()
 64 {
 65     MyBase myBase;
 66     // 基类初始化
 67     myBase.base_number = 10;                            // 成员变量
 68     myBase.inherit_fun = Inherit_Fun_Base;                // 直接继承函数
 69     myBase.hide_fun = Hide_Fun_Base;                    // 隐藏系列
 70     myBase.virtual.virtual_fun = Virtual_Fun_Base;        // 虚函数系列
 71     // 基类初始化
 72 
 73     MyDerived myDerived;
 74     // 派生类初始化
 75     myDerived.base_number = 10;                                // 成员变量 -- 直接继承,保持不变
 76     myDerived.inherit_fun = Inherit_Fun_Base;                // 直接继承函数 -- 直接继承,保持不变
 77     myDerived.hide_fun = Hide_Fun_Derived;                    // 隐藏系列 -- 发生变化,不再相同
 78     myDerived.virtual.virtual_fun = Virtual_Fun_Derived;    // 虚函数系列 -- 虚函数表改变,发生变化
 79     myDerived.derived_fun = Derived_Fun_Derived;            // 派生独有函数
 80     // 派生类初始化
 81 
 82     printf("-----------------\n基类显示:\nbase_number = %d\n",myBase.base_number);
 83     myBase.inherit_fun();
 84     myBase.hide_fun();
 85     myBase.virtual.virtual_fun();
 86 
 87     printf("\n-----------------\n派生类显示:\nbase_number = %d\n",myDerived.base_number);
 88     myDerived.inherit_fun();
 89     myDerived.hide_fun();                
 90     myDerived.virtual.virtual_fun();
 91     myDerived.derived_fun();
 92 
 93     MyBase* pBase;
 94     // 基类指针指向基类对象
 95     pBase = &myBase;
 96     printf("\n-----------------\n基类指针指向基类对象:\nbase_number = %d\n",pBase->base_number);
 97     pBase->inherit_fun();
 98     pBase->hide_fun();
 99     pBase->virtual.virtual_fun();
100     // 基类指针指向派生类对象
101     pBase = (MyBase*)&myDerived;
102     printf("\n-----------------\n基类指针指向派生类对象:\nbase_number = %d\n",pBase->base_number);
103     pBase->inherit_fun();
104     pBase->hide_fun();     // 这里有问题,这是隐藏系列,理论上应该是要调用基类函数的,实际用了派生类函数,错误×
105     pBase->virtual.virtual_fun();
106 
107     return 0;
108 }

问题解决;

2022-10-21

标签:函数,--,派生类,C++,fun,基类,Fun,之虚
From: https://www.cnblogs.com/2015-16/p/16812829.html

相关文章

  • C++20高级编程 第五版 电子书 pdf
    作者:[比]马克·格雷戈勒(MarcGregoire)出版社:清华大学出版社原作名:ProfessionalC++,FifthEdition 链接:C++20高级编程第五版 拥抱C++的深度和复杂性,挖掘更多......
  • 在旧版本centos上编译c++11的程序
    runac++programwithc++11supportinolderCentOSmachine从extras安装SoftwareCollections(SCL)yuminstallcentos-release-scl-rh安装devtoolset:yumin......
  • 关于python的函数调用传递的参数前面的*
    调用(caller)func(*sequence)Passallobjectsinsequenceasindividualpositionalargumentsseq=[1,2,3]func(*seq)->func(1,2,3)func(**dict)Passallke......
  • 【C++入门】(九)使用继承拓展类
    1.什么是继承?基类(classAnimal)可以有多个派生类(classDog:publicAnimal)classAnimal//基类{public:stringname="123";intage;};​classDo......
  • 【C++入门】(七)高级函数
    1.如何重载成员函数?函数重载:编写多个名称相同但参数不同的函数成员函数也可以重载编译器根据参数数量和类型决定调用哪个构造函数classRectangle{public......
  • C++中的四种强制类型转换
    ①static_cast(expression)<type-id>该运算符把expression转换为type_id类型,但没有运行时类型检查来保证转换的安全性,最常用的是基本数据类型之间的转换 ②const_cast......
  • C++面试问题
    如何实现一个不能被继承的类?将其构造函数设置成私有类型 若仍要访问?设置友元类 实现一个可以被继承的类,但不能在外部函数中创建对象的类型?构造函数设置为受保护......
  • fork函数
    复制进程forkpid_tfork(void)函数返回类型pid_t实质是int类型fork函数会生成一个新的进程,调用fork函数的进程称为父进程,新生成的进程称为子进程,在父进程中返回子......
  • Microsoft_Visual_C++_6-0简体中文版
    软件截图​安全认证官方版评价等级⭐️⭐️⭐️⭐️⭐️发布时间2011-10-1719:47:56更新时间2013-07-0219:47:562022-10-2023:24:10软件版本V6.0应用平台Win2003/Wi......
  • 四大函数式接口
    四大函数式接口Fuction函数型接口,有一个输入参数,有一个输出参数函数型接口:输入一个参数,输出输入的参数//Function函数型接口publicclassDemo01{publics......