1、程序员面试-4 及答案解析(总分:100.00,做题时间:90 分钟)一、论述题(总题数:28,分数:100.00)1.C 与 C+变量初始化有什么不同 (分数:3.00)_2.不使用 C/C+字符串库函数,如何自行编写 strcpy()函数 (分数:3.00)_3.如何把数字转换成字符串 (分数:3.00)_4.如何自定义内存复制函数 memcpy() (分数:3.00)_5.编译和链接的区别是什么 (分数:3.00)_6.编译型语言与解释型语言的区别是什么 (分数:3.00)_7.如何判断一段程序是由 C 编译程序还是由 C+编译程序编译的 (分数:3.00)_8.在 C+程序中调用被 C
2、 编译器编译后的函数,为什么要加 extern “C“ (分数:3.00)_9.两段代码共存于一个文件,编译时有选择地编译其中的一部分,如何实现 (分数:3.00)_10.面向对象与面向过程有什么区别 (分数:3.00)_11.面向对象的基本特征有哪些 (分数:3.00)_12.什么是深拷贝?什么是浅拷贝 (分数:3.00)_13.什么是友元 (分数:4.00)_14.复制构造函数与赋值运算符的区别是什么 (分数:4.00)_15.基类的构造函数/析构函数是否能被派生类继承 (分数:4.00)_16.初始化列表和构造函数初始化的区别是什么 (分数:4.00)_17.类的成员变量的初始化顺序是按
3、照声明顺序吗 (分数:4.00)_18.当一个类为另一个类的成员变量时,如何对其进行初始化 (分数:4.00)_19.C+能设计实现一个不能被继承的类吗 (分数:4.00)_20.构造函数没有返回值,那么如何得知对象是否构造成功 (分数:4.00)_21.C+中的空类默认产生哪些成员函数 (分数:4.00)_22.如何设置类的构造函数的可见性 (分数:4.00)_23.public 继承、protected 继承、private 继承的区别是什么 (分数:4.00)_24.C+提供默认参数的函数吗 (分数:4.00)_25.C+中有哪些情况只能用初始化列表而不能用赋值 (分数:4.00)_26
4、.什么是虚函数 (分数:4.00)_27.C+如何实现多态 (分数:4.00)_28.C+中继承、虚函数、纯虚函数分别指的是什么 (分数:4.00)_程序员面试-4 答案解析(总分:100.00,做题时间:90 分钟)一、论述题(总题数:28,分数:100.00)1.C 与 C+变量初始化有什么不同 (分数:3.00)_正确答案:()解析:在 C 语言中,只能用常数对全局变量和静态变量进行初始化,否则编译器会报错。在 C 语言里,全局变量如果不初始化,默认为 0。C 语言中静态变量和全局变量(extern 外部变量属于全局变量)的分配内存空间和初始化是在编译阶段完成的,而其他变量是在编译阶段进
5、行内存空间分配、在程序运行时执行本函数时赋予初值的。 而在 C+中,如果在一个文件中定义了 int a=5,要在另一个文件中定义 int b=a,前面必须对 a 进行声明:extern int a,否则编译不通过,即使是 int b=a,这句话也是分两步进行的:在编译阶段,编译器把 b 当做是未初始化数据而将它初始化为 0;在执行阶段,在 main 被执行前有一个全局对象的构造过程,int b=a,被当做是 int 型对象 b 的复制初始化构造来执行。 在 C+中全局对象、变量的初始化是独立的,如果不是像 int a=5 这样的已初始化数据,那么就是像 b 这样的未初始化数据。而 C+中全局对
6、象、变量的构造函数调用顺序是跟声明有一定关系的,臣口在同一个文件中先声明的先调用。对于不同文件中的全局对象、变量,它们的构造函数调用顺序是未定义的,取决于具体的编译器,不同的编译器、连接器,结果都可能不同,另外,连接时,指定.obj 文件的顺序也有关系。 引申:C 语言中各种变量的默认初始值是什么? 全局变量放在内存的全局数据区,由编译器建立,如果在定义的时候不做初始化,则系统将自动为其初始化,数值型为 0,字符型为 NULL,即 0,指针变量也被赋值为 NULL。静态变量的情况与全局变量类似。而非静态局部变量如果不显示初始化,那么其内容是不可预料的,将是随机数,会很危险,对系统的安全造成非常
7、大的隐患。2.不使用 C/C+字符串库函数,如何自行编写 strcpy()函数 (分数:3.00)_正确答案:()解析:strcpy 的原型为 extem char *strcpy(char *dest,const char *src);它包含在头文件 string.h中,它的返回指向 dest 的指针,其功能是把 src 所指由 NULL 结束的字符串复制到 dest 所指的数组中。值得注意的是,src 和 dest 所指内存区域不可以重叠,且 dest 必须有足够的空间来容纳 src 的字符串,src 字符串尾的字符串结束标识符“/0“也会被复制过去。 char *strcpy(char
8、*strDest,const char *strSrc); assert(strDest!=NULL) if (strDest=strSrc) return strDest; char *address=strDest; while(*strDest+=*strSrc+)!=“/0“) return address; C 语言的 assert()在assert.h中,当使用 assert 时,给它一个参数,即一个判断为真的表达式。strcpy 能把 strSrc 的内容复制到 strDest,返回类型为 char*主要是为了实现链式表达式。例如: int length=strlen(strcp
9、y(strDest,“hello world“); strcpy(strDest,strcpy(strDest1,strSrc); 可以将 strSrc 复制到 strDest1 与 strDest 中,也就是说,可以将函数的返回值做为另一个函数的参数。 程序代码示例如下: #includestdio.h #includestring.h int main() char str=“hello world“; strcpy(str,“h“); printf(“%s/n“,str); printf(”%c/n“,str3); return 0; 程序输出结果: h 1 因为 strcpy 将 K
10、以及/0 一直复制到 str 的地址空间内,覆盖了 hello world 字符串(hello world 是在栈区上分配的内存空间),但是后续的字符并没有被替换,如 str3仍然是 1。将 char str=“hello world“;改为 char* str=“hello world“;是不允许的,因为 str 此时是一个指针变量。 再例如: #includestdio.h #includestring.h int main() char s=“123456789“; char d=“123“; strcpy(d,s); printf(“%s/n%s/n“,d,s); return 0;
11、程序输出结果: 123456789 56789 上例中,数组 s 和数组 d 在内存中的存储结构如图所示。 图 1 数组 s 和数组 d 在内存中的存储结构strcpy 的功能就是把“源字符串”s 复制到“目的字符串”d,直到遇到“源字符串”的结束标识符/0,复制后的数组 d 和数组 S 的存储结构如图 2 所示。 3.如何把数字转换成字符串 (分数:3.00)_正确答案:()解析:C 语言中常用到字符串与数字之间的相互转换,常见的此类库函数有 atof(字符串转换成浮点数)、atoi(字符串转换成整型数)、atol(字符串转换成长整型数)、itoa(整型数转换成字符串)、ltoa(长整型数转
12、换为字符串)等。 为了考查求职者对基本功的掌握,在程序员的面试笔试中经常会见到此类题目,让求职者自定义此类函数的实现,此类题目虽然难度不大,但是需要认真仔细对待。 以自定义 Myatoi()与 Myitoa()函数为例,分别实现自定义字符串转换为整型数函数与自定义整型数转换为字符串函数。以下为自定义 Myatoi()函数的实现以及测试代码。 #includestdio.h int Myatoi(char* str) if(str=NULL) printf(“Invalid Input“); return-1; while(*str=“) str+; while(*str=(char)0xA1)
13、 int nSign=(*str=“-“)?-1:1;确定符号位 if(str=“+“*str=“-“) str+; int nResult=0; while(*str=“0“ str+; return nResult*nSign; int main() printf(“%d/n“,Myatoi(“12345“); return 0; 程序输出结果: 12345 以下为自定义 Myitoa 函数的实现以及测试代码: #includestdio.h char* Myitoa(int num) char str1024; int sign=num,i=0,j=0; char temp11; if(
14、sign0) num=-num; ; do tempi=num%10+“0“; num/=10; i+; while(num0); if(sign0) tempi+=“-“; tempi=“/0“; i-; while(i=0) strj=tempi; j+; i-; strj=“/0“; return str; int main() printf(“%s/n“,Myitoa(-12345); return 0; 程序输出结果: -123454.如何自定义内存复制函数 memcpy() (分数:3.00)_正确答案:()解析:memcpy 是 C 语言中的内存复制函数,它的函数原型为 void
15、 *memcpy(void *dest, const void*src, size_t n)。它的目的是将 src 指向地址为起始地址的连续 n 个字节的数据复制到以 dest 指向地址为起始地址的空间内,函数返回指向 destin 的指针。需要注意的是,src 和 dest 所指内存区域不能重叠,同时,与 strcpy 相比,memcpy 遇到“/0“不结束,而是一定会复制完 n 个字节。而且如果目标数组 dest 本身已有数据,执行 memcpy()之后,将覆盖原有数据(最多覆盖 n)。如果要追加数据,则每次执行 memcpy 后,要将目标数组地址增加到要追加数据的地址。 memcpy()
16、函数用来做内存复制,可以拿它来复制任何数据类型的对象,可以指定复制的数据长度,例如: char a100,b50; memcpy(b,a,sizeof(a); strcpy 和 memcpy 都是用于从一块内存复制一段连续的数据到另一块内存,区别是终结标识不同。strcpy(a,b)从 b 复制内容到 a,然后从 b+1 复制内容到 a+1,依次类推,直到 b+i 的内容是“/0“。而memcpy(a,b,c)从 b 开始复制 c 字节内容到 a,相比 strcpy,memcpy 是确定复制 c 个字节的。所以只要保证 b 开始有 c 字节有效数据,a 开始有 c 字节内存空间就行。 自定义内
17、存复制构造函数示例如下: #includestdio.h void* MyMemCpy(void *dest,const void *src, size_t count) char* pdest=static castchar*(dest); const char* psrc=static_castconst char*(src); if(pdestpsrc)i!=-1;-i) pdesti=psrci; else for(size_t i=0;icount;+i) pdesti=psrci; return dest; int main() char str=“0123456789“; MyM
18、emCpy(str+1,str+0,9); printf(“%s/n“,str); MyMemCpy(str,str+5,5); printf(“%s/n“,str); return 0; 程序输出结果: 0012345678 45678456785.编译和链接的区别是什么 (分数:3.00)_正确答案:()解析:在多道程序环境中,要想将用户源代码变成一个可以在内存中执行的程序,通常分为 3 个步骤:编译、链接、载入。 1)编译:将预处理生成的文件,经过词法分析、语法分析、语义分析以及优化后编译成若干个目标模块。可以理解为将高级语言翻译为计算机可以理解的二进制代码,即机器语言。 2)链接:由链
19、接程序将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的载入模型。链接主要解决模块间的相互引用问题,分为地址和空间分配,符号解析和重定位几个步骤。在编译阶段生成目标文件时,会暂时搁置那些外部引用,而这些外部引用就是在链接时进行确定的,链接器在链接时,会根据符号名称去相应模块中寻找对应符号。待符号确定之后,链接器会重写之前那些未确定的符号的地址,这个过程就是重定位。链接一般分为静态链接、载入时动态链接以及运行时动态链接 3 种。3)载入:由载入程序将载入模块载入内存。 编译和链接是为将用户程序从硬盘上调入内存并将其转换成可执行程序服务的。用编译器时的 compile 就是
20、在进行编译,link 就是链接,运行程序可以看到。 以 C/C+语言为例,把源文件编译成中间代码文件,在 Windows 下面为.obi 文件,在 UNIX、Linux 下面就是.o 文件,即 Object File,该动作被称为编译。然后再把大量的 ObiectFile 合成执行文件,这个动作称为链接。 编译时,编译器需要的是语法正确,函数与变量的声明正确。而一般来说,每个源文件都应该对应于一个中间目标文件(.o 文件或是.obj 文件)。链接时,主要是链接函数和全局变量,所以可以使用这些中间目标文件(.o 文件或是.obj 文件)来链接应用程序。链接就是那些目标文件之间相互链接自己所需要的
21、函数和全局变量,而函数可能来源于其他目标文件或库文件。6.编译型语言与解释型语言的区别是什么 (分数:3.00)_正确答案:()解析:编译型语言:编译是指在应用源程序执行之前,就将程序源代码“翻译”成目标代码(机器语言),因此其目标程序可以脱离其语言环境独立执行,使用比较方便、效率较高。但应用程序一旦需要修改,必须先修改源代码,再重新编译生成新的目标文件(*.obj)才能执行,只有目标文件而没有源代码,修改很不方便。现在大多数的编程语言都是编译型的。编译程序将源程序翻译成目标程序后保存在另一个文件中,该目标程序可脱离编译程序直接在计算机上多次运行。大多数软件产品都是以目标程序形式发行给用户的,
22、不仅便于直接运行,同时又使他人难于盗用其中的技术,C、C+、Fortran、Vistlal Foxpro、Pascal、Delphi、Ada 都是编译实现的。 解释型语言:解释型语言的实现中,翻译器并不产生目标机器代码,而是产生易于执行的中间代码。这种中间代码与机器代码是不同的,中间代码的解释是由软件支持的,不能直接使用硬件,软件解释器通常会导致执行效率较低。用解释型语言编写的程序是由另一个可以理解中间代码的解释程序执行的。与编译程序不同的是,解释程序的任务是逐一将源程序的语句解释成可执行的机器指令,不需要将源程序翻译成目标代码后再执行。解释程序的优点是当语句出现语法错误时,可以立即引起程序员
23、的注意,而程序员在程序开发期间就能进行校正。对于解释型 Basic 语言,需要一个专门的解释器解释执行 Basic 程序,每条语句只有在执行时才被翻译。这种解释型语言每执行一次就翻译一次,因而效率低下。一般地,动态语言都是解释型的,例如 Tc1、Per1、Ruby、VBScript、JavaScript 等。 需要注意的是,Java 是一类特殊的编程语言,Java 程序也需要编译,但是却没有直接编译为机器语言,而是编译为字节码,然后在 Java 虚拟机上以解释方式执行字节码。7.如何判断一段程序是由 C 编译程序还是由 C+编译程序编译的 (分数:3.00)_正确答案:()解析:如果编译器在编
24、译 cpp 文件,那么_cplusplus 就会被定义,如果是一个 C 文件在被编译,那么_STDC_就会被定义。_STDC_是预定义宏,当它被定义后,编译器将按照 ANSIC 标准来编译 C 语言程序。所以,可以采用如下程序示例判断。 #includestdio.h #ifdef_cplusplus #define USING_C 0 #else #define USING_C 1 #endif #includestdio.h int main() if(USING_C) printf(“C/n“); else printf(“(C+/n“); return 0; 在 C+编译环境下,程序输
25、出结果如下: C+ 编写 C 与 C+兼容的代码所需的宏如下: #ifdef_cplusplus extern “C“ #endif 具体的代码 #ifdef_cplusplus #endif 在上例中,_cplusplus 是 cpp 中的自定义宏,当定义了这个宏时,其表示这是一段 cpp 的代码。也就是说,上面代码的含义为如果这是一段 cpp 的代码,那么加入 extern“C“和处理其中的代码。 考虑到 C 语言没有重载函数的概念,所以 C 编译器编译的程序里,所有函数只有函数名对应的入口。而由于 C+支持函数重载,如果只有函数名对应的入口,则会出现混淆,所以 C+编译器编译的程序,应该
26、是函数名+参数类型列表对应到入口。 因为 main 函数是整个程序的入口,所以 main 是不能有重载的。如果一个程序只有 main()函数,是无法确认是 C 还是 C+编译器编译的,此时可以通过 nm 来查看函数名入口。例如,函数 int foo(int i,float j),C 编译的程序通过 nm 查看如下:foo 0x567xxxxxx(地址)。C+编译程序,通过 nm 查看为foo(int,float)0x567xxxxxx。 需要注意的是,如果要在 C+编译器里使用通过 C 编译的目标文件,必须通知 C+编译器。8.在 C+程序中调用被 C 编译器编译后的函数,为什么要加 exte
27、rn “C“ (分数:3.00)_正确答案:()解析:C+语言是一种面向对象编程语言,支持函数重载,而 C 语言是面向过程的编程语言,不支持函数重载,所以函数被 C+编译后在库中的名字与 C 语言的不同。如果声明一个 C 语言函数 float f(int a,char b),C+的编译器就会将这个名字变成像_f_int_char 之类的东西以支持函数重载。然而 C 语言编译器的库一般不执行该转换,所以它的内部名为_f,这样连接器将无法解释 C+对函数 f()的调用。 C+提供了 C 语言 Altemate likage specifications(替代连接说明)符号 extern “C“来解
28、决名字匹配问题,extern 是 C/C+语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在模块或其他模块中使用。extern 后跟一个字符串来指定想声明的函数的连接类型,后面是函数声明。 extern “C“ float f(int a,char b); 该语句旨在告诉编译器 f()是 C 连接的,这样 C+就不会转换函数名。标准的连接类型指定符有“C”和“C+”两种,但编译器开发商可以选择用同样的方法支持其他语言。如果有一组替代连接的声明,可以把它们放在花括号里: extern “C“ float f(int a,charb); .其他函数 或
29、者写成 extern “C“ #include “Myheader.h“ .其他 C 头文件 这就告诉 C+编译器,函数 f 是采用 C 语言方式链接的,应该到库中找名字_f 而不是找_f_int_char。C+编译器开发商已经对 C 标准库的头文件作了 extern “C“处理,所以可以用#include 直接引用这些头文件。9.两段代码共存于一个文件,编译时有选择地编译其中的一部分,如何实现 (分数:3.00)_正确答案:()解析:可以通过以下两种方法实现: 1)在源码中使用条件编译语句,然后在程序文件中定义宏的形式来选择需要的编译代码。 2)在源码中使用条件编译语句,然后在编译命令的命令
30、中加入宏定义命令来实现选择编译。10.面向对象与面向过程有什么区别 (分数:3.00)_正确答案:()解析:面向对象是当今软件开发方法的主流方法之一,它是把数据及对数据的操作方法放在一起,作为一个相互依存的整体,即对象。对同类对象抽象出其共性,即类,类中的大多数数据,只能被本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。程序流程由用户在使用中决定。例如,站在抽象的角度,人类具有身高、体重、年龄、血型等一些特称。人类仅仅只是一个抽象的概念,它是不存在的实体,但是所有具备人类这个群体的属性与方法的对象都叫人,这个对象人是实际存在的实体,每个人都是人这个群
31、体的一个对象。 而面向过程是一种以事件为中心的开发方法,就是自顶向下顺序执行,逐步求精,其程序结构是按功能划分为若干个基本模块,这些模块形成一个树状结构,各模块之间的关系也比较简单,在功能上相对独立,每一模块内部一般都是由顺序、选择和循环三种基本结构组成的,其模块化实现的具体方法是使用子程序,而程序流程在写程序时就已经决定。例如五子棋,面向过程的设计思路就是首先分析问题的步骤:第一步,开始游戏;第二步,黑子先走;第三步,绘制画面;第四步,判断输赢;第五步,轮到白子;第六步,绘制画面;第七步,判断输赢;第八步,返回步骤 2;第九步,输出最后结果。把上面每个步骤用分别的函数来实现,就是一个面向过程
32、的开发方法。 具体而言,两者主要有以下几个方面的不同之处: 1)出发点不同。面向对象是用符合常规思维方式来处理客观世界的问题,强调把问题域的要领直接映射到对象及对象之间的接口上。而面向过程方法则不然,它强调的是过程的抽象化与模块化,它是以过程为中心构造或处理客观世界问题的。 2)层次逻辑关系不同。面向对象方法则是用计算机逻辑来模拟客观世界中的物理存在,以对象的集合类作为处理问题的基本单位,尽可能地使计算机世界向客观世界靠拢,以使问题的处理更清晰直接。面向对象方法是用类的层次结构来体现类之间的继承和发展。而面向过程方法处理问题的基本单位是能清晰准确地表达过程的模块,用模块的层次结构概括模块或模块
33、间的关系与功能,把客观世界的问题抽象成计算机可以处理的过程。 3)数据处理方式与控制程序方式不同。面向对象方法将数据与对应的代码封装成一个整体,原则上其他对象不能直接修改其数据,即对象的修改只能由自身的成员函数完成。控制程序方式上是通过“事件驱动”来激活和运行程序。而面向过程方法是直接通过程序来处理数据,处理完毕后即可显示处理结果。在控制程序方式上是按照设计调用或返回程序,不能自由导航,各模块之间存在着控制与被控制、调用与被调用的关系。 4)分析设计与编码转换方式不同。面向对象方法贯穿软件生命周期的分析、设计及编码之间,是一种平滑过程,从分析到设计再到编码采用一致性的模型表示,即实现的是一种无
34、缝连接。而面向过程方法强调分析、设计及编码之间按规则进行转换,贯穿软件生命周期的分析、设计及编码之间,实现的是一种有缝的连接。11.面向对象的基本特征有哪些 (分数:3.00)_正确答案:()解析:面向对象方法首先对需求进行合理分层,然后构建相对独立的业务模块,最后通过整合各模块,达到高内聚、低耦合的效果,从而满足客户要求。具体而言,它有 3 个基本特征:封装、继承和多态。 1)封装是指将客观事物抽象成类,每个类对自身的数据和方法实行保护。类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。C+中类是一种封装手段,采用类来描述客观事物的过程就是封装,本质上是对客观事物的抽
35、象。 2)继承可以使用现有类的所有功能,而不需要重新编写原来的类,它的目的是为了进行代码复用和支持多态。它一般有 3 种形式:实现继承、可视继承、接口继承。其中,实现继承是指使用基类的属性和方法而无需额外编码的能力;可视继承是指子窗体使用父窗体的外观和实现代码;接口继承仅使用属性和方法,实现滞后到子类实现。前两种(类继承)和后一种(对象组合=接口继承以及纯虚函数)构成了功能复用的两种方式。通过继承创建的新类称为“派生类”或“子类”,被继承的类称为“父类”、“基类”或“超类”,而继承的过程就是从一般到特殊(具体)的过程。 3)多态是指同一个实体同时具有多种形式,它主要体现在类的继承体系中,它是将
36、父对象设置成为和一个或更多的它的子对象相等的技术,赋值以后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单地说,就是允许将子类类型的指针赋值给父类类型的指针。编译时多态是静态多态,在编译时就可以确定对象使用的形式。12.什么是深拷贝?什么是浅拷贝 (分数:3.00)_正确答案:()解析:如果一个类拥有资源(堆或者是其他系统资源),当这个类的对象发生复制过程时,资源重新分配,这个过程就是深拷贝;反之对象存在资源,但复制过程并未复制资源的情况视为浅拷贝。 例如,在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位复制,也就是把对象里的值完全复制给另一个对象,如 A=B,这时,
37、如果类 B 中有一个成员变量指针已经申请了内存,那么类 A 中的那个成员变量也指向同一块内存。这就出现了问题:当 B 把内存释放了,如通过析构函数,这时 A 内的指针就变成野指针了,导致运行错误。 深拷贝的程序示例如下: #includeiostream using namespace std; class CA public: CA(int b,char* cstr); CA(const CA void Show(); CA(); private: int a: char *str; ; CA:CA(int b,char* cstr) a=b; str=new charb; strcpy(str,cstr); CA:CA(const CA str=new chara;深拷贝 if(str!=0) strcpy(str,C.str); void CA