面试学习
Unity3D
函数流程:http://www.cnblogs.com/herenzhiming/articles/5804600.html
基础面试题目:https://www.jianshu.com/p/39c383f45d4e
协同程序:在主线程运行时同时开启另一段逻辑处理,来协助当前程序的执行。换句话说,开启协程就是开启一个可以与程序并行的逻辑。可以用来控制运动、序列以及对象的行为。
请简述ArrayList和List<Int>的主要区别:ArrayList会把所有插入其中的数据都当做Object来处理。装箱拆箱的操作。
反射的实现原理:
导入using System.Reflection; Assembly.Load("程序集");//加载程序集,返回类型是一个Assembly 得到程序集中所有类的名称 foreach (Type type in assembly.GetTypes()) { string t = type.Name; } Type type = assembly.GetType("程序集.类名");//获取当前类的类型 Activator.CreateInstance(type); //创建此类型实例 MethodInfo mInfo = type.GetMethod("方法名");//获取当前方法 mInfo.Invoke(null,方法参数);
包围盒:https://blog.csdn.net/qq_38415161/article/details/78499967
球体:留下较大的空隙、花费大量的预处理时间, 以构造一个好的层次结构逼近对象、变形之后,包围球树需要重新计算。旋转运动时, 包围球不需作任何更新。
AABB盒:AABB就是一个简单的六面体,每一边都平行于一个坐标平面,矩形边界框不一定都是立方体,它的长、宽、高可以彼此不同。特别重要的两个顶点 Pmin = [Xmin Ymin Zmin],Pmax = [ Xmax Ymax Zmax]。
OBB方向包围盒:根据物体本身的几何形状来决定盒子的大小和方向,盒子无须和坐标轴垂直。这样就可以选择最合适的最紧凑的包容盒子。OBB盒子的生成比较复杂。一般是考虑物体所有的顶点在空间的分布,通过一定的算法找到最好的方向(OBB盒子的几个轴)。执行速度慢,并且不太适合动态的或柔性的物体。
生成:360 x 360 x 360 角度计算最小体积;
判断相交:一个盒子的12条边分别投影到以另一个盒子的三个平面上,转化为判断线段与多边形的相交判断。
相应引擎的语言(语言特性要吃透)
C#:https://www.jianshu.com/p/cbbbd1bf63f2
C#中类是否支持多继承?请说明原因。
不支持,需要用接口来实现多继承
C#中类是否支持多继承?请说明原因。
不支持,需要用接口来实现多继承
请简略描述重载和重写的区别
方法重载提供了一个相同的方法但是方法签名的参数不同的调用的实现。
重写提供了子类中改变父类方法行为的实现。
请问能设置类A可被继承,但类A中的某个方法不能被重写吗?
能,将类A的修饰符标记为public、标记类A中的不允许重写的方法为sealed
sealed关键字不仅可以限制类,也可以限制方法。
const和readonly有什么区别
const关键字用来声明编译时的常量 readonly用来声明运行时的常量
什么时候必须声明一个类为抽象类
当这个类中有抽象方法的时候,必须声明类为抽象类;该类没有完全实现父类的抽象方法时,也需要声明为抽象类。
接口interface和抽象类abstract的区别是什么(https://www.cnblogs.com/Mryang-blog-cn/p/cxl.html)
接口中所有的方法都不能有实现,并且不能指定方法的访问修饰符 。抽象类中可以有方法的实现,也可以指定方法的访问修饰符;继承接口的类必须实现接口里的所有方法 而抽象类中抽象方法的实现是由第一个非抽象的派生类来实现
类的私有成员会被子类继承吗?请说明原因。
会被子类继承,但是不能被访问。所以看上去是不能被继承的,实际上确实被继承了。
什么是装箱和拆箱
从值类型接口转换到引用类型装箱。从引用类型转换到值类型拆箱。object objValue = 4;int value = (int)objValue;
能用foreach遍历访问的对象需要实现 ________________接口或声明________________方法的类型。
IEnumerable 、 GetEnumerator
是否可以继承String类
String类是final类故不可以继承
什么是虚函数?什么是抽像函数?
虚函数:可由子类继承并重写的函数。抽像函数:规定其非虚子类必须实现的函数,必须被重写。
C#中 property 与 attribute(https://blog.csdn.net/baidu_16312167/article/details/52089745)的区别,他们各有什么用处,这种机制的好处在哪里
一个是属性,用于存取类的字段,一个是特性,用来标识类,方法等的附加性质
public static const int A=1;这段代码有错误么
const不能用static修饰(const表示静态常数,已经是静态了)
Lua:
if:认为false和nil为假,true 和非nil为真。要注意的是Lua中 0 为 true。
==:Lua认为false不等于nil。false是有效值,而nil是无效值。
table.sort:排序函数必须是稳定的。不稳定会造成问题。
面向对象:
function class(classname, super) local superType = type(super) local cls if superType ~= "function" and superType ~= "table" then superType = nil super = nil end if superType == "function" or (super and super.__ctype == 1) then -- inherited from native C++ Object cls = {} if superType == "table" then -- copy fields from super for k,v in pairs(super) do cls[k] = v end cls.__create = super.__create cls.super = super else cls.__create = super end cls.ctor = function() end cls.__cname = classname cls.__ctype = 1 function cls.new(...) local instance = cls.__create(...) -- copy fields from class to native object for k,v in pairs(cls) do instance[k] = v end instance.class = cls instance:ctor(...) return instance end else -- inherited from Lua Object if super then cls = clone(super) cls.super = super else cls = {ctor = function() end} end cls.__cname = classname cls.__ctype = 2 -- lua cls.__index = cls function cls.new(...) local instance = setmetatable({}, cls) instance.class = cls instance:ctor(...) return instance end end return cls end
元表、元方法:http://www.cnblogs.com/herenzhiming/articles/5798726.html
__add (+) __sub (-) __div (/) __mod (%) __unm 取负 __concat (..) __eq (==) __lt ( < ) __le ( <= )
__call
__call = function(t, a, b, c, whatever) return (a + b + c) * whatever end t(1, 2, 3, 4) -- 24
__tostring 定义如何将一个table转换成字符串
__index 过键来访问table的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的 __index 键
__newindex 类似 __index , __newindex 的值为函数或table,用于按键赋值的情况
C++:
面向对象是一种对现实世界理解和抽象的方法、思想,通过将需求要素转化为对象进行问题处理的一种思想。
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用仅是个别名。“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小。程序为指针变量分配内存区域,而引用不需要分配内存区域。
用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。
java也有内存泄漏:HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
Set<Person> set = new HashSet<Person>(); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孙悟空","pwd2",26); Person p3 = new Person("猪八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 set.remove(p3); //此时remove不掉,造成内存泄漏
"标准"宏MIN : #define MIN(A,B) ((A) <= (B) ? (A) : (B))
int *a[10]; // An array of 10 pointers to integers
int (*a)[10]; // A pointer to an array of 10 integers
int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
int const * a const 指针指向的整型数是不可修改的,同时指针也是不可修改的
volatile:优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。 例子:由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!
int square(volatile int *ptr) { return *ptr * *ptr; }
#include 和 #include “filename.h”:前者用来包含开发环境提供的库头文件,后者用来包含自己编写的头文件
在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”声明:函数和变量被C++编译后在符号库中的名字与C语言的不同,被extern "C"修饰的变
量和函数是按照C语言方式编译和连接的。由于编译后的名字不同,C++程序不能直接调
用C 函数。
C++模板:型无关的,因此具有很高的可复用性。
template <class T> T GetMax (T a, T b) { return (a>b?a:b); }
template <class T> T GetMax (T a, T b) {
return (a>b?a:b);
}
程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。
typedef union {long i; int k[5]; char c;} DATE: 里面最大的变量类型是int[5], 占用20个字节. 所以它的大小是20。
inline:对于一个频繁使用的短小函数。在内部的工作就是在每个for循环的内部任何调用dbtest(i)的地方都换成了(i%2>0)?”奇”:”偶”,这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。
inline char* dbtest(int a) { return (i % 2 > 0) ? "奇" : "偶"; }
进程和线程的差别:
线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
虚函数:每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针。

构造函数中可以调用虚函数吗
可以,但是没有动态绑定的效果,父类构造函数中调用的仍然是父类版本的函数,子类中调用的仍然是子类版本的函数
内联函数、构造函数、静态成员函数可以是虚函数?
内联函数需要在编译阶段展开,而虚函数是运行时动态绑定的,编译时无法展开;构造函数用来创建一个新的对象,而虚函数的运行是建立在对象的基础上,在构造函数执行时,对象尚未形成,所以不能将构造函数定义为虚函数;静态成员函数是以类为单位的函数,与具体对象无关,虚函数是 与对象动态绑定的,因此是两个不冲突的概念
在什么情况下,析构函数需要是虚函数?
若存在类继承关系并且析构函数中需要析构某些资源时,析构函数需要是虚函数,否则当使用父类指针指向子类对象,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,造成内存泄露等问题。
一个对象访问普通成员函数和虚函数哪个更快?
访问普通成员函数更快,因为普通成员函数的地址在编译阶段就已确定,因此在访问时直接调 用对应地址的函数,而虚函数在调用时,需要首先在虚函数表中寻找虚函数所在地址,因此相比普 通成员函数速度要慢一些。
C++中包含哪几种强制类型转换?他们有什么区别和联系?
reinterpret_cast: 转换一个指针为其它类型的指针。它也允许从一个指针转换为整数类型,反之亦 然. 这个操作符能够在非相关的类型之间转换. 操作结果只是简单的从一个指针到别的指针的值的 二进制拷贝. 在类型之间指向的内容不做任何类型的检查和转换。
static_cast: 允许执行任意的隐式转换和相反转换动作(即使它是不允许隐式的),例如:应用到类 的指针上, 意思是说它允许子类类型的指针转换为父类类型的指针(这是一个有效的隐式转换), 同 时, 也能够执行相反动作: 转换父类为它的子类
dynamic_cast: 只用于对象的指针和引用. 当用于多态类型时,它允许任意的隐式类型转换以及相 反过程. 不过,与static_cast不同,在后一种情况里(注:即隐式转换的相反过程),dynamic_cast 会检查操作是否有效. 也就是说, 它会检查转换是否会返回一个被请求的有效的完整对象。检测在 运行时进行. 如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL. 对于引用 类型,会抛出bad_cast异常
const_cast: 这个转换类型操纵传递对象的const属性,或者是设置或者是移除。
define和inline有什么区别?
define只是简单的宏替换,通常会产生二义性;
inline函数是否展开由编译器决定,有时候当函数太大时,编译器可能选择不展开相应的函数
数据结构与算法:http://blog.jobbole.com/110835/
堆排序:
现有一个整数number,请写一个方法判断这个整数是否是2的N次方:
return (num & num - 1) == 0;
求1000!的未尾有几个0:
1000/5=200 (1000里面含有200个5的倍数,但同时也包含了25倍数,125的倍数,625的倍数各一次)
1000/25=40(1000里面含有40个25的倍数,同时也含有125的倍数,625的倍数各一次)
1000/125=8(1000里面含有8个125的倍数,同时也含有625的倍数)
1000/625=1(1000里含有1个625的倍数)
所以1000!里面含 有0的个数为200+40+8+1=249个
二叉树遍历:跟左右 左跟右 左右跟

哈夫曼树的构造过程:首先将原始数据从大到小排序,然后每次从堆中选取值最小两个节点,计算他们的权重之和,作为一个新节点的值。再选取剩余最小,构造一个最大堆。

有一个词典,包含N个英文单词,现在任意给一个字符串,设计算法找出包含这个字符串的所有英文单词
首先把这N个英文单词建立一颗字典树,然后再用给定的字符串到字典树中去找,如果找到就是存在的,找不到就是不存在的。但是如果字典树的深度比较深的话是比较耗费内存的,这时候我们可以使用"字典树+hash表"的方式解决,例如:我们将字典树第10层以后的数据存放到hash表里面。
5亿个整数找它们的中位数,只有1G内存
5亿个整数大约需要2G的内存才能表示的下。我们可以先对整数划分若干个区间,然后每个区间都建立一个小文件,再遍历这个大文件,将里面的数按照大小放到相应的小文件中(1-50放在一个文件,51-100放在一个文件),最后我们只要统计出每个文件中整数的个数,就可以计算出中位数的位置。
给定两个文件,分别有100亿个整数,只提供1G内存,如何找出两文件交集
把一个文件切分成100份,分别把每一份加载到内存中,然后用第二个文件中的数据到每一份中都进行查找。这种方法的时间复杂度是O(N^2)
给定100亿个整数,设计算法找到只出现一次的整数
把这100亿个整数分成100份,这样算下来平均每份大约占400M左右。再把每一份加载到内存中,使用哈希表进行查找只出现一次的数,最后再将这100份的结果汇总到一起再进行查找。
25匹马,5个跑道,每个跑道最多能有1匹马进行比赛,找出前三名比赛多少次?
首先将25匹马分成5组,并分别进行5场比赛之后得到的名次排列如下:
A组: [A1 A2 A3 A4 A5]
B组: [B1 B2 B3 B4 B5]
C组: [C1 C2 C3 C4 C5]
D组: [D1 D2 D3 D4 D5]
E组: [E1 E2 E3 E4 E5]
其中,每个小组最快的马为[A1、B1、C1、D1、E1]。
将[A1、B1、C1、D1、E1]进行第6场,选出第1名的马,不妨设 A1>B1>C1>D1>E1. 此时第1名的马为A1。
将[A2、B1、C1、D1、E1]进行第7场,此时选择出来的必定是第2名的马,不妨假设为B1。因为这5匹马是除去A1之外每个小组当前最快的马。
进行第8场,选择[A2、B2、C1、D1、E1]角逐出第3名的马。
渲染:http://www.cnblogs.com/herenzhiming/articles/7324035.html
渲染管线流程:
本地坐标->世界坐标->观察者坐标->投影->齐次裁剪坐标->裁剪->窗口坐标->光栅化
---------VS-------------------------------
光栅化:三角形设定,三角形设定阶段主要用来计算三角形表面的差异和三角形表面的其他相关数据。三角形遍历,进行逐像素检查操作,检查该像素处的像素中心是否由三角形覆盖,而对于有三角形部分重合的像素,将在其重合部分生成片段(fragment)。PS,贴图。融合,每个像素的信息都储存在颜色缓冲器中,而颜色缓冲器是一个颜色的矩阵列(每种颜色包含红、绿、蓝三个分量)。融合阶段的主要任务是合成当前储存于缓冲器中的由之前的像素着色阶段产生的片段颜色。不像其它着色阶段,通常运行该阶段的GPU子单元并非完全可编程的,但其高度可配置,可支持多种特效。
光照模型:
Lambert模型(漫反射)
环境光:
Iambdiff = Kd*Ia
其中Ia 表示环境光强度,Kd(0<K<1)为材质对环境光的反射系数,Iambdiff是漫反射体与环境光交互反射的光强。
方向光:
Ildiff = Kd * Il * Cos(θ)
其中Il是点光源强度,θ是入射光方向与顶点法线的夹角,称入射角(0<=A<=90°),Ildiff是漫反射体与方向光交互反射的光强,若 N为顶点单位法向量,L表示从顶点指向光源的单位向量(注意顶点指向光源),则Cos(θ)等价于dot(N,L),故又有:
Ildiff = Kd * Il * dot(N,L)
最后综合环境光和方向光源,Lambert光照模型可以写成:
Idiff = Iambdiff + Ildiff = Kd * Ia + Kd * Il * dot(N,L)
Phong模型(镜面反射)
Phong模型认为镜面反射的光强与反射光线和视线的夹角相关:
Ispec = Ks * Il * ( dot(V,R) )^Ns
其中Ks 为镜面反射系数,Ns是高光指数,V表示从顶点到视点的观察方向,R代表反射光方向。由于反射光的方向R可以通过入射光方向L(从顶点指向光源)和物体的法向量求出,
R + L = 2 * dot(N, L) * N 即 R = 2 * dot(N,L) * N - L
所以最终的计算式为:
Ispec = Ks * Il * ( dot(V, (2 * dot(N,L) * N – L ) )^Ns
Blinn-Phong光照模型(修正镜面光)
Blinn-Phong是一个基于Phong模型修正的模型,其公式为:
Ispec = Ks * Il * ( dot(N,H) )^Ns
其中N是入射点的单位法向量,H是光入射方向L和视点方向V的中间向量,通常也称之为半角向量(半角向量被广泛用于各类光照模型,原因不但在于半角向量蕴含的信息价值,也在于半角向量是很简单的计算:H = (L + V) / |L + V| )。
3D数学(游戏客户端开发数学非常重要)
矩阵:
线段与多边形相交判断:
多边形与多边形相交判断:
设计模式
单例:略
工厂:
网络
ISO的七层模型:

protobuff:
zlib:
int compress (Bytef *dest,uLongf *destLen,const Bytef *source,uLong sourceLen);默认级别6
int compress2(Bytef *dest,uLongf *destLen,const Bytef *source, uLong sourceLen,int level);level (1,...,9)
int uncompress (Bytef *dest,uLongf *destLen,const Bytef *source, uLong sourceLen);
libcurl:
easy_handle = curl_easy_init();
curl_easy_setopt(easy_handle, CURLOPT_URL, ftp://127.0.0.1/upload.html);
curl_easy_setopt(easy_handle, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(easy_handle, CURLOPT_READFUNCTION, &read_data); //read_data读取数据的回调函数。
curl_easy_setopt(easy_handle, CURLOPT_READDATA, fp);
curl_easy_setopt(easy_handle, CURLOPT_INFILESIZE_LARGE, file_size);
code = curl_easy_perform(easy_handle);
curl_easy_cleanup(easy_handle);
curl_global_cleanup();
DC优化:
- 使用DC Batching,也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。具体下面会介绍。
- 通过把纹理打包成图集来尽量减少材质的使用。
- 尽量少的使用反光,阴影啦之类的,因为那会使物体多次渲染。projection 投影少一些。
-
使用Occlusion Culling算法,减少可见的物体数量同时也可以减少Draw Call。
-
删除不必要的Shader中的Pass通道,因为一个Pass对应一个DC。
Dynamic Batching 动态批处理
1、动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理,而且动态批处理操作是自动完成的,并不需要你进行额外的操作。(Saved By Batching: 节约DC数目)
静态批处理
1.批处理动态物体需要在每个顶点上进行一定的开销,所以动态批处理仅支持小于900顶点的网格物体,如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;请注意:属性数量的限制可能会在将来进行改变。
2、统一缩放的物体和非统一缩放的物体放在一起进行批处理,如果相同统一缩放尺度一样的物体为n>2时,可以减少DC为n-2;同样当统一缩放的物体和非统一缩放的物体放在一起进行批处理,如果非统一缩放(没有尺度可言)一样的物体为n>2时,可以减少DC为n-2,如(1,1,1),(1,2,3),(1,2,2),(1,2,2)其减少的DC为1。
3、Static Batching网格会合并。
优化GC
触发:在堆内存上进行内存分配操作而内存不够的时候都会触发垃圾回收来利用闲置的内存; GC会自动的触发,不同平台运行频率不一样; GC可以被强制执行。
执行操作:GC会检查堆内存上的每个存储变量;对每个变量会检测其引用是否处于激活状态;如果变量的引用不再处于激活状态,则会被标记为可回收;被标记的变量会被移除,其所占有的内存会被回收到堆内存上。
降低影响:
避免频繁申请内存;
字符串不必要的创建、Debuglog;
对象池;
1.压缩自带类库;
2.将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;
3.释放AssetBundle占用的资源;
4.降低模型的片面数,降低模型的骨骼数量,降低贴图的大小;
5.使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设(Prefab)。

浙公网安备 33010602011771号