你的分享就是我们的动力 ---﹥

[C++面试题]之继承与接口

时间:2011-11-07 09:32来源:www.chengxuyuans.com 点击:

   整个C++程序设计全面围绕面向对象的方式进行。类的继承特性是C++的一个非常重要的机制。继承特性可以使一个新类获得其父类的操作和数据结构,程序员只需在新类中增加原有类没有的成分。

     在面试过程中,各大企业会考量你对虚函数、纯虚函数、私有继承、多重继承等知识点的掌握程度,因此就有了我们这一节的内容,开始吧。
1、以下代码的输出结果是什么?
view sourceprint?
01    #include<iostream>
02    using namespace std;
03    
04    class A
05    {
06    protected:
07        int m_data;
08    public:
09        A(int data = 0)
10        {
11            m_data = data;
12        }
13        int GetData()
14        {
15            return doGetData();
16        }
17        virtual int doGetData()
18        {
19            return m_data;
20        }
21    };
22    
23    class B : public A
24    {
25    protected:
26        int m_data;
27    public:
28        B(int data = 1)
29        {
30            m_data = data;
31        }
32        int doGetData()
33        {
34            return m_data;
35        }
36    
37    };
38    
39    class C : public B
40    {
41    protected:
42        int m_data;
43    public:
44        C(int data = 2)
45        {
46            m_data = data;
47        }
48    };
49    
50    int main ()
51    {
52        C c(10);
53    
54        cout << c.GetData() <<endl;
55        cout << c.A::GetData() <<endl;
56        cout << c.B::GetData() <<endl;
57        cout << c.C::GetData() <<endl;
58    
59        cout << c.doGetData() <<endl;
60        cout << c.A::doGetData() <<endl;
61        cout << c.B::doGetData() <<endl;
62        cout << c.C::doGetData() <<endl;
63        return 0;
64    }

     解析:构造函数从最初始的基类开始构造的,各个类的同名变量没有形成覆盖,都是单独的变量.理解这两个重要的C++特性后解决这个问题就比较轻松了,下面我们看看:

     cout << c.GetData() <<endl;

     本来是要调用C类的GetData(),C中未定义,故调用B中的,但是B中也未定义,故调用A中的GetData(),因为A中的 doGetData()是虚函数,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出 1。

     cout << c.A::GetData() <<endl;

     因为A中的doGetData()是虚函数,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出 1。

     cout << c.B::GetData() <<endl;

     肯定是B类的返回值 1 了。

     cout << c.C::GetData() <<endl;

     跟cout << c.GetData() <<endl;语句是一样的。

 

     cout << c.doGetData() <<endl;

     B类的返回值 1 了。

     cout << c.A::doGetData() <<endl;

     因为直接调用了A的doGetData() ,所以输出0。

     cout << c.B::doGetData() <<endl;
     cout << c.C::doGetData() <<endl;

     这两个都是调用了B的doGetData(),所以输出 1。

     这里要注意存在一个就近调用,如果父类存在相关接口则优先调用父类接口,如果父类也不存在相关接口则调用祖父辈接口。

答案:

    1 1 1 1 1 0 1 1

 
2、以下代码输出结果是什么?
view sourceprint?
01    #include<iostream>
02    using namespace std;
03    
04    class A
05    {
06    public:
07        void virtual f()
08        {
09            cout<<"A"<<endl;
10        }
11    };
12    
13    class B : public A
14    {
15    public:
16        void virtual f()
17        {
18            cout<<"B"<<endl;
19        }
20    };
21    
22    int main ()
23    {
24        A* pa=new A();
25        pa->f();
26        B* pb=(B*)pa;
27        pb->f();
28    
29        delete pa,pb;
30        pa=new B();
31        pa->f();
32        pb=(B*)pa;
33        pb->f();
34    
35        return 0;
36    }

        解析:这是一个虚函数覆盖虚函数的问题。A类里的f()函数是一个虚函数,虚函数是被子类同名函数所覆盖的。而B类里的f()函数也是一个虚函数,它覆盖A类f()函数的同时,也会被它的子类覆盖。但是在 B* pb=(B*)pa;里面,该语句的意思是转化pa为B类型并新建一个指针pb,将pa复制到pb。但是这里有一点请注意,就是pa的指针始终没有发生变化,所以pb也指向pa的f()函数。这里并不存在覆盖的问题。

      delete pa,pb;删除了pa和pb所指向的地址,但是pa、pb指针并没有删除,也就是我们通常说的悬浮指针,现在重新给pa指向新地址,所指向的位置是B类的,而之前pa指针类型是A类的,所以就产生了一个覆盖。pa->f();的值是B。

      pb=(B*)pa;转化pa为B类指针给pb赋值,但pa所指向的f()函数是B类的f() 函数,所以pb所指向的f()函数是B类的f()函数。pb->f();的值是B。

       答案:

    A A B B

 
