购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第5章
程序设计基本概念

为一个求职者或是应届毕业生,公司除了对你的项目经验有所问询之外,最好的考量办法就是你的基本功,包括你的编程风格,你对赋值语句、递增语句、类型转换、数据交换等程序设计基本概念的理解。当然,在考试之前你最好对你所掌握的程序概念知识有所复习,尤其是各种细致的考点要加以重视。以下的考题来自真实的笔试资料,希望读者先不要看答案,自己解答后再与答案加以比对,找出自己的不足。

5.1 赋值语句

面试例题1 :Which of the following statements describe the results of executing the code snippet below in C++?(下列C++代码的输出结果是什么?)[台湾某著名杀毒软件公司2010年7月笔试题]

A.The i within main will have an undefined value.(main()里的i是一个未定义值)

B.The i within main will have a value of 1.(main()里的i值为1)

C.The compiler will not allow this statement.(编译器不允许这种写法)

D.The i within main will have a value of 0.(main()里的i值为0)

解析 :当面试者看到int i=i;时,也许第一反应就是怎么有这么诡异的代码?但是在C++中这样做是完全合法的(但显然不合理)。int i=i,i变量从声明的那一刻开始就是可见的了,main()里的i 不是1,因为它和main()外的i无关,而是一个未定义值。

答案 :A

面试例题2: What does the following program print?(下面程序的结果是多少?)[中国台湾某著名计算机硬件公司2005年12月面试题]

解析:

x*=(y=z=5)的意思是说5赋值给z,z再赋值给y,x=x*y,所以x为2*5=10。

x==(y=z)的意思是说z赋值给y,然后看x和y相等否?不管相等不相等,x并未发生变化,仍然是10。

x=(y==z)的意思是说首先看y和z相等否,相等则返回一个布尔值1,不等则返回一个布尔值0。现在y和z是相等的,都是3,所以返回的布尔值是1,再把1赋值给x,所以x是1。

x=(y&z)的意思是说首先使y和z按位与。y是3,z也是3。y的二进制数位是0011,z的二进制数位也是0011。按位与的结果如下表所示。

所以y&z的二进制数位仍然是0011,也就是还是3。再赋值给x,所以x为3。

x=(y&&z)的意思是说首先使y和z进行与运算。与运算是指如果y为真,z为真,则(y&&z)为真,返回一个布尔值1。这时y、z都是3,所以为真,返回1,所以x为1。

x=(y|z)的意思是说首先使y和z按位或。y是4,z是3。y的二进制数位是0100,z的二进制数位是0011。与的结果如下表所示。

所以y&z的二进制数位是0111,也就是7。再赋值给x,所以x为7。

x=(y||z)的意思是说首先使y和z进行或运算。或运算是指如果y和z中有一个为真,则(y||z)为真,返回一个布尔值1。这时y、z都是真,所以为真,返回1。所以x为1。

答案: 10,10,1,3,1,7。

面试例题3 :以下代码结果是多少?[中国某杀毒软件公司2010年3月笔试题]

A.8 B.9 C.10 D.11

解析 :本题func函数返回值是形参x转化成二进制后包含1的数量。理解这一点就很容易答出来了。9999转化为二进制是:

9999:10011100001111

答案 :A

5.2 i++

面试例题1: 下面两段代码的输出结果有什么不同?[中国著名网络企业XL公司2007年10月面试题]

第1段:

第2段:

解析 :这两段代码的不同点就在for循环那里,前者是for(a=0,x=0;a<=1&&!x++;a++),后者是for(a=0,x=0;a<=1&&!x++;)。

先说第1段代码。

第1步:初始化定义a=0,x=0。

第2步:a小于等于1,x的非为1,符合循环条件。

第3步:x++后x自增为1。

第4步:进入循环体,a++,a自增为1。

第5步:执行for(a=0,x=0;a<=1&&!x++;a++)中的a++,a自增为2。

第6步:a现在是2,已经不符合小于等于1的条件了,所以“&&”后面的“!x++”不执行,x还是1,不执行循环体。

第7步:打印a和b,分别是2和1。

再说第2段代码。

第1步:初始化定义a=0,x=0。

第2步:a小于等于1,x的非为1,符合循环条件。

第3步:x++后x自增为1。

第4步:进入循环体,a++,a自增为1。

