面试题集锦 - 知识点总结

收集一些面试题,针对那些理解模糊的知识点,以便温故知新!

(1)9月15日,中兴面试
小端系统
  1. union{  
  2.         int i;  
  3.         unsigned char ch[2];  
  4. }Student;  
  5.   
  6.   
  7. int main()  
  8. {  
  9.         Student  student;  
  10.         student.i=0x1420;  
  11.         printf("%d  %d",student.ch[0],student.ch[1]);  
  12.     return 0;  
  13. }  
输出结果为?(答案:32 20)
注:联合体union中的变量共享一个内存,按照低字节先存储的顺序,i的值存储顺序应该是
20
14,所以结果是32和20

(2) 问:最后程序输出是多少? 点评: 此题有陷阱,答题需谨慎!

[cpp]  view plain copy
  1. void fun()    
  2. {    
  3.     unsigned int a = 2013;    
  4.     int b = -2;    
  5.     int c = 0;    
  6.     while (a + b > 0)    
  7.     {    
  8.         a = a + b;    
  9.         c++;    
  10.     }    
  11.     printf("%d", c);    
  12. }    
解答:无限循环,因为a是无符号类型,a+b的结果也是无符号类型,又因为a+b不可能等于0,所以a+b的结果永远大于0。

测试代码:

[cpp]  view plain copy
  1. int main(int argc,char *argv[])  
  2. {  
  3.     unsigned int a=1;  
  4.     a-=2;  
  5.     printf("a:%d,a>0:%d\n",a,a>0);      
  6.     return 0;  
  7. }  
输出结果:

a:-1,a>0:1

【科普:C运算转换规则】

自动转换遵循以下规则:

1)若参与运算量的类型不同,则先转换成同一类型,然后进行运算。

2)转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。

a.若两种类型的字节数不同,转换成字节数高的类型

b.若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型

3)所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double,再作运算。

4) char型和short型参与运算时,必须先转换成int型。

5)在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度左边长时,将丢失一部分数据,这样会降低精度,丢失的部分按四舍五入向前舍入。

隐式转换

隐式类型转换分三种,即算术转换、赋值转换和输出转换。

1.算术转换

进行算术运算(加、减、乘、除、取余以及符号运算)时,不同类型数招必须转换成同一类型的数据才能运算,算术转换原则为:

在进行运算时,以表达式中最长类型为主,将其他类型位据均转换成该类型,如:

(1)若运算数中有double型或float型,则其他类型数据均转换成double类型进行运算。

(2)若运算数中最长的类型为long型.则其他类型数均转换成long型数。

(3)若运算数中最长类型为int型,则char型也转换成int型进行运算。算术转换是在运算过程中自动完成的。

2.赋值转换

进行赋值操作时,赋值运算符右边的数据类型必须转换成赋值号左边的类型,若右边的数据类型的长度大于左边,则要进行截断或舍入操作。

下面用一实例说明:

char ch;

inti,result;

floatf;

doubled;

result=ch/i+(f*d-i);

(1)首先计算ch/i,chint型,ch/iint型。

(2)接着计算f*d-i,由于最长型为double型,故fdouble型,idouble型,f*d-idouble型。

(3)(ch/i)(f*d-i)进行加运算,由于f*d-idouble型,故ch/idouble型,ch/i+(f*d-i)double型。

(4)由于resultint型,故ch/i+(f*d-i)doubleint,即进行截断与舍入,最后取值为整型。

3.输出转换

在程序中将数据用printf函数以指定格式输出时,当要输出的盐据类型与输出格式不符时,便自动进行类型转换,如一个long型数据用整型格式(%d)输出时,则相当于将long型转换成整型(int)数据输出;一个字符(char)型数据用整型格式输出时,相当于将char型转换成int型输出。

注意:较长型数据转换成短型数据输出时,其值不能超出短型数据允许的值范围,否则转换时将出错。如:

long a=80000;

printf("%d",a);

运行结果为14464,因为int型允许的最大值为3276780000超出此值,故结果取以32768为模的余数,即进行如下取余运算:

(80000-32768)-32768=14464;

输出的数据类型与输出格式不符时常常发生错误,如:

int d=9;

printf("%f",d);

float c=3.2;

printf("%d",c);

将产生错误的结果。

