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

简介: 动态绑定,多态(带你从本质了解多态)
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;
}

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

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

相关文章
|
6月前
|
存储 C++ 容器
第十四章:C++虚函数、继承和多态详解
第十四章:C++虚函数、继承和多态详解
56 0
|
21天前
实现多态的多种方式
【10月更文挑战第19天】这些多态的实现方式各有特点,在不同的场景中可以灵活运用,以提高代码的灵活性、可扩展性和复用性。
96 63
|
27天前
多态和动态绑定的区别是什么?
【10月更文挑战第14天】多态和动态绑定是面向对象编程中两个重要的概念,但它们有着不同的含义和作用。
21 2
|
2月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
4月前
|
Java
Java面向对象 ( 多态 | final关键字 | 接口 )
Java面向对象 ( 多态 | final关键字 | 接口 )
|
5月前
|
C++
C++一分钟之-继承与多态概念
【6月更文挑战第21天】**C++的继承与多态概述:** - 继承允许类从基类复用代码,增强代码结构和重用性。 - 多态通过虚函数实现,使不同类对象能以同一类型处理。 - 关键点包括访问权限、构造/析构、菱形问题、虚函数与动态绑定。 - 示例代码展示如何创建派生类和调用虚函数。 - 注意构造函数初始化、空指针检查和避免切片问题。 - 应用这些概念能提升程序设计和维护效率。
39 2
|
4月前
|
Java
深入理解Java中的类与对象:封装、继承与多态
深入理解Java中的类与对象:封装、继承与多态
|
5月前
|
编译器 C++ 开发者
通俗讲解 初学者一文看懂!虚函数、函数重载、重写的区别
函数重载允许在同一作用域内定义同名但参数列表不同的函数,提高代码灵活性和可读性,避免命名冲突。通过参数类型自动选择合适版本,如C++中的`print()`可处理整数、浮点数和字符串。虚函数实现运行时多态,基类指针调用时调用实际对象的版本。抽象类至少有一个纯虚函数,不能实例化,用于定义接口规范。抽象类和纯虚函数是构建多态和继承体系的基础,提供接口标准,减少代码冗余,增强代码清晰性和可维护性。
|
5月前
|
Java
JavaSE——面向对象高级二(1/4)-面向对象三大特征之三-多态(认识多态、使用多态的好处、多态下的类型转换问题)
JavaSE——面向对象高级二(1/4)-面向对象三大特征之三-多态(认识多态、使用多态的好处、多态下的类型转换问题)
34 0
|
存储 编译器 C++
【C++】多态及原理
【C++】多态及原理
47 0