C++经典笔试题附答案.docx
《C++经典笔试题附答案.docx》由会员分享,可在线阅读,更多相关《C++经典笔试题附答案.docx(24页珍藏版)》请在冰豆网上搜索。
![C++经典笔试题附答案.docx](https://file1.bdocx.com/fileroot1/2023-8/20/8d810ca5-32d1-41fb-92ad-947ec6361dcb/8d810ca5-32d1-41fb-92ad-947ec6361dcb1.gif)
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)
2)
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++不支持普通函数为虚函数?
,声明为虚函数也没有什么意义,因此编译器会在编译时绑定函数。
为什么C++不支持构造函数为虚函数?
上例己经给出了答案。
2)为什么C++不支持静态成员函数为虚函数?
3)为什么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++采用静态联编,即在构造函数或析构函数内,即使是使用虚函数名”的形式来调用,编译器仍将其解释为静态联编的“本类名:
:
虚函数名”,因而这样会与使用者的意图不符,应该尽量避免。
9.2.2虚函数表指针(vptr)及虚基类表指针(bptr)
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,因为。
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/Linux2.6.22+GCC4.1.3)
虚函数表地址:
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;
2)BaseclassY的大小(为4B)减去“因virtualbaseclassX而配置”的大小(本题中为0),故结果为4B;
3)BaseclassZ的大小(为4B)减去“因virtualbaseclassX而配置”的大小(本题中为0),故结果为4B;
4)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后面加上&或*就是对的。
27:
抽象类为什么不能实例化?
(2011•阿里云)
解答:
。
28:
C++用于的4个操作符是_,_,_,_?
(2012•腾讯)
解答:
,,
在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起来实现:
int*ip;
char*pc=(char*)ip;
效果与使用reinterpret_cast符号相同。
int*ip;
char*pc==reinterpret_cast(ip);
const_cast,顾名思义,将转换掉表达式的const性质。
constchar*pc_str;
char*pc=const_cast(pc_str);
只有使用const_cast才能将const性质转换掉。
在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。
类似地,除了添加或删除const特性,用const_cast符来执行其他任何类型转换,都会引起编译错误。
29:
怎么才能让ptr指向value?
constdoublevalue=0.0f;
double*ptr=NULL