第5步:a现在是1,符合小于等于1的条件,所以“&&”后面的“!x++”被执行,x现在是1,x的非为0,不符合循环条件,不执行循环体,但x++依然执行,自增为2。

第6步:打印a和b,分别是1和2。

答案 :第一段输出结果是21,第二段输出结果是12。

面试例题2: What will be the output of the following C code?(以下代码的输出结果是什么?)[中国著名通信企业H公司2007年7月面试题]

A.8 8 B.130 8 C.7 7 D.7 8

解析 :C中printf计算参数时是从右到左压栈的。

几个输出结果分别如下:

printf("%d\n ",*ptr);此时ptr应指向第一个元素6。

*(ptr++)+=123应为*ptr=*ptr+123;ptr++,此时ptr应指向第二个元素7。

printf("%d\n ",*(ptr-1));此时输出第一个元素129,注意此时是经过计算的。

printf("%d\n ",*ptr);此时输出第二个元素7,此时ptr还是指向第二个元素7。

printf("%d,%d\n ",*ptr,*(++ptr));从右到左运算,第一个是(++ptr),也就是ptr++,*ptr=8,此时ptr指向第三个元素8,所以全部为8。

答案 :A

5.3 编程风格

面试例题: We have two pieces of code,which one do you prefer,and tell why.(下面两段程序有两种写法,你青睐哪种,为什么?)[美国某著名计算机嵌入式公司2005年10月面试题]

A.

写法1:

写法2:

B.

写法1:

写法2:

答案:

A.第一种写法'A'==a 比较好一些。这时如果把“==”误写做“=”的话,因为编译器不允许对常量赋值,就可以检查到错误。

B.第二种写法好一些,将部分加法运算放到了循环体外,提高了效率。缺点是程序不够简洁。

5.4 类型转换

面试例题1: 下面程序的结果是多少?[中国著名通信企业S公司2007年8月面试题]

解析 :在机器上运行一下,可以得到结果,“cout<<(int&)a<<endl;”输出的是1065353216,而不是1。这是因为浮点数在内存里和整数的存储方式不同,(int&)a相当于将该浮点数地址开始的sizeof(int)个字节当成int型的数据输出,因此这取决于float型数据在内存中的存储方式,而不是经过(int&)a显示转换的结果(1)。

因为float a=1.0f 在内存中的表示都是3f800000,而浮点数和一般整型不一样,所以当(int&)a强制转换时,会把内存值3f8000000当做int型输出,所以结果自然变为了1065353216(0x3f800000的十进制表示)。

答案 :false true或者0 1。

面试例题2: 下面程序的结果是多少?[中国著名通信企业S公司2007年8月面试题]

解析 :unsigned int变量赋值给unsigned char变量时会发生字节截断(3位和高于3位的将被程序自动丢弃)。

那么第二个数,也就是char*b=(char*)&a中a本身为一个uint类型的值,把它的地址赋给一个执行char类型数据的指针。char类型的长度只有一个字节,打印char类型的指针指向的值会是多少?

&a的结果是一个指针,它的类型取决于a的类型,此处&a的类型应该是:

上面等价于:

上面的步骤就是将一个unsigned int 型的指针强制转换成一个char型的指针。

所以请注意:这里是char类型的指针转换,而不是char类型的转换。

这样转换后,假设a的地址是x:

影响的是指针的寻址。

答案 :000000f7,fffffff7。

扩展知识

C++定义了一组内置类型对象之间的标准转换,在必要时它们被编译器隐式地应用到对象上。

隐式类型转换发生在下列这些典型情况下。

1.在混合类型的算术表达式中

在这种情况下最宽的数据类型成为目标转换类型,这也被称为算术转换(Arithmetic Conversion),例如:

2.用一种类型的表达式赋值给另一种类型的对象

在这种情况下目标转换类型是被赋值对象的类型。例如在下面第一个赋值中文字常量0的类型是int。它被转换成int*型的指针表示空地址。在第二个赋值中double型的值被截取成int型的值。

3.把一个表达式传递给一个函数,调用表达式的类型与形式参数的类型不相同

在这种情况下目标转换类型是形式参数的类型。例如:

4.从一个函数返回一个表达式的类型与返回类型不相同

在这种情况下返回的表达式类型自动转换成函数类型。例如:

