前言
求职这件事,不管是新人还是老鸟,都得谨慎对待。因为往往就是一面定终生(薪资),而且面试,也是一个很好的充电突破的契机,可以让我们有时间对以往经验知识体系进行梳理,并且了解当前行业动向。我个人总结了以下三点:
1.坚持一个原则。凡事预则立,不预则废。坚决不“裸”面,哪怕HR热情得像你初恋女友,看上去你来了就立马给你办入职。面试时间一定要自己掌控,复习好再应邀,不然你会体验到面试不过,HR态度的冰火两重天。HR寻找并邀请候选人是工作职责,热情是工作态度,并不代表公司一定会录取一个毫无准备的你。
2.抓住两个基本点。HR面试,一些离职原因,工作内容,职业生涯,还有对你个性人品潜在内容的调查,这些是容易忽视的细节,小心无大错。技术面试,这个是重中之重,一般涉及三大块:语言基础(C++, lua), 业务知识(cocos2dx常用游戏开发知识), 项目经历(根据你做过的功能,深挖,或者假设各种突发情况,检验你对业务的理解和解决问题,所以平常工作中一定要多总结,多回顾,多反思)。
3.复习三个方向(除此之外最好先去玩一玩面试公司的游戏):
A.计算机基础:c++, lua, 常用的数据结构与算法,设计模式;
B.cocos2dx专业知识技能,热更新机制,分包加载,屏幕适配,性能优化,帧率优化,内存更新,跨平台,图片压缩处理, UDP/TCP/WebSocket,OpenGL基础知识,AI战斗机制,引擎底层原理;
C.项目经验, 具体自行查看自身简历,简历上写的每个点,都有可能是面试官的提问来源。一定要对自己所做的东西,有个条理分明的认知,对自己做过的业务里面运用到的知识,尽量摸清其原理还有实现细节。
因为本篇博客只是起个引导归纳作用,许多细节或者有出入的地方,还望各位自行查阅。话不多说,下面进入正题
C++部分
C++常见的面试字眼一般是:野指针,引用,内存分配,堆和栈,模板和泛型,STL, SizeOf。详细可以搜索C++面试题等,推荐的面试书籍:《剑指offer》《C++程序员面试秘籍》。面试的书籍只限于临时抱佛脚,C++是一门易学难精的语言,建议注重平时工作中的积累和领悟,像《C++Primer》《C++Primer Plus》《Effective C++》《More Effective C++》等经典书籍,有时间可以通读,没时间可以选中其中一部分精读,面试官也喜欢拿里面的知识点提问。
1.指针与引用有啥区别?
答:1)定义:指针是指存储了一个内存地址的变量,引用是原来变量的一个别名。2)初始化:
引用在创建的同时必须初始化,即引用只能引用一个有效对象。指针在创建的时候可不初始化,可在定义后的任何地方重新赋值。3)空值:引用不能指向Null,指针可以指向任意对象。4).效率:由于引用不会指向空值,使用前可不用测试合法性,因此使用引用的代码效率比使用指针的更高。5).应用:指向对象不变,应该使用引用,不同时刻存在指向不同对象,应该使用指针。
2. 什么是野指针?
答:野指针不是NULL指针,而是指指向“垃圾”内存的指针。产生原因:指针变量在被创建的时候没有被初始化,没有指向合法内存;指针P被Free或者delete之后,没有置NULL,让人误以为p是合法的指针。
3. sizeof有哪些用途?
答:1)与存储分配和I/O系统那样的例程进行通信;
2)查看某个类型的对象在内存中所占的单元字节;
3)在动态分配一对象时,可以让系统知道要分配多少内存;
4).便于一些类型的扩充,在Windows中很多结构类型
4.malloc/free和new/delete?
答:1).malloc与free是C++的标准库函数,new/delete是C++的运算符。它们都是可用于申请动态内存和释放内存。
2) .malloc/free为函数只是开辟空间并释放,new/delete则不仅会开辟空间,并调用构造函数和析构函数进行初始化和清理。
5.内存的分配方式有几种?
答:1).在静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都是存在,例如全局变量。
2).在栈上创建。在执行函数时,函数内局部变量的存储单元可以在栈上创建,函数执行结束时这些存储单元自动被释放,效率高,但是分配的内存容量有限。
3).在堆上分配,亦称动态内存分配。程序在运行的时候用malloC或者new申请任意多的内存,程序员自己负责在何时用free或者delete释放内存。
6.关键字static的作用是什么?
答:1).在函数体内,一个被声明为静态的变量在这一函数被调用的过程中维持其指不变。
2).在模块内,函数体外,一个被声明为静态的变量可以被模块内所有函数访问,但不能被模块外其他函数访问。它时一个本地的全局变量。
3).在模块内,一个被声明为静态的函数只可被这一模块内的其他函数调用。那就是,这个函数被限制在声明它模块的本地范围内使用。
7.构造函数与析构函数的区别?
答:构造函数函数名与类名相同,它的作用是在建立一个对象时,做些初始化工作。析构函数的函数名也和类名相同,不过需要在前面加上~,它主要是一个对象离开作用域时,一个做些清理工作,比如释放从堆中分配的内存。
8. 什么是STL?
答:STL(Standard Template Library),即标准模板库,它涵盖了常用的数据结构和算法,并且具有跨平台的特点。STL是C++标准函数库的一部分,它的基本观念就是把数据与操作分离。
是一个具有工业强度的,高效的C++程序库。它是最新的C++标准函数库中的一个子集,包括容器,算法,迭代器等组件。
9.map和hashmap有什么区别?
答:1).底层数据结构不同,map是红黑树,hashmap是哈希表。
2).map的优点在于元素可以自动按照键值排序,而hash map的优点在于它的个项目操作的平均时间复杂度接近常数。
3).map属于标准的一部分,而hashMap则不是。
lua部分
Lua相对于C++来说,语法相对简单,内容也较少,面试内容一般包括: 语法基础知识,
元表,协同程序,跨平台调用,lua优缺点,热更新机制,垃圾回收机制,虚拟机。在掌握以上内容,最好对Lua的底层实现有一定了解,就是对Lua核心机制的底层源码有一定了解,推荐的话《lua设计与实现》,或者阅读源码。
1.lua的基本数据类型?
答:nil、boolean、number、string、userdata、function、thread 和 table
2.pairs和 ipairs区别?
答:pairs:迭代 table,遍历表中全部key,value, 可以返回 nil;
ipairs:迭代数组,不能返回 nil, 如果某个下标元素不存在,则退出;
4. lua如何实现面向对象?
答:首先面向对象的特征:
a.封装(将事务抽象成类,实现细节和数据只对可信的类和对象操作,对外只提供接口);
b.继承(子类通过对父类继承,实现父类的一些方法和属性,并且可以扩展)
c.多态(一个接口,多个方法。子类可以继承父类方法,但是有可以有不同实现方式。比如可以重写父类的方法。也可以通过和父类方法名相同,参数不同的重载形式实现多态)
d.抽象
Lua可以通过table和元表将面向对象的特征模拟出来,组合成一个类,借用菜鸟教程案例:
-- 元类
Rectangle = {area = 0, length = 0, breadth = 0}
-- 派生类的方法 new
function Rectangle:new (o,length,breadth)
o = o or {}
setmetatable(o, self)
self.__index = self
self.length = length or 0
self.breadth = breadth or 0
self.area = length*breadth;
return o
end
-- 派生类的方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
5. 什么是协同程序?
答:定义:Lua协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西
与线程的区别:线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
主要的语法:create()(创建), resume()(重启), yield()(挂起), status()(协程的状态,包含:dead,suspended,running)。
6. lua跨平台交互?
答:1).lua与Andorid端java交互,主要通过 JNI 提供了完善的接口来操作 Java,比如查找特定的 Class、Method,可以参考(LuaJavaBridge):
https://www.cnblogs.com/mokey/p/4443561.html
2).lua与Ios端Object-c交互,首先cocos2d 对oc和lua调用进行了封装,这就更有利于我们调用cocos/cocos2d/luaoc,网上经典的封装(luaoc ):
https://www.cnblogs.com/guangyun/p/5020201.html
3).lua与C#交互(用于Unity),原理其实和java , OC差不多,都是通过封装一些对外的接口类,通过解释器LuaInterface.dll来互相调用对方的静态函数,具体可以自行搜索,就不班门弄斧了。
7.lua的优缺点?
答:优点:a.可以执行高。在Mac,Win,Windows多个平台轻松编译通过。
b.良好的嵌入性。有丰富的的API,可供宿主和lua脚本之间进行通信和交换数据。
c.空间占用非常小,压缩包只有208KB, 解压缩也不过835KB.
d.效率高,是速度最快的脚本语言之一。
缺点:内建功能少,自带语言库少,lua的学习文档也少,够小够简洁(优缺点)。
8.lua的热更新机制?
答:原理:Lua的 require(modelname) 把一个lua文件加载存package.loaded[modelname]。 当我们加载一个模块的时候,会先判断是否在package.loaded中已存在,若存在则返回改模块,不存在才会加载(loadfile),防止重复加载。
方法:最简单粗暴的热更新就是将package.loaded[modelname]的值置为nil,强制重新加载,但是旧有的模块无法更新。所以,我们在更新某一个模块的时候,需要将引用该模块的地方的值也做对应的更新。
function reload_module(module_name)
local old_module = _G[module_name]
package.loaded[module_name] = nil
require (module_name)
local new_module = _G[module_name]
for k, v in pairs(new_module) do
old_module[k] = v
end
package.loaded[module_name] = old_module
end
9. lua垃圾回收机制
答:1).lua采用了自动内存管理, Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作
2).通过两个数字来控制垃圾收集循环:
*垃圾收集器间歇率:控制着收集器需要在开启新的循环前要等待多久.<100:在开启新的循环前不会有等待。=200:会让收集器等到总内存使用量达到之前的两倍时才开始新的循环。
*垃圾收集器步进倍率:控制着收集器运作速度相对于内存分配速度的倍率,默认值是200 ,增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。
3).主要函数:
collectgarbage("collect"): 做一次完整的垃圾收集循环。通过参数opt 它提供了一组不同的功能:
collectgarbage("count"): 以 K 字节数为单位返回 Lua 使用的总内存数。这个值有小数部分,所以只需要乘上1024 就能得到 Lua 使用的准确字节数(除非溢出)。
collectgarbage("restart"): 重启垃圾收集器的自动运行。
collectgarbage("setpause"): 将 arg 设为收集器的 间歇率。 返回 间歇率 的前一个值。
collectgarbage("setstepmul"): 返回 步进倍率 的前一个值。
collectgarbage("step"): 单步运行垃圾收集器。步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。
collectgarbage("stop"): 停止垃圾收集器的运行。在调用重启前,收集器只会因显式的调用运行。
10.lua虚拟机原理?
答:扮演一个中间件的角色,对上负责解释执行字节码,对下屏蔽平台相关内容。
->程序员编码lua文件
->语法词法分析生成Lua的字节码文件(对应Lua工具链的Luac.exe)
->Lua虚拟机解析字节码,并执行其中的指令集
->输出结果。
cocos2dx部分
经过那么多年的发展,cocos2dx已经有了完善的体系,网上社区也有大量专业的书籍可供参考,比较知名的quick+skynet框架,也被许多大型游戏应用。
1. 导演,节点,场景,层,精灵
答:导演:是游戏的组织者,和领导者,负责游戏开始结束的初始化和销毁工作,游戏场景的切换,暂停或恢复,导演还可以设置和获取系统信息,屏幕大小。
节点:是cocos2d-Lua中可见元素的基础类,场景,层,精灵,标签,菜单等都是继承自Node。它封装了可见元素的基础属性和方法,可以包含字节点,可以运行动作,内部也有跟随节点生命周期的调度器。
场景:是容纳其他可见与不可见元素的容器,一个游戏至少需要一个场景。特定时间内只有一个场景是处于活动状态的。
层:是对场景布局的细分,主要也起容器作用,一般分角色场景层,和系统UI层。
精灵:精灵是容器中盛放的内容。精灵总绑定一个纹理对象或精灵帧对象,引擎渲染精灵实际上是把精灵绑定的纹理或精灵帧按照属性设定渲染到屏幕上。可以说,精灵是图像的载体,游戏中看得见的场景如背景图片,房屋,敌人,玩家角色及子弹等,都可以通过精灵实现。
2. 热更新机制。
答:热更新也叫不停机更新,是在游戏服务器运行期间对游戏进行更新。实现不停机修正bug、修改游戏数据等操作,其基本原理:
1).登入游戏先向服务端请求当前游戏版本号信息,与本地版本号比较,如果相同则说明没有资源需要更新直接进入游戏,而如果不相同,则说明有资源需要更新进入第2步。
2).向服务端请求当前所有资源的列表(资源名+MD5),与本地资源列表比较,找出需要更新的资源。
3).根据找出的需要更新资源,向服务端请求下载下来。
Cocos自带也封装了热更新模块(AssetsManager, AssetsManagerEx)。
3. 如何解决卡顿或崩溃(fps/流畅度)问题?
答:卡顿和崩溃两个问题通常可以放在一起,因为两者都属于性能优化的范畴,可以从CUP和GPU两个方面着手。
成因:造成卡顿也就是掉帧的原因主要是CPU计算量和GPU渲染压力过大。造成程序崩溃的原因基本也是两种情况,一种是代码错误造成的崩溃,另一种是我们主要讨论的内存过高造成的崩溃。
从减少cpu计算量来说:
1).优化代码,保持良好的编码规范,选择恰当时间复杂度较少的算法;
2).游戏逻辑优化,不在主逻辑做复杂操作,减少频繁创建和销毁对象,反馈不高的逻辑可以适当的采取抽帧,不用每帧运算刷新。
3).对游戏游戏引擎级别的优化,如修spine的内存泄漏问题、ui加载速度优化。
从减少GPU渲染压力来说:
1).分帧加载。加载较大的纹理或者资源时,分帧加载的策略可以高效的使用内存防止内存峰值过高造成崩溃。
2).选择合适的资源格式。使用pvr.ccz格式纹理减少内存使用。纹理使用的每种颜色位数越多,图像质量越好,但是越耗内存,使用颜色深度为RGB4444的纹理代替RGB8888。如果图片不需要透明通道就不要加上透明通道。
3).减少绘制调用,即减少DrawCall。可以借鉴U3D的一些图形渲染知识,使用大图集,批处理,共享材质,减少显示精灵数。
4).优化UI布局。减少UI层的嵌套深度,提高绘制遍历的速度。控制资源的规格,将通用的按钮,图标,打成一张通用大图,作为常用资源常驻。
5).UI设计的时候要提高UI通用资源的使用率, 比如使用九宫格,图片缩放旋转。
6).动画优化。帧动画可以适量精简帧数,减少内存占用。骨骼动画,合理的控制骨骼数,减少cpu计算量。
7).音频优化,因为Android平台和iOS平台均支持MP3格式,而且MP3格式经过压缩和硬件加速,推荐使用mp3格式。
4.使用到的网络,UDP/TCP/WebSocket等
答:TCP:著名的三次握手原理(自行搜索),它的特点是,面向连接(在开始真正通信前需要三次握手),安全可靠(每一次通信都要对方应答,如果没收到应答则认为数据包丢失,会按一定策略重发),全双工通信(建立连接后,双方均可通过通道进行数据传输)。
UDP:无需建立连接,只需要指定目标的IP和端口即可向其发送数据报,可以一对一,一对多,多对多,发送数据也是无序状态,容易发生丢包。丢包一般解决方案,借鉴TCP协议原理,增加握手,或者增加回包机制,就是每个发包必须收到回包,才能再发下一个。还有一些如控制发包速度,发包大小,在接收方,将通信和处理分开,增加个应用缓冲区等等。具体需要根据项目要求具体分析解决,丢包只能改善。UDP想要可靠,可以增加回包机制,每个包有递增序号,接收方发现中间丢了包就发重传请求,但是这样其实也就重新实现了TCP。具体详细可以去了解定时重传和RUDP等。
WebSocket:是Html5兴起的一种新的协议,在通信前会做一个类似于TCP建立连接的握手动作。要实现即时通信,一般会采用下面两种方式:
短轮询,即在特定时间间隔内不断发送Http请求来询问服务端是否有最新数据,如果有,服务端就返回最新的数据给客户端,没有,服务端就丢弃这次的轮询请求,缺点是存在大量无用的HTTP请求,十分消耗网络宽带资源。
长轮询,和短轮询大同小异,客户端请求服务端数据,服务端如果没有最新数据就一直保持连接不断开,直到有最新数据后再返回给客户端,断开连接,客户端处理完之后对服务端进行长轮询,周而复始。缺点是,服务端在同一个时间可能需要保持与多个客户端的连接,在用户量大的时候,极其浪费服务器资源。
面试其实是双向选择,公司最终目的也是寻找到能帮公司解决问题,带来价值的人。只要你将你能解决公司问题的能力展现出来,综合素质无大问题,一般都能入选。最后,祝大家都能找到心仪的工作。
5. 屏幕适配
app每种机型用不同的适配方案
游戏内部:等比例缩放,不要黑边,全屏
6.分包加载:
初始化只加载主城资源