同一句语句或表达式如果使用了多种类型的变量和常量(类型混用),C会自动把它们转换成同一种类型。以下是自动类型转换的基本规则:

1.在表达式中,charshort类型的值,无论有符号还是无符号,都会自动转换成int或者unsignedint(如果short的大小和int一样,unsigned short的表示范围就大于int,在这种情况下,unsigned short被转换成unsigned int)。因为它们被转换成表示范围更大的类型,故而把这种转换称为“升级(promotion)”。

2.按照从高到低的顺序给各种数据类型分等级,依次为:long double, double, float, unsigned long long, long long, unsigned long,long, unsigned int int。这里有一个小小的例外,如果longint大小相同,则unsigned int的等级应位于long之上。charshort并没有出现于这个等级列表,是因为它们应该已经被升级成了int或者unsigned int

3.在任何涉及两种数据类型的操作中,它们之间等级较低的类型会被转换成等级较高的类型。

4.在赋值语句中,=右边的值在赋予=左边的变量之前,首先要将右边的值的数据类型转换成左边变量的类型。也就是说,左边变量是什么数据类型,右边的值就要转换成什么数据类型的值。这个过程可能导致右边的值的类型升级,也可能导致其类型降级(demotion)。所谓“降级”,是指等级较高的类型被转换成等级较低的类型。

5.作为参数传递给函数时,charshort会被转换成intfloat会被转换成double。使用函数原型可以避免这种自动升级。


(3)动态链接库与静态链接库的区别

动态链接英文为DLL,是Dynamic Link Library 的缩写形式,DLL是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个DLL 副本的内容。DLL 是一个包含可由多个程序同时使用的代码和数据的库。

静态链接库就是你使用的.lib文件,库中的代码最后需要连接到你的可执行文件中去,所以静态连接的可执行文件一般比较大一些。

通用:格式如:#pragma comment(lib,"XXX.lib")
1、如果使用VC,可以在Project Setting-->Link中加入你的静态库,也可以直接把该.lib文件加入到你的工程中
2、如果使用Visual Studio,位置在 项目→配置属性→连接器→输入→附加依赖项 中加入.lib文件
静态链接库不同于动态链接库(*.dll),在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为*.LIB),Visual C++的编译器在链接过程中将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为"静态链接",此时因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行

 静态链接库是.lib格式的文件,一般在工程的设置界面加入工程中,程序编译时会把lib文件的代码加入你的程序中因此会增加代码大小,你的程序一运行lib代码强制被装入你程序的运行空间,不能手动移除lib代码。
 动态链接库是程序运行时动态装入内存的模块,格式*.dll,在程序运行时可以随意加载和移除,节省内存空间。在大型的软件项目中一般要实现很多功能,如果把所有单独的功能写成一个个lib文件的话,程序运行的时候要占用很大的内存空间,导致运行缓慢;但是如果将功能写成dll文件,就可以在用到该功能的时候调用功能对应的dll文件,不用这个功能时将dll文件移除内存,这样可以节省内存空间
静态库
     静态是指每个用到该库的应用程序都拥有一份自己的库拷贝;应用程序运行的时候,即使将库删除也没有问题,因为应用程序自己已经有了自己的拷贝。
共享库
     一个共享库有可能被多个所有应用程序共享。因此,对每个应用程序来说,即使不再使用某个共享库,也不应将其删除。此外,应用程序需要正确的环境变量设置(LD_LIBRARY_PATH),从而找到共享库所在的位置,否则,应用程序运行时会报告找不到这个库。

4) 指针与引用的区别
相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
4. 引用没有 const,指针有 const;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
7. 指针和引用的自增(++)运算意义不一样;
8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。)

(5)进程与线程、多线程、线程安全
1)进程与线程
①从概念上:
进程:一个程序对一个数据集的动态执行过程,是分配资源的基本单位。
线程:一个进程内的基本调度单位。
线程的划分尺度小于进程,一个进程包含一个或者更多的线程。
②从执行过程中来看:
进程:拥有独立的内存单元,而多个线程共享内存,从而提高了应用程序的运行效率。
线程:每一个独立的线程,都有一个程序运行的入口、顺序执行序列、和程序的出口。但是线程不能够独立的执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
③从逻辑角度来看:(重要区别)
多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但是,操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理及资源分配。)
------也可以这么分析:

a.进程是资源分配的基本单位,线程是cpu调度,或者说是程序执行的最小单位。在Mac、Windows NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。

