动态绑定,多态(带你从本质了解多态)(下)

简介: 动态绑定,多态(带你从本质了解多态)
3.多继承无函数覆盖下的虚函数表

看完了单继承,我们来看看多继承的虚函数表:

#include "stdafx.h"
class Base1{
public:
  int a;
  int b;
  virtual void Base1_1(){
    printf("Base1:Function_1...\n");
  }
  virtual void Base1_2(){
    printf("Base1:Function_2...\n");
  }
};
class Base2{
public:
  int c;
  int d;
  virtual void Base2_1(){
    printf("Base2:Function_1...\n");
  }
  virtual void Base2_2(){
    printf("Base2:Function_2...\n");
  }
};
class Sub1:public Base1,Base2{
public:
  int e;
  virtual void Sub_1(){
    printf("Sub1:Sub_1...\n");
  }
  virtual void Sub_2(){
    printf("Sub1:Sub_2...\n");
  }
};
int main(int argc, char* argv[])
{
  typedef void (*Function)(void);
  Sub1 b;
  int* p;
  p = (int*)&b;
  int* function;
  function = (int*)(*p);
  Function pFn;
  for(int i=0;i<6;i++){
    pFn = (Function)*(function+i);
    pFn();
  }
  return 0;
}

根据我们上面的讲解,应该在虚函数表中有6个函数地址,但是程序在运行的时候照样提醒我:该地址不允许访问,说明在虚函数表中,不足6个函数。

我们看看程序输出窗口:

那么到底哪里出了问题?父类Base2中的虚函数去哪了?

我们先来看一下Sub的大小:

#include "stdafx.h"
class Base1{
public:
  int a;
  int b;
  virtual void Base1_1(){
    printf("Base1:Function_1...\n");
  }
  virtual void Base1_2(){
    printf("Base1:Function_2...\n");
  }
};
class Base2{
public:
  int c;
  int d;
  virtual void Base2_1(){
    printf("Base2:Function_1...\n");
  }
  virtual void Base2_2(){
    printf("Base2:Function_2...\n");
  }
};
class Sub1:public Base1,Base2{
public:
  int e;
  virtual void Sub_1(){
    printf("Sub1:Sub_1...\n");
  }
  virtual void Sub_2(){
    printf("Sub1:Sub_2...\n");
  }
};
int main(int argc, char* argv[])
{
  printf("%d",sizeof(Sub1));
  return 0;
}

我们可以看到程序输出窗口输出了28,我们来看看Sub的成员:继承了Base1的a和b,继承了Base2的c和d,自己的成员e,还有一张虚表,应该一共是24,可是它为什么输出了28?

其实通过课堂上老师的讲解我们已经知道:多重继承函数时会有多张虚表,而Base2的虚表就存在于this指针的第二个成员,他是Base2的虚表。

二.前期绑定和后期绑定

我们知道当程序调用函数的时候,有两种调用方式,一种是直接调用函数的地址,这种地址在程序编译的时候就已经写死了,另一种是通过一个地址,间接调用函数。

这里介绍一个名词:绑定,将函数与地址链接在一起的过程,叫做绑定。

直接调用函数的方式,在编译时就已将函数与地址绑定,我们称为(前期)编译期绑定

间接调用函数的方式,在运行的时候才进行绑定,我们称这种方式为(运行期)动态绑定或者晚绑定

注意:

只有virtual函数是动态绑定

三.多态

了解了前面的过程,多态的概念这里一句话就明白了:动态绑定还有另一个名字:多态。

这里给出多态的书面定义:

C++中的多态分为静态多态和动态多态。静态多态是函数重载,在编译阶段就饿能够确定调用哪个函数。动态多态是由继承产生的,指同一个属性或行为在基类和各派生类中具有不同的语义,不同的对象根据所接受的消息做出不同的响应,这种现象称为多态。

多态的实现需要满足三个条件:

(1)基类中声明虚函数

(2)派生类重写基类的虚函数

(3)将基类指针指向派生类对象,通过基类指针访问虚函数

我们来看看多态的具体实现,看看多态到底是什么:

