从15年毕业到北京以来一直住在北京昌平这边,由于离清河比较近,因此来过几次五彩城这边,而这也是小米的总部,一个曾经一直眺望的地方。而现在我也终于有机会来这里面试了,不但面了,还面了5个小时。不但面了5个小时,而且三面的面试官还把我忘了,是的,把我忘了。
第一轮面试
一个比较严肃的面试官 (貌似我接触的这几个面试官都比较严肃,应该是我比较水的原因),没有笔试,开始就让我自我介绍,然后介绍项目经验。
然后开始问了些C++基础知识,比如memcpy()(自己实现这个函数需要注意什么),多态,TCP、UDP,常用的STL(几乎每个公司都问),是否熟悉map,常用的排序(让我介绍的是堆排序),还有些暂时忘记了,最后出了一道编程题,题目如下:
有一个二维矩阵(我问他是否是正方形,他说不是,只是一般的矩阵)A[i][j],对它进行顺时针打印(剑指offer原题,网上可以搜到,我还做过几遍,但是我记成原题是正方形了,最后做的有点问题)。
void PrintMatrixClockwisely(int **numbers, int cols, int rows) {
if (numbers == NULL || cols < 1 || rows < 1) return ;
int start = 0;
while (cols > start * 2 && rows > start * 2) {
PrintMatrixInCircle(numbers, cols, rows, start);
++start;
}
}
void PrintMatrixInCircle(int **numbers, int cols, int rows, int start) {
int endX = cols - 1 - start;
int endY = rows - 1 - start;
//从左到右打印一行
for (int i = start; i <= endX; ++i) {
printNumber(numbers[start][i]);
}
//从上到下打印一列
if (start < endY) {
for (int i = start + 1; i <= endY; ++i) {
printNumber(numbers[i][endX]);
}
//从右到左打印一行
if (start < endY && start < endX) {
for (int i = endX - 1; i >= start; --i) {
printNumber(numbers[endY][i]);
}
//从上到下打印一列
if (start < endY - 1 && start < endX ) {
for (int i = endY - 1; i >= start + 1; --i) {
printNumber(numbers[i][start]);
}
}
第二轮面试
1.Set 和 Map 的区别
Map,Set属于标准关联容器,使用了非常高效的平衡检索二叉树:红黑树,他的插入删除效率比其他序列容器高是因为不需要做内存拷贝和内存移动,而直接替换指向节点的指针即可。Set和Vector的区别在于Set不包含重复的的数据。Set和Map的区别在于Set只含有Key,而Map有一个Key和Key所对应的Value这两个元素。
另外,Map和Hash_Map的区别是Hash_Map使用了Hash算法来加快查找过程,但是需要更多的内存来存放这些Hash桶元素,因此可以算得上是采用空间来换取时间策略。
Map和Unordered_Map的优缺点以及适用处
- map
优点:
1. 有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多操作
2. 红黑树,内部实现一个红黑树使得map的很多操作在lgn的时间复杂度下就可以实现,一次效率很高
缺点:空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点,孩子节点以及红/黑性质,使得每一个节点都占用大量的空间
适用处:对于那些有顺序要求的问题,用map会更高效一些 - unordered_map
优点:因为内部实现了哈希表,因此查找速度非常快
缺点:哈希表建立比较耗费时间
适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map
2.const的作用以及和#define的区别
这也是公司面试中出现的高频问题之一,和它类似的还有static关键字的作用。
- 修饰变量,用const修饰的变量是不可变的
- 修饰指针,如果const位于的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于的右侧,const就是修饰指针本身,即指针本身是常量。
因此,推荐使用int const* p,而不是使用const int* p(虽然两者意义完全一样),但这样更容易理解。
int a = 10;
const int* p = &a; // 指针指向的内容不能变
int const* p = &a; // 同上
int* const p = &a; // 指针本身不能变
const int* const p = &a; // 两者都不能变
int const* const p = &a; // 同上
- const修饰函数传入参数,将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。同理,将指针参数声明为const,函数将不修改由这个参数所指的对象。通常修饰指针参数和引用参数
void Fun(const A *in); //修饰指针型传入参数
void Fun(const A &in); //修饰引用型传入参数
- 修饰函数返回值,返回值也要相应的付给一个常量或常指针
- 修饰成员函数
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;
const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的;
const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。
而const和#define的区别在于:
- 编译器处理方式不同。define在预处理阶段展开,const常量是在编译运行阶段使用。
- 类型和安全检查不同。define宏没用类型,不做任何安全检查,仅仅是展开。const常量有具体的类型,在编译阶段会执行类型检查。
- 存储方式不同。define宏仅仅是展开,有多少地方使用,就占开多少次,不会分配内存(宏定义不分配内存,变量定义分配内存)。const常量会在内存中分配(可以是堆中,也可以是栈中)。
http://blog.csdn.net/love_gaohz/article/details/7567856
3.二叉树给出前序和中序序列,求后序
前序遍历的第一个节点为该二叉树的根节点,然后中序遍历中根节点左右分别为左右子树,然后到前序中找左右子树的根节点,重复上述过程即可。
4.内存申请相关问题
- new和malloc申请内存的区别
http://www.cnblogs.com/QG-whz/p/5140930.html - 如何避免手动申请的内存忘记释放掉: 可以将申请(new/malloc)放在构造函数中,而同时将相应的释放(delete/free)放在析构函数中,这样就可以自动完成释放过程。
- 4道内存相关程序判断结果题http://buptdtt.blog.51cto.com/2369962/832201
- 类的static成员函数没有this指针,因为static成员是类所有对象共有的。http://c.biancheng.net/cpp/biancheng/view/201.html
5. 描述TCP三次握手的过程。
在TCP/IP协议中,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传输的,因为不能容易哪怕一瞬间的花屏。
6. 指针和引用的异同。
- 相同点:
都是地址的概念,指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。 - 不同点:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
4. 引用没有const,指针有const,const的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
7. 指针和引用的自增(++)运算意义不一样;
8.引用是类型安全的,而指针不是 ,引用比指针多了类型检查
7. 两道内存访问问题
1.实现memmove函数
void * memmove(void *dest, const void *src, size_t count)
{
char *tmp, *s;
if (dest <= src) {
tmp = (char *) dest;
s = (char *) src;
while (count--)
*tmp++ = *s++;
} else {
tmp = (char *) dest + count - 1;
s = (char *) src + count - 1;
while (count--)
*--tmp = *--s;
}
return dest;
}
2.有一段内存,0001数据0001数据0001数据,要求将0001替换成下一段数据的长度。
第三轮面试
编程题,将字符串A中的字符串B替换成字符串C,其中A、B、C都不确定。
结果
最终,以他们希望找一个具有实际工作中项目经验的人为由,将我拒掉了