李洪强经典面试题20

 

50socket链接和http链接的区别c++

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

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

tcp协议:    对应于传输层算法

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

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的参数不能为实型。

            華為

            一、局部变量可否和全局变量重名?

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

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

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

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

            个循环体内

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

            答:extern

            能够用引用头文件的方式,也能够用extern关键字,若是用引用头文件方式来引用某个

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

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

            间报错

            三、全局变量可不能够定义在可被多个.C文件包含的头文件中?为何?

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

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

            初值,此时链接不会出错

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

            答:和while(1)相同。

            五、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

相关文章
相关标签/搜索