算术转换保证了二元操作符,如加法或乘法的两个操作数被提升为共同的类型,然后再用它表示结果的类型。两个通用的指导原则如下:

(1)为防止精度损失,如果必要的话,类型总是被提升为较宽的类型。

(2)所有含有小于整型的有序类型的算术表达式在计算之前其类型都会被转换成整型。

规则的定义如上面所述,这些规则定义了一个类型转换层次结构。我们从最宽的类型long double开始。

如果一个操作数的类型是long double,那么另一个操作数无论是什么类型都将被转换成long doubless。例如在下面的表达式中,字符常量小写字母a将被提升为long double,它的ASC码值为97,然后再被加到long double型的文字常量上:

如果两个操作数都不是long double型,那么若其中一个操作数的类型是double 型,则另一个就将被转换成double 型。例如:

类似地,如果两个操作数都不是double 型而其中一个操作数是float型,则另一个被转换成float 型。例如:

否则如果两个操作数都不是3种浮点类型之一,它们一定是某种整值类型。在确定共同的目标提升类型之前,编译器将在所有小于int的整值类型上施加一个被称为整值提升(integral promotion)的过程。

在进行整值提升时类型char、signed char、unsigned char和short int都被提升为类型int。如果机器上的类型空间足够表示所有unsigned short型的值,这通常发生在short用半个字而int用一个字表示的情况下,则unsigned short int也被转换成int,否则它会被提升为unsigned int。wchar_t和枚举类型被提升为能够表示其底层类型(underlying type)所有值的最小整数类型。例如已知如下枚举类型:

相关联的值是0和1。这两个值可以但不是必须存放在char类型的表示中。当这些值实际上被作为char类型来存储时,char代表了枚举的底层类型,然后status 的整值提升将它的底层类型转换为int。

在下列表达式中:

在确定两个操作数被提升的公共类型之前,cval found和mval 都被提升为int类型。

一旦整值提升执行完毕,类型比较就又一次开始。如果一个操作数是unsigned long型,则第二个也被转换成unsigned long型。在上面的例子中所有被加到ulong上的3个对象都被提升为unsigned long型。如果两个操作数的类型都不是unsigned long 而其中一个操作数是long型,则另一个也被转换成long型。例如:

long类型的一般转换有一个例外。如果一个操作数是long型而另一个是unsigned int型,那么只有机器上的long型的长度足以存放unsigned int的所有值时(一般来说,在32位操作系统中long型和int型都用一个字长表示,所以不满足这里的假设条件),unsigned int才会被转换为long型,否则两个操作数都被提升为unsigned long型。若两个操作数都不是long型而其中一个是unsigned int型,则另一个也被转换成unsigned int型,否则两个操作数一定都是int型。

尽管算术转换的这些规则带给你的困惑可能多于启发,但是一般的思想是尽可能地保留多类型表达式中涉及的值的精度。这正是通过把不同的类型提升到当前出现的最宽的类型来实现的。

5.5 运算符问题

面试例题1: 下面程序的结果是多少?[中国台湾某著名CPU生产公司2010年7月面试题]

A.245 B.246 C.250 D.2

解析 :这道题目考查两个知识点:一是类型转换问题;二是算符的优先级问题。

对于第一个问题:unsigned char b=~a>>4,在计算这个表达式的时候,编译器会先把a和4的值转换为int类型(即所谓整数提升)后再进行计算,当计算结果出来后,再把结果转换成unsigned char赋值给b。

对于第二个问题:因为“~”的优先级高于“>>”和“+”,本题的过程是这样的:先对于1010 0101 取反 0101 1010;再右移,这里有一个问题,是先右移4位再加1呢,还是直接右移5(4+1)位。因为“+”的优先级高于“>>”,所以直接右移5位。结果是0000 0010。

最后的结果应该是2才对,但把如上的指令放到vs2008中运行,答案居然是250。

那么到底是什么地方出了问题?在调试的过程中进入汇编指令。可以看到高级语句转换为汇编语言以后,是先执行取反再位移的。我们看到eax是16位的寄存器,于是在机器中0xA5的寄存中表达是 0000 0000 1010 0101,取反是1111 1111 0101 1010,那么右移5位是0000 0111 1111 1010,由于是unsigned char型的只能表示低8位的数值,即250。

