C++经典笔试题(附答案)_第1页
C++经典笔试题(附答案)_第2页
C++经典笔试题(附答案)_第3页
C++经典笔试题(附答案)_第4页
C++经典笔试题(附答案)_第5页
已阅读5页,还剩20页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

8:以下多重继承时的二义性问题如何解决?classA{//类A的定义public:voidprint(){cout<<"Hello,thisisA"<<endl;}};classB{//类B的定义public:voidprint(){cout<<"Hello,thisisB"<<endl;}};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:"<<endl;}classB:publicA{};classC:publicA{};classD:publicB,publicC{};voidmain(){Dd; //声明一个D类对象dA*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不明确〞。语句“A*pa=(A*)&d;〞产生的二义性是由于d中含有两个基类对象A,隐式转换时不知道让pa指向哪个子对象,从而出错。可改为以下的一种:A*pa=(A*)(B*)&d;//上行转换A*pa=(A*)(C*)&d;//上行转换事实上,使用关键字virtual将共同基类A声明为虚基类,可有效解决上述问题。10:下面哪种情况下,B不能隐式转换为A( )?(2023•腾讯〕A.classB:publicA{} B.classA:publicB{}C.classB{operatorA();} D.classA{A(constB&);}解答:B。因为子类包含了父类局部,所以子类可以转换为父类,但是相反,父类没有子类额外定义的局部,所以不能转换为子类,故A正确,而B错误。非C++内建型别A和B,在以下几种情况下B能隐式转化为A。B公有继承自A,可以是间接继承的。classB:publicA{};此时假设有“Aa;Bb;〞,那么“a=b;〞合法。B中有类型转换函数。classB{operatorA();};此时假设有“Aa;Bb;〞,那么“a=b;〞合法。A实现了非explicit的参数为B(可以有其他带默认值的参数〕的构造函数classA{A(constB&);};此时假设有“Aa;Bb;〞,那么“a=b;〞合法。11:调用一成员函数时,使用动态联编的情况是〔〕。〔2023•淘宝〕A.通过对象调用一虚函数B.通过指针或引用调用一虚函数C.通过对象调用静态函数D.通过指针或引用调用一静态函数解答:B。结合一段例如代码来看虚函数的作用,以帮助大家理解多态的意义所在。例2:下述代码的输出结果是什么?classbase{public:virtualvoiddisp(){cout<<"hello,base1"<<endl;}voiddisp2(){cout<<"hello,base2"<<endl;}};classchild1:publicbase{public:voiddisp(){cout<<"hello,child1"<<endl;}voiddisp2(){cout<<"hello,child2"<<endl;}voidmain(){base*base=NULL;child1objchild1;base=&objchildl;base->disp();base->disp2();解答:输出:hello,childlhello,base2从上述代码可见,通过指针访问函数时:不加virtual时,具体调用哪个版本的函数只取决于指针本身的类型,和指针所指对象的类型无关。而加virtual时,具体调用哪个版本的函数不再取决于指针本身的类型,而是取决于指针所指对象的类型。13:构造函数为什么不能为虚函数?解答:假设有如下代码:classA{A(){}};classB:publicA{B():A() {}};intmain(){Bb;B*pb=&b;}那么构造B类的对象时:根据继承的性质,构造函数执行顺序是:A()B()根据虚函数的性质,如果A的构造函数为虚函数,且B类也给出了构造函数,那么应该只执行B类的构造函数,不再执行A类的构造函数。这样A就不能构造了。这样1和2就发生了矛盾。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。14:哪些函数不能为虚函数?解答:常见的不能声明为虚函数的有:普通函数〔非成员函数)、静态成员函数、构造函数、友元函数,而内联成员函数、赋值操作符重载函数即使声明为虚函数也无意义。为什么C++不支持普通函数为虚函数?普通函数〔非成员函数〕只能被overload(重载),不能被override(覆盖),声明为虚函数也没有什么意义,因此编译器会在编译时绑定函数。为什么C++不支持构造函数为虚函数?上例己经给出了答案。为什么C++不支持静态成员函数为虚函数?静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,它不归某个具体对象所有,所以它没有要动态绑定的必要性。为什么C++不支持友元函数为虚函数?因为C++不支持友元函数的继承,没有实现为虚函数的必要。以下两种函数被声明为虚函数时,虽然编译器不会报错,但是毫无意义。内联函数:内联函数是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后,对象能够准确地执行自己的动作,这是不可能统一的。即使虚函数被声明为内联函数,编译器遇到这种情况根本不会把这样的函数内联展开,而是当作普通函数来处理。赋值运算符:虽然可以在基类中将成员函数operator定义为虚函数,但这样做没有意义。赋值操作符重载函数要求形参与类本身类型相同,故基类中的赋值操作符形参类型为基类类型,即使声明为虚函数,也不能作为子类的赋值操作符。15:以下描述正确的选项是〔〕。〔2023•盛大游戏〕虚函数是可以内联的,可以减少函数调用的开销提高效率类里面可以同时存在函数名和参数都一样的虚函数和静态函数父类的析构函数是非虚的,但是子类的析构函数是虚的,delete子类对象指针会调用父类的析构函数D•以上都不对解答:C。C中delete子类对象指针会调用父类的析构函数〔即使子类的析构函数不是虚的,对子类对象指针调用析构函数,也会调用父类的析构函数),但假设delete父类对象指针却不会调用子类的析构函数〔因为父类的析构函数不是虚函数,不执行动态绑定)。16:以下代码的输出结果是〔〕。〔2023•小米〕classB{public:B(){cout<<〞Bconstructor,〞;s=“B〞;}voidf(){cout<<s;}peivate:strings;};classD:publicB{public:D():B(){cout<<"Dconstructor,";s=“D〞;}voidf(){cout<<s;}private:strings;};intmain(void){B*b=newD();b->f();((D*)b)->f();deleteb;return0;}解答:输出结果是:Bconstructor,Dconstructor,BD假设在类B中的函数f前加上virtual关键字,那么输出结果为:Bconstructor,Dconstructor,DD可见假设函数不是虚函数,那么不是动态绑定。17:以下代码的输出结果是什么?〔2023•网易〕classA{public:virtualvoidFun(intnumber=10){std::cout<<"A::Funwithnumber"<<number<<endl;}};classB:publicA{public:virtualvoidFun(intnumber=20){std::cout<<〞B::Funwithnumber’’<<number<<endl;}};intmain(){Bb;A&a=b;a.Fun();return0;}解答:B::Funwithnumber10。虚函数动态绑定到B,但缺省实参是编译时候确定的10,而非20。构造函数和析构函数中的虚函数构造派生类对象时,首先运行基类构造函数初始化对象的基类局部。在执行基类构造函数时,对象的派生类局部是未初始化的。实际上,此时对象还不是一个派生类对象。撤销派生类对象时,首先撤销它的派生类局部,然后按照与构造顺序的逆序撤销它的基类局部。在这两种情况下,运行构造函数或析构函数时,对象都是不完整的。为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类型对象对待。如果在构造函数或析构函数中调用虚函数,那么运行的是为构造函数或析构函数自身类型定义的版本。18:以下哪些做法是不正确或者应该竭力防止的〔〕。〔多项选择〕〔2023•搜狗〕A.构造函数声明为虚函数B.派生关系中的基类析构函数声明为虚函数C.构造函数调用虚函数D.析构函数调用虚函数解答:ACD。构造函数和析构函数是特殊的成员函数,在其中访问虚函数时,C++采用静态联编,即在构造函数或析构函数内,即使是使用虚函数名〞的形式来调用,编译器仍将其解释为静态联编的“本类名::虚函数名〞,因而这样会与使用者的意图不符,应该尽量防止。虚函数表指针〔vptr)及虚基类表指针〔bptr)C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括:virtualfunction机制:用以支持一个有效率的“执行期绑定〞;virtualbaseclass:用以实现屡次出现在继承体系中的基类,有一个单一而被共享的实体。19:一般情况下,下面哪些操作会执行失败?〔〕〔多项选择〕〔2023•搜狗〕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*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<<c;}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位机器编译环境下的值为〔〕。〔2023•海康威视〕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、82 B.4、4、82、84C.4、4、84、82 D.1、4、82、82解答:A。类B的大小为4是因为B中有指针vptr,可参考图9-1。23:下面代码的输出结果是什么?intmain(){typedefvoid(*Fun)(void);Baseb;FunpFun=NULL;cout<<"虚函数表地址:"<<(int*)(&b)<<endl;cout<<"虚函数表_第―个函数地址:"<<(int*)*(int*)(&b)<<endl;//InvokethefirstvirtualfunctionpFun=(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+VS2023/Linux+GCC4.1.3)虚函数表地址:001EFC90虚函数表一第一个函数地址:00A47874Base::fBase:gBase:h通过这个例如,我们可以看到,我们可以通过强行把&b转成int*,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证〔把int*强制转成了函数指针〕。图示如下:&b注意:在上面这个图中,在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符’\0’—样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在Windows7+VS2023下,这个值是NULL。同时类Base的对象大小为4,即类中仅有一个指针vplr(指向虚函数表)。24:画出以下类A、B、C、D的对象的虚函数表。classA{public:virtualvoida(){cout<<"a()inA"<<endl;}virtualvoidb(){cout<<"b()inA"<<endl;}virtualvoidc(){cout<<"c()inA"<<endl;}virtualvoidd(){cout<<"d()inA"<<endl;}};classB:publicA{public:voida(){cout<<"a()inB"<<endl;}voidb(){cout<<"b()inB"<<endl;}};classC:publicA{public:voida(){cout<<"a()inC"<<endl;}voidb(){cout<<"b()inC"<<endl;}};classD:publicB,publicC{public:voida(){cout<<"a()inD"<<endl;}voidd(){cout<<"d()inD"<<endl;}};解答:如下所示:A::aA::bA::cA::dA的对象vptr以上为A类对象的虚函数表,每个格子记录一个函数的地址。B::aB::bA::cA::dB的对象vptr可见,单基继承时,仅有一个vptr。B类中的函数a与b覆盖了A类中的同名函数,故虚函数表中对应位置替换为新函数的地址。c的对象vptrD的对象可见,单基继承时,仅有一个vptr。C类中的函数a与b覆盖了AD的对象可见,多基继承时,有几个基类就有几个vptr。D类中的函数a与d覆盖了B类中的同名函数,故虚函数表中对应位置替换为新函数的地址。D类中的函数a与d覆盖了C类中的同名函数,故虚函数表中对应位置替换为新函数的地址。25:如下代码的输出结果是什么?classX{};classY:publicvirtualX{};classZ:publicvirtualX{};classA:publicY,publicZ{};intmain(){cout<<"sizeof(X):"<<sizeof(X)<<endl;cout<<"sizeof(Y):"<<sizeof(Y)<<endl;cout<<"sizeof(Z):"<<sizeof(Z)<<endl;cout<<"sizeof(A):"<<sizeof(A)<<endl;解答:1,4,4,8。X类是空的,为什么sizeof(X)=l呢?事实上,在前面章节介绍struct的sizeof值时已经介绍过原因,这是因为事实上X并不是空的,它有一个隐晦的1字节,那是编译器安插进去的一个byte。这使得classX的objects得以在内存中配置独一无二的地址。以下图给出X、Y、Z的对象布局。derivedclassY事实上Y和Z的大小受到三个因素的影响:语言本身所造成的额外负担。当语言支持虚基类〔virtualbaseclasses)时,就会造成一些额外负担。在子类中,这个额外负担反映在bptr上,即增加了一个指针。编译器对于特殊情况所做的优化处理。现在的编译器一般会对空虚基类提供特殊支持〔如VS2023)。在这个策略下,一个空虚基类由于有了一个指针bptr,故不需再像空类一样占用1个字节,也就是说因为有了成员,就不再需要原本为了空类而安插的1个byte。Alignment的限制〔如果需要的话),就是字节对齐。因此,Y和Z的大小都是4字节,其对象内仅包含一个bptr,且不需要对齐处理。下面我们讨论A的大小。这里需要注意的是:一个虚基类子对象只会在继承类中存在一份实体,不管它在继承体系中出现了多少次。如图9-2所示,classA的占用空间由下面几局部构成:被大家共享的唯——个classX实体,大小为1B,目前的编译器通常都做了优化,省去这单单为了占位的1B,故此局部为0;BaseclassY的大小〔为4B)减去“因virtualbaseclassX而配置〞的大小〔此题中为0),故结果为4B;BaseclassZ的大小〔为4B)减去“因virtualbaseclassX而配置〞的大小〔此题中为0),故结果为4B;classA自己的大小:0B;前述四项总和,共8B。然后考虑字节对齐,不需要对齐,故sizeof(A)为8。注意:关于C++对象模型的更深入研究,可参考《深度探索(:抖对象模型》一书。26:假设A为抽象类,以下声明〔〕是正确的?〔2023•迅雷〕A.Afun(int); B.A*p;C.intfun(A) D.AObj;解答:B。抽象类不能定义对象,但是可以作为指针或者引用类型使用。假设在A和C选项中的A后面加上&或*就是对的。27:抽象类为什么不能实例化?〔2023•阿里云〕解答:抽象类中的纯虚函数没有具体的实现,所以没方法实例化。28:C++用于类型转换的4个操作符是_,_,_,_?(2023•腾讯〕解答:dynamiccast,constcast,staticcast,reinterpret_cast;reinterpret_cast在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起来实现:int*ip;char*pc=(char*)ip;效果与使用reinterpret_cast符号相同。int*ip;char*pc==reinterpret_cast<char*>(ip);const_castconst_cast,顾名思义,将转换掉表达式的const性质。constchar*pc_str;char*pc=const_cast<char*>(pc_str);只有使用const_cast才能将const性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。类似地,除了添加或删除const特性,用const_cast符来执行其他任何类型转换,都会引起编译错误。29:怎么才能让ptr指向value?constdoublevalue=0.0f;double*ptr=NULL;解答:强制类型转换,去掉const属性,如:ptr=const_cast<double*>(value);static_cast编译器隐式执行的任何类型转换都可以由static_cast显式完成:doubled=97.0;inti=static_cast<int>(d);等价于:doubled=97.0;inti=d;仅当类型之间可隐式转换时〔除类层次间的下行转换以外),staticcast的转换才是合法的,否那么将出错。类层次间的下行转换是不能通过隐式转换完成的,请看下例。classbase{};classchild:publicbase{};base*b;child*c;c=static_cast<child*>(b);//下行转换,正确c=b;//编译不正确但使用static_cast完成下行转换〔把基类指针或引用转换成子类指针或引用〕,由于没有动态类型检查,所以是不平安的。30:下面程序运行后的结果为〔〕。〔2023•网易游戏〕charstr[]="gladtotestsomething";char*p=str;P++;int*p1=static_cast<int*>(p);p1++;p=static_cast<char*>(p1);printf("%s\n",p);解答:编译错误。在第4章我们介绍了C++类型的指针之间不含有隐式转换(void*除外、const的某些用法为了兼容C语言也可隐式转换),需要显式转换。故char*不能隐式转换为int*,而仅当类型之间可隐式转换时〔除类层次间的下行转换以外),statiC_Cast的转换才是合法的,否那么将出错。假设改为:charstr[]="gladtotestsomething";char*p=str;P++;int*pl=(int*)(p);//orint*pl=reiriterpret_cast<int*>(p);pl++;p=(char*)(pi);//orp=reinterpret_cast<char*>(pl);printf("%s\n",p);那么输出totestsomething。dynamic_cast31:下面〔〕会使这段程序编译错误?〔2023•趋势科技〕classA{public:A(){}};classB:publicA{public:B(){}};A*pb=newB();Bb;A.A*pa=dynamic_cast<A*>(pb); B.A*pa=static_cast<A*>(pb);C.Aa=static_cast<A>(b); D.Aa=dynamic_cast<A>(b);E.Noneofabove解答:D。用dynamic_cast进行转换时,待转换的类型只能是指针或引用,故D错误。A中:A*pa=dynamic_cast<A*>(pb);由于pb本身就是A*类型,实际上不需要转换,事实上,假设将A项改为:A*pa=dynamic_cast<B*>(pb);那么编译错误,提示“运行时dynamic_cast的操作数必须包含多态类类型〞。B中,实际上也并不需要转换。C中B类继承自A类,故B类对象b可隐式转换为A类对象,故C正确。dynamic_cast主要用于类层次间的上行转换和下行转换。dynamic_cast运算符可以在执行期决定真正的类型。如果下行转换是平安的〔也就说,如果基类指针或者引用确实指向一个派生类对象),这个运算符会传回转型过的指针。如果downcast不平安,这个运算符会传回空指针〔也就是说,基类指针或者引用没有指向一个派生类对象)。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更平安。32:下述代码中,假设调用函数func的实参指向一个B类型的对象,语句1和语句2有什么差异?classB{public:B():b(1){}virtualvoidfoo(){};intb;};classD:publicB{public:D():d(2);intd;};voidfunc(B*pb){D*pd1=static_cast<D*>(pb); //语句1cout<<pd1->b<<endl;cout<<pd1->d<<endl;D*pd2=dynamic_cast<D*>(pb); //语句2cout<<pd2->b<<endl;cout<<pd2->d<<endl;}解答:在上面的代码段中,如果pb指向一个D类型的对象,即B*pb=newD();func(pb);那么pdl和pd2是一样的,并且对这两个指针执行D类型的任何操作都是平安的,语句1与语句2后的输出语句都输出1与2;但是,如果pb指向的是一个B类型的对象,即B*pb=newB();func(pb);那么pdl将是一个指向B对象的指针,对它进行D类型的操作将是不平安的〔如访问d),输出d的值时,将会是一个垃圾值,延后了错误的发现;而pd2将是一个空指针,对空指针进行操作,将会发生异常,从而能够更早地发现错误。33:例2中,当B中无虚函数时,会发生什么?解答:B要有虚函数,否那么语句2会编译出错,VS2023会提示“运行时dynamic_cast的操作数必须包含多态类型〞;static_cast那么没有这个限制。dynamic_cast运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表〔关于虚函数表的概念,详细可见本章〕中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的,故会编译错误。34:语句1与语句2效果是否相同?语句3与语句4呢?classBase1{virtualvoidfl(){cout<<"Base1::f1"<<endl;}};classBase2{virtualvoidf2(){cout<<"Base2::f2"<<endl;}};classDerived:publicBasel,publicBase2{voidf1(){cout<<"Derived::f1"<<endl;}voidf2(){cout<<"Derived::f2"<<endl;}Basel*pb=newDerived;Derived*pDl=dynamic_cast<Derived*>(pD); //语句1Berived*pD2=static_cast<Derived*>(pD); //语句2Base2*pB1=d

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论