虚函数表

虚函数表

C++中的虚函数的作用主要是实现了多态的机制。

多态:就是用父类型的指针指向其子类的实例。

虚函数是通过一张虚函数表(Virtual Table)实现的。在这个表中,主要是一个类的虚函数地址表,这张表解决了继承和覆盖等问题。就像一张地图一样,指明了指针实际所应该调用的函数。

举个例子:

class Base{
  public:
  	virtual void f() {cout << "Base::f" << endl;};
  	virtual void g() {cout << "Base::g" << endl;};
  	virtual void h() {cout << "Base::h" << endl;};
}


typedef void(*Fun)(void);
Base b;

cout << "虚函数表地址" << (int*)(&b) << endl; // 获取虚函数表的地址
cout << "虚函数表第一个函数的地址"<< (int*)*(int*)(&b) << endl; // 获取虚函数表第一个函数的地址

pFun = (Fun)*((int*)*(int*)(&b));
pFun(); // 等价于Base::f()

(Fun)*((int*)*(int*)(&b + 0));  //Base::f()
(Fun)*((int*)*(int*)(&b + 1));  //Base::g()
(Fun)*((int*)*(int*)(&b + 2));  //Base::h()


1、 单继承无虚函数覆盖

class Base{
public:
	virtual void func1();
	virtual void func2();
	virtual void func3();
}

class Derived : public Base{
public:
	virtual void func4();
	virtual void func5();
	virtual void func6();
}

img

从虚函数表中我们可以看出:

(1)虚函数按照声明顺序依次放入表中。

(2)父类的虚函数放在子类的虚函数前面。

2、单继承又虚函数覆盖

class Base{
  public:
  	virtual void func1();
  	virtual void func2();
  	virtual void func3();
}

class Derived: public Base{
  public:
  	virtual void func1();
  	virtual void func5();
  	virtual void func6();
}

img

从虚函数表中我们可以看出:

(1)覆盖的 f() 函数被放到了虚表中原来父类虚函数的位置。

(2) 没有被覆盖的依次存放。

3、 多继承无函数覆盖图解

class Base1{
  public:
  	virtual void func1();
  	virtual void func2();
  	virtual void func3();
}
class Base2{
  public:
  	virtual void func1();
  	virtual void func2();
  	virtual void func3();
}
class Base3{
  public:
  	virtual void func1();
  	virtual void func2();
  	virtual void func3();
}


class Derived: public Base1, Base2, Base3{
  public:
  	virtual void func4();
  	virtual void func5();
  	virtual void func6();
}

img

我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

4、 多继承有函数覆盖图解

class Base1{
  public:
  	virtual void func1();
  	virtual void func2();
  	virtual void func3();
}
class Base2{
  public:
  	virtual void func1();
  	virtual void func2();
  	virtual void func3();
}
class Base3{
  public:
  	virtual void func1();
  	virtual void func2();
  	virtual void func3();
}

class Derived: public Base1, Base2, Base3{
  public:
  	virtual void func1();
  	virtual void func5();
  	virtual void func6();
}

img

我们可以看到:
1) 每个父类都有自己的虚表,被覆盖的放在父类原来的位置。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

5、 虚函数常见问题

5.1、 虚函数表的代价

(1)带有虚函数的每个类会产生一个虚函数表,用来存储虚成员函数的指针。

(2)带有虚函数的每个类都会有一个指向虚函数表的指针。

5.2、 那些函数不能是虚函数

(1)构造函数:对象的虚函数表指针需要通过构造函数初始化。

(2)内联函数:内联函数可以在编译阶段进行函数体的替换,而虚函数需要在运行期间进行确定。

(3)静态函数:静态函数不属于对象而属于类,因为静态成员函数没有this指针,所以无法访问对象的虚表指针,也就无法访问类的虚函数表,将静态函数设置成虚函数也就没有任何意义,所以c++语法不支持将静态函数设置成虚函数。

(4)友元函数:友元函数不属于类,也不能被继承,没有继承特性的函数没有虚函数的说法

(5)类外的普通函数:类外普通函数不是类的成员函数,同样不具备继承特性,也就没有虚函数的说法

5.3 虚函数和纯虚函数的区别

1)纯虚函数只有定义,没有实现,虚函数既有定义,又有实现

2)含有纯虚函数的类不能定义对象,含有虚函数的类可以定义对象

5.4、 虚析构函数的作用?父类的析构函数为什么一定要设置成虚函数?

父类虚析构函数就是为了避免内存泄漏,防止子类内存得不到释放造成内存泄漏。

1.当父类的析构函数不声明成虚析构函数时,当子类继承父类,父类指针指向子类对象,delete掉父类指针,只会调动父类的析构函数,而不会调用子类的析构函数,从而造成子类对象内存泄漏

2.当父类的析构函数声明成虚析构函数时,当子类继承父类,父类指针指向子类对象,delete掉父类指针,先调动父类的析构函数,然后调用子类的析构函数,不存在子类对象内存泄漏的问题

只要存在继承关系,则父类的析构函数必须定义成虚函数!

5.5、 构造函数和析构函数中为什么不可以调用虚函数?

1.构造子类对象时,首先调用父类构造函数初始化对象的父类部分,在执行父类的构造函数时,对象的子类部分都是未初始化的,实际上此时对象还不是一个子类对象。

2.析构子类对象时,先析构子类部分,然后按照构造顺序逆序析构父类部分。

所以在运行子类的构造和析构函数时,对象都是不完整的,为了适应这种不完整,编译器视对象类型为当前构造或析构函数所在类的类型,由此造成的结构就是:在父类的构造或析构函数中,会将子类对象当作父类对象看待。

如果我们在父类的构造或析构函数中调用虚函数,调用的往往是当前类的虚函数,达不到多态的效果,跟普通函数调用没有区别。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值