b.进程有独立的地址空间,比如在linux下面启动一个新的进程,系统必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种非常昂贵的多任务工作方式。而运行一个进程中的线程,它们之间共享大部分数据,使用相同的地址空间,因此启动一个线程,切换一个线程远比进程操作要快,花费也要小得多。当然,线程是拥有自己的局部变量和堆栈(注意不是堆)的,比如在windows中用_beginthreadex创建一个新进程就会在调用CreateThread的同时申请一个专属于线程的数据块(_tiddata)。

c.线程之间的通信比较方便。统一进程下的线程共享数据(比如全局变量,静态变量),通过这些数据来通信不仅快捷而且方便,当然如何处理好这些访问的同步与互斥正是编写多线程程序的难点。而进程之间的通信只能通过进程通信的方式进行。

d.由b,可以轻易地得到结论:多进程比多线程程序要健壮。一个线程死掉整个进程就死掉了,但是在保护模式下,一个进程死掉对另一个进程没有直接影响。

e.线程的执行与进程是有区别的。每个独立的线程有有自己的一个程序入口,顺序执行序列和程序的出口,但是线程不能独立执行,必须依附与程序之中,由应用程序提供多个线程的并发控制。

2)什么是线程安全
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。
如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题都是由全局变量静态变量引起的。
若每个线程中对全局变量静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
3)多线程同步与互斥有几种实现方法?都是什么?(C++)

临界区(CS:critical section)、事件(Event)、互斥量(Mutex)、信号量(semaphores),需要注意的是,临界区是效率最高的,因为基本不需要其他的开销,二内核对象涉及到用户态和内核态的切换,开销较大,另外,关键段、互斥量具有线程所有权的概念,因此只可以用于线程之间互斥,而不能用到同步中。只有互斥量能完美解决进程意外终止所造成的“遗弃问题”。

4)多线程同步和互斥有何异同,在什么情况下分别使用他们?举例说明

所谓同步,表示有先有后,比较正式的解释是“线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

所谓互斥,比较正式的说明是“线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。”表示不能同时访问,也是个顺序问题,所以互斥是一种特殊的同步操作。

   举个例子,设有一个全局变量global,为了保证线程安全,我们规定只有当主线程修改了global之后下一个子线程才能访问global,这就需要同步主线程与子线程,可用关键段实现。当一个子线程访问global的时候另一个线程不能访问global,那么就需要互斥。

例:

>>>以下多线程对int型变量x的操作,哪几个需要进行同步:      A. x=y;      B. x++;    C. ++x;    D. x=1;

           答案是ABC,显然,y的写入与x读y要同步,x++和++x都要知道x之前的值,所以也要同步。

>>>多线程中栈与堆是公有的还是私有的

    A:栈公有, 堆私有

    B:栈公有,堆公有

    C:栈私有, 堆公有

    D:栈私有,堆私有

    答案是C,栈一般存放局部变量,而程序员一般自己申请和释放堆中的数据。

>>>在Windows编程中互斥量与临界区比较类似,请分析一下二者的主要区别。

     临界区、互斥量、信号灯、事件的区别总结

     针对这个题目的话,答案主要有以下几点:

      1)互斥量是内核对象,所以它比临界区更加耗费资源,但是它可以命名,因此可以被其它进程访问

      2)从目的是来说,临界区是通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。              互斥量是为协调共同对一个共享资源的单独访问而设计的。 

>>>一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,tally取值范围。 

inttally = 0;//global
    voidThreadProc()
    {
           for(inti = 1; i <= 50; i++)
                tally += 1;
    }


当两线程串行时,结果最大为100,当某个线程运行结束,而此时另外一个线程刚取出0,还未计算时,结果最小为50。
 
 

死锁
产生的原因
(1)竞争资源(2)进程推进顺序不当
必要条件
(1)互斥条件(2)请求和保持条件(3)不剥夺条件(4)环路等待条件
预防死锁
是必要条件其中之一不能成立。条件(1)是设备的固有属性,不仅不能改变,还要加以保证

面向对象三个基本元素:封装、多态、继承
五大基本原则:(貌似跟设计模式的六大基本原则相同???)
(1)单一指责原则(2)里氏替换原则(3)依赖倒置原则
(4)迪米特原则   (5)开放封闭原则(6)接口隔离原则














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值