C++中虚函数的实现背后

上一篇 / 下一篇  2012-09-07 13:22:41 / 个人分类:C++

W4P&O%bm;_i$s0  摘要:虚函数是很多编程语言中一个特性,比如c#,java,当然在c++语言中也有。这三种语言都是面向对象的语言。我们都知道面向对象语言有三个最基本的特征就是:继承,多态,封装。在c++语言中,这种多态的特征就是通过虚函 ...

S6f;Fe1L0

%V4Zl c I zL b C'H0   虚函数是很多编程语言中一个特性,比如c#,java,当然在c++语言中也有。这三种语言都是面向对象的语言。我们都知道面向对象语言有三个最基本的 特征就是:继承,多态,封装。在c++语言中,这种多态的特征就是通过虚函数(virtual)来实现的。这种实现方法在其它语言中(比如c#)中也是如 此。51Testing软件测试网o/z2o} {-iJO`

51Testing软件测试网 Z]ce&k? A

  我要说的就是虚函数到底是怎么实现的。还是以例子来说明。我引用了上一篇随笔中的代码。

Z2N ~j:o8EL0

Pu'de7v*R.o h0class ClassA51Testing软件测试网*NUb tH^ozz
{51Testing软件测试网 m VU _-h#f*[?]nj%s
public:
3z+[ cd?9M?*FC0    void fun1();51Testing软件测试网nd-X*[*D;Y5~;y/C+MS
    void fun2();
~(k EFxIX#u]0    virtual void fun3();
"Y:nE7c@bT*})x/K0};51Testing软件测试网dgOmX!o#c` o?c
void ClassA::fun1()
1o)ZdG1Dr$l0{51Testing软件测试网B/b7Gn"}#f/M l5j`
    cout << "ClassA.fun1"<<endl;
LP5N$?+V#C B0};51Testing软件测试网:U4w3H a5q7LmB+\
void ClassA::fun2()
0RtGT`?.[0{51Testing软件测试网f#H9U`:VsX"h,G
    cout << "ClassA.fun2"<<endl;51Testing软件测试网Z5^ X%j(E+ac`4~7\
};51Testing软件测试网wL$vMv-[5j
void ClassA::fun3()51Testing软件测试网tTZ!p$ax9L y\~
{
v,?#wBd0    cout << "ClassA.fun3"<<endl;51Testing软件测试网!y6c dAJbj+r;q
};
+F'T3pBD-H*B-@5[0class ClassB : public ClassA51Testing软件测试网F;X5Wv Y?1]
{51Testing软件测试网XBR8bGix.G.e,I+L
public:51Testing软件测试网n9iIcI`iQq/f
    void fun1();51Testing软件测试网^_"C;[$EZ
    void fun2();
1So#O qz{,Da0    virtual void fun3();
~K!Nf7Z-J(w [0};51Testing软件测试网|:U1f r Z2OW
void ClassB::fun3()
6Rf;g(o ~S[5zg0{
#q YUO!B }0    cout << "ClassB.fun3"<<endl;51Testing软件测试网.ut_'A u6C
};
3C(\E0Her H8g0class ClassC : public ClassB
^Ms AfS)s6D2F K0{
.lUI#k q [^ y7{)]0public:51Testing软件测试网0\-p7q2Tn
    void fun1();51Testing软件测试网*}'EN m[(T
    void fun2();51Testing软件测试网$EbC ?:J#L~ mK5D
    virtual void fun3();51Testing软件测试网fvd,MfD T
};
0_2zwRF F'q)}0void ClassC::fun3()51Testing软件测试网Y"dh_^RV+X"?
{
]V%F&\#RW6s){0    cout << "ClassC.fun3"<<endl;
4`gN ~#Q$jO2C0};
Y:S'B g:B([|g0void main()
!c#o'sE Kr H+b ?IL0{51Testing软件测试网H-c)Zk-G ]O,VO
    ClassA *a[3];51Testing软件测试网`v0I },D t{E
    ClassA a1;51Testing软件测试网6~D&M6?D/YP
    ClassB b1;
1Z} q#V4r\ W0    ClassC c1;
51Testing软件测试网h9U3p.@2j i q rM

51Testing软件测试网 T-c|Eg:l0OD

    a1.fun3();
%g{!F M5ZOqE0    b1.fun3();51Testing软件测试网1~;g] f)AaX
    c1.fun3();

r,E9P;D Cho051Testing软件测试网 Bym,V,[BGl

    a[0] = &a1;51Testing软件测试网&G^1Ep;a
    a[1] = &b1;
4X9z*@-mL#Q Z#k"N0    a[2] = &c1;51Testing软件测试网+a+~Y/F~4f$q
    cout << "virtual function arraytest" <<endl;51Testing软件测试网{6WyY2?}
    for(int i=0;i<3;i++)51Testing软件测试网*e#sM^`,w
    {
V.FNYZ[*m7K/B0        a[i]->fun3();
R8ll+d5A0p?!XvQ0    }
51Testing软件测试网r9Cbq&?3]

EUJ?l7Er'}|-w5o0    cout << "((ClassA)&b1).fun3():";
*mh;jebWI,Z0    ((ClassA*)&b1)->fun3();
;v#g,i jmny?9]+r0    //object slicing
HY R;a!B^'b6T h0    cout << "object slicing"<<endl;51Testing软件测试网t;L"Q/|H:TzQ
    cout <<"((ClassA)b1).fun3():";
G1K/MO3C8M ~mtd0    ((ClassA)b1).fun3();51Testing软件测试网y o!O"Vf v&y
}

T'Y KN.xC~3Z:N4[0

i.[(Z}|8d8a0  类继承结构图如下:51Testing软件测试网:i,G:z;Am9D J

?Lg F4h"Q)M0

  其中fun3是虚拟函数,对ClassB,ClassC子类中分别进行了重写。

u&?f4yB-r;\8tf?%U0

(L&|\;X c0  下面我解释一下虚函数的背后是怎么实现的:51Testing软件测试网G&z$QhsPr/jx{+@

W/mY0m#s0  我们都知道,虚函数可以做到动态绑定,为了实现动态绑定,编译器是通过一个表格(虚拟函数表),在运行时间接的调用实际上绑定的函数来达到动态 绑定,其中这个我刚所说的表格其实现就是一个“虚拟函数表”。这张表对我们程序来说是透明的。是编译器为我们的代码自动加上去的(更准确的讲,并不是为所 有的代码都添加一张虚拟函数表,而是只针对那些包括虚函数的代码才加上这张表的)。

I_3n&`;[D#M0

:zUn-s*UKx,w0  既然有了这么一张虚拟函数表,自然而然我们就会想到,这个虚拟函数表里到底是存放一些什么东西呢?很简单,即然叫做虚拟函数表,当然是存放虚拟 函数了,呵呵,在c++中,该表每一行的元素应该就是我们代码中虚拟函数地址了,也就是一个指针。有了这个地址,我们可以调用实际代码中的虚拟函数了。

x*@[7cM*P{qeI0

1nPLoC8u0  编译器既然为我们的代码加了一张虚拟函数表,那这张虚拟函数表怎么与我们的代码关联起来呢? 要实现动态绑定,我们应该利用这张虚拟函数表来调用虚拟函数,为了达到目的,编译器又会为我们的代码增加一个成员变量,这个成员变量就是一个指向该虚拟函 数表的指针,该成员变量通常被命名为:vptr。51Testing软件测试网'Tq/Fi'|

51Testing软件测试网dTF&I,jTz8]

  说到了这里,上面代码中的ClassA中的在内存中应该如下图所示:

)k^~ [C/q8n_1`X0

51Testing软件测试网LL;k3e^

  每一个ClassA的实例,都会有一个虚拟函数表vptr,当我们在代码中通过这个实例来调用虚拟函数时,都是通过 vptr先找到虚拟函数表,接着在虚拟函数表中再找出指向的某个真正的虚拟函数地址。虚拟函数表中的内容就是类中按顺序声明的虚拟函数组织起来的。在派生 的时候,子类都会继承父类的虚拟函数表vptr,我们只在把这个vptr成员在继承体系中一般看待就成了。

+k6se"~!U4j0

  有一点要说明一下,当子类在改写了父类中的虚拟函数时,同时子类的vptr成员也会作修改,此时,子类的vptr成 员指向的虚拟函数表中的存放的虚拟函数指针不再是父类的虚拟函数地址了,而是子类所改写父类的虚拟函数地址。理解这一点就很容易想到了:原来多态体现在这 里!51Testing软件测试网:O6jb5HB0\7_/z

  有了上面的说明,接下来ClassB,ClassC类的内存占据空间应该如下图所示:

F H+n#j Saf0

51Testing软件测试网gJ.G+] xMvQR

  同理,ClassC也一样。(这些图画得真是丑啊!)51Testing软件测试网;jw V?+lw ^D

  于是一个指向ClassA的对象的实例,调用 fun3就是ClassA::fun3(),一个指向ClassB的对象的实例,调用 fun3就是ClassB::fun3(),一个指向ClassC的对象的实例,调用 fun3就是ClassC::fun3(),这些调用通过都是通过虚拟函数表来进行的。51Testing软件测试网T1_$Ss]+O ds

  最后,上面代码中main函数的示例的执行结果也就是恍然大悟了,答案就在上一篇随笔的回复里面。已经有人帮我回复了,就此谢过了! 欢迎大家一起探讨!

zC&IV-[ g$D B,E!p%m0

TAG:

 

评分:0

我来说两句

Open Toolbar