C/C++笔试系列--经典C++笔试题解析4

经典C++笔试题解析4

 

Sailor_forever sailing_9806@163.com 转载请注明

http://blog.csdn.net/sailor_8318/archive/2008/10/31/3188958.aspx

 

46、每个有虚函数的类都有一个自己的VTABLE,画图解释继承链中的VTABLE关系

 

各种类型的对象具备和instrument相同的接口,因此可以响应相同的信息。因此其地址都可以放在instrument类型的指针数组中,但是编译器只知道其指向了一个instrument对象。

 

当创建含有虚函数的类或者从含有虚函数的类派生而来,编译器将为每个类创建唯一个VTABLE。在该表中包含了本类的或者从基类继承而来的虚函数的地址,当派生类中没有overriding基类中的虚函数时,将采用前一个基类的虚函数地址。当采用简单的继承机制时,每个对象只有一个VPTR,其由构造函数初始化指向该类的VTABLE

 

当通过基类地址调用虚函数时,因为编译器并没有完整信息实现early binding,故其将插入代码获取虚函数地址。所有类在相同的位置即通常为第一个位置含有VPTR指针,因此编译器可以通过VPTR获得VTABLE,其中所有虚函数地址按照相同顺序排列。

 

47、虚函数调用的实质 

Instrument * pi;

Brass B;

i = &B;

i->adjust(1);

写出最后一行的汇编代码:

28:       Instrument* pi;

29:       Brass B;

004010A 8 8D 4D F8             lea         ecx,[ebp-8]

004010AB E8 73 FF FF FF       call        @ILT+30(Brass::Brass) (00401023)

30:       pi=&B;

004010B0 8D 45 F 8             lea         eax,[ebp-8]

004010B3 89 45 FC             mov         dword ptr [ebp-4],eax

31:       pi->adjust(1);

004010B6 8B F4                mov         esi,esp

004010B8 6A 01                push        1 ;参数入栈

004010BA 8B 4D FC             mov         ecx,dword ptr [ebp-4] this指针

004010BD 8B 11                mov         edx,dword ptr [ecx] VPTR指针

004010BF 8B 4D FC             mov         ecx,dword ptr [ebp-4] ;作参数

004010C 2 FF 52 08             call        dword ptr [edx+8] ;取虚函数

非静态成员函数的调用都涉及到将this指针传递给函数

 

48、什么是抽象基类,纯虚函数?

抽象基类可不可以有实例? 不能

纯虚函数可不可以有实现?

 

抽象基类中至少含有一个纯虚函数,其提供派生类的一些通用接口。

=0这告诉编译器在VTABLE中为该虚函数留一个位置,而不填充虚函数的地址。只要有一个成员函数被声明为纯虚函数,VTABLE表就是不完整的。当创建该类的对象时,编译器将给出错误信息告警。

 

也可以在基类中提供纯虚函数的实现,但是在派生类中仍然必须实现该纯虚函数,否则派生类变为纯虚函数。唯一的好处是在派生类中使用基类部分提供的该虚函数的公共代码

唯一的例外情况是,纯虚析构函数无需在派生类中重新实现

 

class X{

public:

        virtual void f()=0       {

                cout<<"haha"<<endl;

        }

};

另外内联函数不能是虚函数,因为虚函数表需要显示的函数地址

 

49、在派生类中增加新的虚函数,能不能用基类的指针调用之,为什么?请从VTPRVTABLE的角度来解释。

如果此时确切的知道基类指针指向的是派生类对象,需要调用该函数,该如何做?

 

不能调用基类中没有的虚函数。因为基类指针可以指向任意的派生类对象,有的含有该接口有些不含,为了确保正确,只能调用基类中已有的虚函数接口

 

通过该基类指针调用新增的虚函数,可以通过强制转换,但是其违背了虚函数的机制,因此只能采用RTTI动态类型识别机制了

       

class X{

public:

        virtual void f(){cout<<"haha"<<endl;}

};

class Y :public X{

public:

        virtual void f(){cout<<"hehe"<<endl;}

        virtual void ff(){cout<<"hehehehe"<<endl;}

};

void main(){

        X* p;

        Y y;

        p=&y;

        Y* py=NULL;

        py=static_cast<Y*>(p);

        if(py) py->ff();

}

 

50、看下面的例子,谈谈为什么要避免值传递:

class X{

        int xi;

public:

 X(int i){xi=i;}

        virtual int f()     {return xi; }

};

class Y :public X{

        int yj;

public:

        Y(int i, int j):X(i),yj(j){}

