CC++年试题文档格式.docx
《CC++年试题文档格式.docx》由会员分享,可在线阅读,更多相关《CC++年试题文档格式.docx(28页珍藏版)》请在冰点文库上搜索。
![CC++年试题文档格式.docx](https://file1.bingdoc.com/fileroot1/2023-5/4/34292e2c-6b01-4ac4-9e34-8d3994a75263/34292e2c-6b01-4ac4-9e34-8d3994a752631.gif)
引用名=目标变量名;
例1
1.inta;
2.constint&
ra=a;
3.ra=1;
//错误
4.a=1;
//正确
例2
1.stringfoo();
2.voidbar(string&
s);
那么下面的表达式将是非法的:
1.bar(foo());
2.bar("
helloworld"
);
原因在于foo()和"
串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。
因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
引用型参数应该在能被定义为const的情况下,尽量定义为const.
5.将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
格式:
类型标识符&
函数名(形参列表及类型说明){//函数体}好处:
在内存中不产生被返回值的副本;
(注意:
正是因为这点原因,所以返回一个局部变量的引用是不可取的。
因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtimeerror!
注意事项:
(1)不能返回局部变量的引用。
这条可以参照EffectiveC++[1]的Item31.主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"
无所指"
的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。
这条可以参照EffectiveC++[1]的Item31.虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。
例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memoryleak.(3)可以返回类成员的引用,但最好是const.这条原则可以参照EffectiveC++[1]的Item30.主要原因是当对象的属性是与某种业务规则(businessrule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。
如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)流操作符重载返回值申明为“引用”的作用:
流操作符<
<
和>
>
,这两个操作符常常希望被连续使用,例如:
cout<
"
hello"
<
endl;
因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。
可选的其它方案包括:
返回一个流对象和返回一个流对象指针。
但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<
操作符实际上是针对不同对象的!
这无法让人接受。
对于返回一个流指针则不能连续使用<
操作符。
因此,返回一个流对象引用是惟一选择。
这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。
赋值操作符=.这个操作符象流操作符一样,是可以连续使用的,例如:
x=j=10;
或者(x=10)=100;
赋值操作符的返回值必须是一个左值,以便可以被继续赋值。
因此引用成了这个操作符的惟一返回值选择。
例3
1.#include<
iostream.h>
2.int&
put(intn);
3.intvals[10];
4.interror=-1;
5.voidmain()
6.{
7.put(0)=10;
//以put(0)函数值作为左值,等价于vals[0]=10;
8.put(9)=20;
//以put(9)函数值作为左值,等价于vals[9]=20;
9.cout<
vals[0];
10.cout<
vals[9];
11.}
12.int&
put(intn)
13.{
14.if(n>
=0&
&
n<
=9)returnvals[n];
15.else{cout<
"
subscripterror"
;
returnerror;
}
16.}
(5)在另外的一些操作符中,却千万不能返回引用:
+-*/四则运算符。
它们不能返回引用,EffectiveC++[1]的Item23详细的讨论了这个问题。
主要原因是这四个操作符没有sideeffect,因此,它们必须构造一个对象作为返回值,可选的方案包括:
返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。
根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。
静态对象的引用又因为((a+b)==(c+d))会永远为true而导致错误。
所以可选的只剩下返回一个对象了。
6.“引用”与多态的关系?
引用是除指针外另一个可以产生多态效果的手段。
这意味着,一个基类的引用可以指向它的派生类实例。
例4
1.ClassA;
2.ClassB:
ClassA{...};
3.Bb;
4.A&
ref=b;
7.“引用”与指针的区别是什么?
指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。
程序中使用指针,程序的可读性差;
而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。
此外,就是上面提到的对函数传ref和pointer的区别。
8.什么时候需要“引用”?
流操作符<
、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
以上2-8参考:
9.结构与联合有和区别?
1.结构和联合都是由多个不同的数据类型成员组成,但在任何同一时刻,联合中只存放了一个被选中的成员(所有成员共用一块地址空间),而结构的所有成员都存在(不同成员的存放地址不同)。
2.对于联合的不同成员赋值,将会对其它成员重写,
原来成员的值就不存在了,而对于结构的不同成员赋值是互不影响的。
10.下面关于“联合”的题目的输出?
a)
stdio.h>
2.union
3.{
4.inti;
5.charx[2];
6.}a;
7.voidmain()
8.{
9.a.x[0]=10;
10.a.x[1]=1;
11.printf("
%d"
a.i);
12.}
答案:
266(低位低地址,高位高地址,内存占用情况是Ox010A)
b)
1.main()
3.union{
/*定义一个联合*/
5.struct{
/*在联合中定义一个结构*/
6.charfirst;
7.charsecond;
8.}half;
9.}number;
10.number.i=0x4241;
/*联合成员赋值*/
%c%c\n"
number.half.first,mumber.half.second);
12.number.half.first='
a'
/*联合中结构成员赋值*/
13.number.half.second='
b'
14.printf("
%x\n"
number.i);
15.getch();
AB
(0x41对应'
A'
,是低位;
Ox42对应'
B'
,是高位)
6261(number.i和number.half共用一块地址空间)
11.已知strcpy的函数原型:
char*strcpy(char*strDest,constchar*strSrc)其中strDest是目的字符串,strSrc是源字符串。
不调用C++/C的字符串库函数,请编写函数strcpy.
答案:
1.char*strcpy(char*strDest,constchar*strSrc)
3.if(strDest==NULL||strSrc==NULL)
4.returnNULL;
5.if(strDest==strSrc)
6.returnstrDest;
7.char*tempptr=strDest;
8.while((*strDest++=*strSrc++)!
=‘\0’);
9.returntempptr;
12.已知String类定义如下:
1.classString
3.public:
4.String(constchar*str=NULL);
//通用构造函数
5.String(constString&
another);
//拷贝构造函数
6.~String();
//析构函数
7.String&
operater=(constString&
rhs);
//赋值函数
8.private:
9.char*m_data;
//用于保存字符串
10.};
尝试写出类的成员函数实现。
1.String:
:
String(constchar*str)
3.if(str==NULL)//strlen在参数为NULL时会抛异常才会有这步判断
4.{
5.m_data=newchar[1];
6.m_data[0]='
\0'
;
7.}
8.else
9.{
10.m_data=newchar[strlen(str)+1];
11.strcpy(m_data,str);
13.}
14.String:
String(constString&
another)
15.{
16.m_data=newchar[strlen(another.m_data)+1];
17.strcpy(m_data,other.m_data);
18.}
19.String&
String:
operator=(constString&
rhs)
20.{
21.if(this==&
22.return*this;
23.delete[]m_data;
//删除原来的数据,新开一块内存
24.m_data=newchar[strlen(rhs.m_data)+1];
25.strcpy(m_data,rhs.m_data);
26.return*this;
27.}
28.String:
~String()
29.{
30.delete[]m_data;
31.}
13..h头文件中的ifndef/define/endif的作用?
防止该头文件被重复引用。
14.#include<
file.h>
与#include"
file.h"
的区别?
前者是从StandardLibrary的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h.15.在C++程序中调用被C编译器编译后的函数,为什么要加extern“C”?
首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。
例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。
这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;
它会在连接阶段中从模块A编译生成的目标代码中找到此函数extern"
C"
是连接申明(linkagedeclaration),被extern"
修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。
函数被C++编译后在符号库中的名字与C语言的不同。
例如,假设某个函数的原型为:
voidfoo(intx,inty);
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangledname”)。
_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。
例如,在C++中,函数voidfoo(intx,inty)与voidfoo(intx,floaty)编译生成的符号是不相同的,后者为_foo_int_float.同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。
用户所编写程序的类成员变量可能与全局变量同名,我们以"
."
来区分。
而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
未加extern"
声明时的连接方式假设在C++中,模块A的头文件如下
1.//模块A头文件 moduleA.h
2.#ifndefMODULE_A_H
3.#defineMODULE_A_H
4.intfoo(intx,inty);
5.#endif
在模块B中引用该函数:
1.//模块B实现文件 moduleB.cpp
2.#include"
moduleA.h"
3.foo(2,3);
实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!
加extern"
声明后的编译和连接方式
声明后,模块A的头文件变为:
4.extern"
intfoo(intx,inty);
在模块B的实现文件中仍然调用foo(2,3),其结果是:
(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo.
如果在模块A中函数声明了foo为extern"
类型,而模块B中包含的是externintfoo(intx,inty),则模块B找不到模块A中的函数;
反之亦然。
所以,可以用一句话概括extern“C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。
我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):
实现C++与C及其它语言的混合编程。
明白了C++中extern"
的设立动机,我们下面来具体分析extern"
通常的使用技巧:
extern"
的惯用法
(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:
1.extern"
3.#include"
cExample.h"
4.}
而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern"
声明,在。
c文件中包含了extern"
时会出现编译语法错误。
C++引用C函数例子工程中包含的三个文件的源代码如下:
1./*c语言头文件:
cExample.h*/
2.#ifndefC_EXAMPLE_H
3.#defineC_EXAMPLE_H
4.externintadd(intx,inty);
6./*c语言实现文件:
cExample.c*/
7.#include"
8.intadd(intx,inty)
10.returnx+y;
12.//c++实现文件,调用add:
cppFile.cpp
13.extern"
14.{
15.#include"
17.intmain(intargc,char*argv[])
18.{
19.add(2,3);
20.return0;
21.}
如果C++调用一个C语言编写的。
DLL时,当包括。
DLL的头文件或声明接口函数时,应加extern"
{ }.
(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern"
,但是在C语言中不能直接引用声明了extern"
的该头文件,应该仅将C文件中将C++中定义的extern"
函数声明为extern类型。
C引用C++函数例子工程中包含的三个文件的源代码如下:
1.//C++头文件cppExample.h
2.#ifndefCPP_EXAMPLE_H
3.#defineCPP_EXAMPLE_H
intadd(intx,inty);
6.//C++实现文件cppExample.cpp
cppExample.h"
12./*C实现文件cFile.c
13./*这样会编译出错:
#include"
*/
14.externintadd(intx,inty);
15.intmain(intargc,char*argv[])
16.{
17.add(2,3);
18.return0;
19.}
15题目的解答请参考《C++中extern“C”含义深层探索》注解:
16.关联、聚合(Aggregation)以及组合(Composition)的区别?
涉及到UML中的一些概念:
关联是表示两个类的一般性联系,比如“学生”和“老师”就是一种关联关系;
聚合表示has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:
从实现的角度讲,聚合可以表示为:
classA{...}
classB{A*a;
.....}
而组合表示contains-a的关系,关联性强于聚合:
组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:
实现的形式是:
classA{...}classB{Aa;
...}
参考文章:
17.面向对象的三个基本特征,并简单叙述之?
1.封装:
将客观事物抽象成类,每个类对自身的数据和方法实行protection(private,protected,public)
2.继承:
广义的继承有三种实现形式:
实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。
前两种(类继承)和后一种(对象组合=>
接口继承以及纯虚函数)构成了功能复用的两种方式。
3.多态:
是将