(四)引用、对象模型、虚指针和虚表、this指针

本文详细介绍了C++中的引用、指针和对象模型,包括引用的特性、作用以及与指针的区别。讨论了多态的概念,特别是虚函数和虚表在动态绑定中的作用。此外,还探讨了继承、复合与组合在面向对象编程中的应用,以及静态绑定和动态绑定的区别。最后,通过实例阐述了虚函数如何实现多态,并总结了C++中实现多态的关键要素。
摘要由CSDN通过智能技术生成

引用

  • 从内存的角度去看,变量有三种,一种是值value,一种是指针,一种是引用
  • 大小
    • 32位电脑上,int是四个字节,double是四个字节,指针也是四个字节
    • 引用是其所引用对象的别名,但是引用的实现都是指针,引用的大小就是它所绑定对象的大小,即sizeof引用和sizeof它所绑定的对象得到的结果是相等的。对引用取地址与对它所绑定到对象取地址得到的是一样的结果。
  • 引用一定要有初值,引用一旦被定义就不能改变绑定的对象
  • 引用的常见用途
    • 参数传递,传入函数参数,返回值传递
    • 引用传递效率高,且调用接口与传值基本一致
void func1(Cls* pobj) { pobj ->xxx();}
void func2(Cls obj)   { obj.xxx();   }//被调用端用法相同,很好
void func3(Cls& obj)  { obj.xxx();   }//被调用端用法相同,很好
...
Cls obj;
func1(&obj);
func2(obj);//接口端接口相同,很好
func3(obj);//接口端接口相同,很好
double imag(const double& im) /*const*/{ ... }
double imag(const double im)  { ... }
//上面的二者无法通过编译,有二义性
//它们的函数签名是相同的,签名不包含返回值类型
//const是函数签名的一部分

对象模型

  • 面向对象实际上谈的是对象和对象之间的关系,对象是由类创建出来的,实际上谈的是类与类之间的关系。
    • 继承
      • 子类对象里面有父类的成分,如桃子是水果
      • 构造要由内而外,即先构建基类的成分,再构建派生类的成分
      • 析构要由外而内,先执行释放派生类的成分,再释放基类的成分
    • 复合has a
      • 一个类里面有另一个类
      • 构造由内而外,先构造成员类,再构造外部的类
      • 析构要由外而内,先调用释放外部的类,再释放内部的类
    • 组合
      • 基类有派生类,派生类中有一个成员类,相当于派生类对象中有两个类的部分,一个是基类,另一个是成员类
      • 构造由内而外,先构造内部的两个类的成分,再构造自己的部分
      • 析构由外而内,先释放自己的部分,再释放基类以及成员类的部分

虚指针和虚表

  • 只要类中有虚函数,它的对象中就会多一个指针,称作虚指针,类对象的大小等于所有的数据大小之和加一个指针的大小

  • 继承不但继承了基类的数据,还继承了基类函数的调用权,而不是函数的内存大小,函数的内存大小是无法计算的

  • 父类有虚函数,子类一定有

  • 虚指针指向虚表,虚表里面放的都是函数指针,指向虚函数

    • 基类有两个虚函数,如果派生类改写了基类的一个虚函数,则派生类对象的虚指针所指向的虚表的两个函数指针,一个与基类的相同因为派生类没有重写该虚函数,重写虚函数则与基类不同
    • 编译器发现对象调用虚函数时会通过虚表找到对应的虚函数
  • 静态绑定与动态绑定

    • 静态绑定:编译器看到一个调用的动作,会将它编译成callxxxx,xxxx为所调用函数的地址。要调用该函数,编译器就将它解析出来,跳到那个地方去,运行完在return回来,这就是静态绑定
    • 通过指针去调用虚函数不能做静态绑定。
    • 动态绑定:通过指向对象的指针,找到虚指针,再找到虚表,再根据函数名寻找真正要调用的函数,取出其中的第n个,把它当成函数指针去调用。解析成c的语法如下图底部,n表示虚表中的第n个。n是由编译器在编代码时根据函数声明的顺序确定的,n从0开始。
      在这里插入图片描述
  • 示例

    • 设计一个shape类,它派生出各种形状
    • 为了让容器能容纳各式各样的形状,每个形状所表示的类对象的大小不同,所以容器存放的必须是指向父类的指针
    • 对每一个图形,都要有自己的draw函数,将draw设计为虚函数,避免了c中用if else依次判断指针指向的到第是哪种图形。且c的这种写法,如果后期再增加新的图形,又要重新增加if else程序
      在这里插入图片描述
  • 总结

    • c++编译器看到一个函数调用,它会考虑是将它静态绑定还是动态绑定
    • 静态绑定就是CALL+函数的地址
    • 满足三个条件编译器会进行动态绑定
      • 必需通过指针调用
      • 该指针必需是向上的关系up cast,即new一个桃子,必需声明为水果,实际指向的必须是子类对象,这个保证安全
      • 调用的是虚函数
    • 从虚指针到虚表到虚函数,这一条路线称作虚机制
    • 动态绑定要看指针实际上指的是哪种对象,
    • 虚函数的这种用法称作多态,一种声明的指针却可以指向不同类型的对象,指针有很多的类型,叫多态
    • 多态、虚函数、动态绑定(虚指针+虚表),这些实际上是同一间事情

this指针

  • 通过对象来调用函数,对象的地址就是this的值
  • 虚函数在使用时一般有两种形式的用法
    • 一个是多态,如上例
    • 另一个是模板方法,是一种设计模式
      在这里插入图片描述
  • 有两个类,基类是CDocument,派生类是CMyDoc。基类有一个叫OnFileOpen的函数,它将普通的处理工作写完,对于最重要的Serialize函数,它现阶段无法写,需要子类重写该函数。
  • main函数中,子类创建了一个对象,通过子类对象调用父类的函数,
  • 灰色的这条线是实际的调用过程
    • 子类对象调用父类的函数,对象的地址就是this指针的值,函数执行到Serialize时,该函数是虚函数,实际调用的是子类中的Serialize函数,满足动态绑定的三个条件
      • 通过指针调用
      • 指针指向的是子类对象
      • 调用的是虚函数
    • 编译器将调用虚函数的代码编译成如上图左上角的形式,继续动态绑定
    • Serialize执行结束后,继续执行OnFileOpen中剩下的部分
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值