        virtual int f()     {return X::f()+yj;}

};

void call(X B){cout<<B.f()<<endl;}

void main(){    

        Y A(5,8);

        call(A);

}

OUTPUT5

类对象在进行值传递时发生了对象切片,无论是默认的拷贝构造函数还是手工提供的,编译器都会在其中放一段代码对VPTR进行初始化。这里call的参数类型声明为基类,则调用了基类的拷贝构造函数,将对象的VTPR指向了基类的VTABLE(原本应该指向派生类的VTABLE的)。于是错误发生了。

回忆一下,什么方法可以迫使程序员放弃值传递? 私有拷贝构造函数

或者在基类中定义纯虚函数,则编译器将阻止进行值传递时,因为纯虚基类不能定义对象

 

51、无论是手工提供的构造函数还是默认的构造函数,无论是拷贝构造函数还是一般构造函数,编译器都会在其中加上一段代码用以初始化VPTR(当然是有虚函数的类的构造函数)

 

52、编译器插入到构造函数中的秘密代码一般有哪几部分?

a.     初始化VPTR(对于有虚函数的类来说)

b.     检查this指针(因为对象有可能没有构造成功,operator new

c.     调用基类的构造函数(如果在派生类的初始化参数列表中没有显式的调用基类的构造函数,则编译器会自动调用基类的无参数构造函数(默认的或者手工的)即使派生类构造函数在函数体内部调用了基类构造函数也不例外,如果这时基类没有无参数构造函数(比如它只手工提供了带参数的构造函数)则编译出错)

class X{

public:

        X()   {cout<<"X::X()"<<endl;}

    X(int i){cout<<"X::X(int)"<<endl;}

};

class Y :public X{

public:

    Y(int i){X(5);}

};

void main(){

        Y y(6);

}

OUTPUTX::X()

X::X(int)

 

53、说明下例为什么无法编译通过?

class X{

public:

    X(int i)      {cout<<"X::X(int)"<<endl;}

};

class Y :public X{

public:

};

void main(){

        Y y;

}

如何才能通过?

Y自动生成无参构造函数,但X没有无参构造函数

 

54、上例中如果派生类改为:

class Y :public X{

public:

Y(){}

};

为何不能编译通过?如何修改?

规则:应该始终在初始化列表中调用基类构造函数。尽量避免调用默认的构造函数

 

55、如果在构造函数中调用了虚函数,只有虚函数的本地版本被调用

class X{

public:

virtual void f(){cout<<"X::f"<<endl;}

    X()    {f();}

};

class Y : public X{

public:

virtual void f(){cout<<"Y::f"<<endl;}

    Y()   {f();}

};

class Z : public Y{

public:

virtual void f(){cout<<"Z::f"<<endl;}

    Z()    {f();}

};

 

void main(){

Z z;

}

OUTPUT

X::f

Y::f

Z::f

Z的构造函数被调用时,编译器会先调用X的构造函数,然后是Y的,因为构造函数在真正运行之前会自动插入段代码,其将对象的VPTR指向自己类型的VTABLE,因此当构造到达X的构造函数体内时,VPTR是指向XVTABLE的,因此调用的函数就是X版本的函数。

 

56、构造函数能不能是虚函数? 

不能。

但试想用一个基类指针去析构一个派生类对象时会发生什么事情?如何解决?

若通过基类指针操作对象如delete,因为不知道基类指针指向的具体类型,因此安全万能的做法只能调用基类的析构函数

令析构函数为虚函数,则通过基类指针析构时会自动调用派生类的析构函数。

析构函数不需要像构造函数那样显式的调用基类的析构函数,编译器会自动调用基类的构造函数。

 

57、若在析构函数中调用了虚函数,应该调用虚函数的哪个版本? 

析构函数知道其自身由谁派生而来,但不知道谁会继承自己,因此只会调用自身和基类的析构函数。

其不会去处理一个也许几经被析构的派生类数据。因此晚捆绑机制在析构函数中被忽略。

 

59、多重继承应避免。当多重集成中出现菱形继承时,为了消除继承人中包含两个base类的实体,该如何?  

虚拟继承

class base{};

class d1: virtual base{};

class d2: virtual base{};

class mi: public d1, public d2{};

 

60.  RTTI。一般我们将一个指针向上映射为一个基类指针,然后通过虚函数使用基类的接口。但是偶尔我们需要知道一个基类指针指向对象的确切类型,于是有了RTTI

注意,RTTI依赖与驻留在虚函数表中的类型信息,所以在一个没有虚函数的类上使用RTTI是会出错的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值