李洪强经典面试题20

 

50socket连接和http连接的区别

简单说,你浏览的网页(网址以http://开头)都是http协议传输到你的浏览器的, 而http是基于socket之上的。socket是一套完成tcp,udp协议的接口。

HTTP协议:简单对象访问协议,对应于应用层  ,HTTP协议是基于TCP连接的

tcp协议:    对应于传输层

ip协议:     对应于网络层 
TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。

Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。

http连接:http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉;

socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会是连接断开,比如说:服务器端或客户端主机down了,网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输,那么为了维持连接需要发送心跳消息~~具体心跳消息格式是开发者自己定义的

我们已经知道网络中的进程是通过socket来通信的,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),这些函数我们在后面进行介绍。我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。
1)Socket是一个针对TCP和UDP编程的接口,你可以借助它建立TCP连接等等。而TCP和UDP协议属于传输层 。
  而http是个应用层的协议,它实际上也建立在TCP协议之上。 

 (HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。)

 2)Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口。

51什么是TCP连接的三次握手

第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)

52利用Socket建立网络连接的步骤

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

1。服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

2。客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

3。连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

53进程与线程

进程(process)是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。

进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。

在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。

由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。

简而言之,一个程序至少有一个进程,一个进程至少有一个线程.一个程序就是一个进程,而一个程序中的多个任务则被称为线程。

 

线程只能归属于一个进程并且它只能访问该进程所拥有的资源。当操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。应用程序(application)是由一个或多个相互协作的进程组成的。

另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

54多线程

多线程编程是防止主线程堵塞,增加运行效率等等的最佳方法。而原始的多线程方法存在很多的毛病,包括线程锁死等。在Cocoa中,Apple提供了NSOperation这个类,提供了一个优秀的多线程编程方法。

本次介绍NSOperation的子集,简易方法的NSInvocationOperation:

 

一个NSOperationQueue 操作队列,就相当于一个线程管理器,而非一个线程。因为你可以设置这个线程管理器内可以并行运行的的线程数量等等

55oc语法里的@perpoerty不用写@synzhesize了,自动填充了。并且的_name;

写方法时候不用提前声明。llvm 全局方法便利。

枚举类型。enum hello:Integer{  } 冒号后面直接可以跟类型,以前是:

enum hello{} 后面在指定为Integer .

桥接。ARC 自动release retain 的时候 CFString CFArray . Core Fountion. 加上桥接_brige  才能区分CFString 和NSString 而现在自动区分了,叫固定桥接。

 

下拉刷新封装好了。

UICollectionViewController. 可以把表格分成多列。

 

Social Framework(社交集成)

UIActivityViewController来询问用户的社交行为

 

缓存:就是存放在临时文件里,比如新浪微博请求的数据,和图片,下次请求看这里有没有值。

56Singleton(单例模式),也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。 

代码如下: 

static ClassA *classA = nil;//静态的该类的实例 

+ (ClassA *)sharedManager 