3、派生类的3种继承方式?

       答案:

        (1)公有继承方式:

         基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。这里保护成员与私有成员相同。

         基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员可见,基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态;基类的私有成员不可见,基类的私有成员仍然是私有的,派生类不可访问基类中的私有成员。

         基类成员对派生类对象的可见性对派生类对象来说,基类的公有成员是可见的,其他成员是不可见的。

        (2)私有继承方式:

         基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。

         基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员可见,基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问;基类的私有成员不可见,派生类不可访问基类中的私有成员。

         基类成员对派生类对象的可见性对派生类对象来说,基类的所以成员都是不可见的。

         所以说,在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。

        (3)保护继承方式:

         这种继承方式与私有继承方式的情况相同,两者的区别仅在于对派生类的成员而言,基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。

         基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员可见,基类的公有成员和保护成员都作为派生类的保护成员,并且不能被这个派生类的子类所访问;基类的私有成员不可见,派生类不可访问基类中的私有成员。

         基类成员对派生类对象的可见性对派生类对象来说,基类的所以成员都是不可见的。

         所以说,在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。

 
4、下面程序运行结果是什么?
view sourceprint?
01    #include<iostream>
02    using namespace std;
03    
04    class A
05    {
06        char k[3];
07    public:
08        virtual void aa(){};
09    };
10    
11    class B : public virtual A
12    {
13        char j[3];
14    public:
15        virtual void bb(){};
16    };
17    
18    class C : public virtual B
19    {
20        char i[3];
21    public:
22        virtual void cc(){};
23    };
24    
25    int main ()
26    {
27        cout << "sizeof(A):" << sizeof(A) << endl;
28        cout << "sizeof(B):" << sizeof(B) << endl;
29        cout << "sizeof(C):" << sizeof(C) << endl;
30        return 0;
31    }

      解析:(1)对于A类,由于有一个虚函数,那么必须有一个对应的虚函数表来记录对应的函数入口地址。每个地址需标有一个虚指针,指针的大小为4。类中还有一个char k[3],每一个char值所占空间是1,所以char k[3]所占大小是3。做一个数据对齐后变为4。所以,sizeof(A)的结果就是char k[3]所占大小4和虚指针所占大小4之和等于8。

    (2)对于B类,由于B继承了A,同时还拥有自己的虚函数,那么B中首先拥有一个vfptr_B,指向自己的虚函数表。还有char j[3],大小为4,可虚继承该如何实现呢?首先要通过加入一个虚类指针(记vbptr_B_A)来指向其父类,然后还要包含父类的所有内容,所以 sizeof(B)的大小是:A类所占大小8,char j[3]所占大小4,vfptr_B所占大小4,vbptr_B_A所占大小4,它们之和等于20。

    (3)对于C类和B类差不多,结果是32。

     答案:

    sizeof(A):8

    sizeof(B):20

    sizeof(C):32

 
5、什么是虚继承?它与一般的继承有什么不同?它有什么用?写出一段虚继承的C++代码。

    答案:

    虚拟继承是多重继承中特有的概念。虚拟基类是为了解决多重继承而出现的,请看下图:

    QQ截图20111106094148

     

    在图 1中,类D接触自类B和类C,而类B和类C都继承自类A,因此出现了图 2所示的情况。

    在图 2中,类D中会出现两次A。为了节省内存空间,可以将B、C对A的继承定义为虚拟继承,而A成了虚拟基类。最后形成了图 3。

    代码如下:
    view sourceprint?
    1    class A;
    2    class B : public virtual A;
    3    class C : public virtual A;
    4    class D : public B,public C;

 

     继承与接口这一节写完了,感觉收获很多啊,希望你们也和我一样。因为本人算是初学者,所以很多知识点说得不够简练,甚至不正确的,请多多包涵和指正。
 

本文地址http://www.chengxuyuans.com/job_interview/30790.html

其他频道: