unity算法面试_Unity面试准备
前言
為Unity面試而準備一些問題,如果有什么說的不對的,歡迎指正
語言機制類
1、GC機制
在C/C++中程序員要自己負責系統的內存管理,而C#采用Garbage Collection自動回收內存,
當Java虛擬機(VM)或.NETCLR發覺內存資源緊張的時候,就會自動地去清理無用對象(沒有被引用到的對象)所占用的內存空間,
查找沒有被引用的對象有一系列的算法,
Lua語言的GC機制采用是Mark and Sweep算法。
2、協程線程進程
進程一個具有獨立功能的程序在一個數據集合上的一次動態執行過程(程序是靜態的代碼,進程是動態執行的過程)
線程:進程是分配資源的基本單位,而線程則是系統調度的基本單位。
一個進程內部的線程可以共享該進程的所分配到的資源。線程的創建與撤消,線程之間的切換所占用的資源比進程要少很多。
總的來說就是為了更進一步提高系統的并發性,提高CPU的利用率。
協程:是一種比線程更加輕量級的存在,協程調度不是被操作系統內核所管理,而完全是由程序所控制(也就是在用戶態執行),你可以隨時把它掛起。
一個線程里可以有多個協程。協程的切換幾乎沒有操作系統的內核開銷,不會像線程切換那樣消耗資源(線程切換要改Context)。
協程的一大好處就是可以避免數據訪問沖突的問題因為它的粒度相對多線程的大很多,所以往往很少出現沖突現象。協程寫入死循環會將線程卡死。
(配圖有些問題,yield return null是讓協程在下一個yield null執行,如果在update前啟動了協程,那么在update后的yield的null是會執行的)
3、裝箱與拆箱
值類型變量封裝object類型變量或者對應的object將這個值類型從棧區copy到堆區產生一個引用類型叫做裝箱;
把這個堆上的obj轉成值類型,從堆上往棧上搬東西叫做拆箱,裝箱和拆箱會損失一定性能。
按理說C#被設計成一種完全面向對象的語言。因此,包括數字、字符、日期、布爾值等等在內的一切,都是對象。似乎只需要一種方式來對待這些對象就可以了。
但是C#必須為性能而妥協,我們知道,對于CPU來說,處理一個完整的對象,需要很多的指令,
對于內存來說,又需要很多的內存。如果連整數都是對象,那么性能自然很低。
C#于是使用了一種機制,使得這些基本類型在一般的編程中被當作非對象的簡單類型處理,
在另一些場合(比如需要多個腳本引用同一個對象)又允許它們被視作是一個對象。(摘自評論)
4、值類型和引用類型的區別
值類型的數據存儲在內存的棧中,引用類型的數據存儲在內存的堆中
值類型存取速度快,引用類型存取速度慢
類型表示實際數據放在棧中;引用類型表示指向存儲在內存堆中的數據的指針或引用,在棧中存放實際數據的地址,堆上存放實際數據
棧的內存分配是自動釋放;而堆在.NET中會有GC來釋放
值類型繼承自System.ValueType,引用類型繼承自System.Object
5、lua如何實現面向對象?lua的元表?
由于暫時沒怎么學lua,TODOOOOOOOOOO
6、閉包
閉包是指有權訪問另一個函數作用域中的變量的函數
閉包這個詞本身指的是一種函數。而創建這種特殊函數的一種常見方式是在一個函數中創建另一個函數。
閉包中引用的變量實際上就是把原來聲明在主體中的變量轉移到用匿名密封類或者結構體里,
最終在主體中訪問的變量就是生成的匿名類中對象。來自博客
C#里可以在函數里放一個委托或者匿名函數引用局部變量。
7、反射
程序一般都是操作數據,但有時程序也操作程序和程序類型本身的信息,這種信息稱為元數據,意義為數據的數據。
反射指程序可以訪問、檢測和修改它本身狀態或行為的一種能力。
程序集包含模塊,而模塊包含類型,類型又包含成員。反射則提供了封裝程序集、模塊和類型的對象。
您可以使用反射動態地創建類型的實例,將類型綁定到現有對象,或從現有對象中獲取類型。然后,可以調用類型的方法或訪問其字段和屬性。它允許程序創建和控制任何類的對象,無需知道目標類,沒有類型依賴,而且還能訪問對象的各個成員。
Unity中的GetComponent的方法都是使用了反射機制,獲取當前GameObject下動態運行的組件,然后對其進行操作。大部分情況我們使用的是被封裝好的反射。
Unity常用組件類
1、Canvas的三種渲染模式
Screen Space - Overlay:UI元素直接覆蓋在任何攝像機的上方。
Screen Space - Camera:需要指定一個攝像機,UI組件可以控制和攝像機的距離,可以和場景里的物體做一些遮擋之類的交互。
World Space :畫布相對于世界空間,和場景物體一樣有遮擋關系,可以旋轉,3d UI制作,沒有屏幕自適應。
2、Mono生命周期
分階段作展示,這里就不配圖了,主要是生命周期太長了
初始化:Awake -> OnEnable ->Start ->
當腳本實例被載入時,Awake()將會被調用,你可以在這里進行初始化操作。 GameObject如果為一開始setActive為false則一開始不會執行。
當對象變為激活狀態時(active = true),就會調用這個方法。一般來說,添加委托或事件可以放在 OnEnable(),取消委托或事件可以放在 OnDisable()。
Awake Start一次,OnEnable設置enabled可多次執行
物理計算:FixedUpdate -> OnTrigger/Collision->
游戲邏輯:InputEvent -> Update-> yield(喚醒掛起的協程,前面fixedUpdate也有yield) ->
動畫運算:OnAnimatorMove/IK(取決于Animator的 UpdateMode怎么選而有區別,如果是Normal則處于Update后,如果是Physics的話則處于FixedUpdate之后)
-> LateUpdate->
渲染:Scence Rendering -> Gizmos ->GUI ->
應用處理:OnApplicationPause -> OnApplicationQuit ->
OnApplicationQuit:在退出游戲之前,將在所有的游戲對象上調用該方法。如果是在編輯器中,那么就是當用戶點下停止按鈕時調用。
退出:OnDisable -> OnDestory
等Update 執行完畢后 在去執行LateUpdate,LateUpdate常用于攝像機跟隨等滯后操作,Update里適合寫邏輯
3、堆Heap棧Stack
堆中存儲的信息在被調用完畢不會立即被清理掉。
【申請速度】
堆的申請速度較慢,容易產生內存碎片,但是用起來比較方便。
而棧的申請速度較快,但卻不受程序員的控制。
【申請大小】
棧申請的容量較小1-2M,堆申請大小雖受限于系統中有效的虛擬內存,但較大
【存儲內容】
棧負責保存我們代碼執行或調用的路徑,而堆則負責保存對象或者說數據的路徑。
棧是自行維護的,也就是說內存自動維護棧,當棧頂的內容不再被使用,該內容將會被跑出。相反,堆需要考慮垃圾回收。棧會保存函數的調用入口
如何決定放哪?
這里有一條黃金規則:
1.引用類型總是放在堆中。
2.值類型和指針總是放在他們被聲明的地方。
4、存儲的幾種方式
PlayerPref本地持久化
小的短的數據,面板的屬性設置可以使用
每個變量一個key GetInt SetInt等方法,值可以在注冊表里查看
BinaryFormatter
二進制存儲,序列化、反序列化(強制類型轉換),字節流存儲,文件不具有可讀性
對于字節數組,使用FileStream寫入
save class要有serializable的特性
頭文件 system.IO
二進制文件大小端問題文件存儲不兼容?
JSON
JavaScript Object Notation
一種數據交換的格式,便于人的閱讀和編寫
常用于客戶端和服務器傳輸和整個場景的存儲信息
輕量的交換語言,格式壓縮,占用帶寬小,解析速度快
具有自我描述性,key-value鍵值對,作為文本格式即是一個string類型的
{ }表示對象,[ ]表示數組
創建對象存儲,序列化save對象為string,反序列化string變為save對象
JsonUtility是unity自帶的解析工具FromJson和ToJson,FromJson 模板需要指定類型
向文件中寫入一些string時使用StreamWriter/Reader類,他對不同編碼有不同處理格式
常用Application.dataPath或Application.persistDataPath + “\name.json”,persistDataPath工程是隱藏的
C#中的List對象會變成Json的[ ]
需要自動換行的軟件幫助
XML
設計用來結構化存儲和傳輸數據
eXtensible Markup Language:可標記擴展語言
有時大量的數據需要經過轉換才能被讀取,部分不兼容的數據可能在這個過程中丟失
xml以純文本格式,本質就是交換字符串,數據可以供各種數據使用
Tag是自定義的
可以使用XmlDocument類來保存、加載(System.Xml)
保存:
CreatElement創建節點并給出tag名字
AppendChild添加子節點
xmlElement.inerText可以獲取xmlElement節點的內部信息
所有的節點都要按照實例化節點和添加的方式來使用
最后把root添加進XmlDocument里去,用save把它保存到一個text文件里
加載:
getElementsByTagName,可以獲得一個XmlNodeList,只有一個元素則獲取一個
同樣使用InerText加載到游戲數據中
存儲是一種樹狀結構
相比JSON代碼量多很多,且里面有大量的冗余標簽(表示結構)
解析xml也要花費較多的時間
多數時候我們不需要冗余信息,但是一旦需要的時候沒有就是不行。
根據項目的需求選擇適合的
數據結構類
1、Dictionary的插入時間復雜度?
Dictionary內部實現為一個哈希表,通過設置好的哈希映射,達到常數級的時間復雜度。
2、平衡二叉樹的插入時間復雜度?
查詢是Log n的,查到應該插在哪即可用常數級去插入,合起來時間復雜度Log n。
3、手寫快排
快速排序思想:
關鍵步驟:1、選主元;2、兩個指針從兩頭掃描確定大于主元和小于主元的集合;3、對兩個區間遞歸
快速排序對小規模的數據不如使用插入排序,數據大概在1000左右時兩個性能差不多
void InsertionSort(vector& A, int left, int right)
{ /* 插入排序 */
int P, i;
int Tmp;
for (P = left + 1; P <= right; P++) {
Tmp = A[P]; /* 取出未排序序列中的第一個元素*/
for (i = P; i > left && A[i - 1] > Tmp; i--)
A[i] = A[i - 1]; /*依次與已排序序列中元素比較并右移*/
A[i] = Tmp; /* 放進合適的位置 */
}
}
int Median3(vector& A, int Left, int Right)
{
//采用頭中尾取中位數的方式確定主元,
//確定好主元之后再把主元移動到左端或右端保持一個連續的集合來給quickSort使用
int Center = (Left + Right) / 2;
if (A[Left] > A[Center])
swap(A[Left], A[Center]);
if (A[Left] > A[Right])
swap(A[Left], A[Right]);
if (A[Center] > A[Right])
swap(A[Center], A[Right]);
/* 此時A[Left] <= A[Center] <= A[Right] */
swap(A[Center], A[Right - 1]); /* 將基準Pivot藏到右邊*/
/* 只需要考慮A[Left+1] … A[Right-2] */
return A[Right - 1]; /* 返回基準Pivot */
}
void Qsort(vector& A, int Left, int Right)
{ /* 核心遞歸函數 */
int Pivot, Cutoff, Low, High;
Cutoff = 1000;
if (Cutoff <= Right - Left) { /* 如果序列元素充分多,進入快排 */
Pivot = Median3(A, Left, Right); /* 選基準 */
Low = Left; High = Right - 1;
while (1) { /*將序列中比基準小的移到基準左邊,大的移到右邊*/
while (A[++Low] < Pivot);
while (A[--High] > Pivot);
if (Low < High) swap(A[Low], A[High]);
else break;
}
swap(A[Low], A[Right - 1]); /* 將基準換到正確的位置 */
Qsort(A, Left, Low - 1); /* 遞歸解決左邊 */
Qsort(A, Low + 1, Right); /* 遞歸解決右邊 */
}
else InsertionSort(A, Left, Right); /* 元素太少,用簡單排序 */
}
void QuickSort(vector& A, int N)
{ /* 統一接口 */
Qsort(A, 0, N - 1);
}
4、散列算法&&哈希沖突
散列算法:所謂查找就是已知對象找位置。如何快速搜索到需要的關鍵詞?如果關鍵詞是字符串不方便比較怎么辦(strcmp)?散列查找就是為了解決上面的問題,它主要做兩件事:計算位置、解決沖突;
通過構造散列函數來計算關鍵詞的存儲位置,如果構造的函數計算多個關鍵詞的存儲位置相同則要解決沖突。
他的時間復雜度是O(1),查找時間和問題規模無關,評價散列函數的性能主要看成功平均查找次數和不成功平均查找次數,表中的元素不應當過多,裝填因子超過0.85或其他表的查找就會由于沖突而性能下降。
裝填因子公式:m為表的大小,n為元素個數
alpha = n / m
常見的計算位置的方法:
對數字:
直接定址:一個線性的安排
H(key) = a *key+b
除留余數:TableSize為表的大小,一般取素數
H(key) = key % TableSize
數字分析:比如身份證號,取隨機性大的幾位作為地址
折疊法:56793542轉變為 542 + 793+056=1391 取三位391
平方取中法:顧名思義,把這個數平方然后取中間幾位,每一位對運算結果都有影響
對字母:
會想辦法把它映射成一個整數
前幾個字母ASCII相加求余:沖突比較嚴重,eat和tea等
移位法:讓每一位字母都起作用,每一位字母看成一個26進制的一個數字,然后按照進制轉換的方式變成一個10進制的值再和TableSize求余(用32是因為2五次方可以用位運算)
常見的解決沖突的辦法:
分離鏈接法:表是一個鏈表頭節點的結構數組,沖突的關鍵詞放該位置的鏈表上,鏈長了查找效率會下降
線性探測:設置一個線性的增量序列:1,2,3,4,5...,在當前位置沖突了,就有
H_1(key) = (H(key)+i) mod TableSize
這樣的缺點是會有沖突聚集現象
平方探測:增量序列有變化 di = +-i^2,左看看右看看,一般結合TableSize為 = 4k+3保證平方探測法不會出現死循環而探測不全空間
上面兩種探測都屬于開放定址法:要注意Hash表刪除數據時并不能真的刪除,而是要打上標記,以防止其他因為這個結點而產生沖突的數據查找不到
雙哈希:沖突了就換個哈希算法再算一次
Resize表的大小:隨著元素變多則考慮把所有元素重構一遍
設計模式類
1、MVC
Model模型對象
View 展示界面
Controller 控制器
用戶使用Controller里面的方法控制Controller關聯的一個Model,然后更新View視圖,Controller相當于一個訪問對象的中介器。
個人感覺unity、一般的3d建模、繪圖軟件的結構都是MVC的,這種模式用于應用程序的分層開發。
2、工廠模式
管理對象創建,用來解耦編譯階段的new關鍵字而用工廠方法去完成對象的創建,調用者通過制定一個對象工廠來確定創建什么對象,是一種從編譯耦合到運行耦合的轉移
紅色為穩定部分,一個是產品基類一個是工廠基類
總結
以上是生活随笔為你收集整理的unity算法面试_Unity面试准备的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: windows 下安装wamp环境
- 下一篇: 第14章:傅里叶变换