虚函数的原理(c语言虚函数是什么)
首先要知道函数是有地址的,虚函数也不例外。虚拟函数的地址存储在虚拟函数表中,运行时
多态是通过虚函数和虚函数表实现的。类对象中会有一个指向虚拟表的指针。通过这个
指针调用虚函数,虚函数的调用将被编译器转换为对虚拟表的访问。虚拟表指针的名称也会被编译器修改,所以在多重继承下,类内可能有多个虚拟表指针。
==========华丽的分界线===========
虚函数提供动态多态,但也可以抑制。让我们看看下面的例子。
甲级
{
公众号:
virtual void f(){ STD : cout ' a 1:3360 f ' STD : endl;}
};
乙级:公共甲级
{
公众号:
virtual void f(){ STD : cout ' b :3360 f ' STD : endl;}
};
int main()
{
乙乙;
b . f();//因为B重载了F的实现,所以会调用B的F。
//那我们怎么才能调用基类的F呢?我们可以通过域名运营商来实现。
b.a : f();
}
==========华丽的分界线===========
hxdjm的派生类中只有一个虚函数表。这个虚函数表和基类的虚函数表不是一个表。举个例子吧。
A1类
{
公众号:
A1(int _a1=1):a1(_a1){}
virtual void f(){ STD : cout ' a 1:3360 f ' STD : endl;}
virtual void g(){ STD : cout ' a 1 :3360g ' STD : endl;}
私人:
int a1
};
b 1:公共A1类
{
公众号:
B1(int _a1=1,int _b1=4):A1(_a1),b(_b1){}
私人:
int b;
};
int main()
{
B1 b1
B1 . g();
返回0;
}
如果派生类没有重写基类的虚函数,则派生类中虚函数表的虚函数地址与基类的虚函数地址相同。
==========华丽的分界线===========
在多重继承的情况下,派生类中有多个虚函数表,虚函数表的排列与继承顺序一致,派生类会重写函数。
覆盖所有同名虚函数表的内容,派生类定义的新虚函数将扩展到第一类虚函数表之后。
这里有一个例子:
A1类
{
公众号:
A1(int _a1=1) : a1(_a1) { }
虚空f(){ cout ' a 1: f ' endl;}
虚空g(){ cout ' a 1:g ' endl;}
虚空h(){ cout ' a 1:h ' endl;}
~A1() {}
私人:
int a1
};
A2类
{
公众号:
A2(int _a2=2) : a2(_a2) { }
虚空f(){ cout ' a 2: f ' endl;}
虚空g(){ cout ' a 2:g ' endl;}
虚空h(){ cout ' a 2:h ' endl;}
~A2() {}
私人:
int a2
};
A3级
{
公众号:
A3(int _a3=3) : a3(_a3) { }
虚空f(){ cout ' a : f ' endl;}
虚拟空间g() { cou
t << "A3::g" << endl; }virtual void h() { cout << "A3::h" << endl; }
~A3() {}
private:
int a3;
};
class B : public A1, public A2, public A3
{
public:
B(int _a1 = 1, int _a2 = 2, int _a3 = 3, int _b = 4) :A1(_a1), A2(_a2), A3(_a3), b(_b) { }
virtual void f1() { cout << "B::f" << endl; }
virtual void g1() { cout << "B::g" << endl; }
virtual void h1() { cout << "B::h" << endl; }
private:
int b;
};
==========华丽的分割线==============
如果多重继承的情况下,派生类重载了基类的虚函数,虚表是什么样子的呢?
下面我们上代码看看
class A1
{
public:
A1(int _a1 = 1) : a1(_a1) { }
virtual void f() { cout << "A1::f" << endl; }
virtual void g() { cout << "A1::g" << endl; }
virtual void h() { cout << "A1::h" << endl; }
~A1() {}
private:
int a1;
};
class A2
{
public:
A2(int _a2 = 2) : a2(_a2) { }
virtual void f() { cout << "A2::f" << endl; }
virtual void g() { cout << "A2::g" << endl; }
virtual void h() { cout << "A2::h" << endl; }
~A2() {}
private:
int a2;
};
class A3
{
public:
A3(int _a3 = 3) : a3(_a3) { }
virtual void f() { cout << "A3::f" << endl; }
virtual void g() { cout << "A3::g" << endl; }
virtual void h() { cout << "A3::h" << endl; }
~A3() {}
private:
int a3;
};
class B : public A1, public A2, public A3
{
public:
B(int _a1 = 1, int _a2 = 2, int _a3 = 3, int _b = 4) :A1(_a1), A2(_a2), A3(_a3), b(_b) { }
virtual void f() { cout << "B::f" << endl; }
virtual void g() { cout << "B::g" << endl; }
virtual void h() { cout << "B::h" << endl; }
private:
int b;
};
从编译器给出的信息我们可以看到在第二个虚函数表中有adjustor{8}的字样,这就是A类的大小,也就是
说这就是告诉编译器需要进行8字字节的偏移。当B类用不同的基类指针指向的时候,运行的是不同的基类中的
虚函数,例如A2类指向B的时候虚函数指针是自动跳到B类中的A2类所在的地方的。经过这样的调整
A1 A2 A3都会指向正确的类的位置。当B类重写了函数之后 A2 A3的虚函数表所指向的已经不再是简单的函数指针了
而是一个trunk对象,这就是c++的trunk技术。所谓的trunk就是一段汇编代码 这段汇编代码可以适当的偏移来
调整this指针来跳到对应的虚函数中去 并调用这个函数,也就是当使用A1的指针指向B的对象的时候 不需要发生偏移
而使用A2指向B时则需要进行sizeof(A1)字节的偏移,并跳转到A2中的函数来执行,这就是通过trunk的jump
指令跳转到这个函数的。