#include "stdafx.h"
class Base{
public:
  int x;
  Base(){
    x=100;
  }
  virtual void Base_1(){
    printf("Base:Function_1...\n");
  }
  virtual void Base_2(){
    printf("Base:Function_2...\n");
  }
};
class Sub:public Base{
public:
  int e;
  Sub(){
    x=200;
  }
  virtual void Base_1(){
    printf("Sub1:Sub_1...\n");
  }
};
void Test(Base* p){
  int n = p->x;
  printf("%d\n",n);
  p->Base_1();
  p->Base_2();
}
int main(int argc, char* argv[])
{
  Base b;
  Base* p = &b;
  Test(p);
  return 0;
}

首先我们定义了一个基类对象,并且通过基类指针去访问函数,我们来看看程序输出框:

我们定义了基类对象,并且通过基类指针去访问函数,当然是没有任何问题的。

接下来我们看看多态的实现:

创建一个基类,再创建一个派生类,将基类函数覆盖,通过基类指针访问派生类:

#include "stdafx.h"
class Base{
public:
  int x;
  Base(){
    x=100;
  }
  virtual void Base_1(){
    printf("Base:Function_1...\n");
  }
  virtual void Base_2(){
    printf("Base:Function_2...\n");
  }
};
class Sub:public Base{
public:
  int e;
  Sub(){
    x=200;
  }
  virtual void Base_1(){
    printf("Sub1:Sub_1...\n");
  }
};
void Test(Base* p){
  int n = p->x;
  printf("%d\n",n);
  p->Base_1();
  p->Base_2();
}
int main(int argc, char* argv[])
{
  Sub b;
  Base* p = &b;
  Test(p);
  return 0;
}

我们来看看程序输出窗口:

我们可以看到,基类中属性的值也被改变,并且基类中函数也被覆盖,这就是我们所说的多态,同一个属性或行为在基类和各派生类中具有不同的语义,不同的对象根据所接受的消息做出不同的响应。

相关文章
|
设计模式 Unix Shell
ECF机制:信号 (Signal)
ECF机制:信号 (Signal)
278 0
|
11月前
多态和动态绑定的区别是什么?
【10月更文挑战第14天】多态和动态绑定是面向对象编程中两个重要的概念,但它们有着不同的含义和作用。
232 57
|
存储 网络协议 Ubuntu
【Linux开发实战指南】基于UDP协议的即时聊天室:快速构建登陆、聊天与退出功能
UDP 是一种无连接的、不可靠的传输层协议,位于IP协议之上。它提供了最基本的数据传输服务,不保证数据包的顺序、可靠到达或无重复。与TCP(传输控制协议)相比,UDP具有较低的传输延迟,因为省去了建立连接和确认接收等过程,适用于对实时性要求较高、但能容忍一定数据丢失的场景,如在线视频、语音通话、DNS查询等。 链表 链表是一种动态数据结构,用于存储一系列元素(节点),每个节点包含数据字段和指向下一个节点的引用(指针)。链表分为单向链表、双向链表和循环链表等类型。与数组相比,链表在插入和删除操作上更为高效,因为它不需要移动元素,只需修改节点间的指针即可。但访问链表中的元素不如数组直接,通常需要从
481 2
|
存储 网络协议 Ubuntu
如何在 Ubuntu 14.04 上使用 Iptables 实现基本防火墙模板
如何在 Ubuntu 14.04 上使用 Iptables 实现基本防火墙模板
189 0
|
12月前
|
算法 Go
Golang限流器time/rate正确打开姿势
本文详细探讨了 Go 语言限流工具 `golang.org/x/time/rate` 包下的 `Limiter` 类,并通过示例展示了如何使用该工具实现 QPS 限流功能。作者深入分析了 `Limiter` 的内部工作机制,揭示了其独特的算法设计,并指出了在动态调整限流参数时可能遇到的问题及解决方法。此外,还对比了该算法与传统令牌桶和滑动窗口算法的区别,总结了其优缺点。最后,作者给出了修正限流问题的具体代码示例。
202 0
Golang限流器time/rate正确打开姿势
|
消息中间件 分布式计算 负载均衡
软件体系结构 - 架构风格(6)进程通信架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(6)进程通信架构风格
329 0
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的可视化的学习系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的可视化的学习系统附带文章源码部署视频讲解等
98 0
|
机器学习/深度学习 存储 算法
C语言:分支与循环
C语言:分支与循环
138 0
|
Linux C语言
【Linux线程】二、线程控制原语
【Linux线程】二、线程控制原语
274 0
【Linux线程】二、线程控制原语
|
Rust
salvo rust解析请求数据的各种姿势
salvo rust解析请求数据的各种姿势
484 0