【C++】学习笔记——多态_2

简介: 【C++】学习笔记——多态_2

十三、多态

4. 抽象类

概念

在虚函数的后面写上 =0 ,则这个函数为纯虚函数包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承

#include<iostream>
using namespace std;
// 抽象类
class Car
{
public:
  // 纯虚函数
  virtual void Drive() = 0;
};
int main()
{
  // 抽象类不能实例化对象
  Car c1;
  return 0;
}

#include<iostream>
using namespace std;
// 抽象类
class Car
{
public:
  // 纯虚函数
  virtual void Drive() = 0;
};
// 子类必须重载纯虚函数,否则也不能实例化对象
class Benz :public Car
{
public:
  virtual void Drive()
  {
    cout << "Benz-舒适" << endl;
  }
};
class BMW :public Car
{
public:
  virtual void Drive()
  {
    cout << "BMW-操控" << endl;
  }
};
int main()
{
  Car* pBenz = new Benz;
  pBenz->Drive();
  Car* pBMW = new BMW;
  pBMW->Drive();
  return 0;
}

接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

静态绑定和动态绑定

静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载。

动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

5. 单继承和多继承关系的虚函数表

单继承中的虚函数表

虚函数表在哪个区域?

#include<iostream>
using namespace std;
class Base
{
public:
  virtual void Func1()
  {
    cout << "Base::Func1()" << endl;
  }
  virtual void Func2()
  {
    cout << "Base::Func2()" << endl;
  }
  void Func3()
  {
    cout << "Base::Func3()" << endl;
  }
private:
  int _b = 1;
};
class Derive : public Base
{
public:
  virtual void Func1()
  {
    cout << "Derive::Func1()" << endl;
  }
private:
  int _d = 2;
};
int main()
{
  int i = 1;
  static int j = 2;
  int* p1 = new int;
  const char* p2 = "XXXXXXX";
  printf("栈:%p\n", &i);
  printf("静态区:%p\n", &j);
  printf("堆:%p\n", p1);
  printf("常量区:%p\n", p2);
  Base b;
  Derive d;
  Base* p3 = &b;
  Derive* p4 = &d;
  // 一般情况下虚函数表都存在对象中的起始位置,所以强转成int*便可读取前4个字节
  printf("Base虚函数表地址:%p\n", *(int*)p3);
  printf("Derive虚函数表地址:%p\n", *(int*)p4);
  return 0;
}

从上面的结果来看,很明显,虚函数表存在于常量区。

我们想打印一下虚函数表,应该怎么实现呢?我们知道虚函数表的地址,但是不知道结束条件,无法遍历指针数组。

我们发现vs下虚函数表(指针数组)都是以空指针结束,由此就可以判定结束条件从而打印虚函数表。

#include<iostream>
using namespace std;
class Base
{
public:
  virtual void Func1()
  {
    cout << "Base::Func1()" << endl;
  }
  virtual void Func2()
  {
    cout << "Base::Func2()" << endl;
  }
  void Func3()
  {
    cout << "Base::Func3()" << endl;
  }
private:
  int _b = 1;
};
class Derive : public Base
{
public:
  virtual void Func1()
  {
    cout << "Derive::Func1()" << endl;
  }
  virtual void Func3()
  {
    cout << "Derive::Func3()" << endl;
  }
private:
  int _d = 2;
};
// 函数指针
typedef void(*VF_PTR)();
// 打印虚函数表,本质上是打印(虚函数)指针数组
void PrintVFT(VF_PTR* vft)
{
  for (size_t i = 0; vft[i] != nullptr; ++i)
  {
    printf("[%d]:%p->", i, vft[i]);
    // 回调函数
    VF_PTR f = vft[i];
    f();
  }
}
int main()
{
  Derive d;
  // 强转成int*来找到虚函数表地址,然后强转成指针数组传参
  PrintVFT((VF_PTR*)(*(int*)&d));
  return 0;
}

多继承中的虚函数表

我们来看看下面这种情况:

class Base1
{
public:
  virtual void func1()
  {
    cout << "Base1::func1" << endl;
  }
  virtual void func2()
  {
    cout << "Base1::func2" << endl;
  }
private:
  int b1;
};
class Base2
{
public:
  virtual void func1()
  {
    cout << "Base2::func1" << endl;
  }
  virtual void func2()
  {
    cout << "Base2::func2" << endl;
  }
private:
  int b2;
};
// Derive 继承了 Base1 和 Base2
class Derive : public Base1, public Base2
{
public:
  virtual void func1()
  {
    cout << "Derive::func1" << endl;
  }
  virtual void func3()
  {
    cout << "Derive::func3" << endl;
  }
private:
  int d1;
};

