1.Untiy3D中C#的底層原理
Unity底層在運(yùn)行C#程序時(shí)有兩種機(jī)制:一種是Mono,另一種是IL2CPP。Mono存在的目的是為了跨平臺(tái),因?yàn)樽畛魿#只支持Windows。而IL可以看成是一種匯編語(yǔ)言且完全基于堆棧,必須運(yùn)行在虛擬機(jī)上。也就是說(shuō)C#會(huì)被編譯器編譯成IL,當(dāng)需要他們時(shí)就會(huì)被實(shí)時(shí)的加載到運(yùn)行庫(kù)中,由虛擬機(jī)動(dòng)態(tài)的編譯成匯編代碼(JIT)再進(jìn)行執(zhí)行。
注:Unity中其他的兩門腳本語(yǔ)言Boo,Untiy Script(這兩暫時(shí)還沒(méi)接觸到)也是被編譯成IL后再由Mono虛擬機(jī)解釋并執(zhí)行的。
IL有三種轉(zhuǎn)譯模式:
1、Just-in-time(JIT)模式:在程序運(yùn)行過(guò)程中將CIL(IL)轉(zhuǎn)譯為機(jī)器碼。
2、Ahead-of-Time(AOT)模式:將IL轉(zhuǎn)譯成機(jī)器碼并存儲(chǔ)在文件中,此文件并不能完全獨(dú)立運(yùn)行。通常此種模式可產(chǎn)生出絕大部分JIT模式所產(chǎn)生的機(jī)器碼,只有部分例外。例如trampolines或是控制監(jiān)督相關(guān)的代碼任然需要JIT來(lái)運(yùn)行。
3、完全靜態(tài)編譯:這種模式只支持少數(shù)平臺(tái),它基于AOT編譯模式更進(jìn)一步所產(chǎn)生的機(jī)器碼。完全靜態(tài)編譯可以使得程序在運(yùn)行期間不需要JIT,這種做法適用于IOS,PS3,Xbox360等不允許使用JIT的操作系統(tǒng)
?
Mono使用垃圾回收機(jī)制來(lái)管理內(nèi)存,應(yīng)用程序向垃圾回收器申請(qǐng)內(nèi)存,最終由垃圾回收器決定是否回收。當(dāng)我們向垃圾回收器申請(qǐng)內(nèi)存時(shí),如果發(fā)現(xiàn)內(nèi)存不足,就會(huì)自動(dòng)出發(fā)垃圾回收(也可以主動(dòng)觸發(fā)),垃圾回收器會(huì)遍歷內(nèi)存中所有對(duì)象的引用關(guān)系,如果沒(méi)有被任務(wù)對(duì)象引用則會(huì)釋放內(nèi)存。
3.1.1之后Mono正式將(Simple Generational GC)SGen-GC設(shè)置為默認(rèn)的垃圾回收器,這種方式的主要思想是將對(duì)象分給兩個(gè)內(nèi)存池,一個(gè)新的一個(gè)老的。那些存活時(shí)間長(zhǎng)的對(duì)象都會(huì)放到較老的對(duì)象池中。這種設(shè)計(jì)基于一個(gè)事實(shí),程序經(jīng)常會(huì)申請(qǐng)一些小的臨時(shí)對(duì)象,用完了馬上釋放,而如果某個(gè)對(duì)象一直不釋放則,往往以后的很長(zhǎng)時(shí)間內(nèi)也不會(huì)被釋放。
此外,我們稱IL編碼為托管代碼,這部分代碼由C#自動(dòng)生成,由虛擬機(jī)JIT編譯執(zhí)行,且其中的對(duì)象由GC管理。而使用C++或C#以不安全類型寫的代碼稱為非托管代碼,虛擬機(jī)無(wú)法追蹤到這類代碼對(duì)象,故程序員需要對(duì)這部分代碼的對(duì)象手動(dòng)進(jìn)行管理避免內(nèi)存泄漏的問(wèn)題。(一般情況下,我們使用托管代碼來(lái)編寫游戲邏輯,非托管代碼用于更底層的架構(gòu)、第三方庫(kù)或者是操作系統(tǒng)相關(guān)接口)
?
(中間一部分為 List與Dictionary的源碼剖析,可自行查閱源碼進(jìn)行學(xué)習(xí))
這里只對(duì)部分函數(shù)做結(jié)論和總結(jié):
List:
List使用Array數(shù)組作為底層數(shù)據(jù)結(jié)構(gòu),優(yōu)點(diǎn)是隨機(jī)存儲(chǔ),索引快,缺點(diǎn)是擴(kuò)容時(shí)會(huì)很糟糕,每次針對(duì)數(shù)組進(jìn)行new操作都會(huì)造成內(nèi)存垃圾,會(huì)給GC帶來(lái)負(fù)擔(dān)。
Remove()函數(shù),原理是使用Array.Copy()對(duì)數(shù)組進(jìn)行覆蓋,復(fù)雜度為O(N).
Clear()函數(shù),并不會(huì)對(duì)內(nèi)存進(jìn)行操作真正的刪除元素,而是將對(duì)象內(nèi)的count置0,因?yàn)閷?duì)內(nèi)存進(jìn)行操作會(huì)產(chǎn)生極大的性能消耗。
Enumerator()接口,這是枚舉迭代部分細(xì)節(jié)的接口,其中Enumerator這個(gè)結(jié)構(gòu),每次在獲取迭代器時(shí)都會(huì)被創(chuàng)建出來(lái)。如果大量使用迭代器,例如 foreach語(yǔ)句,就會(huì)產(chǎn)生大量的垃圾。這也就是為什么我們常常說(shuō)要避免在Update()中使用foreach的底層原因。?
Sort()函數(shù),內(nèi)部使用Array.Sort()接口,使用快速排序進(jìn)行排序。
總結(jié):
1.List的效率并不是很高,只是通用性強(qiáng),底層使用的大部分算法都是線性算法。
2.同時(shí)其內(nèi)存分配方式也是不合理的,當(dāng)增加元素需要對(duì)數(shù)組進(jìn)行擴(kuò)充時(shí),原數(shù)組會(huì)被拋棄,這會(huì)給GC帶來(lái)很大的壓力。
3.內(nèi)部代碼是線程不安全的,底層沒(méi)有對(duì)多線程做任何加鎖和限制,如實(shí)現(xiàn)需要多線程操作需要手動(dòng)加鎖。
?
Dictionary:
private struct Entry{ public int hashCode; // 低31位哈希值,如果未使用則為-1 public int next; // 下一個(gè)實(shí)例索引,如果是最后一個(gè)則為-1 public Tkey key; // 實(shí)例的Key public TValue value; // 實(shí)例的Value }
Dictionary使用鏈地址法來(lái)解決哈希沖突,內(nèi)部有兩個(gè)數(shù)組一個(gè)int類型的bucket,還有一個(gè)Entry類型(如上)的entries。
?其中bucket就是下圖中的紫色部分,也就是哈希表。
Entry代表了下圖中綠色部分,每一個(gè)Entry保存自己的Key,Value,以及哈希值,并且可以索引到下一個(gè)實(shí)例(如果有)。
總結(jié):
1. Dictionary在效率上與List一樣,最好在實(shí)例化對(duì)象時(shí)就確定大致的數(shù)量,這樣會(huì)減少分配內(nèi)存的頻率。另外,使用值作為鍵值比使用對(duì)象要更高效,因?yàn)轭悓?duì)象的Hash值通常都是由地址內(nèi)存再計(jì)算得到的。
2.從內(nèi)存操作上看,Dictionary增長(zhǎng)速度大約是兩倍,刪除數(shù)據(jù)時(shí),同List一樣不對(duì)內(nèi)存進(jìn)行操作
3.Dictionary是線程不安全的,需要自己進(jìn)行l(wèi)ock操作
2.浮點(diǎn)數(shù)精度問(wèn)題
在實(shí)際使用過(guò)程中,許多人都會(huì)想要使用double類型來(lái)代替float從而解決精度問(wèn)題,但最后往往以失敗告終。
因?yàn)闊o(wú)論是double類型還是float類型,在計(jì)算機(jī)底層存儲(chǔ)都是以二進(jìn)制的形式存儲(chǔ)的,并且存儲(chǔ)的位數(shù)有限(32位/64位)。所以十進(jìn)制在轉(zhuǎn)為二進(jìn)制時(shí)很多數(shù)字會(huì)丟失精度。
2.1精度問(wèn)題可能會(huì)發(fā)生的情況?
(1)數(shù)值比較不相等
編寫代碼時(shí)通常會(huì)遇到數(shù)值觸及閾值從而觸發(fā)邏輯的情景。例如某一個(gè)變量,從0開(kāi)始,每次加一個(gè)小于0.01的數(shù),加到剛好0.23時(shí)觸發(fā)事件。但是我們不能確定每次加多少,所以結(jié)果會(huì)出現(xiàn)要么比0.23大,要么比0.23小,很難會(huì)出現(xiàn)剛好等于0.23的情況。這時(shí)使用>,<,==等這些符號(hào)就不好處理,如果一定要比較,可以使用ABS()<0.001這樣的方式解決。
(2)數(shù)值計(jì)算不確定
例如x=1f, y=2f, z = (1f/5555f)* 11110f, 我們預(yù)期x/y應(yīng)當(dāng)是等于z的,但是由于精度問(wèn)題,z結(jié)果可能為0.4999999,這樣x/y與z就不會(huì)相等。
(3)不同設(shè)備計(jì)算結(jié)果不同
不同計(jì)算機(jī)上操作系統(tǒng),CPU寄存器都不同,計(jì)算時(shí)會(huì)有一定的精度偏差
2.2如何解決精度問(wèn)題?
(1)由一臺(tái)計(jì)算機(jī)進(jìn)行運(yùn)算
在網(wǎng)絡(luò)同步方面,可以使用一臺(tái)計(jì)算機(jī),例如特定的一臺(tái)服務(wù)器用來(lái)進(jìn)行計(jì)算。
(2)改用int,long類型來(lái)替代浮點(diǎn)數(shù)
例如,4080,與4.08*1000在數(shù)學(xué)中是完全成立的,我們可以使用4080當(dāng)做4.08進(jìn)行存儲(chǔ),從而避免把數(shù)據(jù)放在小數(shù)點(diǎn)后面(精度截?cái)啵?/p>
(3)用定點(diǎn)數(shù)保持一致性并縮小精度問(wèn)題
定點(diǎn)數(shù)就是,小數(shù)部分與整數(shù)部分分開(kāi)存儲(chǔ)。從而與方法二一樣,其小數(shù)部分可以使用int或long來(lái)存儲(chǔ),避免小數(shù)截?cái)嗟膯?wèn)題。
struct MyNum{ int Interger; int Decimal; }
(4)用字符串代替浮點(diǎn)數(shù)
當(dāng)精度要求特別高時(shí),可以使用字符串來(lái)代替浮點(diǎn)數(shù),這種方式不用擔(dān)心越界問(wèn)題,并且還可以自由的控制精度。缺點(diǎn)是很消耗CPU和內(nèi)存。使用字符串代替浮點(diǎn)數(shù),一次計(jì)算量相當(dāng)于好幾萬(wàn)次普通浮點(diǎn)數(shù)。所以當(dāng)精度要求特別高,且計(jì)算次數(shù)不多時(shí)可以考慮這種方法。
3.委托、事件、裝箱、拆箱
3.1委托與事件
C#中指針被封裝進(jìn)了底層類中,絕大多數(shù)的情況都看不到指針,但回調(diào)函數(shù)依然存在,于是C#中多了委托這一概念。委托可以看成是一種更高級(jí)的函數(shù)指針,它不僅會(huì)把地址指向另一個(gè)函數(shù),而且還能傳遞參數(shù)、獲取返回值等多個(gè)信息。此外,系統(tǒng)還會(huì)為委托對(duì)象自動(dòng)生成同步、異步的調(diào)用方法,開(kāi)發(fā)人員可以使用BeginInvoke(),EndInvoke()來(lái)避開(kāi)Thread類,從而直接使用多線程調(diào)用。
委托(delegate)不是一個(gè)語(yǔ)言的基本類型,在創(chuàng)建委托的時(shí)候?qū)嶋H上就是在創(chuàng)建一個(gè)delegate類的實(shí)例,這個(gè)delegate類繼承了Sysytem.MulticastDeleate類,類實(shí)例中有BeginInvoke(),EndInvoke(),Invoke()三個(gè)函數(shù),分別表示異步調(diào)用開(kāi)始,結(jié)束,以及直接調(diào)用。
注:我們不能直接寫一個(gè)類來(lái)繼承Sysytem.MulticastDeleate類,和它的父類Delegate類。官方文檔中的解釋是這兩個(gè)類是特殊的類,編輯器或其他工具可以從他這里繼承,但是你不能直接繼承它。
Delegate中有一個(gè)變量用來(lái)存儲(chǔ)函數(shù)地址可以認(rèn)為是一個(gè)鏈表。重寫了+=,-=操作符,用來(lái)向這個(gè)鏈表中添加或者刪除元素。也就是把函數(shù)地址加入或者刪除到鏈表中。
所以delegate關(guān)鍵字只是一個(gè)修飾用詞,背后C#編輯器會(huì)重寫代碼,我們可以認(rèn)為編譯過(guò)程把delegate關(guān)鍵字識(shí)別的對(duì)象轉(zhuǎn)譯成為Delegate類對(duì)象。
事件(event)則是在delegate上又做了一層封裝,這次封裝的意義是,限制用戶直接操作delegate實(shí)例中的變量的權(quán)限。封裝后,用戶不再能通過(guò)直接賦值的方式(=操作符)來(lái)改變委托變量。只能通過(guò)注冊(cè)或者注銷委托的方式來(lái)增減委托函數(shù)的數(shù)量。通過(guò)這樣的方式更好的維護(hù)了委托的秩序,增加了系統(tǒng)穩(wěn)定性。
3.2裝箱與拆箱?
裝箱:把值類型實(shí)例轉(zhuǎn)換成為引用類型實(shí)例。拆箱:把引用類型實(shí)例轉(zhuǎn)為成為值類型實(shí)例。(引用對(duì)象有:string類型,class實(shí)例,數(shù)組)(值對(duì)象有:所有整數(shù),浮點(diǎn)數(shù),bool,struct實(shí)例)
?
int a = 5; object obj = a;
上面就是一個(gè)簡(jiǎn)單的裝箱過(guò)程,因?yàn)閍是一個(gè)值類型,是直接有數(shù)據(jù)的變量,obj為引用變量,指針與內(nèi)存拆分開(kāi)來(lái),把a(bǔ)賦值給obj,實(shí)際上就是obj為自己創(chuàng)建了一個(gè)指針,并指向a的數(shù)據(jù)空間。
a = (int)obj
而這段代碼就是一個(gè)簡(jiǎn)單的拆箱的過(guò)程,相當(dāng)于把obj指向的內(nèi)存空間復(fù)制一份給了a,因?yàn)閍是值引用,所以它不允許指向某個(gè)內(nèi)存空間,只能靠復(fù)制數(shù)據(jù)來(lái)傳遞數(shù)據(jù)。
3.3為何需要裝箱?
值類型是在聲明之后就立即被初始化的了的,因?yàn)樗坏┞暶?,就有了自己的空間,因此它不可能為null,也不能為null。而引用類型在分配內(nèi)存以后,只是一個(gè)空殼子,可以認(rèn)為是指針,初始化后不指向任何空間,因此默認(rèn)為null。
這里要注意Struct部分,很多人會(huì)把它當(dāng)成是類,而類是引用類型,結(jié)構(gòu)體是值類型。所以a,b同是結(jié)構(gòu)的實(shí)例,a賦值給了b,而b更改數(shù)據(jù)之后a的數(shù)值并沒(méi)有改變。這是因?yàn)閍,b是值類型,各自占用了一片內(nèi)存空間,修改b對(duì)象不會(huì)對(duì)a有影響。在a賦值給b的過(guò)程實(shí)際上是將數(shù)據(jù)原封不動(dòng)復(fù)制一份給了b。
后面涉及到了堆棧內(nèi)存,所以來(lái)說(shuō)說(shuō)堆棧內(nèi)存是怎么回事?
棧是存放對(duì)象的一種特殊的容器,它是最基本的數(shù)據(jù)結(jié)構(gòu)之一,遵循先進(jìn)后出的原則。它是一段連續(xù)的內(nèi)存,所以對(duì)棧數(shù)據(jù)定位比較快速;堆則是隨機(jī)分布的空間,定位數(shù)據(jù)時(shí)需要堆內(nèi)存的創(chuàng)建和刪除節(jié)點(diǎn)時(shí)間復(fù)雜度是O(logN)。顯然棧的速度更快,但是棧對(duì)象有嚴(yán)格的聲明周期,并且??臻g有限,會(huì)有棧溢出的問(wèn)題;而堆對(duì)象聲明周期不確定,空間也幾乎沒(méi)有限制。所以并不是所有情況都應(yīng)該使用棧。
那值類型和引用類型就是堆和棧內(nèi)存分配的區(qū)別嗎?不是!
引用類型指向的內(nèi)存塊都在堆內(nèi),一般這些內(nèi)存塊都在委托堆內(nèi),這樣便于內(nèi)存塊的回收和控制,GC會(huì)對(duì)這些堆進(jìn)行回收和整理。也有非委托內(nèi)存不歸委托堆管理的部分,這些部分需要自行管理。
大部分時(shí)候,只有當(dāng)程序邏輯和接口需要更加通用的時(shí)候才需要裝箱。比如一個(gè)含類型為object的參數(shù)的方法,該object可支持任意類型,以便通用。當(dāng)你需要一個(gè)值類型為(如nt32)傳入時(shí),就需要裝箱。又比如一個(gè)非泛型的容器為了保證通用,而將元素類型定義為object,當(dāng)值類型數(shù)據(jù)加入容器時(shí),就需要裝箱。
3.4裝箱的優(yōu)化
由于裝箱拆箱的過(guò)程,都是生成全新的對(duì)象。不斷地分配和銷毀內(nèi)存不但會(huì)大量消耗CPU,同時(shí)也會(huì)增加內(nèi)存碎片,降低性能。所以我們要盡可能的減少裝箱拆箱。Struct類型比較特殊,它即是值類型,又可以繼承接口,用途多,稍不留神就會(huì)增加性能消耗。以下為Struct的優(yōu)化技巧:
1)Struct通過(guò)重載函數(shù)來(lái)避免拆箱,裝箱
如果Strcut沒(méi)有重載一些函數(shù),實(shí)例調(diào)用它們的時(shí)候就會(huì)先裝箱在調(diào)用,所以對(duì)于用到的那些需要調(diào)用的引用方法時(shí),必須重載。
2)通過(guò)泛型來(lái)避免拆箱、裝箱
Struct可以繼承Interface接口,我們可以利用Interface做泛型接口,使用泛型來(lái)傳遞參數(shù),這樣就不會(huì)在裝箱后再傳遞值了。比如B,C繼承A,就有了這個(gè)泛型反復(fù)方法 Void Test(T t) where T : A, 以避免使用object引用類型來(lái)傳遞參數(shù)。
3)通過(guò)繼承統(tǒng)一接口提前拆箱、裝箱,避免多次重復(fù)拆箱、裝箱
比如Struct A和Struct B都繼承了接口1,我們調(diào)用的方法時(shí)void Test(I i)。當(dāng)調(diào)用Test方法時(shí),傳進(jìn)去的Struct A或 Struct B的實(shí)例相當(dāng)于提前執(zhí)行了裝箱操作,Test方法拿到了參數(shù)后就不用擔(dān)心后面會(huì)進(jìn)行裝箱拆箱操作了。
4.業(yè)務(wù)邏輯優(yōu)化技巧
4.1使用List和Dictionary時(shí)提高效率?
? 每次使用List的Insert,Contain,Remove函數(shù)時(shí),都是會(huì)順序遍歷List的,如果經(jīng)常用到他們就會(huì)有比較大的性能消耗。
Dictionary也有很多問(wèn)題,它使用一個(gè)Hash沖突方案來(lái)解決關(guān)鍵字的字典組件,因此Hash值與容器中數(shù)組的映射和獲取Hash值的函數(shù)GetHashCode()比較關(guān)鍵。Hash沖突與數(shù)組大小有很大關(guān)系,數(shù)組越大,哈希沖突率越小。在實(shí)際使用過(guò)程中應(yīng)當(dāng)初始化一個(gè)合理的大小,使得Hash沖突不那么頻繁,且放任其自由擴(kuò)容也會(huì)增加GC負(fù)擔(dān);GetHashCode()繼承自基類Object類中的方法,用來(lái)獲取類實(shí)例的Hash值,這個(gè)函數(shù)實(shí)質(zhì)是用算法將內(nèi)存地址轉(zhuǎn)化成哈希值的過(guò)程,不會(huì)有任何緩存的過(guò)程。如果頻繁的使用GetHashCode(),我們應(yīng)當(dāng)要關(guān)注此時(shí)這個(gè)函數(shù)的算力損耗,并確認(rèn)是否可以作為唯一ID。
4.2巧用struct
? 由于struct是一個(gè)值引用對(duì)象,所以傳遞struct時(shí),其實(shí)是在不斷地克隆數(shù)據(jù)。
struct A{ public int gold; } public void main(){ A a = new A(); a.gold = 1; A b = a; b.gold =2; }
舉這個(gè)例子,上述struct中有一個(gè)整數(shù)變量gold,實(shí)例a的gold值為1,將a賦值給b后,b的gold設(shè)置為2,此時(shí)a中的gold仍為1,因?yàn)閍和b是兩個(gè)不同的內(nèi)存。
struct這樣的值變量對(duì)性能優(yōu)化有什么好處呢,如果在函數(shù)中被定義成局部變量,則struct的值類型變量分配的內(nèi)存是在棧上的,棧是連續(xù)內(nèi)存,并且在函數(shù)調(diào)用結(jié)束后,棧的回收非??焖俸秃?jiǎn)單,只有將尾指針置零就可以了,這樣既不會(huì)產(chǎn)生內(nèi)存碎片,又不需要內(nèi)存垃圾回收,CPU讀取數(shù)據(jù)對(duì)連續(xù)內(nèi)存也非常高效。
除了以上,struct數(shù)組對(duì)提高內(nèi)存訪問(wèn)速度也有所幫助,因?yàn)閮?nèi)部存儲(chǔ)時(shí)連續(xù)存儲(chǔ)的,CPU在讀取數(shù)據(jù)時(shí),連續(xù)內(nèi)存可以幫助我們提高CPU的緩存命中率,因?yàn)镃PU在讀取內(nèi)存時(shí)會(huì)把一個(gè)大塊內(nèi)存放入緩存,當(dāng)下次讀取時(shí)先從緩存中查詢,如果命中則不需要再向內(nèi)存讀取數(shù)據(jù)(緩存比內(nèi)存塊100倍),非連續(xù)內(nèi)存的緩存命中率比較低,而CPU緩存命中率的高低很影響CPU的效率。
4.3盡可能得使用對(duì)象池
對(duì)象的創(chuàng)建與銷毀都會(huì)引起內(nèi)存分配時(shí)的性能損耗以及垃圾回收時(shí)的艱難。尤其是當(dāng)業(yè)務(wù)邏輯大,數(shù)據(jù)量多的時(shí),垃圾回收需要檢查的內(nèi)存也越多,如果回收后依然內(nèi)存不足,就得向系統(tǒng)請(qǐng)求分配更多內(nèi)存。對(duì)象池的使用并不麻煩,我們是需要在創(chuàng)建銷毀對(duì)象的時(shí)候調(diào)用對(duì)象池管理對(duì)象即可。關(guān)于對(duì)象池的代碼有很多讀者可自行查找相關(guān)代碼。
4.4字符串導(dǎo)致的性能問(wèn)題
? 在C#中由于string是引用類型變量,每次動(dòng)態(tài)創(chuàng)建一個(gè)string,C#都會(huì)在堆內(nèi)存中分配一個(gè)內(nèi)存用于存放字符串。
sting strA = "test"; for(int i=0;i<100;i++){ string strB = strA + i.ToString(); string[] strC = strB.Split('e'); strB = strB + strC[0]; string strD = string.Format("Hello{0}, this is {1} and {2}."strB,strC[0],strC[1]); }
4.5字符串隱藏問(wèn)題
? 字符串隱藏問(wèn)題,當(dāng)兩個(gè)字符比較時(shí),首先會(huì)比較兩個(gè)字符串的地址是否一致(引用變量),如果不一致才會(huì)遍歷字符串各個(gè)字符,如果都一致則返回true。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-482186.html
該問(wèn)題涉及到ToCharArray(),Clone(),Compare()函數(shù)。string.ToCharArray()返回的char []數(shù)組是一個(gè)新創(chuàng)建的字符串?dāng)?shù)組,與原有的string無(wú)關(guān),我們修改返回的字符串時(shí)不會(huì)影響原來(lái)的string對(duì)象。而Clone和ToString接口則是直接返回該對(duì)象,并不會(huì)重新創(chuàng)建一個(gè)新對(duì)象。?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-482186.html
到了這里,關(guān)于Unity3D高級(jí)編程主程手記 學(xué)習(xí)筆記二:C#技術(shù)要點(diǎn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!