吐蕃地图:虚函数继承

来源:百度文库 编辑:偶看新闻 时间:2024/05/09 16:58:51

“虚函数继承”本来就是“虚函数”的“继承”嘛。
比如我举的那个例子,如果不是virtual,那么输出就是A,加了virtual输出就是B,“虚函数继承”可以根据实际对象调用不同的函数,而普通继承会在编译阶段就根据p变量的类型来决定调用哪个函数!
其他的什么就是玩文字游戏了,弄清楚了也没意义……

通俗的讲,虚继承就是为了节约内存的,他是多重继承中的特有的概念。适用与菱形继承形式。
如:类B、C都继承类A,D继承类B和C。为了节省内存空间,可以将B、C对A的继承定义为虚拟继承,此时A就成了虚拟基类。
class A;
class B:public vitual A;
class C:public vitual A;
class D:public B,public C;
 
虚函数继承就是覆盖。即基类中的虚函数被派生类中的同名函数所覆盖。
class parent
{
  public:
  vitual void foo(){cout<<"foo from parent";};
  void foo1(){cout<<"foo1 from parent";};
};
class son:public parent
{
  void foo(){cout<<"foo from son";};
  void foo1(){cout<<"foo1 from son";};
};
int main()
{
 parent *p=new son();
 p->foo();
 p->foo1();
 return 0;
}

其输出结果是:
foo from son,foo1 from parent


 1#include 
 2#include 
 3#include 
 4
 5using namespace std;
 6class A
 7{
 8      char k[3];
 9      public:
10             virtual void aa(){};
11};
12
13class B : public virtual A
14{
15      char j[3];
16      public:
17             virtual void bb(){};
18};
19
20class C : public virtual B
21{
22      char i[3];
23      public:
24             virtual void cc(){};
25};
26
27int main(int argc, char *argv[])
28{
29    cout<<"sizeof(A):"<30    cout<<"sizeof(B):"<31    cout<<"sizeof(C):"<32    system("PAUSE");
33    return EXIT_SUCCESS;
34}问题:程序运行的结果?
答案:8,16,24。
解释:
(1)对于类A,由于有一个虚函数,那么必须得有一个对应的虚函数表来记录对应的函数入口地址。每个地址需要一个虚指针,指针的大小为4。类中还有一个char k[3],当然大小为3。为什么是8呢?因为在计算机里,是以4为单位,所以第一条输出的结果为8。
(2)对于类B,同类A一样,自己的大小为8,但是由于虚继承类A,所以在虚表中要加入一个虚类指针来指向其类A,然后在包含类A的所有成员,sizeof(A)为8,结果便是16。
(3)对于类C,同类B一样,自己的大小8,加上sizeof(B),结果为24。 


在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。比如在微软的MFC类库中,你会发现很多函数都有virtual关键字,也就是说,它们都是虚函数。难怪有人甚至称虚函数是C++语言的精髓。

        那么,什么是虚函数呢,我们先来看看微软的解释:

        虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。

                                                               ——摘自MSDN

        这个定义说得不是很明白。MSDN中还给出了一个例子,但是它的例子也并不能很好的说明问题。我们自己编写这样一个例子:

#include "stdio.h"

#include "conio.h"



class Parent

{

public:

char data[20];

void Function1();

virtual void Function2();   // 这里声明Function2是虚函数

}parent;

void Parent::Function1()

{

printf("This is parent,function1 ");

}

void Parent::Function2()

{

printf("This is parent,function2 ");

}



class Child:public Parent

{

void Function1();

void Function2();

} child;

void Child::Function1()

{

printf("This is child,function1 ");

}

void Child::Function2()

{

printf("This is child,function2 ");

}



int main(int argc, char* argv[])

{

Parent *p; // 定义一个基类指针

if(_getch()=='c')     // 如果输入一个小写字母c

       p=&child;         // 指向继承类对象

else

       p=&parent;       // 否则指向基类对象

p->Function1();   // 这里在编译时会直接给出Parent::Function1()的

                                   入口地址。

p->Function2();    // 注意这里,执行的是哪一个Function2?

return 0;

}

        用任意版本的Visual C++或Borland C++编译并运行,输入一个小写字母c,得到下面的结果:

This is parent,function1

This is child,function2

      为什么会有第一行的结果呢?因为我们是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以我们看到了第一行的结果。

        那么第二行的结果又是怎么回事呢?我们注意到,Function2()函数在基类中被virtual关键字修饰,也就是说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。如果我们在运行上面的程序时任意输入一个非c的字符,结果如下:

This is parent,function1

This is parent,function2

        请注意看第二行,它的结果出现了变化。程序中仅仅调用了一个Function2()函数,却可以根据用户的输入自动决定到底调用基类中的 Function2还是继承类中的Function2,这就是虚函数的作用。我们知道,在MFC中,很多类都是需要你继承的,它们的成员函数很多都要重载,比如编写MFC应用程序最常用的CView::OnDraw(CDC*)函数,就必须重载使用。把它定义为虚函数(实际上,在MFC中OnDraw不仅是虚函数,还是纯虚函数),可以保证时刻调用的是用户自己编写的OnDraw。虚函数的重要用途在这里可见一斑。 

2、      析构函数是虚函数的优点是什么

用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明:

有下面的两个类:

class ClxBase

{

public:

    ClxBase() {};

    virtual ~ClxBase() {};

 

    virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };

};

 

class ClxDerived : public ClxBase

{

public:

    ClxDerived() {};

    ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };

 

    void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };

};

 

代码

 

ClxBase *pTest = new ClxDerived;

pTest->DoSomething();

delete pTest;

 

输出结果是:

 

Do something in class ClxDerived!

Output from the destructor of class ClxDerived!

 

这个很简单,非常好理解。

但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:

Do something in class ClxDerived!

也就是说,类ClxDerived的析构函数根本没有被调用!一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。

所以,文章开头的那个问题的答案就是--这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

 

说实话,这个也是今天才深刻认识到的。

 

当然还问到很多数据结构和算法方面(空间复杂度和时间复杂度之类的东东,说真的也是基础性的)的问题,至于那些东西,自己说实话抛开没用他们已经很长时间了,真可以说忘的差不多了,考这种真的很怕,也怪平时没怎么用到。不知道大家用的多不?

好久没有正式参加过面试了,今天突然来一次觉得自己基础还是不够扎实。


一般来说 编译期的多态是指 template的,函数重载 是函数重载
运行期的多态一半是通过虚表来实现的。