常见问题总结
C++
1. 实现memcpy, strcpy
如果目标地址大于源地址,先拷贝高位源地址;
如果目标地址小于源地址,先拷贝低位源地址。
1 void mymemcpy(void* dst, const void* src, size_t num) { 2 assert(src != NULL && dst != NULL); 3 const char* psrc = (const char*)src; 4 char* pdst = (char*) dst; 5 if (pdst > psrc && pdst < psrc + num) { 6 psrc = psrc + num - 1; 7 pdst = pdst + num - 1; 8 while (num--) 9 *pdst-- = *psrc--; 10 } 11 } else { 12 while (num--) { 13 *pdst++ = *psrc++; 14 } 15 } 16 } 17 18 /* 19 易错点: 20 1. 函数接口:用void*比较通用一些;而且,src要加上const。 21 2. 判空是个很重要的考察点!这里用assert实现; 22 3. 下面对指针从void*转换到char* 和const char*; 23 4. 如果对size_t进行操作时一定要注意,size_t是unsigned,它降到0之后再减一就变成INT_MAX了,可不是-1! 24 */
follow up:如何优化?
默认的memcpy是逐字节拷贝; 优化方法是,不用一个字节一个字节的拷贝; 可以多字节拷贝,或者整块的拷贝,也可以是用int指针拷贝(一个int占4字节)。 如果用int指针拷贝,需要分别计算一下 wordNum = num / 4; slice = num % 4;
拓展:strcpy等一系列经典函数实现 ref ref2 ref3 ref4
除了memcpy外,其他函数在实现时可以不用考虑内存覆盖问题。具体可以和interviewer沟通。
1 char* strcpy(char* dst, const char* src) { 2 assert(dst != NULL && src != NULL); 3 char* pdst = dst; 4 const char* psrc = src; 5 while (*psrc != '\0') { 6 *pdst++ = *psrc++; 7 } 8 *pdst = '\0'; 9 return dst; 10 }
1 char* mystrcpy(char* dst, const char* src) { 2 assert(dst != NULL && src != NULL); 3 int size = strlen(src) + 1; // 4 const char* psrc = src; 5 char* pdst = dst; 6 if (psrc < pdst && psrc + size > pdst) { 7 psrc += size - 1; 8 pdst += size - 1; 9 while (size--) { 10 *pdst-- = *psrc--; 11 } 12 } else { 13 while (size--) { 14 *pdst++ = *psrc++; 15 } 16 } 17 return dst; 18 } 19 20 /* 21 需要注意的是,计算size时应该在strlen(src)基础上+1,因为要把字符串末尾的'\0'计算进去。 22 有返回值的原因,是为了支持链式表达式,比如: 23 int iLength=strlen(strcpy(strA,strB)); 24 25 http://www.cnblogs.com/kedebug/archive/2012/12/23/2829945.html 26 */
1 char* mystrncpy(char* dst, const char* src, size_t size) { 2 assert(dst != NULL && src != NULL); 3 char* pdst = dst; 4 const char* psrc = src; 5 size_t i = 0; 6 while (i++ < size && (*pdst++ = *psrc++) != '\0'); 7 if (*pdst != '\0') { 8 *pdst = '\0'; 9 } 10 return dst; 11 }
1 char* strcat(char* dst, const char* src) { 2 assert(dst != NULL && src != NULL); 3 char* pdst = dst; 4 const char* psrc = src; 5 while (*pdst != '\0') { 6 pdst++; 7 } 8 while (*psrc != '\0') { 9 *pdst++ = *psrc++; 10 } 11 *pdst = '\0'; 12 return dst; 13 }
1 int strcmp(const char* str1, const char* str2) { 2 assert(str1 != NULL && str2 != NULL); 3 while (*str1++ == *str2++) { 4 if (*str1 == '\0') { 5 return 0; 6 } 7 } 8 return *str1 - *str2; 9 }
2. STL中vector的实现及原理
1 // Vector.h 2 3 #ifndef VECTOR_H 4 #define VECTOR_H 5 6 template <typename Object> 7 class Vector 8 { 9 public: 10 explicit Vector(): objects(0), theSize(0),theCapacity(0) {} 11 12 explicit Vector(int initSize) 13 : theSize(initSize), theCapacity(initSize + SPARE_CAPACITY) 14 { 15 objects = new Object[theCapacity]; 16 for(int i=0; i<initSize; ++i) 17 objects[i] = 0; 18 } 19 20 Vector(const Vector & rhs) 21 { operator=(rhs); } 22 23 ~Vector() 24 { delete [] objects; } 25 26 const Vector & operator= (const Vector & rhs) 27 { 28 if(this != &rhs) // check for aliasing 29 { 30 delete [] objects; 31 theSize= rhs.size(); 32 theCapacity = rhs.capacity(); 33 34 objects = new Object[ capacity() ]; 35 for(int k = 0; k < size(); ++k) 36 objects[k] = rhs.objects[k]; 37 } 38 return *this; 39 } 40 41 void resize(int newSize) 42 { 43 if(newSize > theCapacity) 44 reserve(newSize * 2 + 1); 45 theSize = newSize; 46 } 47 48 void reserve(int newCapacity) 49 { 50 if(newCapacity < theSize) 51 return; 52 53 Object * oldArray = objects; 54 55 objects = new Object[ newCapacity ]; 56 for(int k = 0; k < theSize; ++k) 57 objects[k] = oldArray[k]; 58 59 theCapacity = newCapacity; 60 61 delete [] oldArray; 62 } 63 64 Object & operator[] (int index) 65 { 66 return objects[index]; 67 } 68 const Object & operator[] (int index) const 69 { 70 return objects[index]; 71 } 72 73 bool empty() const 74 { 75 return size() == 0; 76 } 77 int size() const 78 { 79 return theSize; 80 } 81 int capacity() const 82 { 83 return theCapacity; 84 } 85 86 void push_back(const Object & x) 87 { 88 if(theSize == theCapacity) 89 reserve(2 * theCapacity + 1); 90 objects[ theSize++ ] = x; 91 } 92 void pop_back() 93 { 94 theSize--; 95 } 96 97 const Object & back() const 98 { 99 return objects[theSize-1]; 100 } 101 102 typedef Object * iterator; 103 typedef const Object * const_iterator; 104 105 iterator begin() 106 { return &objects[0]; } 107 const_iterator begin() const 108 { return &objects[0]; } 109 iterator end() 110 { return &objects[size()]; } 111 const_iterator end() const 112 { return &objects[size()]; } 113 114 enum{ SPARE_CAPACITY = 16 }; 115 private: 116 int theSize; 117 int theCapacity; 118 Object * objects; 119 }; 120 121 #endif
ref1 ref2:Weiss<Data Structure in C++> Ch3.4 ref3
拓展:
2.1
vector已经申请的内存空间并不会缩减,即使调用clear()函数,也只是清空vector的所有元素,真正占用的内存空间也不会减少。(因此,先在vector里插入1w个元素再挨个删去直到剩10个元素,vector占用的内存空间还是维持在1w个元素时的状态。) 解决办法: vector<int>(nums).swap(nums); 类似地, string(s).swap(s); 原理: 先创建一个临时拷贝并利用拷贝构造函数对其初始化,值得注意的是,使用拷贝构造函数时其容量是尽可能小的符合所需数据的。紧接着将该拷贝与原先的vector v进行交换(swap)。执行交换后,临时变量会被销毁,内存得到释放。此时的v即为原先的临时拷贝,而交换后的临时拷贝则为容量非常大的vector(不过已经被销毁) http://blog.jobbole.com/37700/ 《Effective STL》-Item17 http://www.cnblogs.com/summerRQ/articles/2407974.html
map,set,deque...
3. 实现一个string类
4. const的用法
1. const用于指针时: const在星号左侧,则const修饰指针所指向的变量,即指针指向的是常量; const在星号右侧,则const修饰指针本身,即指针本身是常量。 可参考ref1的例子。 ref1: http://blog.csdn.net/wen294299195/article/details/6857395 2. const用于函数时: 若const在函数名之后,说明该函数不能修改对象中的数据。如果有特例需要让该函数修改的话,可以把类中的那几个特例成员变量用mutable修饰。ref 若const在函数名之前,则作用于返回值,意味着该函数返回的值只能被读取,而不能被修改。 参考ref2的例子。 ref2: http://www.iteblog.com/archives/214
拓展:
follow up: 垃圾回收机制。
6. 运算符重载
7. 实现一个单例模式类
follow up: 如何定义只能在堆上生成的对象? 只能在栈上生成的呢?
8. struct对齐问题
对齐规则:
1)在没有#pragma pack(n)的情况下,struct内各个成员的对齐系数为自己的长度。
2)在有#pragma pack(n)的情况下,struct内各个成员的对齐系数为 min(n, 自己的长度) .
3)struct整体的对齐系数为struct内各成员对齐系数的最大值。
4)struct内每个成员相对于struct首地址的offset都是该成员自身对齐系数的整数倍(否则要补全);而且,struct整体也要保证占用的总大小是struct整体对齐系数的整数倍(末尾不够要补全)。
9. C++易混淆概念
10. C++虚函数原理及虚继承
11. C++对象内存模型
Q: 画一下有虚函数的情况下内存布局大概是什么样的?
12. C++ this指针,动态绑定和静态绑定,空指针调用成员函数
看一下这几篇文章: ref1 ref2 ref3 ref4 ref5
static关键字相关 看一下 ref
静态成员函数不可以调用类的非静态成员,因为静态成员函数不含this指针。【注意:是类的,不是随便一个非静态变量】
局部静态变量的构造是线程安全的。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
其他C++常见问题:
何海涛总结5篇C++面试题: ref1 ref2 ref3 ref4 ref5
http://wenku.baidu.com/view/ad50b2d765ce05087632138c.html
http://blog.csdn.net/worldwindjp/article/details/18909079
http://www.cnblogs.com/wuchanming/p/4367398.html
http://blog.csdn.net/chhuach2005/article/details/40322895
=======================================================================================================================
概率题 reference
1. 给N张扑克牌和一个随机函数,设计一个洗牌算法
2. 如何等概率地从n个数中随机抽出m个数?
follow up: 如果上题中n大小不确定,如何做?
3. 给定一个能够生成0,1两个数的等概率随机数生成器”,如何生成⼀个产生0,1,2,3的等概率随机数生成器?
3.1 如何用rand7生成rand9?
3.2 有1枚硬币,以p的概率产生正面,以1-p的概率产生背面,如何利用它产生一个0.5概率的生成器?
4. A,B,C三人轮流扔硬币,第一个扔到正面的人算赢,问三个人赢的概率分别为多大?
5. A有n个硬币,B有n+1个硬币,谁丢的正面多谁赢,问A不输的概率?
6. 一个机器人在原点,右边有一个距离为k的点,机器人以p的概率右移一步,1-p概率左移一步, 问经过M步机器人处于k点的概率?
7. 扔硬币直到连续两次出现正面,求扔的期望次数
ref1里面有一些概率题总结的不错。
ref1: http://noalgo.info/414.html
=======================================================================================================================
操作系统 reference
1 1. Linux中线程互斥/同步有哪几种方式? 2 3 2. 同样可以实现互斥,互斥锁和信号量有什么区别? 4 5 3. 请用普通的互斥锁编程实现一个读写锁 6 7 4. 编程产生三个线程ABC,并让它们顺次打印ABC 8 9 5. 死锁是怎么产生的?如何避免? 10 11 6. Linux中进程通信有哪些方式? 12 13 7. Linux中进程空间布局 14 15 8. Linux内存分配原理 16 17 9. malloc函数实现原理 18 19 10. 使用mmap读写文件为什么比普通读写函数要快? 20 21 11. 静态链接库、动态链接库原理 22 23 12. Linux中signal实现原理
Linux常用命令/知识: ref
1. 进程和线程有什么区别和联系
2. 内存管理
伙伴系统
进程间通信
实现malloc/ malloc原理 ref1 ref2 ref3
键盘上敲击一下键/按一下鼠标,后面都有哪些操作?
缓存,LRU算法,进程、线程的概念。
进程调度算法、文件系统。
<多线程编程>
=======================================================================================================================
海量数据
1. 100亿个整数,如何找到中位数
内存足够的情况: 可以使用类似quick sort的思想进行[QuickSelection算法],均摊复杂度为O(n),算法思想如下:
• 随机选取一个元素,将比它小的元素放在它左边,比它大的元素放在右边
• 如果它恰好在中位数的位置,那么它就是中位数,可以直接返回
• 如果小于它的数超过一半,那么中位数一定在左半边,递归到左边处理
• 否则,中位数一定在右半边,根据左半边的元素个数计算出中位数是右半边的第几大,然后递归到右半边处理
内存不足的情况:
方法一:二分法
思路:一个重要的线索是,这些数都是整数。整数就有范围了,32位系统中就是[-2^32, 2^32- 1], 有了范围我们就可以对这个范围进行二分,然后找有多少个数小于Mid,多少数大于mid,然后递归,和基于quicksort思想的第k大方法类似【即QuickSelection算法】
方法二:分桶法
思路:化大为小,把所有数划分到各个小区间,把每个数映射到对应的区间里,对每个区间中数的个数进行计数,数一遍各个区间,看看中位数落在哪个区间,若够小,使用基于内存的算法,否则继续划分。
=======================================================================================================================
网络
TCP/IP协议相关
TCP和UDP的区别? ref
三次握手,四次挥手 ref
为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
因为tcp是全双工模式,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,但是己方的数据可能还没有发完,有可能在发送对FIN的ACK后再发送一会数据,然后才发FIN。所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
关于tcp中time_wait状态的4个问题: ref ref2 ref3 ref4
http协议相关
拓展:参考下这篇文章,非常好。
其他网络面试题
http://www.nowcoder.com/discuss/1937?type=&order=0&pos=6&page=1?from=wb 【这个总结非常好!认真看一下!】
http://www.cnblogs.com/obama/p/3292335.html
<网络编程>
IO复用 summarize
epoll水平触发,边缘触发
select与epoll
用户态与内核态,以及如何切换
ref0: 这个博主写的一系列网络编程文章都特别好(条件变量,epoll)。多看一下
reference 这篇文章对网络编程的知识点总结的非常好!认真学习一下。
epoll面试相关
http://blog.csdn.net/tom555cat/article/details/24870469
http://www.cppblog.com/peakflys/archive/2012/08/26/188344.html
http://blog.csdn.net/lianxiang_biancheng/article/details/9025881 【看一下这篇里面epoll源码,让人快速理解epoll流程】
nginx入门 reference
=======================================================================================================================
智力题
1. 25匹马,5个跑道,最少比多少次能找到前3名
2.
=======================================================================================================================
算法和数据结构
1.常见排序算法复杂度、稳定性
选择排序是每次遍历一遍剩余元素,从中选出最小的与前面已排好序的元素的下一位置进行swap。最优情况下复杂度是O(n^2), 平均和最坏复杂度也是O(n^2)。
冒泡和插入排序的最优复杂度是O(n), 平均和最坏都是O(n^2)。
归并排序和堆排序的最优、平均、最坏复杂度都是O(nlgn)。
快速排序最坏情况下是O(n^2), 平均和最优都是O(nlgn).空间复杂度是O(lgN)(因为需要每次记录pivot的位置)。
稳定性:
不稳定:快速排序、堆排序、选择排序、希尔排序
稳定:归并排序、基数排序、冒泡排序、插入排序。
希尔排序是按照不同步长对元素进行插入排序。
=======================================================================================================================
ref: http://www.nowcoder.com/ta/nine-chapter?page=1