C++多态(下)

简介: C++多态(下)

多继承的虚函数表

#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;
};
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) ();
typedef void(*p)();
void PrintVFTbale(p vft[])//打印虚表
{
  for (int i = 0; vft[i] != nullptr; i++)
  {
    printf("[%d]:%p->", i, vft[i]);
    vft[i]();
  }
}
int main()
{
  Base1 b1;
  Base2 b2;
  PrintVFTbale((p*)(*(void**)&b1));
  PrintVFTbale((p*)(*(void**)&b2));
  Derive d;
  PrintVFTbale((p*)(*(void**)&d));//d有两个虚表
  PrintVFTbale((p*)(*(void**)((char*)/*不强转就不是+1跳过一个字节了*/ &d + sizeof(Base1))));//想要打印第二个虚表就要跳过第一个虚表的地址
  return 0;
}

我们发现,d中的func3虚函数放进了第一个虚表里面。

菱形虚拟继承

#include<iostream>
using namespace std;
class A
{
public:
  virtual void func1(){}
  int _a;
};
class B : public A
//class B : virtual public A
{
public:
  virtual void func1() {}
  int _b;
};
class C : public A
//class C : virtual public A
{
public:
  virtual void func1() {}
  int _c;
};
class D : public B, public C
{
public:
  int _d;
};
int main()
{
  D d;
  d.B::_a = 1;
  d.C::_a = 2;
  d._b = 3;
  d._c = 4;
  d._d = 5;
  return 0;
}

如果是菱形继承,这样重写虚函数是没有问题的,因为B和C都有一个A。

但如果是菱形虚拟继承就不可以了,因为A类变成了一个,B和C进行重写A类的函数的时候编译器无法分辨应该按照B还是C进行重写。

所以这种情况只能在d中进行重写。

在d中重写之后,d就有三张虚表,分别是A,B,C。

#include<iostream>
using namespace std;
class A {
public:
  A(const char* s) { cout << s << endl; }
  ~A() {}
};
class B :virtual public A
{
public:
  B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class C :virtual public A
{
public:
  C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class D :public B, public C
{
public:
  D(const char* s1, const char* s2, const char* s3, const char* s4) :B(s1, s2),C(s1, s3),A(s1)
  {
    cout << s4 << endl;
  }
};
int main() {
  D* p = new D("class A", "class B", "class C", "class D");
  delete p;
  return 0;
}

这里要注意:

1.因为是菱形虚拟继承,A只有一份,定义D是直走D当中的初始化列表的A类初始化,不会走B和C类的A类初始化,因为A是B和C共享的,B和C去初始化都不合适,所以A只被D初始化了一次。

但是B和C类中的A类初始化必须有,因为万一要创建B和C类就需要有A类的初始化了。

2.初始换是按照成员声明顺序初始化(如果是继承就看继承顺序),而不是看初始化列表的顺序进行初始化。

那么如果只是菱形继承呢?

#include<iostream>
using namespace std;
class A {
public:
  A(const char* s) { cout << s << endl; }
  ~A() {}
};
class B :public A
{
public:
  B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class C :public A
{
public:
  C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class D :public B, public C
{
public:
  D(const char* s1, const char* s2, const char* s3, const char* s4) :B(s1, s2),C(s1, s3)//这里就不用初始化A了,因为有两份A,在B和C中,B和C就会去调用A的初始化
  {
    cout << s4 << endl;
  }
};
int main() {
  D* p = new D("class A", "class B", "class C", "class D");
  delete p;
  return 0;
}

继承与多态的常见问题

1.什么是多态?

静态的多态和动态的多态。就是重载和虚函数的重写。

2.内联函数能不能是虚函数呢?

语法上来说是可以的,但是其实是并不行的,因为inline关键字只是提出一个建议,到底需不需要去变成内联函数是要看实际情况的,如果将虚函数变成内联函数是没有办法放进虚表里面的,编译器就忽略inline属性,这个函数就不再是inline。

也就是说,如果是多态调用就没有内联属性,但是普通调用可以继续保持内联属性。

3.为什么父类的对象无法完成多态调用呢?

#include <iostream>
using namespace std;
class A
{
public:
  virtual void add() { cout << "A:add" << endl; }
  int _a;
};
class B :public A
{
public:
  virtual void add() { cout << "B:add" << endl; }
  int _b;
};
void app(A a)//这里传过来的如果是B类的对象,顶多是切片过来的A类一部分,不可能是拷贝过来所有的内容
{
  a.add();//这里就是静态绑定,普通调用
}
int main()
{
  A a;
  B b;
  app(a);
  app(b);//如果拷贝到A a中,里面的内容就乱套了,所以只能用引用或者是指针才能指向子类对象完成多态的调用
  return 0;
}

4.静态成员可以是虚函数吗?

不可以,因为静态成员函数没有this指针(this指针指向的对象中存有虚函数表指针,没有this指针就找不到对象,也就找不到虚函数表指针了),使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

5.构造函数能不能是虚函数?

虚函数要放进虚表,在没有调用构造函数之前,虚表还有没进行初始化,构造函数之后才进行初始化。

这也说明,如果构造函数都是虚函数,那么我们就无法找到构造函数的地址了。

6.对象访问普通函数快还是虚函数更快?

首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

7.虚函数表是在什么阶段生成的,存在哪的?

虚函数表是在编译阶段就生成的初始化列表中初始化虚表指针,一般情况下存在代码段(常量区)的。

相关文章
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++】多态
多态是面向对象编程中的重要特性,允许通过基类引用调用派生类的具体方法,实现代码的灵活性和扩展性。其核心机制包括虚函数、动态绑定及继承。通过声明虚函数并让派生类重写这些函数,可以在运行时决定具体调用哪个版本的方法。此外,多态还涉及虚函数表(vtable)的使用,其中存储了虚函数的指针,确保调用正确的实现。为了防止资源泄露,基类的析构函数应声明为虚函数。多态的底层实现涉及对象内部的虚函数表指针,指向特定于类的虚函数表,支持动态方法解析。
32 1
|
2月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
47 2
C++入门12——详解多态1
|
7月前
|
C++
C++中的封装、继承与多态:深入理解与应用
C++中的封装、继承与多态:深入理解与应用
171 1
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
88 1
|
4月前
|
存储 编译器 C++
|
5月前
|
存储 编译器 C++
【C++】深度解剖多态(下)
【C++】深度解剖多态(下)
57 1
【C++】深度解剖多态(下)
|
5月前
|
存储 编译器 C++
|
5月前
|
机器学习/深度学习 算法 C++
C++多态崩溃问题之为什么在计算梯度下降时需要除以批次大小(batch size)
C++多态崩溃问题之为什么在计算梯度下降时需要除以批次大小(batch size)
|
5月前
|
Java 编译器 C++
【C++】深度解剖多态(上)
【C++】深度解剖多态(上)
58 2
|
5月前
|
程序员 C++
【C++】揭开C++多态的神秘面纱
【C++】揭开C++多态的神秘面纱