1、程序员面试-3 及答案解析(总分:100.00,做题时间:90 分钟)一、论述题(总题数:28,分数:100.00)1.一些结构声明中的冒号和数字是什么意思 (分数:3.00)_2.最有效的计算 2 乘以 8 的方法是什么 (分数:3.00)_3.如何实现位操作求两个数的平均值 (分数:3.00)_4.unsigned int i=3;printf(“%u/n“,i*-1)输出为多少 (分数:3.00)_5.如何求解整型数的二进制表示中 1 的个数 (分数:3.00)_6.不能用 sizeof()函数,如何判断操作系统是 16 位还是 32 位的 (分数:3.00)_7.嵌入式编程中,什么是大
2、端?什么是小端 (分数:3.00)_8.考虑 n 位二进制数,有多少个数中不存在两个相邻的 1 (分数:3.00)_9.不用除法操作符如何实现两个正整数的除法 (分数:3.00)_10.怎么样写一个接受可变参数的函数 (分数:3.00)_11.函数指针与指针函数有什么区别 (分数:3.00)_12.C+函数传递参数的方式有哪些 (分数:3.00)_13.重载与覆盖有什么区别 (分数:4.00)_14.是否可以通过绝对内存地址进行参数赋值与函数调用 (分数:4.00)_15.默认构造函数是否可以调用单参数构造函数 (分数:4.00)_16.C+中函数调用有哪几种方式 (分数:4.00)_17.什
3、么是可重入函数?C 语言中如何写可重入函数 (分数:4.00)_18.int a22=1,2,3,则 a01的值是多少 (分数:4.00)_19.如何合法表示二维数组 (分数:4.00)_20.a 是数组,(int*)(scanf(“%s“,str)是否安全 (分数:4.00)_23.行存储与列存储中哪种存储效率高 (分数:4.00)_24.全局变量和静态变量有什么异同 (分数:4.00)_25.局部变量需要“避讳”全局变量吗 (分数:4.00)_26.如何建立和理解非常复杂的声明 (分数:4.00)_27.变量定义与变量声明有什么区别 (分数:4.00)_28.不使用第三方变量,如何交换两个
4、变量的值 (分数:4.00)_程序员面试-3 答案解析(总分:100.00,做题时间:90 分钟)一、论述题(总题数:28,分数:100.00)1.一些结构声明中的冒号和数字是什么意思 (分数:3.00)_正确答案:()解析:C 语言的结构体可以实现位段,它的定义形式是在一个定义的结构体成员后面加上冒号,然后是该成员所占的位数。位段的结构体成员必须是 int 或者 unsigned int 类型,不能是其他类型。位段在内存中的存储方式是由具体的编译器决定的。 首先,定义位段的长度不能大于存储单元的长度。存储单元是指该位段的类型大小,不是计算机的存储单元字节。其次,一个位段如果不能放在一个存储单
5、元里,那么它会把这个存储单元中剩余的空间闲置,而从下一个存储单元开始存储下一个位段,即一个位段不能存储在两个存储单元内,位段在一个存储单元中的存储是紧凑的。再次,位段名缺省时称作无名位段,无名位段的存储空间通常不用,而位段长度为 0 位表示下一个位段存储在一个新的存储单元中,位段长度为 0 的时候位段名必须缺省(不能定义位段名)。最后,一个结构体中既可以定义位段成员也可以同时定义一般的结构体成员。这个时候,一般成员不和位段存储在同一个存储单元中。 程序示例分析如下: #includestdio.h typedef struct int a:2: int b:2: int c:1: test;
6、int main() test t; t.a=1; t.b=3; t.c=1; printf(“%d/n%d/n%d/n“,t.a,t.b,t.c); return 0; 程序输出结果: 1 -1 -1 由于 a 占两位,而 a 被赋值为 1,二进制就是 01,因此%d 输出的时候输出 1;b 也占了两位,赋值为 3,二进制也就是 11,由于使用了%d 输出,表示的是将这个 b 作为有符号 int 型来输出,这样的话二进制的11 将会有一位被认为是符号位,并且两位的 b 也会被扩展为 int 类型,也就是 4 字节,即 32 位。其实 a也做了这种扩展,只是扩展符号位的时候,由于数字在计算机中
7、存储都是补码形式,因此扩展符号位的时候正数用 0 填充高位,负数则用 1 填充高位。因此对于 a 来说,输出的时候被扩展为 00000000 00000000 00000000 00000001,也就是 1,而 b 则扩展为 11111111 11111111 11111111 11111111,也就是-1 了,c的显示也是这样的。2.最有效的计算 2 乘以 8 的方法是什么 (分数:3.00)_正确答案:()解析:23。 虽然直接进行乘法操作符运算也可以进行 2 与 8 的相乘,但是该种方法并非最优,通过移位方法会比较高效。因为将一个数左移 n 位,相当于乘以了 2 的 n 次方。因此,一个
8、数乘以 8,而 8 是 2 的 3 次方,所以只要该数左移 3 位即可实现乘以 8 的目的。 常规的乘法运算也可以实现,但 CPU 直接支持位运算,效率最高,所以操作 2 乘以 8 的最有效的方法是23。 引申:如何快速求取一个整数的 7 倍? 相比移位运算,如果直接使用乘法运算符的话,则执行效率相对比较慢,所以快速的方法就是将这个乘法转换成加减法和移位操作。由于移位运算相当于乘法运算或除法运算,左移相当于乘法运算,右移运算相当于除法运算,所以此时可以先将此整数左移 3 位(相当于将数字乘以 8),然后再减去原值,即(x3)-x 就获得了 x 的 7 倍。此处需要注意的是,由于-的优先级高于,
9、所以不能去掉括号,否则结果不正确。3.如何实现位操作求两个数的平均值 (分数:3.00)_正确答案:()解析:一般而言,求解平均数的方法就是将两者相加,然后除以 2,以变量 x 与 y 为例,两者的平均数为(x+y)/2。 但是采用上述方法,会存在一个问题,当两个数比较大时,如两者的和大于了机器位数能够表示的最大值,可能会存在数据溢出的情况,而采用位运算方法则可以避免这一问题,(x printf(“%d/n“,(x+y)/2); printf(“%d/n“,(x return 0; 在 32 位机器下,程序输出结果如下: -1 2147483647 程序的输出正好验证了这一算法的可行性。 引申
10、:如何利用位运算计算数的绝对值? 以 x 为负数为例来分析。因为在计算机中,数字都是以补码的形式存放的,求负数的绝对值,应该是不管符号位,执行按位取反,末位加 1 操作即可。 对于一个负数,将其右移 31 位后会变成 0xfffffff,而对于一个正数而言,右移 31 位则为 0x00000000,而 0ffffffffx+x=-1,因为 10111111=0100,任何数与 1111 异或,其实质都是把 x 的 0 和 1 进行颠倒计算。如果用变量 Y 表示 x 右移 31 位,(xy)-y 则表示的是 x 的绝对值。 程序示例如下: #includestdio.h int MyAbs(in
11、t x) int y; y=x31; return (xy)-y;此处还可以写为(x+y)y int main() printf(“%d/n“,MyAbs(2); printf(“%d/n“,MyAbs(-2); return 0; 程序输出结果: 2 2 上例中,在函数 MyAbs 中,对局部变量 y 进行赋值时,由于是对 X 进行右移 31 位,如果 x 为正数,则y=0;如果 x 为负数,则 y=-1。4.unsigned int i=3;printf(“%u/n“,i*-1)输出为多少 (分数:3.00)_正确答案:()解析:运行如下程序: #includestdio.h int ma
12、in() unsigned int i=3; printf(“%u/n“,i*-1); return 0; 程序输出结果: 4294967293 在 32 位机器中,i*-1 的值为 4294967293。在 32 位机器中,无符号 int 的值域是0,4294967295,有符号 int 的话,值域是-2147483648,2147483647,两个值域的个数都是 4294967296 个,即 0,4294967295=0,2147483647U2147483648,4294967295 有符号 int 的-2147483648,-1对应于无符号 int 的2147483648,429496
13、7295区域,两个区域的值是一一映射关系。所以,-1 对应 4294967295,-2 对应 4294967294,-3 对应 4294967293。 引申:unsigned short A=10; printf(“A=%u/n“,A);输出是什么? 因为 A 为无符号短整型变量,值为 10,在 32 位机器中,转换为二进制为 0000 0000 0000 0000 0000 0000 0000 1010,对 A 取反操作,所以A 的二进制位为 1111 1111 1111 1111 1111 1111 1111 0101,十六进制表示即为 0xFFFFFFF5,而如果将该数转换为符号整型的话
14、则为-11,因为输出的是无符号整型,无符号整型的范围为 04294967295,而 0xFFFFFFF5 转换为无符号十进制整型为 4294967285。 所以程序的输出结果为 4294967285。5.如何求解整型数的二进制表示中 1 的个数 (分数:3.00)_正确答案:()解析:求解整型数的二进制表示中 1 的个数有以下两种方法: 方法一,程序代码如下: #includestdio.h int func(int x) int countx=0; while(x) countx +; x=x return countx; int main() printf(“%d/n“,func(9999
15、); return 0; 程序输出结果: 8 在上例中,函数 func()的功能是将 x 转化为二进制数,然后计算该二进制数中含有的 1 的个数。首先以9 为例来分析,9 的二进制为 1001,8 的二进制为 1000,两者执行 while(n) count+=n n=1; return count; int main() printf(“%d/n“,func(9999); return 0; 程序输出结果: 8 需要注意的是,上例中,0xlu 表示的是十六进制的无符号数 1。6.不能用 sizeof()函数,如何判断操作系统是 16 位还是 32 位的 (分数:3.00)_正确答案:()解析
16、:如果没有强调不许使用 sizeof,一般可以使用 sizeof 计算字节长度来判断操作系统的位数,如在32 位机器上,sizeof(int)=4,而在 16 位机器上,sizeof(int)=2。除此之外,还有以下两种方法。 方法一:一般而言,机器位数不同,其表示的数字的最大值也不同,根据这一特性,可以判断操作系统的位数。 例如,运行如下代码: #include stdio.h int main() int i=65536; printf(“%d/n“,i); int j=65535; printf(“%d/n“,j); return 0; 由于 16 位机器下,无法表示这么大的数,会出现越
17、界情况,所以程序输出为 0 -1 而在 32 位机器下,则会正常输出,程序输出为 65536 65535 之所以会有区别,是因为在 16 位机器下,能够表示的最大数为 65535,所以会存在最高位溢出的情况。当变量的值为 65536 时,输出为 0;当变量的值为 65535 时,输出为-1。而在 32 位机器上,则不会出现溢出的情况,所以输出为正常输出。 方法二:对 0 值取反,不同位数下的 0 值取反,其结果不一样。例如,在 32 位机器下,按位取反运算,结果为 11111111111111111111111111111111。运行如下代码: #include stdio.h int mai
18、n() unsigned int a=0: if(a65536) printf(“32 位/n“); else printf(“16 位/n“); return 0; 程序输出为 32 位7.嵌入式编程中,什么是大端?什么是小端 (分数:3.00)_正确答案:()解析:采用小端模式的 CPU 对操作数的存放方式是从低字节到高字节,而大端模式对操作数的存放方式是从高字节到低字节。例如,16 位宽的数 0x1234 在小端模式 CPU 内存中的存放方式(假设从地址 0x4000 开始存放)见下表 1,而在大端模式 CPU 内存中的存放方式见表 2。 表 1 0x1234 在小端模式 CPU 内存中
19、的存放方式 内存地址 存放内容 0x4000 0x34 0x4001 0x12 表 2 0x1234 在大端模式 CPU 内存中的存放方式 内存地址 内存内容 0x4000 0x12 0x4001 0x34 32 位宽的数 0x12345678 在小端模式 CPU 内存中的存放方式(假设从地址 0x4000 开始存放)见表 3,而在大端模式 CPU 内存中的存放方式见表 4。 表 3 0x12345678 在小端模式 CPU 内存中的存放方式 内存地址 存放内容 0x4000 0x78 0x4001 0x56 0x4002 0x34 0x4003 0x12 表 4 0x12345678 在大端
20、模式 CPU 内存中的存放方式 内存地址 存放内容 0x4000 0x12 0x4001 0x34 0x4002 0x56 0x4003 0x78 以如下程序为例。 #includestio.h struct mybitfields unsigned short a:4; unsigned short b:5; unsigned short c:7; test; int main() int i; test.a=2; test.b=3; test.c=0; i=*(short*) printf(“%d/n“,i); return 0; 程序输出结果: 50 上例中 sizeof(test)=2
21、,上例的声明方式是把一个 short(也就是一块 16 位内存)分成 3 部分,各部分的大小分别是 4 位、5 位、7 位,赋值语句 i=*(short*) unsigned int uiVa1_2=0; unsigned char aucVal4=0x12, 0x34, 0x56, 0x78; unsigned short usVa1_1=0; unsigned short usVa1_2=0; memcpy( usVa1_1=(unsigned short)uiVa1_1;在这儿截断,都取得的是低位 usVa1_2=(unsigned short)uiVa1_2;在这儿截断 printf(
22、“usVa1_1:%x/n“,usVa1_1);这儿又转化回来 printf(“usVa1_2:%x/n“,usVa1_2);这边又转化回来 return 0; 小端模式是低地址存放低字节,高地址存放高字节,结构如图所示。 8.考虑 n 位二进制数,有多少个数中不存在两个相邻的 1 (分数:3.00)_正确答案:()解析:当 n=1 时,满足条件的二进制数为 0、1,一共两个数;当 n=2 时,满足条件的二进制数有00、01、10,一共 3 个数;当 n=3 时,满足条件的二进制数有 000、001、010、100、101,一共 5 个数。对 n 位二进制数,设所求结果为 a(n),对于第 n
23、 位的值,分为 0 或者 1 两种情况: 1)第 n 位为 0,则有 a(n-1)个数。 2)第 n 位为 1,则要满足没有两个相邻为 1 的条件,第 n-1 位为 0,有 a(n-2)个数,因此得到结论 a(n)=a(n-1)+a(n-2)。 通过观察 2)中的表达式可以发现,式子满足斐波拉契数列,而求解斐波拉契数列一般有两种方法:递归方法与非递归方法,本题只将递归的方法写出来,有兴趣的读者可以自己编写非递归的斐波拉契数列求解方法。 程序代码示例如下: #includestdio.h long Fibonacci(int i) if(i=1|i=2) return 1; else retur
24、n(Fibonacci(i-1)+Fibonacci(i-2); int main() printf(“%1d/n“,Fibonacci(7); return 0; 程序输出结果: 139.不用除法操作符如何实现两个正整数的除法 (分数:3.00)_正确答案:()解析:在回答本问题前,先学习一些有关位运算的知识。 1)常用的等式:-n=(n-1)=n+1。 2)获取整数 n 的二进制中最后一个 1:n if(b=0) printf(“除数不能为 0/n“); return result; while(a=b) result+; a=a+b; return result; int main()
25、printf(“%d/n“,div(10,3); return 0; 程序输出结果: 3 这个算法每次都以一倍的被除数进行叠加,算法效率并不高,尤其是当 a 很大,b 很小时,效率会非常低。方法二,递归法求解。以 100/3 为例,方法一提出的方法分别比较 97,94,91,.,4,1,-2,最后余数为-2,退出 while 循环,整个算法需要比较 34 次。如果每次采用将比较数翻倍的比较方法,则算法效率能够得到极大优化。 程序示例如下: #includestdio.h int MyDiv(int a, int b) int k=0; int c=b; int res=0; if(b=0) p
26、rintf(“除数不能为 0/n“); if(ab) return 0; for(;a=c;c=1,k+) if(a-cb) return 1k; return MyDiv(a-(c1),b)+(1(k-1); int main() printf(“%d/n“,MyDiv(100,3); return 0; 程序输出结果: 33 方法三:采用移位操作实现,位操作的效率一般都比较高效。程序示例如下: #includestdio.h int div(const int x,const int y) int left_num=x; int result=0; while(left_num=y) in
27、t multi=1; while(y*multi=(left_num1) multi=multi1; result +=multi; left_num-=y*multi; return result; int main() printf(“%d/n”,div(10,3); return 0; 程序输出结果: 3 引申 1:如何只用逻辑运算实现加法运算? 实现两个正整数相加,一般直接使用加号运算符即可。考虑到题目中的要求,与上例中的方法二类似,也可以通过移位操作符来进行正整数的加法运算。例如,5 与 7 求和,转换为二进制求和为 101 与 111 求和,其二进制结果为 1100。对于二进制的加
28、法而言,1+1=0,1+0=1,0+1=1,0+0=0,通过对比位运算中的异或方法,不难发现,此方法与位运算中的异或类似。那么第一个数值就是:tempNum1=num1num2;0+0 的进位是 0,1+0 的进位是 0,只有 1+1 的进位有效,该思路与位运算的 int sumTemp=num1num2; int carry=(num1 return add(sumTemp,carry); int main() printf(“%d/n“,add(100,200); return 0; 程序输出结果: 300 将递归思想转换为非递归思想之后,就成了另外一种思路,程序示例如下: #includ
29、estdio.h int add(int num1,int num2) int sum=0; int num3=0; int num4=0; while(num1 num4=num1 num1=num3; num2=num41; sum=num1num2; return sum; int main() printff(%d/n“,add(100,200); return 0; 程序输出结果: 300 引申 2:如何只用逻辑运算实现乘法运算? 先看一个实例:1011*1010,因为二进制运算的特殊性,可以将该乘法运算表达式拆分为两个运算,1011*0010 与 1011*1000 的和,而对于二
30、进制的运算,左移 1 位,等价于乘以 0010,左移 3 位,等价于乘以 1000,所以两者的乘积为 10110 与 1011000 的和,即为 1101110。 因而乘法可以通过一系列移位和加法完成。最后一个 1 可通过 b int multiply(int a,int b) bool neg=(b0); if(b0) b=-b; int sum=0; mapint,intbit_map; for(int i=0;i32;i+) bit_map.insert(pairint,int(1i,i); while(b0) int last_bit=bit_mapb sum+=(alast_bit)
31、; b if(neg) sum=-sum; return sum; int main() prinff(“%d/n“,multiply(3,5); return 0; 程序输出结果: 15 函数是程序的基本组成单位,利用函数,不仅能够实现程序的模块化,而且简单、直观,能极大地提高程序的易读性和可维护性,所以将程序中的一些计算或操作抽象成函数以供随时调用,理解函数的执行原理以及应用是一个优秀程序员应该具备的基本能力。10.怎么样写一个接受可变参数的函数 (分数:3.00)_正确答案:()解析:C 语言中支持函数调用的参数为变参形式。例如,printf()这个函数,它的函数原型是intprintf
32、(const char* fofmat,.),它除了有一个参数 format 固定以外,后面跟的参数的个数和类型都是可变的,可以有以下多种不同的调用方法: 1)printf(“%d“,i); 2)printf(“%s“,s); 3)printf(“the number is%d,string is:%s“,i,s); printf()函数是一个有着变参的库函数,在 C 语言中,程序员也可以根据实际的需求编写变参函数。如下程序示例代码,实现了一个变参函数 add2(),该函数实现多参数求和运算。 #includestdio.h int add2(char num,.) int sum=0; in
33、t index=0; int *p=NULL; p=(int*) for(;index(int)num;+index) sum+=*p+; return sum; int main() int i=1; int j=2; int k=3; printf(“%d/n“,add2(3,i,j,k); return 0; ; 程序输出结果: 611.函数指针与指针函数有什么区别 (分数:3.00)_正确答案:()解析:指针函数是指带指针的函数,本质上是一个函数,函数返回类型是某一类型的指针。其形式一般如下: 类型标识符*函数名(参数列表) 例如,int*f(x,y),它的意思是声明一个函数 f(x,
34、y),该函数返回类型为 int 型指针。 而函数指针是指向函数的指针变量,即本质是一个指针变量,表示的是一个指针,它指向的是一个函数。其形式一般如下: 类型说明符(*函数名)(参数) 例如,int(*pf)(int x),它的意思就是声明一个函数指针,而 pf=func 则是将 func 函数的首地址赋值给指针。 下面为一个函数指针的实例。 #includestdio.h #define NULL 0 #define ASGN 1 #define MUL 2 int asgn(int* a,int b) return*a=b; int mul(int* a,int b) return *a*b
35、; int(*func(int op)(int*,int) switch(op) case ASGN; return case MUL; return default: return NULL; return NULL; int main() int i=0xFEED,j=0xBEEF; printf(“%x、n“,func(ASGN)( printf(“%x/n“,func(MUL)( printf(“%x,%x/n,i,j); return 0; 程序输出结果: beef 8e67a321 beef,beef 引申:数组指针/指针数组、函数模板/模板函数、类模板,模板类、指针常量,常量指针
36、分别有什么区别? (1)数组指针/指针数组 数组指针就是指向数组的指针,它表示的是一个指针,它指向的是一个数组,它的重点是指针。例如,int(*pa)8声明了一个指针,该指针指向了一个有 8 个 int 型元素的数组。 #includestdio.h int main() int(*p)4; int a34=1,2,3,4,5,6,7,8),9,10,11,12); p= for(int i=0;i12;i+) printf(“%d“,(*p)i); printf(“/n“); return 0: 程序输出结果: 1 2 3 4 5 6 7 8 9 10 11 12 指针数组就是指针的数组,表
37、示的是一个数组,它包含的元素是指针,它的重点是数组。例如,int* ap8声明了一个数组,该数组的每一个元素都是 int 型的指针。 #includestdio.h int main() int*p 4; int a4=1,2,3,4; p0= p1= p2= p3= for(int i=0;i4;i+) printf(“%d“,*pi); printf(“/n“); return 0; 程序输出结果: 1 2 3 4 (2)函数模板/模板函数 函数模板是对一批模样相同的函数的说明描述,它不是某一个具体的函数;而模板函数则是将函数模板内的“数据类型参数”具体化后得到的重载函数(就是由模板而来的函数)。简单地说,函数模板是抽象的,而模板函数则是具体的。 函数模板减少了程序员输入代码的工作量,是 C+中功能最强的特性之一,是提高软件代码重用性的重要手段之一。函数模板的形式一般如下: template模板类型形参表 返回值类型函数名(模板函数形参表) 函数体 其中模板函数形参表的类型可以是任何类型,包括基本数据类型和类类型。需要注意的是,函数模板并不是一个实实在在的函数,它是一组函数的描述,并不能直接执行,需要实例化为模板函数后才