昨天上午参加了中兴通讯2011实习生招聘笔试,我参加的是软件方向的笔试,试题的难度不大,但是面广而细,考察了C、C++、数据结构、算法、软件工程、数据库、操作系统原理、嵌入式等方面的内容。题目类型有单选、多选,问答和编程题。原以为对C语言比较熟悉,但是经过一考发现疏漏不少,不得不说回归基础很重要啊。下面我根据回忆挑选了几道还有映象的试题分析一下,同时将笔试中的最后两道编程题回来后上机调试了一下一并贴出来,作为2011年第一次笔试的纪念。
选择题中根据记忆挑选了几个有代表性的题目:
1、 Switch…Case语句中的参数类型
分析:这个是很基础的问题,Switch后面括号内的表达式,ANSI标准允许它为任何类型
Case后面括号内的表达式,只能是整型或字符型的常量或常量表达式。
2. 若有说明int i,j,k;则执行i=10;j=20;k=30;k*=i+j后k的值为900。
分析:a*=b相当于a=a*b,所以k*=i+j相当于k=k*(i+j)
3. int i=1;
int j;
j=(i++)+(++i)+(i++);
运算结果为i=4,j=6
分析:虽然自增自减是C语言的基础语句,但是平时不会有人写出这么无聊混淆的语句除非它有病。无奈啊,中国的考官就是这么无聊,伤不起啊!!!下面是根据http://topic。csdn。net/u/20100329/23/2fc2d30e-3c79-4f55-ada6-cd7b3bbed3ba。html中复制过来的分析,分析得很好。
(1)++i表示,i自增1后再参与其它运算;而i++ 则是i参与运算后,i的值再自增1。
(2)i++的理解应该是执行完整个表达式的其他操作后,然后才自增,++j是先自增然后再参加其它运算。
例一:
int i=3;
int j=4;
i++;
++j;
printf("%d, %d/n", i, j);
对此,大家都不会有什么困惑,结果就是 4,5;下面我们来做一点小改动:
int i=3;
int j=4;
int a = i++;
int b = ++j;
printf("%d, %d/n", a, b);
结果又是多少呢?这里就开始体现出++前置与后置的区别了,结果是3,5。结合此例,我们回头再来理解一下“++前置:i自增1后再参与其它运算;++后置:i参与运算后,i的值再自增1”。很明显,a = i++;由于是先执行赋值运算,再自增,所以结果是a=3,i=4;而b = ++j;
则因先自增,然后再赋值,所以b,j均为5。
其实基本道理就这么简单了,但在更复杂点的情况下又会如何呢,请看:
例二:
int i=3;
int j=4;
int a = i++ + i++;
int b = ++j + ++j;
printf("%d, %d/n", a, b);
问题又来了,i++ + i++是先自增一次,相加,再自增,然后赋值呢,还是先相加赋值然后自增两次呢。另外,++j又将如何表现呢?
结果是:6,12
这下明白了,原来 i++的理解应该是执行完整个表达式的其他操作后,然后才自增,所以例子中的a=3+3=6;而后i再自增2次,i=5;相反,++j是先自增然后再参加其它运算,所以b=6+6=12。
到此,是否就彻底明了了呢?然后回到引子中的问题:
例三:
int i=3;
int j=4;
int a = i++ + i++ + i++;
int b = ++j + ++j + ++j;
printf("%d, %d/n", a, b);
有人可能会说,这很简单,我全明白了:a=3+3+3=9,i=6,b=5+5+5=15,j=5。真的是这样吗?
结果却是:9,19
这下可好,又糊涂了。对于a = i++ + i++ + i++;我们已经没有疑问了,++后置就是执行完整个表达式的其他操作后,然后才自增,上例中也得到了验证,但 b = ++j + ++j + ++j;又该如何理解呢?
原理表达式中除了预算法本身的优先级外,还有一个结合性问题。在++j + ++j + ++j;中,因为存在两个同级的+运算,根据+运算符的左结合性,在编译时,其实是先处理前面的(++j + ++j)这部分,然后再将此结果再和++j相加。
例四:
int i=1;
int j=1;
int a = i++ + i++ + i++ + i++ + i++ + i++ + i++; // 七个
int b = ++j + ++j + ++j + ++j + ++j + ++j + ++j;
printf("%d, %d/n", a, b);
printf("%d, %d/n", i, j);
规则就是规则,咱的计算机可不是黑客帝国的母体,总是要遵循它的
a = 1+1+1+1+1+1+1 = 7, i=8
b = 3+3+4+5+6+7+8 = 36, j=8
一切OK,恭喜你还生活在21世纪的地球。注:以上结果及解释出自VC编译器,但对于++这个问题是和编译器的解析有关的,不同厂家可能理解不一致,因手头没有其他开发环境,暂无法做全面分析,本文只是为了说明++,--这运算符的一些特性,尤其是前置后置的区别这个问题。类似的问题如果有困惑,最好是写程序做试验解决,请勿生搬硬套。谢谢!在实际的编程实践中,类似的问题除了要试验搞清外,应该尽量避免引入环境相关的编程技巧。
4. 关系数据库中关系的完整性
a.域完整性
域完整性是对数据表中字段属性的约束,它包括字段的值域、字段的类型及字段的有效规则等约束,它是由确定关系结构时所定义的字段的属性决定的。
b.实体完整性
实体完整性是对关系中的记录唯一性,也就是主键的约束。准确地说,实体完整性是指关系中的主属性值不能为Null且不能有相同值。
c.参照完整性
参照完整性是对关系数据库中建立关联关系的数据表间数据参照引用的约束,也就是对外键的约束。准确地说,参照完整性是指关系中的外键必须是另一个关系的主键有效值,或者是NULL。
5. GSM中UM接口(具体题目内容不记得了,此处简要分析)
分析:Um接口被定义为MS(mobile station)与BTS(Base Transceiver Station)之间的通信接口,也称为无线接口。在所有的GMS接口中,Um接口是最重要的。首先,完善的无线接口实现了各种制造商的移动台与不同运营商的网络间的兼容性,从而实现了移动台的漫游。其次,蜂窝系统的频谱效率是以关键的经济因素,它完全由无线接口上的传输决定。GSM无线传输的两个主要方面是:多址方式和信号处理。很明显,Um接口实现了MS到GSM系统固定部分的物理链接,即无线链路,同时它还传递了无线资源管理,、移动性管理和接续管理等信息。GSM系统使用类似OSI协议模型的简化协议,包括物理层(L1)、数据链路层(L2)和应用层(L3)。L1是协议模型最底层,提供物理媒介传输比特流所需的全部功能。L2保证正确传递消息及识别单个呼叫。在GSM系统中,无线接口(Um)上的L1和L2分别是TDMA帧和LAPDm协议。在网络侧,Abis接口和A接口使用的L1均为E1传输方式,L2分别为LAPD和MTP协议。在Um接口,MS每次呼叫时都有一个L1和L2层的建立过程,在此基础上再与网络侧建立L3上的通信。在网络侧(A和Abis接口),其L1和L2(SCCP除外)始终处于连接状态。L3层的通信消息按阶段和功能的不同,分为无线资源管理(RR)、移动性管理(MM)和呼叫控制(CC)三部分。
6. static变量相关(考了几个题目,具体题目不记得了,下面对static用法做个总结)
(1) 修饰变量:
静态全局变量:作用域仅限于被定义的文件中,其他文件即使使用extern声明也没法使用,准确的说:作用域是从定义之处开始,到文件结尾处结束,在定义之处前面的那些代码行也不能使用它,要想使用就得在前面再加extern。
静态局部变量:在函数体里面定义,就只能在这个函数里用了,同一个文档中的其他函数也用不了。
(2) 修饰函数:函数的作用域仅局限于本文件。
问答题:
1、 进程和线程的区别:
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
一个程序至少有一个进程,一个进程至少有一个线程。
进程有自己独立的地址空间,一个进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程程序要比多线程程序健壮,但在进程切换时耗费资源较大,效率要差一些,但对于某些要求同时进行且要共享某些变量的并发操作,不能用进程。
2、 中断服务程序的原理
保护现场->中断处理->恢复现场
3、 简述Stack Frame,并画出当函数A调用函数B时A和B各自的堆栈布局图
分析:笔试时此题知道概念描述不清楚,现在好好理一下。
大多数CPU上的程序实现使用栈来支持函数调用操作。栈被用来传递函数参数、存储返回信息、临时保存寄存器原有值以备恢复以及用来存储局部数据。单个函数调用操作所使用的栈部分被称为栈帧结构,栈帧结构的两端有两个指针来指定。寄存器ebp通常用作帧指针,而esp则用作栈指针。在函数执行过程中,栈指针esp会随着数据的入栈和出栈而移动,因此函数中对大部分数据的访问都基于帧指针ebp进行。设在函数A中调用函数B,函数B中有两个形参m,n,则栈帧结构如下图:
调用之前:
调用之后:
两道编程题
1、 编写函数实现链表反转:
Struct node
{
int data;
struct node *next;
}
typedef struct node Node;已知头结点head。
程序实现:该题的原题是微软早年的经典面试题
Node* ReverseIteratively(Node* pHead) { Node* pReversedHead = NULL; Node* pNode = pHead; Node* pPrev = NULL; while(pNode != NULL) { // get the next node, and save it at pNext Node* pNext = pNode->Next; // if the next node is null, the currect is the end of original // list, and it's the head of the reversed list if(pNext == NULL) pReversedHead = pNode; // reverse the linkage between nodes pNode->pNext = pPrev; // move forward on the the list pPrev = pNode; pNode = pNext; } return pReversedHead; }
2、 用递归法实现整型数到字符串的转换,例如:输入1234,转换后得到“1234”,输入整数的范围不超过5位。
程序实现:
#include <stdio.h> #define M 6 void intToString(char *p,int origin) { if(origin/10==0) { p[0]=origin%10+'/0'; } else { intToString(p+1,origin/10); } } int main() { char s[M]; int i; scanf("%d",&i); if(i>65535||i<-65535) return -1; intToString(&s[0],i); printf("/n%s",s); return 0; }