Derive继承了两个父类,那他的对象应该有多少个虚表?我们应该这样看,我们将Derive的对象分成三份,Base1部分、Base2部分和自己的部分。因此Derive应该有两个虚表,每继承一个父类,其内部就有一个虚表。

那么:

int main()
{
  Derive d;
  
  // 这里切片会有问题吗
  Base1* ptr1 = &d;
  Base2* ptr2 = &d;
  return 0;
}

对象d中有Base1的部分和Base2的部分,切片会导致 ptr1 和 ptr2 一样吗?当然不会,编译器会自动产生偏移进行切片的。

这里 ptr1指向的地址和d的地址一样,但是这两个意义是不一样的,一个是对象d的首地址,一个是Base1切片部分的首地址ptr2 和 ptr1之间产生了偏移

那么新问题:Derive自己的虚函数放在哪个虚函数表里呢?

#include<iostream>
using namespace std;
class Base1
{
public:
  virtual void func1()
  {
    cout << "Base1::func1" << endl;
  }
  virtual void func2()
  {
    cout << "Base1::func2" << endl;
  }
private:
  int b1;
};
class Base2
{
public:
  virtual void func1()
  {
    cout << "Base2::func1" << endl;
  }
  virtual void func2()
  {
    cout << "Base2::func2" << endl;
  }
private:
  int b2;
};
// Derive 继承了 Base1 和 Base2
class Derive : public Base1, public Base2
{
public:
  virtual void func1()
  {
    cout << "Derive::func1" << endl;
  }
  virtual void func3()
  {
    cout << "Derive::func3" << endl;
  }
private:
  int d1;
};
typedef void(*VFPTR)();
void PrintVFT(VFPTR* vft)
{
  for (size_t i = 0; vft[i] != nullptr; ++i)
  {
    printf("[%d]:%p->", i, vft[i]);
    VFPTR f = vft[i];
    f();
  }
  cout << endl;
}
int main()
{
  Derive d;
  Base1* ptr1 = &d;
  Base2* ptr2 = &d;
  // 打印Base1的虚函数表
  PrintVFT((VFPTR*)(*(int*)ptr1));
  // 打印Base2的虚函数表
  PrintVFT((VFPTR*)(*(int*)ptr2));
  return 0;
}

由此可见,子类自己的虚函数放在了第一个继承的虚函数表里


未完待续

目录
相关文章
|
3月前
|
C++
c++学习笔记07 结构体
C++结构体的详细学习笔记07,涵盖了结构体的定义、使用、数组、指针、嵌套、与函数的交互以及在结构体中使用const的示例和解释。
40 0
|
1月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
38 2
C++入门12——详解多态1
|
1月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
80 1
|
2月前
|
安全 C语言 C++
C++学习笔记
C++学习笔记
|
3月前
|
C++
【学习笔记】【C/C++】 c++字面值常量
【学习笔记】【C/C++】 c++字面值常量
39 1
|
3月前
|
存储 C++
c++学习笔记05 函数
C++函数使用的详细学习笔记05,包括函数的基本格式、值传递、函数声明、以及如何在不同文件中组织函数代码的示例和技巧。
35 0
c++学习笔记05 函数
|
3月前
|
编译器 C++
【C/C++学习笔记】C++声明与定义以及头文件与源文件的用途
【C/C++学习笔记】C++声明与定义以及头文件与源文件的用途
47 0
|
3月前
|
存储 C++
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
43 0
|
3月前
|
存储 编译器 C++
C++多态实现的原理:深入探索与实战应用
【8月更文挑战第21天】在C++的浩瀚宇宙中,多态性(Polymorphism)无疑是一颗璀璨的星辰,它赋予了程序高度的灵活性和可扩展性。多态允许我们通过基类指针或引用来调用派生类的成员函数,而具体调用哪个函数则取决于指针或引用所指向的对象的实际类型。本文将深入探讨C++多态实现的原理,并结合工作学习中的实际案例,分享其技术干货。
75 0
|
3月前
|
C++
c++学习笔记09 引用
C++引用的详细学习笔记,解释了引用的概念、语法、使用注意事项以及引用与变量的关系。
44 0