程序员面试笔试宝典学习记录(一)(常见面试笔试题目)
摘选著名的互联网企业的面试笔试真题:
1.extern的作用
自己理解:应该需要区分extern在C语言中和C++语言中的作用,C语言中extern声明的函数和变量可以被该文件外部模块引用,C++语言中除了该作用还可以声明extern “C”声明一段代码编译连接的方法为C语言的方法。
参考:其实extern的百度词条解释的很清楚,具体的也是跟我上面自己理解差别不是很大。
(a) extern是C/C++语言中声明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量在本模块或其他模块中使用(通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。)
(b) 被extern “C”修饰的变量和函数是按照C语言的方式编译和链接的。(C语言不支持函数重载,所以函数的C++和C的编译方式不同,这一句的作用就是实现C++和C及其他语言混合编程)
2.strstr()函数的作用
strstr()函数的原型一般为extern char * strstr(const char *src , const char *dest) , 其作用就是寻找目标字符串在源字符串中第一次出现的位置。
3.windows线程优先级问题( 进程和线程的区别和联系 )
我觉得这个概念可能面试、笔试的时候不是很适合,毕竟平台相关,大多数公司可能更多的倾向于linux开发,这个问题更换为进程和线程的区别更好,这个是笔试,面试常见的知识考查。
(a) 通常一个进程可以包含若干个线程,它们可以利用进程所拥有的资源。进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本不拥有系统资源,只拥有一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),线程可与同属于一个进程的其他线程共享进程所拥有的全部资源。
线程和进程区别归纳:
- 地址空间和其他资源:进程间互相独立,同一个进程的各线程共享。
- 通信:进程间通信IPC,线程间可以直接读写进程序数据段(如全局变量)来进行通信-需要进行同步和互斥的辅助。
- 调度和切换:线程上下文切换比进程上下文切换快速,高效。
- 多线程的OS中,进程不是一个可执行的实体。
4.多方法交换x与y的值
5.指针的自加与引用
6.前置++与后置++
前置++和后置++我觉得一个比较重要的问题是C++中重载两个操作符的时候如何区别:区分前置和后置 函数的参数有一个 (函数重载),后置++有一个(int)参数。
7.inline的作用
inline函数不像正常函数在调用时存在压栈和call的操作,它会把程序代码直接嵌入到调用代码段中,也就是说使用inline函数会增大二进制程序的体积,但是会使执行速度加快。
同时,编译期间可以对参数进行强类型的检查,这是inline优于宏的一个方面。
8.二维数组的表示
9.ifndef的作用
条件编译的语法,一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。
10.KMP算法
字符串匹配的高级算法
11.函数调用方式
- __cdecl 堆栈由调用者清除 参数从右至左的顺序压入堆栈内
- __stdcall 堆栈由被调用者清除 参数从右至左的顺序压入堆栈内
- __fastcall 堆栈由被调用者清除 部分参数保存在寄存器中,然后其他的压入堆栈内
- thiscall(非关键字) 堆栈由被调用者清除 参数压入堆栈内,this指针保存在ECX寄存器内
12.重载函数
函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。不能利用返回类型进行重载!类中函数const和非const可以进行重载,其实原理是利用this指针的类型是const和非const进行重载,其实原理就是参数类型不同,const指针orconst引用调用的为const版本的函数~更多函数重载的知识。
13.构造函数和析构函数
虚拟析构函数的使用场景是指向父类的指针实则为子类指针,调用delete的时候使用虚拟析构函数,防止部分内存泄露。
构造函数不能声明为虚拟函数,因为对象的虚拟函数表的指针其实是在构造函数内编译器添加完成的代码,所以在构造函数执行之前无法访问到虚拟函数表的。
14.合并两个有序链表
类似归并排序,两个指针归并即可。
15.100亿条记录的文本文件,取出重复数最多的前10条
类似top k算法,无法全部读入内存的top k算法是利用容量为k的最大堆,达到线性时间的top k算法。
首先利用hash table预处理每个元素出现的次数,然后利用次数执行top k算法。
16.设计一个双向链表,并且提供一个可根据值删除元素的函数
STL中的list底层及为双链表实现。
17.二叉树的多种遍历算法实现
18.有读和写的两个线程和一个队列,读线程从队列中读数据,写线程往队列中写数据
生产者和消费者模型:
使用信号灯和互斥量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
semaphore mutex = 1; semaphore fillCount = 0; semaphore emptyCount = BUFFER_SIZE; procedure producer() { while ( true ) { item = produceItem(); down(emptyCount); down(mutex); putItemIntoBuffer(item); up(mutex); up(fillCount); } } procedure consumer() { while ( true ) { down(fillCount); down(mutex); item = removeItemFromBuffer(); up(mutex); up(emptyCount); consumeItem(item); } } |
不使用信号灯和互斥量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
volatile unsigned int produceCount, consumeCount; TokenType buffer[BUFFER_SIZE]; void producer( void ) { while (1) { while (produceCount - consumeCount == BUFFER_SIZE) sched_yield(); // 缓冲区满 buffer[produceCount % BUFFER_SIZE] = produceToken(); produceCount += 1; } } void consumer( void ) { while (1) { while (produceCount - consumeCount == 0) sched_yield(); // 缓冲区空 consumeToken( buffer[consumeCount % BUFFER_SIZE]); consumeCount += 1; } } |
19.stack,heap,memory-pool
20.TCP的流量控制和拥塞控制机制
TCP的流量控制就是让发送方的发送速率不要太快,让接收方来得及接收。利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。TCP的窗口单位是字节,不是报文段,发送方的发送窗口不能超过接收方给出的接收窗口的数值。
所谓的拥塞控制为防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制索要做的都有一个前提,就是网络能承受现有的网络负荷。流量控制往往指点对点通信量的控制,是一个端到端的问题。因特网建议标准RFC2581定义了进行拥塞控制的四种算法,即慢开始(Slow-start),拥塞避免(Congestion Avoidance)快重传(Fast Restrangsmit)和快回复(Fast Recovery)。
21.写一个函数,返回一个字符串中只出现一次的第一个字符
目前想到的方法就是利用hash表记录每个字符出现的次数,然后两次遍历即可找到只出现一次的第一个字符。
22.求一个数组中第k大的数的位置
23.面向对象继承,多态问题,如多态的实现机制
虚拟函数,指针and引用
24.内联函数什么时候不展开
在内联函数内不允许用循环语句和开关语句。 如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数(自己调用自己的函数)是不能被用来做内联函数的。内联函数只适合于只有1~5行的小函数。对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现。
25.成员函数初始化列表有什么作用?什么必须在成员初始化列表中进行初始化?
类的static变量在类的构造函数前已进行初始化!
类对象的构造顺序:
(a)分配内存,调用构造函数时,隐式/显式的初始化各数据成员(顺序和类中声明对象一致)。如果无成员初始化列表。隐式初始化阶段按照声明的顺序依次调用所有基类的缺省构造函数,然后所有成员类对象的缺省构造函数。
(b)进入构造函数执行函数体内语句,函数体内的数据成员的设置被认为赋值,而不是初始化。
所以,使用初始化列表的两个情况:
1)必须使用初始化列表进行初始化![1]数据成员为类对象并且该类对象仅提供带参数的构造函数[2]const修饰的数据成员[3]引用数据成员;
2)考虑效率的时候!因为未利用初始化列表而是在构造函数体内进行赋值,则调用了缺省构造函数和赋值运算符操作。如果数据成员为自定义的类对象,则效率比直接利用构造函数初始化低很多。
27.指针与引用的区别
相同点:
- 都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
不同点:
- 指针是一个实体,而引用仅是个别名
- 引用只能并且必须在定义时被初始化一次,之后不可变(类似常量指针,引用自带常量指针属性);指针可变;
- 引用没有const,指针有const,const的指针不能够改变;(int & const refer 不存在,因为引用本身就初始化一次不可变,但是const int &refer是存在的,指引用所指向的值不可改变)
- 引用不能为空,指针可以为空
- sizeof针对指针得到的是指针的大小,针对引用得到的是指向对象的大小;
- 指针的++操作和引用的++操作完全不同,指针为移动指针地址,引用++操作作用于指向的对象;
- 引用是类型安全的,而指针不是类型安全的。
28.创建空类时,哪些成员函数是系统默认的?
构造函数,拷贝构造,赋值函数,析构函数,取址运算符,const取址运算符
29.有10W个IP段,这些IP段之间都不重合,随便给定一个IP,求出属于哪个IP段
30.网络编程(网络编程范式,非阻塞connect)
常见的IO模型有阻塞、非阻塞、IO多路复用、异步。
31.TCP/IP
32.LINUX的命令,原理及底层实现
33.LINUX编程,包括所有互斥的方法,多线程编程,进程间的通信
34.一个一维数轴上的不同线段,求重复最长的两个线段
35.有向带权图最短路径
36.内存溢出和内存泄露有什么区别?
- 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
- 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
37.利用互斥量和条件变量设计一个消息队列,具有以下功能:1)创建消息队列(消息中所含的元素);2)消息队列中插入消息;3)取出一个消息(阻塞方式);4)取出第一个消息(非阻塞方式)。注意互斥量,条件变量和队列系统提供
38.非递归方法实现二叉树的遍历
利用栈stack的方法和morris遍历方法,分别O(lgn)和O(1)的额外空间。
39.cnwap和cnnet的区别
CMWAP 和 CMNET 只是中国移动人为划分的两个GPRS接入方式。前者是为手机WAP上网而设立的,后者则主要是为PC、笔记本电脑、PDA等利用GPRS上网服务。它们在实现方式上并没有任何差别,但因为定位不同,所以和CMNET相比,CMWAP便有了部分限制,资费上也存在差别。
40.设计一个内存管理策略,要求可以保证多线程安全,防止内存越界等,效率不低于malloc()/free()函数
41.排列组合问题
排列递归dfs版本穷举,另外还可以有一些带剪枝的题目。
42.若有序的关键字序列为{b,c,d,e,f,g,q,r,s,t},则在二分查找关键字b的过程中,先后进行比较的关键字依次是什么?
此问题向上取整和向下取整也相关,而且与结束条件相关。
[0,9]->4(f)
[0,3]->1(c)
[0,0]->0(b)
如果是向下取整则为以上比较过程,依次f,c,b。
43.有一个虚拟存储系统,若进程在内存中占3页(初始状态为空),若采用先进先出页面淘汰算法,当执行如下访问下列后,1,2,3,4,5,1,2,5,1,2,3,4,5, 会发生多少缺页?
另外一个比较重要的淘汰算法LRU,最近最久未使用。
3+8 = 11
44.有一个顺序栈S,元素s1,s2,s3,s4,s5,s6,依次进栈,如果6个元素出栈顺序s2,s3,s4,s6,s5,s1,则顺序栈的容量至少应该有多少?
s1进入,1,s2进入,2,s2弹出,s3进入,2,s3弹出,s4进入,2,s4弹出,s5进入,2,s6进入,3,s6弹出,s5弹出,s1弹出。
根据以上过程所以顺序栈的容量至少为3才可以。
45.[0,2,1,4,3,9,5,8,6,7]是以数组形式存储的最小堆,删除堆顶元素0后的结果是多少?
自己理解:[1,2,5,4,3,9,7,8,6,0]
46.某页式存储管理系统中,地址寄存器长度为24位,其中号占14位,则主存的分块大小是多少字节?
页式存储管理是把主存储器分成大小相等的许多区,每个区称为一块,与此对应,编制程序的逻辑地址也分成页,页的大小与块的大小相等。
(a) 地址转换:绝对地址 = 块号 * 块长 +块内地址
块号是页号根据页表查询得到
这里寄存器长度24位,号占14位,所以页面大小占10位,所以块的大小也为10位。
47.运算符重载
48.各种排序算法的使用与比较
49.一维数组默认初始化问题
- 如果不对数组进行任何初始化操作, 仅定义一个数组, 那么数组中这些元素的值是不确定的, 是系统中随机的一个值。
50.const char *p1 = "hello";char *const p2 = "hello",有什么区别。
const在指针和引用声明中位置的不同分别代表不同的意义。
- const char *p1 等价于 char const *p1为指针指向内容为常量,指向字符串常量的指针;
- char *const p2 为常量指针,指向字符串的常量指针。
51.struct与class有什么区别和联系
struct与class在C++中看起来没什么区别,只是使用习惯,不过说到区别可能还有2点吧。
(a)默认的访问控制,struct默认访问控制public,class默认访问控制private,写代码时最好标明确访问控制。
(b)class这个关键字还用于定义模板参数,like "typename"。关键字struct不能用于定义模板参数。
52.函数指针和指针函数
函数指针:char (*p)();p为指向函数的指针
指针函数:char *p();返回指针的函数
53.指针数组和数组指针
指针数组:char *cp_array[];
数组指针:char (*p_array)[];
54.大端小端
- 大端模式,是指数据的高位,保存在内存的低地址中,而数据的低位,保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
- 小端模式,是指数据的高位保存在内存的高地址中,而数 据的低位保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。
1
2
3
|
static union { char c[4]; unsigned long l; } endian_test = { { 'l' , '?' , '?' , 'b' } }; #define ENDIANNESS ((char)endian_test.l) //(如果ENDIANNESS=’l’表示系统为little endian,为’b’表示big endian )。 |
利用union共享存储单元的原理。
55.虚函数问题
针对每个类存在一个虚拟函数表,每个类的实例中存在一个指向虚拟函数表的指针。
56.如果判断单链表是否有环
快慢指针,相遇则存在环。