C经典笔试题附答案Word下载.docx
- 文档编号:17566127
- 上传时间:2022-12-07
- 格式:DOCX
- 页数:20
- 大小:44.45KB
C经典笔试题附答案Word下载.docx
《C经典笔试题附答案Word下载.docx》由会员分享,可在线阅读,更多相关《C经典笔试题附答案Word下载.docx(20页珍藏版)》请在冰豆网上搜索。
//声明一个D类对象d
A*pa=(A*)&
d;
//上行转换产生二义性
d.print();
//print()具有二义性,系统不知道是调用B类的还是C类的print()函数
注意:
把子类的指针或引用转换成基类指针或引用是上行转换,把基类指针或引用转换成子类指针或引用是下行转换。
1)main函数中语句“d.print();
”编译错误,可改为以下的一种:
d.B:
print();
d.C:
若改为“d.A:
”又会如何呢?
由于d对象中有两个A类对象,故编译会报“基类A不明确”。
2)语句“A*pa=(A*)&
”产生的二义性是由于d中含有两个基类对象A,隐式转换时不知道让pa指向哪个子对象,从而出错。
可改为以下的一种:
A*pa=(A*)(B*)&
//上行转换
A*pa=(A*)(C*)&
//上行转换
事实上,使用关键字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();
”,则“a=b;
3)A实现了非explicit的参数为B(可以有其他带默认值的参数)的构造函数
classA{
A(constB&
11:
调用一成员函数时,使用动态联编的情况是()。
(2011•淘宝)
A.通过对象调用一虚函数B.通过指针或引用调用一虚函数
C.通过对象调用静态函数D.通过指针或引用调用一静态函数
结合一段示例代码来看虚函数的作用,以帮助大家理解多态的意义所在。
例2:
下述代码的输出结果是什么?
classbase{
virtualvoiddisp(){cout<
hello,base1"
}voiddisp2(){cout<
hello,base2"
endl;
classchild1:
publicbase{
voiddisp(){cout<
hello,child1"
}
voiddisp2(){cout<
hello,child2"
base*base=NULL;
child1objchild1;
base=&
objchildl;
base->
disp();
disp2();
输出:
hello,childl
hello,base2
从上述代码可见,通过指针访问函数时:
1)不加virtual时,具体调用哪个版本的函数只取决于指针本身的类型,和指针所指对象的类型无关。
2)而加virtual时,具体调用哪个版本的函数不再取决于指针本身的类型,而是取决于指针所指对象的类型。
13:
构造函数为什么不能为虚函数?
假设有如下代码:
classA{
A(){}
B():
};
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{
B(){
cout<
”Bconstructor,”;
s=“B”;
voidf(){cout<
s;
peivate:
strings;
classD:
publicB{
D():
B(){
Dconstructor,"
;
s=“D”;
}private:
intmain(void){
B*b=newD();
b->
f();
((D*)b)->
f();
deleteb;
return0;
输出结果是:
Bconstructor,Dconstructor,BD
若在类B中的函数f前加上virtual关键字,则输出结果为:
Bconstructor,Dconstructor,DD
可见若函数不是虚函数,则不是动态绑定。
17:
下列代码的输出结果是什么?
(2012•网易)
classA{
virtualvoidFun(intnumber=10){
std:
A:
Funwithnumber"
number<
publicA{
virtualvoidFun(intnumber=20){
cout<
”B:
Funwithnumber’’<
A&
a=b;
a.Fun();
B:
Funwithnumber10。
虚函数动态绑定到B,但缺省实参是编译时候确定的10,而非20。
构造函数和析构函数中的虚函数
构造派生类对象时,首先运行基类构造函数初始化对象的基类部分。
在执行基类构造函数时,对象的派生类部分是未初始化的。
实际上,此时对象还不是一个派生类对象。
撤销派生类对象时,首先撤销它的派生类部分,然后按照与构造顺序的逆序撤销它的基类部分。
在这两种情况下,运行构造函数或析构函数时,对象都是不完整的。
为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。
在基类构造函数或析构函数中,将派生类对象当作基类型对象对待。
如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。
18:
以下哪些做法是不正确或者应该极力避免的()。
(多选)(2012•搜狗)
A.构造函数声明为虚函数B.派生关系中的基类析构函数声明为虚函数
C.构造函数调用虚函数D.析构函数调用虚函数
ACD。
构造函数和析构函数是特殊的成员函数,在其中访问虚函数时,C++采用静态联编,即在构造函数或析构函数内,即使是使用虚函数名”的形式来调用,编译器仍将其解释为静态联编的“本类名:
虚函数名”,因而这样会与使用者的意图不符,应该尽量避免。
C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括:
virtualfunction机制:
用以支持一个有效率的“执行期绑定”;
virtualbaseclass:
用以实现多次出现在继承体系中的基类,有一个单一而被共享的实体。
19:
一般情况下,下面哪些操作会执行失败?
()(多选)(2012•搜狗)
stringa;
voidfl(){printf("
HelloWorld"
}voidf2(){
a="
printf("
%s"
a.c_str());
virtualvoidf3(){printf("
}virtualvoidf4(){
a="
printf("
A.A*aptr=NULL;
aptr->
f1();
B.A*aptr=NULL;
f2();
C.A*aptr=NULL;
f3();
D.A*aptr=NULL;
f4();
BCD。
因为A没有使用任何成员变量,且fl函数是非虚函数(不存在于具体
对象中),是静态绑定的,所以A不需要使用对象的信息,故正确。
在B中使用了成员变量,而成员变量只能存在于对象中:
C中f3是虚函数,需要使用虚表指针(存在于具体对象中);
D同C。
可见BCD都需要有具体存在的对象,故不正确。
20:
请问下面代码的输出结果是什么?
A(){a=l;
b=2;
private:
inta;
intb;
B(){c=3;
voidprint(){cout<
c;
intc;
intmain(intargc,char*argv[]){
Aa;
B*pb=(B*)(&
a);
pb->
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{};
B();
virtual〜B();
classC{
#pragmapack(4)
inti;
shortj;
floatk;
char1[64];
longm;
char*p;
#pragmapack()
classD{
#pragmapack
(1)
longm;
%d\n"
sizeof(A));
sizeof(B));
sizeof(C));
sizeof(D));
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:
下面代码的输出结果是什么?
typedefvoid(*Fun)(void);
Baseb;
FunpFun=NULL;
虚函数表地址:
(int*)(&
b)<
虚函数表_第―个函数地址:
(int*)*(int*)(&
//Invokethefirstvirtualfunction
pFun=(Fun)*((int*)*(int*)(&
b));
//Base:
f()
pFun();
b)+1);
//Base:
g()
pfun();
pFun==(Fun)*((int*)*(int*)(&
b)+2);
h()
实际运行结果如下:
(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的对象的虚函数表。
virtualvoida(){cout<
a()inA"
}virtualvoidb(){cout<
b()inA"
}virtualvoidc(){cout<
c()inA"
}virtualvoidd(){cout<
d()inA"
voida(){cout<
a()inB"
voidb(){cout<
b()inB"
voida(){cout<
a()inC"
}voidb(){cout<
b()inC"
publicB,publicC{
voida(){cout<
a()inD"
voidd(){cout<
d()inD"
如下所示:
a
c
d
A的对象
vptr
以上为A类对象的虚函数表,每个格子记录一个函数的地址。
B的对象vptr
可见,单基继承时,仅有一个vptr。
B类中的函数a与b覆盖了A类中的同名函数,故虚函数表中对应位置替换为新函数的地址。
c的对象vptr
D的对象
C类中的函数a与b覆盖了A类中的同名函数,故虚函数表中对应位置替换为新函数的地址。
可见,多基继承时,有几个基类就有几个vptr。
D类中的函数a与d覆盖了B类中的同名函数,故虚函数表中对应位置替换为新函数的地址。
D类中的函数a与d覆盖了C类中的同名函数,故虚函数表中对应位置替换为新函数的地址。
25:
如下代码的输出结果是什么?
classX{};
classY:
publicvirtualX{};
classZ:
classA:
publicY,publicZ{};
intmain(){
sizeof(X):
sizeof(X)<
sizeof(Y):
sizeof(Y)<
sizeof(Z):
sizeof(Z)<
sizeof(A):
sizeof(A)<
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;
抽象类不能定义对象,但是可以作为指针或者引用类型使用。
若在A和C选项中的A后面加上&
或*
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 经典笔试题附答案 经典 笔试 答案
![提示](https://static.bdocx.com/images/bang_tan.gif)