又是一篇关于面经的博客。距离写上篇博客已经过去了快一周了。两个多月秋招的屡战屡败已经彻底磨灭了我的信心和尊严,看见周围的同学纷纷拿到offer上岸而我一个offer都没有的时候我忍不住开始怀疑人生,明明大家条件都差不多,我也付出了不少努力,怎么我就没有人要呢(望天)。
今天下午参加了米x游的一面(他们公司不让在网上发面经,虽然我觉得没有人会看我的博客,不过保险起见我还是给名字来个无效打码吧),我在8月底投的简历,9月上旬做的笔试,然后10月下旬才进入面试流程。其实都到了这个时间点,大家都心知肚明公司已经没hc了,我是抱着见见世面以及刷经验的态度参加了一面。虽然面试比我想象中的要简单,但是整体上还是很硬核的,没有自我介绍,一上来就开始问问题,总共你问我答了40分钟,大概问了十几二十来个问题,内容涵盖C++,计网,操作系统还有设计模式,以及三道常规的口述算法题,图形学倒是没问。这涵盖面,不愧是游戏行业的top级难度啊。
我总结了一些面试里我答得有点烂的问题,我觉得都挺有价值:
1. 重载和重写的区别?如果两个同名函数的形参分别是int i
和 const int i
,这样子可以重载吗?如果是int& i
和const int& i
呢?
第一个问题比较容易回答,重载是编译时多态,而重写是运行时多态,和虚函数和类的继承有关。第二、三个问题让我愣了一下,由于我之前写代码的时候遇到过类似的问题,所以我觉得这二者应该都是不可以重载的,但是具体为啥我也没说出个所以然来。。。
在查阅了一番资料以及自己写代码试了试之后,发现int i
和const int i
是不可以重载的,会显示重定义,因为函数调用的时候,存在形实结合的过程,而这二者都不会改变实参的值。
对于后一种情况,int& i
和const int& i
,是可以重载的(答错了,555),因为前者引用的是变量,而后者引用的是常量,有点类似于变量指针和常量指针。所以类似的,int *
和const int *
也是可以重载的。
2. STL里的迭代器是用来做什么的?如果给迭代器加个const会变成什么样,const_iterator
呢?
对于第一个问题,迭代器就是在不暴露容器内部的情况下,为我们提供一个访问容器元素的途径,同时还可以通过迭代器修改内部的值(由此面试官引出了第二个问题)。至于第二个问题……我没这么写过代码啊!所以我当时猜测前者应该会变得和常量指针一样,迭代器的指向可以改,但是迭代器指向的内容不可以改,至于const_iterator
,愚笨如我没用过这玩意……我猜这二者的关系应该类似于常量指针和指针常量。
后来去网上查了资料得到了更加详细的解释,很不幸的是,我猜错了,这二者的作用我说反了。。const iterator
必须要被初始化,它的指向不可以改变,只能改变指向的值;而const iterator
,它的指向可变,但是不能改变容器内部的元素值,只能读不能写。当时太紧张了,现在静下心来回想了一下,const iterator
前面都有const
了,那可不就不能改变它的指向了吗。
3. TCP和UDP的区别是?现在HTTP 3已经可以不用TCP了,如果我们想让UDP实现可靠传输,可以做些什么?
对于第一个问题,TCP是面向连接,而UDP是面向无连接,TCP可靠,UDP不可靠,TCP慢UDP快,简单地概括就是这样。至于后者,我之前瞄过一眼,但是面试的时候一紧张就全忘了,悔しい!脑子里只记得可以通过在应用层模仿TCP的一些特性来实现UDP可靠传输了,太紧张了也没说出个所以然来。
返回去看了下资料,大概是这样的(其实很简单):
-
添加seq/ack机制,确保数据发送到对端。
-
添加发送和接收缓冲区,可以实现超时重传。
同时也有一些开源的实现UDP可靠传输的程序,比如RUDP,RTP,UDT。
4. 内存分配问题,你知道栈啊堆啊这些东西在内存里的位置顺序吗?
忘记答得啥了,反正乱答了一通肯定是错的,以下是我找来的一张内存的分布图:
5. 进程和线程的区别?线程共享进程的那些资源呢?
其实应该是很常规的问题,但是由于我操作系统实在是看得不多,所以第二个问题没答上来……我找到了这样一篇文章,觉得讲得很合理:线程间到底共享了哪些进程资源?。
面试官问的远远不止以上这么多,不过可能是因为其他问题我答出来了所以没什么印象了,就不写了。
总结:
大概摸清了米x游面试官的套路,先问你一个常规问题涨涨自信,然后再在这个基础上深入问一个问题,我觉得这种面试套路可以很好地考验应聘者的水平,只是苦了身为应聘者的我。。。。面试结束之后我问面试官何时可以收到反馈,他表示不知道,我真的不知道,我问他对我有什么评价,他说我感觉你还行,我真的觉得你还行……
得,八成是凉了。
10.20更新
我居然过了第一轮面试!感谢米,让我觉得自己好像也没有那么差。
10.28 更新
我面完米x游的二面啦,再次感叹,不愧是业界top!能够参与这种面试真的很难得,是否通过都不重要了(我也很清楚八成要挂在这一轮了)。
面试官非常温和,开场依旧不让自我介绍,一上来就说:“我们先考两道简单的算法题吧!”
第一道算法题
然后就出了一道用数组设计循环队列的题,并且不可以用vector,要用动态数组,队列的长度无限制。可恶啊,我上leetcode看了眼,这题我两个月前做过,但是我居然忘了!然后磕磕碰碰搞了半个小时好不容易写出来了,中途面试官提示了好多次……
我感觉这道题涉及到了两个问题,一个是数组扩容问题,我们都知道vector是可以自动扩容的,但是由于这道题不让用vector,并且面试官说数据的数量没有限制,所以我们需要自己写一个扩容的功能出来。于是我就模仿了vector的扩容机制,当发现容量满了之后就申请两倍的内存,然后把数组里的东西都挪过去,也不知道写对了没有,头秃……
此外就是要设计一个循环队列,也就是记录当前队首元素所在的位置,新插入的元素要么在数组末尾,要么就放到队首元素前面,如果二者都不可的话那说明超出最大容量了,得扩容。
害,反正修修补补地写完了,最后面试官问了个问题,如果是多线程的话怎么保证线程安全呢?我觉得答案应该是在扩容的地方加一个锁,防止有多个线程同时扩容。
第二道算法题
勉强解决了第一个问题之后,boss又抛来了新的疑问!如何实现二叉树的序列化和反序列化呢?赫赫,这可难不倒我!毕竟这道题我前两天刚在剑指offer上做过呢,于是刷刷刷打了一大堆代码。
然后和蔼的boss问到,假如我们需要考虑数据压缩呢,有什么压缩二叉树序列的方法吗?
唉,我没有学过字符串压缩的算法诶,没答出来,本轮失败。
附加题
问完算法之后,已经过去了大半个小时,面试官问我喜欢什么游戏,我噼里啪啦说了一大通,其中提到了饥荒。
面试官:好,那就考你两个饥荒的问题!(我:这也能考?)
问题一:饥荒加载地图的时候怎么解决cpu消耗呢?
虽然对于游戏开发而言我算是个外行,但是我比较懂饥荒(还好我没有说自己不熟悉的游戏,不然寄了),我认为饥荒会在进入游戏的时候把整个地图的元素全都加载一遍,然后在游戏过程中只会更新用户身边的一小块区域。
似乎这位面试官没有玩过饥荒,他以为饥荒地图是没有边缘的。
问题二: 那如果你身边有很多物品呢,怎么节约cpu?
同样的,我借助着丰富的饥荒经验勉强回答了这个问题,首先饥荒中有些物品你不去碰他的话,它是不会变的,比如草和树枝(但是夏天的时候会自燃,显然面试官不知道这个),所以这类物品只要用户和他交互的时候更新一下就行了;另外一些物品,比如树,他过一段时间可能会长大,这个时候可以维护一个计时器,等计时器到0的时候就更新(了解这一点是因为安了一个mod可以显示距离下一次的时间);对于另外一些东西,比如跑来跑去的动物,可能只能不停地刷新状态了。
嘛,虽然我不知道我的回答是不是他所期待的,但是应该是正确的,毕竟我所观察到的饥荒的游戏机制就是这样的吧,只能说还好这哥们虽然问的饥荒,但是他没玩过饥荒,让我糊弄过去了。
第二轮面试总共花了1小时10分钟,很难但是不会让人觉得很丧气,如果这轮过的话,下一轮似乎是更加黑暗的技术boss战……不过我大概率也就交代在这一轮了吧。最近和家里人就找工作发生了一些争执,似乎进入游戏业的可能性也比较小了,做游戏开发也渐渐地变成了一个遥不可及的梦想(毕竟跑去北京的小公司当着月光族做独立游戏实在不是一个稳定的选择,而且无法满足父母对于“面子”的看重),我和游戏业的交集除了平时买游戏氪金之外,也就仅限于这几次面试了。
11.03 更新
周一就收到感谢信了,果然被挂了。要和游戏行业说再见啦。
11.09 更新
收到了岗位调剂,是一个比较小众的岗位,但是jd和我的背景比较符合,所以我同意了。
米,你看我还有机会吗?