答案 :C

扩展知识

运算符优先级如下表所示。

面试例题2: 用一个表达式,判断一个数X是否是2 N 次方(2,4,8,16,…),不可用循环语句。[中国台湾某著名CPU生产公司2007年10月面试题]

解析: 2、4、8、16这样的数转化成二进制是10、100、1000、10000。如果X减1后与X做与运算,答案若是0,则X是2 N 次方。

答案 :!(X&(X-1))

面试例题3 :下面代码:

(729,271)=______

解析 :这道题如果使用笨办法来求解,就都转化成二进制然后按位与。但这样的做法显然不是面试官所期待的。仔细观察一下题目,x&y是取相同的位与,这个的结果是x和y相同位的一半,x^y是取x和y的不同位,右移相当于除以2,所以这个函数的功能是取两个数的平均值。(729+271)/2=500。

答案 :500

5.6 a、b交换

面试例题1: There are two int variables:a and b,don’t use“if”,“?;”,“switch”or other judgement statements,find out the biggest one of the two numbers.(有两个变量a和b,不用“if”、“?:”、“switch”或其他判断语句,找出两个数中间比较大的。)[美国某著名网络开发公司2005年面试题]

答案:

方案一:

方案二:

面试例题2 :如何将a、b的值进行交换,并且不使用任何中间变量?

解析:

简而言之,用异或语句比较容易,不用担心超界的问题。

如果采用:

这样做的缺点就是如果a、b都是比较大的两个数,a=a+b时就会超界。

而采用:

无须担心超界的问题,这样就比较好。

这样做的原理是按位异或运算。按位异或运算符“∧”是双目运算符,其功能是参与运算的两数各对应的二进制位相异或,当对应的二进制位相异时,结果为1。参与运算数仍以补码形式出现。例如9∧5可写成如下算式:

答案:

5.7 C和C++的关系

面试例题1: 在C++程序中调用被C编译器编译后的函数,为什么要加extern″C″?

答案 :C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为void foo(int x,int y)。该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。

C++提供了C连接交换指定符号extern″C″解决名字匹配问题。 面试例题2: 头文件中的ifndef/define/endif是干什么用的?

答案 :防止该头文件被重复引用。

面试例题3: 评价一下C与C++的各自特点。如果一个程序既需要大量运算,又要有一个好的用户界面,还需要与其他软件大量交流,应该怎样选择合适的语言?

答案 :C是一种结构化语言,重点在于算法和数据结构。C程序的设计首先考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制)。而对于C++,首先考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。

对于大规模数值运算,C/C++和Java/.NET之间没有明显的性能差异。不过,如果运算设计向量计算、矩阵运算,可以使用FORTRAN或者MATLAB编写计算组件(如COM)。

大规模用户界面相关的软件可以考虑使用.NET进行开发(Windows环境下),而且.NET同COM之间的互操作十分容易,同时.NET对数据库访问的支持也相当好。

5.8 程序设计的其他问题

面试例题 :编写一个函数,实现把C/C++程序代码中的注释去掉,并把结果返回。[日本著名软件企业H公司2007年11月面试题]

解析

解答本题的基本步骤如下:

一次读取一行,分两种情况,因为有两种注释:

(1)在读取到的一行中查找“//”,如果找到,则把“//”及其后的部分扔掉。

(2)在读取到的一行中查找“/*”,记录位置pos1,然后再在这行中查找“*/”,如果找到,也记录位置pos2,扔掉它们与其中的内容;以pos2开始,继续查找“/*”,如果在当前行中没有找到,则去掉当前行中“/*”及其后的内容,读取新的一行,查找“*/”,如没有查找,去掉读取到的这一行,再读一行,查找“*/”,如找到,记录位置pos2,去掉这一行的0到pos2之间的字符。

(3)进行步骤1、步骤2,直到程序结束。

编程时要考虑的特殊情况如下:

●“”中的“//”和“/*”

●‘’中的“//”和“/*”

●“//”与“/*”的嵌套关系,比如:

///*

/*//*/……

答案:

可以编译通过的程序如下所示: VRoUgmM2jpMHEUXkAIyq5iZ7K339t00gfB+5R8Ug/cY95ac9DR8msnyMP6Xtg+rS

点击中间区域
呼出菜单
上一章
目录
下一章
×