{ 

@synchronized(self) { 

if (!classA) { 

classA = [[super allocWithZone:NULL]init]; 

return classA; 

} 

+ (id)allocWithZone:(NSZone *)zone { 

return [[self sharedManager] retain]; 

- (id)copyWithZone:(NSZone *)zone { 

return self; 

- (id)retain { 

return self; 

- (NSUIntger)retainCount { 

return NSUIntgerMax; 

- (oneway void)release { 

- (id)autorelease { 

return self; 

-(void)dealloc{ 

57请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1 int checkCPU( ) {   

     {           

       union w      

            {        

                     int a;      

                     char b;         

             } c;             

            c.a = 1;    

        return  (c.b ==1);      

  } 

剖析:嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节, Big-endian  模式的CPU对操作数的存放方式是从高字节到低字节。在弄清楚这个之前要弄清楚这个问题:字节从右到坐为从高到低! 假设从地址0x4000开始存放: 0x12345678,是也个32位四个字节的数据,最高字节是0x12,最低字节是0x78:在Little-endian模式CPU内存中的存放方式为: (高字节在高地址, 低字节在低地址) 

内存地址0x4000 0x4001 0x4002 0x4003 

存放内容 0x78 0x56 0x34 0x12 

大端机则相反。 

 

有的处理器系统采用了小端方式进行数据存放,如Intel的奔腾。有的处理器系统采用了大端方式进行数据存放,如IBM半导体和Freescale的PowerPC处理器。不仅对于处理器,一些外设的设计中也存在着使用大端或者小端进行数据存放的选择。因此在一个处理器系统中,有可能存在大端和小端模式同时存在的现象。这一现象为系统的软硬件设计带来了不小的麻烦,这要求系统设计工程师,必须深入理解大端和小端模式的差别。大端与小端模式的差别体现在一个处理器的寄存器,指令集,系统总线等各个层次中。   联合体union的存放顺序是所有成员都从低地址开始存放的。以上是网上的原文。让我们看看在ARM处理器上union是如何存储的呢?   地址A ---------------- |A     |A+1   |A+2   |A+3    |int a; |      |         |         |          -------------------- |A     |char b; |      | ---------                                                                            如果是小端如何存储c.a的呢?  

                                         地址A ----------- 

------------------- |A    |A+1   |A+2    |A+3 | int a; 

|0x01 |0x00   |0x00   |0x00 | ------------------------------------- |A    |char b; |     | ---------                                  

                                如果是大端如何存储c.a的呢?   

  地址A --------------------- 

--------- |A      |A+1    |A+2     |A+3     |int a; |0x00   |0x00   |0x00    |0x01    | ------------------------------------------ |A      |char b; |       | ---------                                                                                                                                                        现在知道为什么c.b==0的话是大端,c.b==1的话就是小端了吧。

58

堆和栈上的指针 

指针所指向的这块内存是在哪里分配的,在堆上称为堆上的指针,在栈上为栈上的指针. 

在堆上的指针,可以保存在全局数据结构中,供不同函数使用访问同一块内存. 

在栈上的指针,在函数退出后,该内存即不可访问. 

59什么是指针的释放? 

具体来说包括两个概念. 

1 释放该指针指向的内存,只有堆上的内存才需要我们手工释放,栈上不需要. 

2 将该指针重定向为NULL. 

60数据结构中的指针? 

其实就是指向一块内存的地址,通过指针传递,可实现复杂的内存访问. 

7 函数指针? 

指向一块函数的入口地址. 

 

8 指针作为函数的参数? 

比如指向一个复杂数据结构的指针作为函数变量 

这种方法避免整个复杂数据类型内存的压栈出栈操作,提高效率. 

注意:指针本身不可变,但指针指向的数据结构可以改变. 

 

9 指向指针的指针? 

指针指向的变量是一个指针,即具体内容为一个指针的值,是一个地址. 

此时指针指向的变量长度也是4位. 

61指针与地址的区别? 

区别: 

1指针意味着已经有一个指针变量存在,他的值是一个地址,指针变量本身也存放在一个长度为四个字节的地址当中,而地址概念本身并不代表有任何变量存在. 

2 指针的值,如果没有限制,通常是可以变化的,也可以指向另外一个地址. 

   地址表示内存空间的一个位置点,他是用来赋给指针的,地址本身是没有大小概念,指针指向变量的大小,取决于地址后面存放的变量类型. 

62指针与数组名的关系? 

  其值都是一个地址,但前者是可以移动的,后者是不可变的. 

 

12 怎样防止指针的越界使用问题? 

  必须让指针指向一个有效的内存地址, 

1 防止数组越界 

2 防止向一块内存中拷贝过多的内容 

3 防止使用空指针 

4 防止改变const修改的指针 

5 防止改变指向静态存储区的内容 

6 防止两次释放一个指针 

7 防止使用野指针. 

 

 

13 指针的类型转换? 

指针转换通常是指针类型和void * 类型之前进行强制转换,从而与期望或返回void指针的函数进行正确的交接. 

63static有什么用途?(请至少说明两种)
            1.限制变量的作用域
            2.设置变量的存储域
            7. 引用与指针有什么区别?
            1) 引用必须被初始化,指针不必。
            2) 引用初始化以后不能被改变,指针可以改变所指的对象。
            2) 不存在指向空值的引用,但是存在指向空值的指针。
            8. 描述实时系统的基本特性
            在特定时间内完成特定的任务,实时性与可靠性

64全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
            全局变量储存在静态数据库,局部变量在堆栈
            10. 什么是平衡二叉树?
            左右子树都是平衡二叉树且左右子树的深度差值的绝对值不大于1

65堆栈溢出一般是由什么原因导致的?

            没有回收垃圾资源

            12. 什么函数不能声明为虚函数?

            constructor

            13. 冒泡排序算法的时间复杂度是什么?

            O(n^2)

            14. 写出float x 与“零值”比较的if语句。

            if(x>0.000001&&x<-0.000001)

            16. Internet采用哪种网络协议?该协议的主要层次结构?

            tcp/ip 应用层/传输层/网络层/数据链路层/物理层

            17. Internet物理地址和IP地址转换采用什么协议?

            ARP (Address Resolution Protocol)(地址解析協議)

            18.IP地址的编码分为哪俩部分?

            IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区

            分哪些是网络位哪些是主机位。

            2.用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写

            出C程序。

            循环链表,用取余操作做

            3.不能做switch()的参数类型是:

            switch的参数不能为实型。

            華為

            1、局部变量能否和全局变量重名?

            答:能,局部会屏蔽全局。要用全局变量,需要使用"::"

            局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而

            不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变

            量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那

            个循环体内

            2、如何引用一个已经定义过的全局变量?

            答:extern

            可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个

            在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你

            用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期

            间报错

            3、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?

            答:可以,在不同的C文件中以static形式来声明同名全局变量。

            可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋

            初值,此时连接不会出错

            4、语句for( ;1 ;)有什么问题?它是什么意思?

            答:和while(1)相同。

            5、do……while和while……do有什么区别?

            答:前一个循环一遍再判断,后一个判断以后再循环

661.IP Phone的原理是什么?

            IPV6

            2.TCP/IP通信建立的过程怎样,端口有什么作用?

            三次握手,确定是哪个应用程序使用该协议

            3.1号信令和7号信令有什么区别,我国某前广泛使用的是那一种?

            4.列举5种以上的电话新业务?

            微软亚洲技术中心的面试题!!!

            1.进程和线程的差别。

            线程是指进程内的一个执行单元,也是进程内的可调度实体.

            与进程的区别:

            (1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

            (2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行

            (3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属

            于进程的资源.

            (4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开

            销明显大于创建或撤消线程时的开销。

            2.测试方法

            人工测试:个人复查、抽查和会审

            机器测试:黑盒测试和白盒测试

            2.Heap与stack的差别。

            Heap是堆,stack是栈。

            Stack的空间由操作系统自动分配/释放,Heap上的空间手动分配/释放。

            Stack空间有限,Heap是很大的自由存储区

            C中的malloc函数分配的内存空间即在堆上,C++中对应的是new操作符。

            程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的

            传递也在栈上进行

            3.Windows下的内存是如何管理的?

            4.介绍.Net和.Net的安全性。

            5.客户端如何访问.Net组件实现Web Service?

            6.C/C++编译器中虚表是如何完成的?

            7.谈谈COM的线程模型。然后讨论进程内/外组件的差别。

            8.谈谈IA32下的分页机制

            小页(4K)两级分页模式,大页(4M)一级

            9.给两个变量,如何找出一个带环单链表中是什么地方出现环的?

            一个递增一,一个递增二,他们指向同一个接点时就是环出现的地方

            10.在IA32中一共有多少种办法从用户态跳到内核态?

            通过调用门,从ring3到ring0,中断从ring3到ring0,进入vm86等等

            11.如果只想让程序有一个实例运行,不能运行两个。像winamp一样,只能开一个窗

            口,怎样实现?

            用内存映射或全局原子(互斥变量)、查找窗口句柄..

            FindWindow,互斥,写标志到文件或注册表,共享内存。

67如何截取键盘的响应,让所有的‘a’变成‘b’?
            键盘钩子SetWindowsHookEx
            13.Apartment在COM中有什么用?为什么要引入?
            14.存储过程是什么?有什么用?有什么优点?
            我的理解就是一堆sql的集合,可以建立非常复杂的查询,编译运行,所以运行一次后,
            以后再运行速度比单独执行SQL快很多
            15.Template有什么特点?什么时候用?
            16.谈谈Windows DNA结构的特点和优点。
            网络编程中设计并发服务器,使用多进程与多线程,请问有什么区别?
            1,进程:子进程是父进程的复制品。子进程获得父进程数据空间、堆和栈的复制品。
            2,线程:相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进程的其
            他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
            两者都可以提高程序的并发度,提高程序运行效率和响应时间。
            线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进程
            正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
            思科

682.找错题

 

  试题1:

 

void test1()

{

 char string[10];

 char* str1 = "0123456789";

 strcpy( string, str1 );

}

  试题2:

 

void test2()

{

 char string[10], str1[10];

 int i;

 for(i=0; i<10; i++)

 {

  str1 = 'a';
 }
 strcpy( string, str1 );

}

  试题3:

 

void test3(char* str1)

{

 char string[10];

 if( strlen( str1 ) <= 10 )

 {

  strcpy( string, str1 );

 }

}

  解答:

 

  试题1字符串str1需要11个字节才能存放下(包括末尾的’\0’),而string只有10个字节的空间,strcpy会导致数组越界;

 

  对试题2,如果面试者指出字符数组str1不能在数组内结束可以给3分;如果面试者指出strcpy(string, str1)调用使得从str1[url=]内存[/url]起复制到string内存起所复制的字节数具有不确定性可以给7分,在此基础上指出库函数strcpy工作方式的给10分;

 

  对试题3,if(strlen(str1) <= 10)应改为if(strlen(str1) < 10),因为strlen的结果未统计’\0’所占用的1个字节。

 

  剖析:

 

  考查对基本功的掌握:

 

  (1)字符串以’\0’结尾;

 

  (2)对数组越界把握的敏感度;

 

  (3)库函数strcpy的工作方式,如果编写一个标准strcpy函数的总分值为10,下面给出几个不同得分的答案:

 

  2分

 

void strcpy( char *strDest, char *strSrc )

{

  while( (*strDest++ = * strSrc++) != ‘\0’ );

}

  4分

 

void strcpy( char *strDest, const char *strSrc )

//将源字符串加const,表明其为输入参数,加2分

{

  while( (*strDest++ = * strSrc++) != ‘\0’ );

}

  7分

 

void strcpy(char *strDest, const char *strSrc)

{

 //对源地址和目的地址加非0断言,加3分

 assert( (strDest != NULL) && (strSrc != NULL) );

 while( (*strDest++ = * strSrc++) != ‘\0’ );

}

  10分

 

//为了实现链式操作,将目的地址返回,加3分!

 

char * strcpy( char *strDest, const char *strSrc )

{

 assert( (strDest != NULL) && (strSrc != NULL) );

 char *address = strDest;

 while( (*strDest++ = * strSrc++) != ‘\0’ );

  return address;

}

  从2分到10分的几个答案我们可以清楚的看到,小小的strcpy竟然暗藏着这么多玄机,真不是盖的!需要多么扎实的基本功才能写一个完美的strcpy啊!

 

  (4)对strlen的掌握,它没有包括字符串末尾的'\0'。

 

  读者看了不同分值的strcpy版本,应该也可以写出一个10分的strlen函数了,完美的版本为: int strlen( const char *str ) //输入参数const

 

{

 assert( strt != NULL ); //断言字符串地址非0

 int len;

 while( (*str++) != '\0' )

 {

  len++;

 }

 return len;

}

  试题4:

 

void GetMemory( char *p )

{

 p = (char *) malloc( 100 );

}

 

void Test( void )

{

 char *str = NULL;

 GetMemory( str );

 strcpy( str, "hello world" );

 printf( str );

}

  试题5:

 

char *GetMemory( void )

{

 char p[] = "hello world";

 return p;

}

 

void Test( void )

{

 char *str = NULL;

 str = GetMemory();

 printf( str );

}

  试题6:

 

void GetMemory( char **p, int num )

{

 *p = (char *) malloc( num );

}

 

void Test( void )

{

 char *str = NULL;

 GetMemory( &str, 100 );

 strcpy( str, "hello" );

 printf( str );

}

  试题7:

 

void Test( void )

{

 char *str = (char *) malloc( 100 );

 strcpy( str, "hello" );

 free( str );

 ... //省略的其它语句

}

  解答:

 

  试题4传入中GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完

 

char *str = NULL;

GetMemory( str );

  后的str仍然为NULL;

 

  试题5中

 

char p[] = "hello world";

return p;

  的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。

 

  试题6的GetMemory避免了试题4的问题,传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句

 

*p = (char *) malloc( num );

  后未判断内存是否申请成功,应加上:

 

if ( *p == NULL )

{

 ...//进行申请内存失败处理

}

  试题7存在与试题6同样的问题,在执行

 

char *str = (char *) malloc(100);

  后未进行内存是否申请成功的判断;另外,在free(str)后未置str为空,导致可能变成一个“野”指针,应加上:

 

str = NULL;

  试题6的Test函数中也未对malloc的内存进行释放。

 

  剖析:

 

  试题4~7考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正确的回答其中50~60的错误。但是要完全解答正确,却也绝非易事。

 

  对内存操作的考查主要集中在:

 

  (1)指针的理解;

 

  (2)变量的生存期及作用范围;

 

  (3)良好的动态内存申请和释放习惯。

 

  再看看下面的一段程序有什么错误:

 

swap( int* p1,int* p2 )

{

 int *p;

 *p = *p1;

 *p1 = *p2;

 *p2 = *p;

}

  在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序运行的崩溃。在VC++中DEBUG运行时提示错误“Access Violation”。该程序应该改为:

 

swap( int* p1,int* p2 )

{

 int p;

 p = *p1;

 *p1 = *p2;

 *p2 = p;

}[img=12,12]file:///D:/鱼鱼软件/鱼鱼多媒体日记本/temp/{56068A28-3D3B-4D8B-9F82-AC1C3E9B128C}_arc_d[1].gif[/img] 3.内功题

  试题1:分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)

 

  解答:

 

   BOOL型变量:if(!var)

 

   int型变量: if(var==0)

 

   float型变量:

 

   const float EPSINON = 0.00001;

 

   if ((x >= - EPSINON) && (x <= EPSINON)

 

   指针变量:  if(var==NULL)

 

  剖析:

 

  考查对0值判断的“内功”,BOOL型变量的0判断完全可以写成if(var==0),而int型变量也可以写成if(!var),指针变量的判断也可以写成if(!var),上述写法虽然程序都能正确运行,但是未能清晰地表达程序的意思。

 一般的,如果想让if判断一个变量的“真”、“假”,应直接使用if(var)、if(!var),表明其为“逻辑”判断;如果用if判断一个数值型变量(short、int、long等),应该用if(var==0),表明是与0进行“数值”上的比较;而判断指针则适宜用if(var==NULL),这是一种很好的编程习惯。

 

  浮点型变量并不精确,所以不可将float变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。如果写成if (x == 0.0),则判为错,得0分。

 

  试题2:以下为Windows NT下的32位C++程序,请计算sizeof的值

 

void Func ( char str[100] )

{

 sizeof( str ) = ?

}

 

void *p = malloc( 100 );

sizeof ( p ) = ?

  解答:

 

sizeof( str ) = 4

sizeof ( p ) = 4

  剖析:

 

  Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

 

  数组名的本质如下:

 

  (1)数组名指代一种数据结构,这种数据结构就是数组;

 

  例如:

 

char str[10];

cout << sizeof(str) << endl;

  输出结果为10,str指代数据结构char[10]。

 

  (2)数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;

 

char str[10];

str++; //编译出错,提示str不是左值 

  (3)数组名作为函数形参时,沦为普通指针。

 

  Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str ) 、sizeof ( p ) 都为4。

 

  试题3:写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?

 

least = MIN(*p++, b);

  解答:

 

#define MIN(A,B) ((A) <= (B) ? (A) : (B))

  MIN(*p++, b)会产生宏的副作用

 

  剖析:

 

  这个面试题主要考查面试者对宏定义的使用,宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对“参数”进行的是一对一的替换。

 

  程序员对宏定义的使用要非常小心,特别要注意两个问题:

 

  (1)谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格地讲,下述解答:

 

#define MIN(A,B) (A) <= (B) ? (A) : (B)

#define MIN(A,B) (A <= B ? A : B )

  都应判0分;

 

  (2)防止宏的副作用。

 

  宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是:

 

((*p++) <= (b) ? (*p++) : (*p++))

 

  这个表达式会产生副作用,指针p会作三次++自增操作。

 

  除此之外,另一个应该判0分的解答是:

 

#define MIN(A,B) ((A) <= (B) ? (A) : (B));

  这个解答在宏定义的后面加“;”,显示编写者对宏的概念模糊不清,只能被无情地判0分并被面试官淘汰。

 

  试题4:为什么标准头文件都有类似以下的结构?

 

#ifndef __INCvxWorksh

#define __INCvxWorksh

#ifdef __cplusplus

 

extern "C" {

#endif

/*...*/

#ifdef __cplusplus

}

 

#endif

#endif /* __INCvxWorksh */

  解答:

 

  头文件中的编译宏

 

#ifndef __INCvxWorksh

#define __INCvxWorksh

#endif

  的作用是防止被重复引用。

 

  作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:

 

void foo(int x, int y);

  该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是考这种机制来实现函数重载的。

 

  为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。[img=12,12]file:///D:/鱼鱼软件/鱼鱼多媒体日记本/temp/{C74A38C4-432E-4799-B54D-73E2CD3C5206}_arc_d[1].gif[/img]

试题5:编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是“abcdefghi”如果n=2,移位后应该是“hiabcdefgh”

 

  函数头是这样的:

 

//pStr是指向以'\0'结尾的字符串的指针

//steps是要求移动的n

 

void LoopMove ( char * pStr, int steps )

{

 //请填充...

}

  解答:

 

  正确解答1:

 

void LoopMove ( char *pStr, int steps )

{

 int n = strlen( pStr ) - steps;

 char tmp[MAX_LEN];

 strcpy ( tmp, pStr + n );

 strcpy ( tmp + steps, pStr);

 *( tmp + strlen ( pStr ) ) = '\0';

 strcpy( pStr, tmp );

}

  正确解答2:

 

void LoopMove ( char *pStr, int steps )

{

 int n = strlen( pStr ) - steps;

 char tmp[MAX_LEN];

 memcpy( tmp, pStr + n, steps );

 memcpy(pStr + steps, pStr, n );

 memcpy(pStr, tmp, steps );

}

  剖析:

 

  这个试题主要考查面试者对标准库函数的熟练程度,在需要的时候引用库函数可以很大程度上简化程序编写的工作量。

 

  最频繁被使用的库函数包括:

 

  (1) strcpy

 

  (2) memcpy

 

  (3) memset

 

posted @ 2016-07-06 09:18  李洪强  阅读(346)  评论(0编辑  收藏  举报