C经典笔试题附答案.docx
- 文档编号:4631214
- 上传时间:2022-12-07
- 格式:DOCX
- 页数:20
- 大小:44.45KB
C经典笔试题附答案.docx
《C经典笔试题附答案.docx》由会员分享,可在线阅读,更多相关《C经典笔试题附答案.docx(20页珍藏版)》请在冰豆网上搜索。
C经典笔试题附答案
8:
下列多重继承时的二义性问题如何解决?
classA{//类A的定义
public:
voidprint(){cout<<"Hello,thisisA"< }; classB{//类B的定义 public: voidprint(){cout<<"Hello,thisisB"< }; classC: publicA,publicB{//类C由类A和类B共同派生而来 public: voiddisp(){print();}//编译器无法决定采用A类中定义的版本还是B类中的版本 }; 解答: 若两个基类中具有同名的数据成员或成员函数,应使用成员名限定来消除二义性,如: voiddisp(){ A: : print();//加成员名限定A: : } 但更好的办法是在类C中也定义一个同名print函数,根据需要调用A: : print()还是B: : print(),从而实现对基类同名函数的隐藏 9: 下列公共基类导致的二义性如何解决? classA{//公共基类 public: //public成员列表 voidprint(){ cout<<"thisisxinA: "< classB: publicA{}; classC: publicA{}; classD: publicB,publicC{}; voidmain(){ Dd;//声明一个D类对象d A*pa=(A*)&d;//上行转换产生二义性 d.print();//print()具有二义性,系统不知道是调用B类的还是C类的print()函数 } 注意: 把子类的指针或引用转换成基类指针或引用是上行转换,把基类指针或引用转换成子类指针或引用是下行转换。 解答: 1)main函数中语句“d.print();”编译错误,可改为以下的一种: d.B: : print();d.C: : print(); 若改为“d.A: : print();”又会如何呢? 由于d对象中有两个A类对象,故编译会报“基类A不明确”。 2)语句“A*pa=(A*)&d;”产生的二义性是由于d中含有两个基类对象A,隐式转换时不知道让pa指向哪个子对象,从而出错。 可改为以下的一种: A*pa=(A*)(B*)&d;//上行转换 A*pa=(A*)(C*)&d;//上行转换 事实上,使用关键字virtual将共同基类A声明为虚基类,可有效解决上述问题。 10: 下面哪种情况下,B不能隐式转换为A()? (2011•腾讯) A.classB: publicA{}B.classA: publicB{} C.classB{operatorA();}D.classA{A(constB&);} 解答: B。 因为子类包含了父类部分,所以子类可以转换为父类,但是相反,父类没有子类额外定义的部分,所以不能转换为子类,故A正确,而B错误。 非C++内建型别A和B,在以下几种情况下B能隐式转化为A。 1)B公有继承自A,可以是间接继承的。 classB: publicA{ }; 此时若有“Aa;Bb;”,则“a=b;”合法。 2)B中有类型转换函数。 classB{ operatorA(); }; 此时若有“Aa;Bb;”,则“a=b;”合法。 3)A实现了非explicit的参数为B(可以有其他带默认值的参数)的构造函数 classA{ A(constB&); }; 此时若有“Aa;Bb;”,则“a=b;”合法。 11: 调用一成员函数时,使用动态联编的情况是()。 (2011•淘宝) A.通过对象调用一虚函数B.通过指针或引用调用一虚函数 C.通过对象调用静态函数D.通过指针或引用调用一静态函数 解答: B。 结合一段示例代码来看虚函数的作用,以帮助大家理解多态的意义所在。 例2: 下述代码的输出结果是什么? classbase{ public: virtualvoiddisp(){cout<<"hello,base1"< }; classchild1: publicbase{ public: voiddisp(){cout<<"hello,child1"< voiddisp2(){cout<<"hello,child2"< voidmain(){ base*base=NULL; child1objchild1; base=&objchildl; base->disp(); base->disp2(); 解答: 输出: hello,childl hello,base2 从上述代码可见,通过指针访问函数时: 1)不加virtual时,具体调用哪个版本的函数只取决于指针本身的类型,和指针所指对象的类型无关。 2)而加virtual时,具体调用哪个版本的函数不再取决于指针本身的类型,而是取决于指针所指对象的类型。 13: 构造函数为什么不能为虚函数? 解答: 假设有如下代码: classA{ A(){} }; classB: publicA{ B(): A(){} }; intmain(){ Bb; B*pb=&b; } 则构造B类的对象时: 1.根据继承的性质,构造函数执行顺序是: A()B() 2.根据虚函数的性质,如果A的构造函数为虚函数,且B类也给出了构造函数,则应该只执行B类的构造函数,不再执行A类的构造函数。 这样A就不能构造了。 3.这样1和2就发生了矛盾。 另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。 14: 哪些函数不能为虚函数? 解答: 常见的不能声明为虚函数的有: 普通函数(非成员函数)、静态成员函数、构造函数、友元函数,而内联成员函数、赋值操作符重载函数即使声明为虚函数也无意义。 1)为什么C++不支持普通函数为虚函数? 普通函数(非成员函数)只能被overload(重载),不能被override(覆盖),声明为虚函数也没有什么意义,因此编译器会在编译时绑定函数。 为什么C++不支持构造函数为虚函数? 上例己经给出了答案。 2)为什么C++不支持静态成员函数为虚函数? 静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,它不归某个具体对象所有,所以它没有要动态绑定的必要性。 3)为什么C++不支持友元函数为虚函数? 因为C++不支持友元函数的继承,没有实现为虚函数的必要。 以下两种函数被声明为虚函数时,虽然编译器不会报错,但是毫无意义。 内联函数: 内联函数是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后,对象能够准确地执行自己的动作,这是不可能统一的。 即使虚函数被声明为内联函数,编译器遇到这种情况根本不会把这样的函数内联展开,而是当作普通函数来处理。 赋值运算符: 虽然可以在基类中将成员函数operator定义为虚函数,但这样做没有意义。 赋值操作符重载函数要求形参与类本身类型相同,故基类中的赋值操作符形参类型为基类类型,即使声明为虚函数,也不能作为子类的赋值操作符。 15: 以下描述正确的是()。 (2011•盛大游戏) A.虚函数是可以内联的,可以减少函数调用的开销提高效率 B.类里面可以同时存在函数名和参数都一样的虚函数和静态函数 C.父类的析构函数是非虚的,但是子类的析构函数是虚的,delete子类对象指针会调用父类的析构函数 D•以上都不对 解答: C。 C中delete子类对象指针会调用父类的析构函数(即使子类的析构函数不是虚的,对子类对象指针调用析构函数,也会调用父类的析构函数),但若delete父类对象指针却不会调用子类的析构函数(因为父类的析构函数不是虚函数,不执行动态绑定)。 16: 以下代码的输出结果是()。 (2012•小米) classB{ public: B(){ cout<<”Bconstructor,”; s=“B”; } voidf(){cout< peivate: strings; }; classD: publicB{ public: D(): B(){ cout<<"Dconstructor,"; s=“D”; } voidf(){cout< strings; }; intmain(void){ B*b=newD(); b->f(); ((D*)b)->f(); deleteb; return0; } 解答: 输出结果是: Bconstructor,Dconstructor,BD 若在类B中的函数f前加上virtual关键字,则输出结果为: Bconstructor,Dconstructor,DD 可见若函数不是虚函数,则不是动态绑定。 17: 下列代码的输出结果是什么? (2012•网易) classA{ public: virtualvoidFun(intnumber=10){ std: : cout<<"A: : Funwithnumber"< } }; classB: publicA{ public: virtualvoidFun(intnumber=20){ std: : cout<<”B: : Funwithnumber’’< } }; intmain(){ Bb; A&a=b; a.Fun(); return0; } 解答: B: : Funwithnumber10。 虚函数动态绑定到B,但缺省实参是编译时候确定的10,而非20。 构造函数和析构函数中的虚函数 构造派生类对象时,首先运行基类构造函数初始化对象的基类部分。 在执行基类构造函数时,对象的派生类部分是未初始化的。 实际上,此时对象还不是一个派生类对象。 撤销派生类对象时,首先撤销它的派生类部分,然后按照与构造顺序的逆序撤销它的基类部分。 在这两种情况下,运行构造函数或析构函数时,对象都是不完整的。 为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。 在基类构造函数或析构函数中,将派生类对象当作基类型对象对待。 如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。 18: 以下哪些做法是不正确或者应该极力避免的()。 (多选)(2012•搜狗) A.构造函数声明为虚函数B.派生关系中的基类析构函数声明为虚函数 C.构造函数调用虚函数D.析构函数调用虚函数 解答: ACD。 构造函数和析构函数是特殊的成员函数,在其中访问虚函数时,C++采用静态联编,即在构造函数或析构函数内,即使是使用虚函数名”的形式来调用,编译器仍将其解释为静态联编的“本类名: : 虚函数名”,因而这样会与使用者的意图不符,应该尽量避免。 C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括: virtualfunction机制: 用以支持一个有效率的“执行期绑定”; virtualbaseclass: 用以实现多次出现在继承体系中的基类,有一个单一而被共享的实体。 19: 一般情况下,下面哪些操作会执行失败? ()(多选)(2012•搜狗) classA{ public: stringa; voidfl(){printf("HelloWorld");}voidf2(){ a="HelloWorld";printf("%s",a.c_str()); } virtualvoidf3(){printf("HelloWorld");}virtualvoidf4(){ a="HelloWorld"; printf("%s",a.c_str());} }; A.A*aptr=NULL;aptr->f1(); B.A*aptr=NULL;aptr->f2(); C.A*aptr=NULL;aptr->f3(); D.A*aptr=NULL;aptr->f4(); 解答: BCD。 因为A没有使用任何成员变量,且fl函数是非虚函数(不存在于具体 对象中),是静态绑定的,所以A不需要使用对象的信息,故正确。 在B中使用了成员变量,而成员变量只能存在于对象中: C中f3是虚函数,需要使用虚表指针(存在于具体对象中); D同C。 可见BCD都需要有具体存在的对象,故不正确。 20: 请问下面代码的输出结果是什么? classA{ public: A(){a=l;b=2;} private: inta;intb; }; classB{ public: B(){c=3;} voidprint(){cout< private: intc; }; intmain(intargc,char*argv[]){ Aa; B*pb=(B*)(&a); pb->print(); return0; } 解答: 1。 这里将一个指向B类型的指针指向A类型的对象,由于函数print并不位于对象中,且print是非虚函数,故执行静态绑定(若是动态绑定,则需要virtual的信息,而对象a中不存在virtual信息,则执行会出错)。 当调用print函数时,需要输出c的值,程序并不知道指针pb指向的对象不是B类型的对象,只是盲目地按照偏移值去取,c在类B的对象中的偏移值跟a在类A的对象中的偏移值相等(都位于对象的起始地址处),故取到a的值1。 21: sizeof(Test)=4? sizeof(s)=4? sizeof(testl)=1? classTest{inta;staticdoublec;} Test*s; classtest1{}; 解答: sizeof(Test)=4,因为static数据成员并不存放在类的对象中。 sizeof(s)=4,因为s为一个指针。 sizeof(testl)=l,因为空类大小为1。 22: 下列表达式在32位机器编译环境下的值为()。 (2012•海康威视) classA{}; classB{ public: B(); virtual〜B(); }; classC{ private: #pragmapack(4) inti;shortj;floatk;char1[64];longm;char*p; #pragmapack() }; classD{ private: #pragmapack (1) inti;shortj;floatk;char1[64];longm;char*p; #pragmapack() }; intmain(void){ printf("%d\n",sizeof(A)); printf("%d\n",sizeof(B)); printf("%d\n",sizeof(C)); printf("%d\n",sizeof(D)); return0; } A.1、4、84、82B.4、4、82、84 C.4、4、84、82D.1、4、82、82 解答: A。 类B的大小为4是因为B中有指针vptr,可参考图9-1。 23: 下面代码的输出结果是什么? intmain(){ typedefvoid(*Fun)(void); Baseb; FunpFun=NULL; cout<<"虚函数表地址: "<<(int*)(&b)< cout<<"虚函数表_第―个函数地址: "<<(int*)*(int*)(&b)< //Invokethefirstvirtualfunction pFun=(Fun)*((int*)*(int*)(&b));//Base: : f() pFun(); pFun=(Fun)*((int*)*(int*)(&b)+1);//Base: : g() pfun(); pFun==(Fun)*((int*)*(int*)(&b)+2);//Base: : h() pFun(); return0; } 解答: 实际运行结果如下: (Windows7+VS2010/Linux 虚函数表地址: 001EFC90 虚函数表一第一个函数地址: 00A47874 Base: : fBase: gBase: h 通过这个示例,我们可以看到,我们可以通过强行把&b转成int*,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base: : f(),这在上面的程序中得到了验证(把int*强制转成了函数指针)。 图示如下: &b 注意: 在上面这个图中,在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符’\0’—样,其标志了虚函数表的结束。 这个结束标志的值在不同的编译器下是不同的。 在Windows7+VS2010下,这个值是NULL。 同时类Base的对象大小为4,即类中仅有一个指针vplr(指向虚函数表)。 24: 画出下列类A、B、C、D的对象的虚函数表。 classA{ public: virtualvoida(){cout<<"a()inA"< }; classB: publicA{ public: voida(){cout<<"a()inB"< voidb(){cout<<"b()inB"< }; classC: publicA{ public: voida(){cout<<"a()inC"< }; classD: publicB,publicC{ public: voida(){cout<<"a()inD"< voidd(){cout<<"d()inD"< }; 解答: 如下所示: A: : a A: : b A: : c A: : d A的对象 vptr 以上为A类对象的虚函数表,每个格子记录一个函数的地址。 B: : a B: : b A: : c A: : d B的对象vptr 可见,单基继承时,仅有一个vptr。 B类中的函数a与b覆盖了A类中的同名函数,故虚函数表中对应位置替换为新函数的地址。 c的对象vptr D的对象 可见,单基继承时,仅有一个vptr。 C类中的函数a与b覆盖了A类中的同名函数,故虚函数表中对应位置替换为新函数的地址。 可见,多基继承时,有几个基类就有几个vptr。 D类中的函数a与d覆盖了B类中的同名函数,故虚函数表中对应位置替换为新函数的地址。 D类中的函数a与d覆盖了C类中的同名函数,故虚函数表中对应位置替换为新函数的地址。 25: 如下代码的输出结果是什么? classX{}; classY: publicvirtualX{};classZ: publicvirtualX{};classA: publicY,publicZ{};intmain(){ cout<<"sizeof(X): "< "< cout<<"sizeof(Z): "< "< 解答: 1,4,4,8。 X类是空的,为什么sizeof(X)=l呢? 事实上,在前面章节介绍struct的sizeof值时已经介绍过原因,这是因为事实上X并不是空的,它有一个隐晦的1字节,那是编译器安插进去的一个byte。 这使得classX的objects得以在内存中配置独一无二的地址。 下图给出X、Y、Z的对象布局。 derivedclassY 事实上Y和Z的大小受到三个因素的影响: 1)语言本身所造成的额外负担。 当语言支持虚基类(virtualbaseclasses)时,就会造成一些额外负担。 在子类中,这个额外负担反映在bptr上,即增加了一个指针。 2)编译器对于特殊情况所做的优化处理。 现在的编译器一般会对空虚基类提供特殊支持(如VS2010)。 在这个策略下,一个空虚基类由于有了一个指针bptr,故不需再像空类一样占用1个字节,也就是说因为有了成员,就不再需要原本为了空类而安插的1个byte。 3)Alignment的限制(如果需要的话),就是字节对齐。 因此,Y和Z的大小都是4字节,其对象内仅包含一个bptr,且不需要对齐处理。 下面我们讨论A的大小。 这里需要注意的是: 一个虚基类子对象只会在继承类中存在一份实体,不管它在继承体系中出现了多少次。 如图9-2所示,classA的占用空间由下面几部分构成: 1)被大家共享的唯——个classX实体,大小为1B,目前的编译器通常都做了优化,省去这单单为了占位的1B,故此部分为0; BaseclassY的大小(为4B)减去“因virtualbaseclassX而配置”的大小(本题中为0),故结果为4B; 2)BaseclassZ的大小(为4B)减去“因virtualbaseclassX而配置”的大小(本题中为0),故结果为4B; 3)classA自己的大小: 0B; 前述四项总和,共8B。 然后考虑字节对齐,不需要对齐,故sizeof(A)为8。 注意: 关于C++对象模型的更深入研究,可参考《深度探索(: 抖对象模型》一书。 26: 假设A为抽象类,下列声明()是正确的? (2012•迅雷) A.Afun(int);B.A*p;C.intfun(A)D.AObj; 解答: B。 抽象类不能定义对象,但是可以作为指针或者引用类型使用。 若在A和C选项中的A后面加上&或*
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 经典笔试题附答案 经典 笔试 答案