北京
本书的思路
从涉及的开发领域来说,C/C++无疑是目前所有语言之中的翘楚,在Windows编程、嵌入式编程、各种通信编程中都有 C/C++的影子。因为涉及的领域众多,社会对 C/C++的人才需求也越来越多。不管招聘的职位是嵌入式下的开发还是 Windows 下的开发,熟悉 C/C++语言基础是必需的招聘要求。
虽然全世界每个月都可能会出现新的语言,但从 TIOBE 世界编程语言排行榜的数据来看,从2009年到现在,C/C++一直都在前3甲中。不论历史的车轮如何滚滚向前,学好C/C++永远不会落后。
本书针对的是刚毕业或刚学完 C/C++的入门读者,目的是帮助读者找到更好的工作并复习所学的C/C++基础。
本书的特点
本书全面讲解了 C/C++面试的各种知识点,并对一些重点和难点进行了细致的分析。特点如下:
本书条理清晰,章节内容由易至难,由浅入深,先从C 程序设计入手,再详细讲解C++面向对象的高级特性,最后讲解泛型编程和STL。
本书对于每个面试例题都有详细的讲解以及源代码分析。
书中还讨论了数据结构和算法,给出了一些经典的数据结构和算法的 C 语言实现,便于读者快速掌握面试中所需的知识。
针对面试中出现越来越多的智力测试部分,本书对大部分常见的智力题和逻辑思维题进行了归类及分析解答。
本书的内容
本书内容突出了在 C/C++面试中或者是项目开发中,必须掌握的技能和容易忽略的内容。对 C/C++面试者来说,可以快速掌握面试过程中考查的知识点,减少面试准备时间,提高面试成功率。本书共分为12章,有310余道面试题。
第1章 C/C++程序基础
本章介绍了赋值语句、递增语句、类型转换、数据交换等程序设计的基本概念。希望读者在面试之前复习这些概念,并重视那些比较细微却基础的考点。
第2章 预处理、const、static 与sizeof
本章介绍了 C/C++设计语言中的难点,这也是各个企业面试中反复出现的考点。尤其是static和sizeof,它们在许多笔试以及面试的题目中都出现过。
第3章 引用和指针
本章介绍了引用和指针,这是 C/C++的基础,又是学习过程中最难翻越的一道坎。本章通过编写实例的方式讲解了数组指针、函数指针、常量指针、指针传值、多维指针等容易让读者混淆的概念。
第4章 字符串
本章介绍了字符串的应用和字符串处理的一些函数。字符串是笔试以及面试的热门考点,通过字符串测试可以考察程序员的编程规范以及编程习惯。其中也包括了许多知识点,例如内存越界、指针与数组操作等。许多公司在面试时会要求应试者写一段 strcpy 复制字符串或字符串子串操作的程序。
第5章 位运算与嵌入式编程
本章介绍了在位运算与嵌入式开发中容易出现的面试题。C 语言是嵌入式开发所必需的编程技术,因此在招聘嵌入式系统程序员时会要求必须非常熟悉C/C++语言。
第6章 C++面向对象
C语言是面向过程的,而C++作为C语言的超集支持,它是面向对象的。面向对象(ObjectOriented)是当前计算机界关心的重点,它是当今软件开发方法的主流,因此也是各大公司的重要考点。
第7章 C++继承和多态
继承和多态是 C++面向对象程序设计的关键。继承机制使得派生类能够获得基类的成员数据和方法,只需要在派生类中增加基类没有的成员。多态是建立在继承的基础上的,它使用了C++编译器最核心的一个技术,即动态绑定技术。这些都是面试必考题型。
第8章 数据结构
算法的设计依赖于数据的逻辑结构,算法的实现依赖于数据的存储结构,所以数据结构选择得好坏,对程序的质量影响甚大。掌握基本的数据结构知识,是程序设计水平提高的必要条件。算法和数据结构也是面试中的必考题型。
第9章 排序
排序法属于算法中解决数据排列问题的解决方案。本章演示了插入排序、选择排序、交换排序、归并排序和分配排序的实现过程。每一种排序法都可能成为一道面试题。
第10章 泛型编程
泛型编程是一种新的编程思想,它基于模板技术,有效地将算法与数据结构分离,降低了模块间的耦合度。本章演示了泛型在 C/C++中的应用,如函数模板和类模板。这些内容是难点,也是考点。
第11章 STL
STL 是标准模板库,它涵盖了常用的数据结构和算法,并且具有跨平台的特点。将泛型编程思想和STL库用于系统设计中,明显降低了开发强度,提高了程序的可维护性及代码的可重用性。这也是越来越多的笔试和面试中考查STL相关知识的原因。
第12章 智力测试题
有很多有趣的逻辑思考题目出现于跨国企业的招聘面试中,它对考查一个人的思维方式及思维方式的转变能力有极其明显的作用。据一些研究显示,这样的能力往往也与工作中的应变与创新状态息息相关。本章面试题不一定都有固定的答案,有时候只是考查应聘者的逻辑思维。
本书的读者群
即将步入IT 行业的应届大学毕业生;
有一定工作经验但C/C++编程基础不好的程序员;
想跳槽又怕找不到适合自己的工作的C/C++程序员;
刚从培训机构学习完C/C++的入门者;
C/C++培训机构的课后阅读图书;
C/C++语言爱好者。
编者
作为程序员,你在求职时,公司会询问你的项目经验,例如你做过什么类型的项目、担任的是何种角色,以及做项目时如何与他人沟通,等等。除此之外,当然还要考查你的编程能力。这里包括你的编程风格,以及你对于赋值语句、递增语句、类型转换、数据交换等程序设计基本概念的理解。因此,最好在考试之前复习这些程序设计的基本概念,并且要特别重视那些比较细致的考点问题。本章列出了一些涉及C/C++程序设计基本概念的考题,希望读者在读完后能有所收获。
考点:一般赋值语句的概念和方法
出现频率:★★★
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int x = 3, y, z;
6
7 x *= (y = z = 4); printf("x = %d\n", x);
8
9 z = 2;
10 x = (y = z); printf("x = %d\n", x);
11 x = (y == z); printf("x = %d\n", x);
12 x = (y & z); printf("x = %d\n", x);
13 x = (y && z); printf("x = %d\n", x);
14
15 y = 4;
16 x = (y | z); printf("x = %d\n", x);
17 x = (y || z); printf("x = %d\n", x);
18
19 x = (y == z)? 4: 5;
20 printf("x = %d\n", x);
21
22 x = (y == z)? 1: (y < z)? 2: 3;
23 printf("x = %d\n", x);
24
25 return 0;
26 }
【解析】
程序的说明如下:
程序执行至第8 行时,x 的值为3,y 和z 未被初始化。此行的执行顺序是首先执行z=4,然后执行y=z,最后执行x*=y。因此x的值为3*4=12。
程序执行至第 10 行时,z 的值为 2。此行的执行顺序是首先执行 y=z,然后执行x=y。因此x的值为2。
程序执行至第11 行时,y 和z 的值都为2。此行的执行顺序是首先执行y==z,比较y和z的值是否相等,然后将比较的结果赋给x。因此x的值为1。
程序执行至第12 行时,y和z 的值都为2。此行把y和z 做按位与(&)运算的结果赋给变量x。y 和z 的二进制都是10,因此y & z 的结果为二进制10。因此x的值为2。
程序执行至第13 行时,y和z 的值都为2。此行把y和z 做逻辑与(&&)运算的结果赋给变量x。此时y和z 的值都不是0,因此y && z 的结果为1。因此x 的值为1。
程序执行至第16 行时,y的值为4,z 的值为2。此行把y和z 做按位或(|)运算的结果赋给变量x。此时y和z的二进制表示分别为100和010,因此y|z的结果为110。因此x的值为110,十进制表示为6。
程序执行至第17 行时,y 的值为4,z 的值为2。此行把y 和z 做逻辑或(||)运算的结果赋给变量x。此时y和z的值都不是0,因此y||z的结果为1。因此x的值为1。
程序执行至第19 行时,y的值为4,z 的值为2。此行首先比较y 和z 的大小是否相等,如果相等,则将x取4和5的前者,否则x取4和5的后者。在这里,y不等于z,因此x的值为5。
程序执行至第22 行时,y的值为4,z 的值为2。此行首先比较y 和z 大小是否相等,如果相等,x取1,否则,判断y是否大于z,如果是,则取2,否则取3。在这里,y的值大于z的值,因此x的值为3。
总结:这个考题只是考查各种基本的赋值运算。这里,读者要注意位运算与逻辑运算的区别,以及三元操作符的用法。通过程序代码 17 行以及 19 行的举例,我们可以发现三元操作符有时可以代替条件判断if/else/else if的组合。
【答案】
x = 12
x = 2
x = 1
x = 2
x = 1
x = 6
x = 1
x = 5
x = 3
考点:C++域操作符的使用
出现频率:★★★
请指出下面这个程序在C和C++中的输出分别是什么。
1 #include <stdio.h>
2
3 int value = 0;
4
5 void printvalue()
6 {
7 printf("value = %d\n", value);
8 };
9
10 int main()
11 {
12 int value = 0;
13
14 value = 1;
15 printf("value = %d\n", value);
16
17 ::value = 2;
18 printvalue();
19
20 return 0;
21 }
【解析】
如果将文件保存为后缀名为.c的文件,则在Visual C++ 6.0中不能通过编译并且提示17行有语法错误。而如果文件保存为后缀名为.cpp的文件,则在Visual C++ 6.0中就能顺利通过编译并且运行。
这段程序有两个变量,其名字都是value。不同的是,其中一个是在main函数之前就声明的全局变量,而另外一个是在main函数内部声明的局部变量。这两个变量的作用域是不一样的。
这里要注意:在函数printvalue里打印的是全局变量的值,在main函数的15行打印的是局部变量的值。这是因为在main函数里的局部变量value引用优先。在C++中可以通过域操作符“::”来直接操作全局变量(代码的17行操作的value是全局变量),但是在C中不支持这个操作符,因此会报错。注意:在 C 中不推荐这种局部变量与全局变量同名的设计方式。
【答案】
在C中编译不能通过,并指示17行符号错误。
在C++中的输出如下:
value=1(局部变量value)
value=2(全局变量value)
考点:i++和++i的区别
出现频率:★★★★★
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int i=8;
6
7 printf("%d\n",++i);
8 printf("%d\n",--i);
9 printf("%d\n",i++);
10 printf("%d\n",i--);
11 printf("%d\n",-i++);
12 printf("%d\n",-i--);
13 printf("------\n");
14
15 return 0;
16 }
【解析】
程序的说明如下:
程序第7 行,此时i 的值为8。这里先i 自增1,再打印i 的值。因此输出9,并且i的值也变为9。
程序第8 行,此时i 的值为9。这里先i 自减1,再打印i 的值。因此输出8,并且i的值也变为8。
程序第9 行,此时i 的值为8。这里先打印i 的值,再i 自增1。因此输出8,并且i的值也变为9。
程序第10 行,此时i 的值为9。这里先打印i 的值,再i 自减1。因此输出9,并且i的值也变为8。
程序第11 行,此时i 的值为8。这里的“-”表示负号运算符。因此先打印-i 的值,再i自增1。因此输出-8,并且i的值也变为9。
程序第12 行,此时i 的值为9。这里的第一个“-”表示负号运算符,后面连在一起的两个“-”表示自减运算符。因此先打印-i的值,再i自减1。因此输出-9,并且i的值也变为8。
【答案】
9
8
8
9
-8
-9
------
考点:i++和++i的效率比较
出现频率:★★★
【解析】
在这里声明,简单地比较前缀自增运算符和后缀自增运算符的效率是片面的,因为存在很多因素影响这个问题的答案。首先考虑内建数据类型的情况:如果自增运算表达式的结果没有被使用,而是仅仅简单地用于增加一员操作数,答案是明确的,前缀法和后缀法没有任何区别,编译器的处理都应该是相同的,很难想象得出有什么编译器实现可以别出心裁地在二者之间制造任何差异。我们看看下面这个程序。
1 #include <stdio.h>
2
3 int main()
4 {
5 int i = 0;
6 int x = 0;
7
8 i++;
9 ++i;
10 x = i++;
11 x = ++i;
12
13 return 0;
14 }
上面的代码在VC++ 6.0 中编译,得到的汇编如下。
; Line 5
mov DWORD PTR _i$[ebp], 0
; Line 6
mov DWORD PTR _x$[ebp], 0
; Line 8
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax
; Line 9
mov ecx, DWORD PTR _i$[ebp]
add ecx, 1
mov DWORD PTR _i$[ebp], ecx
; Line 10
mov edx, DWORD PTR _i$[ebp]
mov DWORD PTR _x$[ebp], edx
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax
; Line 11
mov ecx, DWORD PTR _i$[ebp]
add ecx, 1
mov DWORD PTR _i$[ebp], ecx
mov edx, DWORD PTR _i$[ebp]
mov DWORD PTR _x$[ebp], edx
代码段第8行和第9 行生成的汇编代码分别对应Line 8 和Line 9 下对应的汇编代码,可以看到3个步骤几乎完全一样。
代码段第10~11 行生成的汇编代码分别对应Line 10 和Line 11 下对应的汇编代码,可以看到都是5个步骤,只是在加1的先后顺序上有一些区别,效率也是完全一样的。
由此说明,考虑内建数据类型时,它们的效率差别不大(去除编译器优化的影响)。所以在这种情况下,我们大可不必关心。
现在让我们再考虑自定义数据类型(主要是指类)的情况。此时我们不需要再做很多汇编代码的分析了,因为前缀式(++i)可以返回对象的引用,而后缀式(i++)必须返回对象的值,所以导致在大对象的时候产生了较大的复制开销,引起效率降低。因此处理使用者自定义类型(注意不是指内建类型)的时候,应该尽可能地使用前缀式递增/递减,因为它天生“体质”较佳。
【答案】
内建数据类型的情况,效率没有区别。
自定义数据类型的情况,++i效率较高。
考点:良好的编程风格
出现频率:★★★★
A.假设布尔变量名字为flag,它与零值比较的标准if语句如下。
第一种:
1 if (flag == TRUE)
2 if (flag == FALSE)
第二种:
1 if (flag)
2 if (!flag)
B.假设整型变量的名字为value,它与零值比较的标准if语句如下。
第一种:
1 if (value == 0)
2 if (value != 0)
第二种:
1 if (value)
2 if (!value)
C.假设浮点变量的名字为 x,它与 0.0的比较如下。
第一种:
1 if (x == 0.0)
2 if (x != 0.0)
第二种:
1 if ((x >= -EPSINON) && (X <= EPSINON))
2 if ((x < -EPSINON) || (X > EPSINON))
其中,EPSINON是允许的误差(精度)。
D.指针变量 p与 0的比较如下。
第一种:
1 if (p == NULL)
2 if (p != NULL)
第二种:
1 if (p == 0)
2 if (p != 0)
【解析】
A 的第二种风格较良好。根据布尔类型的语义,零值为“假”(记为 FALSE),任何非零值都是“真”(记为TRUE)。TRUE的值究竟是什么并没有统一的标准。例如Visual C++将TRUE定义为1,而Visual Basic 则将TRUE 定义为-1。因此不可将布尔变量直接与TRUE、FALSE进行比较。
B 的第一种风格较良好,第二种风格会让人误解value 是布尔变量,应该将整型变量用“==”或“!=”直接与0比较。
C 的第二种风格较良好。注意:无论是float 还是double 类型的变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
D 的第一种风格较良好,指针变量的零值是“空”(记为 NULL)。尽管 NULL的值与0相同,但是两者意义不同。用p与NULL显式比较,强调p是指针变量。
如用p与0比较,容易让人误解p是整型变量。
考点:有符号变量与无符号变量的区别和联系
出现频率:★★★★
1 #include <stdio.h>
2
3 char getChar(int x, int y)
4 {
5 char c;
6 unsigned int a = x;
7
8 (a + y > 10)? (c = 1): (c = 2);
9 return c;
10 }
11
12 int main(void)
13 {
14 char c1 = getChar(7, 4);
15 char c2 = getChar(7, 3);
16 char c3 = getChar(7, -7);
17 char c4 = getChar(7, -8);
18
19 printf("c1 = %d\n", c1);
20 printf("c2 = %d\n", c2);
21 printf("c3 = %d\n", c3);
22 printf("c4 = %d\n", c4);
23
24 return 0;
25 }
【解析】
首先说明getChar()函数的作用:它有两个输入参数,分别是整型的x和y。在函数体内,把参数x的值转换为无符号整型后再与y相加,其结果与10进行比较,如果大于10,则函数返回1,否则返回2。在这里,我们要注意:当表达式中存在有符号类型和无符号类型时,所有的操作数都自动转换成无符号类型。因此,这里由于a是无符号数,在代码第8行中, y值会首先自动转换成无符号的整数,然后与a相加,最后再与10进行比较。以下是在main函数中各调用getChar()函数的分析。
代码第14 行,传入的参数分别为7 和4,两个数相加后为11,因此c1 返回1。
代码第15 行,传入的参数分别为7 和3,两个数相加后为10,因此c2 返回2。
代码第16 行,传入的参数分别为7 和-7,-7 首先被转换成一个很大的数,然后与7相加后正好溢出,其值为0,因此c3返回2。
代码第17 行,传入的参数分别为7 和-8,-8 首先被转换成一个很大的数,然后与7相加。两个数相加后为很大的整数(差1就正好溢出了),因此c4返回1。
我们可以看到,由于无符号整数的特性,getChar()当参数x为7时,如果y等于区间[-7,3]中的任何整数值,getChar()函数都将返回2。当y的值在区间[-7,3]之外时,函数返回-1。
总之,我们在看表达式时要很小心地注意符号变量与无符号变量之间的转换、占用不同字节内存的变量之间的赋值等操作,否则可能会出现我们意想不到的结果。
【答案】
c1 = 1
c2 = 2
c3 = 2
c4 = 1
考点:两个变量的值的交换方法
出现频率:★★★★
【解析】
请参考以下C++程序代码。
1 #include <stdio.h>
2
3 void swap1(int& a, int& b)
4 {
5 int temp = a; //使用局部变量temp完成交换
6 a = b;
7 b = temp;
8 };
9
10 void swap2(int& a, int& b)
11 {
12 a=a+b; //使用加减运算完成交换
13 b=a-b;
14 a=a-b;
15 };
16
17 void swap3(int& a, int& b)
18 {
19 a^=b; //使用异或运算完成交换
20 b^=a;
21 a^=b;
22 };
23
24 int main(void)
25 {
26 int a1 = 1, b1 = 2;
27 int a2 = 3, b2 = 4;
28 int a3 = 5, b3 = 6;
29 int a = 2147483647, b = 1;
30
31 swap1(a1, b1); //测试使用临时变量进行交换的版本
32 swap2(a2, b2); //测试使用加减运算进行交换的版本
33 swap3(a3, b3); //测试使用异或运算进行交换的版本
34
35 printf("after swap...\n");
36 printf("a1 = %d, b1 = %d\n", a1, b1);
37 printf("a2 = %d, b2 = %d\n", a2, b2);
38 printf("a3 = %d, b3 = %d\n", a3, b3);
39
40 swap2(a, b);
41 printf("a = %d, b = %d\n", a, b);
42
43 return 0;
44 }
以上的C++程序中有3个swap函数,都是采用引用传参的方式。
swap1()采用的是我们在许多教科书里看到的方式,用一个局部变量temp 保存其中一个值来达到交换目的。当然,这种方式不是本题要求的答案。
swap2()采用的是一种简单的加减算法来达到交换a、b 的目的。这种方式的缺点是做a+b和a-b运算时可能会导致数据溢出。
swap3()采用了按位异或的方式交换a、b。按位异或运算符“^”的功能是将参与运算的两数各对应的二进制位相异或,如果对应的二进制位相同,则结果为0,否则结果为1。这样运算3次即可交换a、b的值。
代码第31~32行做了调用3种swap函数的举例,注意第40行的调用,这里在swap2函数栈中的运算会有数据溢出发生。我们知道,在32位平台下,int占4个字节内存,其范围是-2147483648~2147483647,因此2147483647加1就变成了-2147483648。不过通过运行结果我们可以看到,虽然产生了溢出,但是交换操作依然是成功的。下面是程序运行结果。
after swap…
a1 = 2, b1 = 1
a1 = 4, b1 = 3
a1 = 6, b1 = 5
a1 = 1, b1 = 2147483647
【答案】
采用程序代码中swap2和swap3的交换方式。swap2有可能发生数据溢出的缺点。相比于swap2,推荐swap3,采用按位异或的方式。
考点:C和C++的联系与区别
出现频率:★★★★
【答案】
C是一个结构化语言,它的重点在于算法和数据结构。对语言本身而言,C是C++的子集。C程序的设计首要考虑的是如何通过一个过程,对输入进行运算处理,得到输出。对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够配合对应的问题,这样就可以通过获取对象的状态信息得到输出或实现过程控制。
因此,C与C++的最大区别在于,它们用于解决问题的思想方法不一样。
C实现了C++中过程化控制及其他相关功能。而在C++中的C,相对于原来的C还有所加强,引入了重载、内联函数、异常处理等。C++更是拓展了面向对象设计的内容,如类、继承、虚函数、模板和包容器类等。
在 C++中,不仅需要考虑数据封装,还需要考虑对象粒度的选择、对象接口的设计和继承、组合与继承的使用等问题。
相对于C,C++包含了更丰富的设计概念。
考点:C++与C的区别
出现频率:★★★
【答案】
C 是面向过程化的,但是 C++不是完全面向对象化的。在 C++中也完全可以写出与 C一样过程化的程序,所以只能说C++拥有面向对象的特性。Java是真正面向对象化的。
为什么标准头文件都有类似以下的结构?
考点:标准头文件中一些通用结构的理解
出现频率:★★★★
1 #ifndef __INCvxWorksh
2 #define __INCvxWorksh
3 #ifdef __cplusplus
4 extern "C" {
5 #endif
6 /*...*/
7 #ifdef __cplusplus
8 }
9 #endif
10 #endif /* __INCvxWorksh */
【解析】
显而易见,代码第1、2、10行的作用是防止该头文件被重复引用。代码第3行的作用是表示当前使用的是C++编译器。如果要表示当前使用的是C编译器,可以这样指定:
1 #ifdef __STDC__
那么代码第4~8 行中的extern "C"有什么作用呢?
extern "C"包含双重含义。
首先,被它修饰的目标是“extern”的。也就是告诉编译器,其声明的函数和变量可以在本模块或其他模块中使用。通常,在模块的头文件中对本模块提供给其他模块引用的函数和全局变量以关键字extern声明。例如,当模块B欲引用该模块A中定义的全局变量和函数时,只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
其次,被它修饰的目标是“C”的,意思是其修饰的变量和函数是按照C语言方式编译和连接的。我们来看看C++中对类似C的函数是怎样编译的。作为一种面向对象的语言, C++支持函数重载,而过程式语言 C 则不支持。函数被 C++编译后在符号库中的名字与 C语言的不同。例如下面两个函数:
1 void foo( int x, int y );
2 void foo( int x, float y );
这两个函数编译生成的符号是不相同的,前者可能为_foo_int_int 之类,而后者可能为_foo_int_float之类。可以发现,这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。这样,如果在C中连接C++编译的符号时,就会因找不到符号问题发生连接错误。
如果加extern "C"声明后,模块编译生成foo 的目标代码时,就不会对其名字进行特殊处理,采用了C语言的方式,也就是_foo之类,不会加上后面函数参数数量及类型信息相关的那一串了。因此extern "C"是C++编译器提供的与C 连接交换指定的符号,用来解决名字匹配问题。
【答案】
代码第1、2、10行的作用是防止该头文件被重复引用。
代码第3行的作用是表示当前使用的是C++编译器。
代码第4~8行中的extern "C"是C++编译器提供的与C 连接交换指定的符号,用来解决名字匹配问题。
考点:头文件引用中<>与""的区别
出现频率:★★★★
【答案】
尖括号< >表明这个文件是一个工程或标准头文件。查找过程会首先检查预定义的目录,我们可以通过设置搜索路径环境变量或命令行选项来修改这些目录。
如果文件名用一对引号括起来,则表明该文件是用户提供的头文件,查找该文件时将从当前文件目录(或文件名指定的其他目录)中寻找文件,然后在标准位置寻找文件。
考点:atexit()函数的使用
出现频率:★★★★
【解析】
很多时候,我们需要在程序退出的时候做一些诸如释放资源的操作,但程序退出的方式有很多种,例如main()函数运行结束,在程序的某个地方用exit()结束程序,用户通过Ctrl+C等操作发信号来终止程序,等等,因此需要有一种与程序退出方式无关的方法来进行程序退出时的必要处理。方法就是用atexit()函数来注册程序正常终止时要被调用的函数。
atexit()函数的参数是一个函数指针,函数指针指向一个没有参数也没有返回值的函数。atexit()的函数原型是:
1 int atexit (void (*)(void));
在一个程序中最多可以用atexit()注册32个处理函数,这些处理函数的调用顺序与其注册的顺序相反,即最先注册的最后调用,最后注册的最先调用。请看下面的程序代码。
1 #include<stdlib.h> //使用atexit()函数必须包含头文件stdlib.h
2 #include<stdio.h>
3
4 void fn1(void);
5 void fn2(void);
6
7 int main(void)
8 {
9 atexit(fn1); //使用atexit注册fn1()函数
10 atexit(fn2); //使用atexit注册fn2()函数
11 printf("main exit...\n");
12 return 0;
13 }
14
15 void fn1()
16 {
17 printf("calling fn1()...\n"); //fn1()函数打印内容
18 }
19
20 void fn2()
21 {
22 printf("calling fn2()...\n"); //fn2()函数打印内容
23 }
上面的程序代码在main函数中调用atexit()函数依次注册了fn1()和fn2()函数。运行这个程序,我们可以得到下面的输出。
main exit…
calling fn1()…
calling fn2()…
在这里,fn2()与 fn1()在 main()函数结束后被依次调用,并且它们被调用的顺序与它们在main()函数被注册的顺序相反。
【答案】
可以用 atexit()函数来注册程序正常终止时要被调用的函数,并且在 main()函数结束时,调用这些函数的顺序与注册它们的顺序相反。
图书在版编目(CIP)数据
C和C++程序员面试秘笈/董山海编著.--北京:人民邮电出版社,2014.3
ISBN 978-7-115-34113-6
Ⅰ.①C… Ⅱ.①董… Ⅲ.①C语言—程序设计 Ⅳ.①TP312
中国版本图书馆CIP数据核字(2013)第307367号
内容提要
众多高级语言都从 C/C++有所借鉴,所以说 C/C++的语言基础对从事软件开发的人员来说非常重要。
本书是一本解析 C/C++面试题的书,可以帮助求职者更好地准备面试。本书共包含12 章,囊括了目前企业中常见的面试题类型和考点,包括 C/C++程序基础,预处理、const、static 与 sizeof,引用和指针,字符串,位运算与嵌入式编程,C++面向对象,C++继承和多态,数据结构,排序,泛型编程,STL,算法和逻辑思维等最常见的面试题。本书通过技术点解析、代码辅佐的方式让读者能深刻领会每个考点背后的技术。
本书紧扣面试精髓,对各种技术的剖析一针见血,是目前想找工作的 C/C++程序员和刚毕业的大学生的面试宝典。
◆编著 董山海
责任编辑 陈冀康
责任印制 程彦红 焦志炜
◆人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
北京昌平百善印刷厂印刷
◆开本:800×1000 1/16
印张:29
字数:564千字 2014年3月第1版
印数:1–3000册 2014年3月北京第1次印刷
定价:59.00元
读者服务热线:(010)81055410 印装质量热线:(010)81055316
反盗版热线:(010)81055315