C#基礎
- ==1. 簡述值類型和引用類型有什么區別==
- ==2. C# String類型比 stringBuilder 類型的優勢是什么?==
- ==3.面向對象的三大特點==
- ==4.請簡述private,public,protected,internal的區別==
- ==5.結構體和類==
- ==6.請描述Interface與抽象類之間的不同==
- ==7.在類的構造函數前加上static會報什么錯?為什么?==
- ==8.虛函數實現原理==
- ==9.指針和引用的區別==
- ==10.C#中ref和out關鍵字有什么區別?==
- ==11.請簡述關鍵字Sealed用在類聲明和函數聲明時的作用==
- ==12.C#中委托和接口有什么區別?各用在什么場合?==
- ==C#中委托和事件的區別==
- ==13. .Net與 Mono 的關系?==
- ==14.請簡述GC(垃圾回收)產生的原因,并描述如何避免?==
- ==15.協變與逆變==
- ==16.重載和重寫的區別==
- ==17.C#函數 Func(string a, string b)用 Lambda 表達式怎么寫?==
- ==18.數列1,1,2,3,5,8,13...第 n 位數是多少?用 C#遞歸算法實現==
- ==19.冒泡排序(手寫代碼)==
- ==20.C#中有哪些常用的容器類,各有什么特點。==
- ==21.C#中常規容器和泛型容器有什么區別,哪種效率高?==
- ==22.有哪些常見的數值類?==
- ==23.反射的實現原理?==
- ==24.C#中unsafe關鍵字是用來做什么的?什么場合下使用?==
- ==25.請簡述ArrayList和 List的主要區別==
- ==26.想要在for循環中刪除List(或者vector,都行)中的元素時,有可能出現什么問題,如何避免?==
- ==27.For,foreach,Enumerator.MoveNext的使用,與內存消耗情況==
- ==28.函數中多次使用string的+=處理,會產生大量內存垃圾(垃圾碎片),有什么好的方法可以解決。==
- ==29.當需要頻繁創建使用某個對象時,有什么好的程序設計方案來節省內存?==
- ==30.JIT和AOT區別==
- ==31.給定一個存放參數的數組,重新排列數組==
- ==32.Foreach循環迭代時,若把其中的某個元素刪除,程序報錯,怎么找到那個元素?以及具體怎么處理這種情況?(注:Try.....Catch捕捉異常,發送信息不可行)==
- ==33.GameObject a=new GameObject() GameObject b=a 實例化出來了A,將A賦給B,現在將B刪除,問A還存在嗎?==
- ==34.你擁有A塊錢,一瓶水B塊錢,每瓶水可以得到一個瓶蓋,每C個瓶蓋可以換一瓶水請寫出函數求解上面題目,上面題目ABC為參數==
- ==35.有一排開關,第一個人把所有的開關打開,第二個人按2的倍數的開關,第三個人按3的倍數的開關,以此類推,現在又n個開關,k個人,寫函數求最后等兩者的開關,輸入參數n和k==
- ==36.數制轉換,將任意整數轉換成8進制形式==
- ==37.找出200以內的素數。==
- ==38.打印楊輝三角形==
- ==39.中國有句俗話“三天打魚兩天曬網”,某人從2000年1月1日起開始“三天打魚兩天曬網”,問這個人在今后的某天中“打魚”還是”曬網”==
- ==40.假設當前市場價一只雞10元,一只鴨12元5角。請寫一個函數ShowPrice,輸入參數分別為雞和鴨的個數(非負整型),功能為顯示出總價錢,精確到分。例如調用ShowPrice(5,10)后輸出175.00。請注意程序的可讀性和易于維護性。==
- ==41.請寫一個函數,用于返回n!(階乘)結果末尾連續0的個數,如GetZeroCount(5)返回1,因為5! = 120,末尾連續1個0==
- ==42、C#中 委托和事件的區別==
- ==43、協同程序的執行代碼是什么?有何用處,有何缺點?==
- ==45、什么是里氏代換元則?==
- ==46、編寫一個函數,輸入一個32位整數,計算這個整數有多少個bit為1==
- ==47、簡述static和const關鍵字的作用==
- ==48、計算 s = 1!+2!+3!+…+num!的代碼。num為輸入,s為輸出。(!代表階乘 3!= 1 * 2 * 3)==
- ==49、用你熟悉的語言從一個字符串中去掉相連的重復字符,例如原字符串“adffjkljaalkjhl”變為“adfjkljalkjhl”==
- ==50、在一個單鏈表中,如何判斷是否有環?==
- ==51、有限狀態機和行為樹的區別==
- 附錄:
- ==C#常用的數據結構詳解 :Array,ArrayList,List,LinkedList,Queue,Stack,Dictionary,t>==
- 1、數組
- 2、ArrayList:
- 3、List (泛型)
- 4、Queue
- 5、Stack
- 6、Dictionary<K,T>
1. 簡述值類型和引用類型有什么區別
1.1介紹
值類型:int,bool,float,char,struct,enum。
引用類型:string,object,delegate,interface,class,array。
1.2 區別
值類型存儲在棧中,引用類型存儲在堆中。 值類型存儲快,引用類型存儲慢。 值類型表示實際數據,引用類型表示指向在內存堆中的指針和引用。
值類型在棧中可以自動釋放,引用類型在堆中需要GC來釋放 值類型繼承于 System.ValueType,(System.ValueType繼承于System.Object),引用類型繼承于System.Object。
值類型在棧中存儲的是直接的值,引用類型數據本身實在堆中,棧中存放的是一個引用的地址。
1.3 底層
1.引用類型在實例化時,先在棧內開辟空間,用于存儲堆中對象的地址,然后在堆內開辟空間,存儲引用對象。
2.而值類型直接在棧中開辟空間存儲對象。值類型也有引用地址,但都在棧內的同一空間。
3.在參數對象進入方法體內,實則是在棧中開辟了新的臨時空間。(也就是參數對象的副本)棧內值類型的修改,由于棧中地址不同,所以值類型不會影響到主體。而引用類型的存儲數據是一個堆內的地址,所以對于引用類型的修改是直接修改堆內的對象。
4.值類型對象中的引用類型在堆中(struct中定義的string等),引用類型對象中的值類型也在堆中(class中的int等)
C#中所有引用類型的基類是什么? 引用類型的基類是System.Object值類型的基類是 System.ValueType 同時,值類型也隱式繼承自System.Object
2. C# String類型比 stringBuilder 類型的優勢是什么?
2.1 介紹
string的修改,實則是new 一個新的string,在堆內新開辟空間。而此時棧內的副本也會指向堆內新對象。因此string改變是新建的對象,和本體沒有聯系。
2.2 解決
當頻繁堆一個字符串進行修改時,利用StringBuilder代替String
2.3 StringBuilder的底層實現?
StringBuilder 是支持擴容的(char類型)數組,在每次空間不足時,會開辟原先數組大小的容量,類似于鏈表,新建的數組指向上一個已經用完的數組,本身不會產生gc。
2.4 擴展:
StringBuffer是線程安全,一般用于多線程(C#端不存在)
StringBuilder是非線程安全,所以性能略好,一般用于單線程
2.5 用StringBuilder拼接字符串就一定比string要好嗎?
答:極少拼接(或者短字符串)的情況下 String甚至優于StringBuilder,因為String是公用API,通用性好,用途廣泛,讀取性能高,占用內存較小,Stringbuilder初始化花費時間更大。
如果是處理字符串的話,用string中的方法每次都需要創建一個新的字符串對象并且分配新的內存地址,而 stringBuilder是在原來的內存里對字符串進行修改,所以在字符串處理方面還是建議用stringBuilder這樣比較節約內存。但是 string類的方法和功能仍然還是比 stringBuilder 類要強。
string類由于具有不可變性(即對一個 string 對象進行任何更改時,其實都是創建另外一個 string
類的對象),所以當需要頻繁的對一個 string 類對象進行更改的時候,建議使用StringBuilder 類,StringBuilder類的原理是首先在內存中開辟一定大小的內存空間,當對此 StringBuilder 類對象進行更改時, 如果內存空間大小不夠,會對此內存空間進行擴充,而不是重新創建一個對象,這樣如果對一個字符串對象進行頻繁操作的時候,不會造成過多的內存浪費,其實本質上并沒有很大區別,都是用來存儲和操作字符串的,唯一的區別就在于性能上。
String主要用于公共 API,通用性好、用途廣泛、讀取性能高、占用內存小。
StringBuilder主要用于拼接 String,修改性能好。
不過現在的編譯器已經把String的 + 操作優化成 StringBuilder 了, 所以一般用String 就可以了
String是不可變的,所以天然線程同步。
StringBuilder可變,非線程同步。
2.6 字符串池
字符串池有什么用,原理是什么?
字符串池是CLR一種針對于反復修改字符串對象的優化措施,作用能夠一定程度減少內存消耗。原理是內部開辟容器通過鍵值對的形式注冊字符串對象,鍵是字符串對象的內容,值是字符串在托管堆上的引用。這樣當新創建的時候,會去檢查,如果不存在就在這個容器中開辟空間存放字符串。
3.面向對象的三大特點
繼承
提高代碼重用度,增強軟件可維護性的重要手段,符合開閉原則。繼承最主要的作用就是把子類的公共屬性集合起來,便與共同管理,使用起來也更加方便。你既然使用了繼承,那代表著你認同子類都有一些共同的特性,所以你把這些共同的特性提取出來設置為父類。繼承的傳遞性:傳遞機制 a?b; b?c; c具有a的特性 。繼承的單根性:在C#中一個類只能繼承一個類,不能有多個父類。
封裝
封裝是將數據和行為相結合,通過行為約束代碼修改數據的程度,增強數據的安全性,屬性是C#封裝實現的最好體現。就是將一些復雜的邏輯經過包裝之后給別人使用就很方便,別人不需要了解里面是如何實現的,只要傳入所需要的參數就可以得到想要的結果。封裝的意義在于保護或者防止代碼(數據)被我們無意中破壞。
多態性
多態性是指同名的方法在不同環境下,自適應的反應出不同得表現,是方法動態展示的重要手段。多態就是一個對象多種狀態,子類對象可以賦值給父類型的變量。 例如叫聲,在鳥這個類中是“鳴啼”在狗這個類中是“犬吠”。
4.請簡述private,public,protected,internal的區別
public:對任何類和成員都公開,無限制訪問
private:僅對該類公開
protected:對該類和其派生類公開
internal:只能在包含該類的程序集中訪問該類
protected internal: protected + internal
5.結構體和類
區別
- 結構體是值類型,類是引用類型。結構體存在棧中,類存在堆中。
- 值類型存取快,引用類型存取慢。
- 值類型表示實際數據,引用類型表示指向存儲在內存堆中的數據的指針和引用。
- 棧的內存是自動釋放的,堆內存是.NET 中會由 GC 來自動釋放。
- 值類型繼承自 System.ValueType,引用類型繼承自 System.Object。
- 結構體變量和類對象進行類型傳遞時,結構體變量進行的就是值傳遞,而類對象進行的是引用傳遞,或者說傳遞的是指針,這樣在函數中改變參數值,結構體對象的值是不變的,而類對象的值是變化了。
- 在C#中結構體類型定義時,成員是不能初始化的,這樣就導致了,定義結構體變量時,變量的所有成員都要自己賦值初始化。但對于類,在定義類時,就可以初始化其中的成員變量,所以在定義對象時,對象本身就已經有了初始值,你可以自己在重新給個別變量賦值。(注意在C++中,類的定義中是不能初始化的,初始化要放在構造函數中)
- 結構體不能申明無參的構造函數,而類可以。
- 聲明了結構類型后,可以使用new運算符創建構造對象,也可以不使用new關鍵字。如果不使用new,那么在初始化所有字段之前,字段將保持未賦值狀態且對象不可用。
- 結構體申明有參構造函數后,無參構造不會被頂掉。
- 結構體不能申明析構函數,而類可以。
- 結構體不能被繼承,而類可以。
- 結構體需要在構造函數中初始化所有成員變量,而類隨意。
- 結構體不能被靜態static修飾(不存在靜態結構體),而類可以。
使用環境
結構體
- 結構是值類型在棧中,棧的存取速度比堆快,但是容量小,適合輕量級的對象,比如點、矩形、顏色。
- 如果對象時數據集合時,優先考慮接結構體(位置,坐標)
- 在變量傳值的時候,希望傳遞對象的是拷貝,而不是對象的引用地址,這個時候就可以使用結構體。
類
- 類是引用類型,存儲在堆中,堆的容量大,適合重量級的對象,棧的空間不大,大量的對應當存在于堆中。
- 如果對象需要繼承和多態特征,用類(玩家、怪物)。
什么時候用結構體呢?
結構體在堆棧中創建,是值類型,而類是引用類型。
每當需要一種經常使用的類型,而且大多數情況下該類型只是一些數據時,使用結構體能比使用類獲得更佳性能。
6.請描述Interface與抽象類之間的不同
區別
1.接口不是類 不能實例化 抽象類可以間接實例化
2.接口是完全抽象 抽象類為部分抽象
3.接口可以多繼承 抽象類是單繼承
- 接口不是類(無構造函數和析構函數) ,不能被實例化,抽象類可以間接實例化(可以被繼承,有構造函數,可以實例化子類的同時間接實例化抽象類這個父類)。
- 接口只能做方法申明,抽象類中可以做方法申明,也可以做方法實現。
- 抽象類中可以有實現成員,接口只能包含抽象成員。因此接口是完全抽象,抽象類是部分抽象。
- 抽象類要被子類繼承,接口要被類實現。
- 抽象類中所有的成員修飾符都能使用,接口中的成員都是對外的,所以不需要修飾符修飾。
- 接口可以實現多繼承,抽象類只能實現單繼承,一個類只能繼承一個類但可以實現多個接口。
- 抽象方法要被實現,所以不能是靜態的,也不能是私有的。
使用環境:
使用抽象類是為了代碼的復用,而使用接口的動機是為了實現多態性。
抽象類適合用來定義某個領域的固有屬性,也就是本質,接口適合用來定義某個領域的擴展功能。
抽象類
1.當2個或多個類中有重復部分的時候,我們可以抽象出來一個基類,如果希望這個基類不能被實例化,就可以把這個基類設計成抽象類。
2.當需要為一些類提供公共的實現代碼時,應優先考慮抽象類。因為抽象類中的非抽象方法可以被子類繼承下來,使實現功能的代碼更簡單。
接口
當注重代碼的擴展性跟可維護性時,應當優先采用接口。
①接口與實現它的類之間可以不存在任何層次關系,接口可以實現毫不相關類的相同行為,比抽象類的使用更加方便靈活;
②接口只關心對象之間的交互的方法,而不關心對象所對應的具體類。接口是程序之間的一個協議,比抽象類的使用更安全、清晰。一般使用接口的情況更多。
7.在類的構造函數前加上static會報什么錯?為什么?
介紹
- 靜態構造函數既沒有訪問修飾符,也沒有參數。
- 在創建第一個類實例或任何靜態成員被引用時,.NET將自動調用靜態構造函數來初始化類。
- 一個類只能有一個靜態構造函數。
- 無參數的構造函數可以與靜態構造函數共存。
- 最多只運行一次。
- 靜態構造函數不可以被繼承。
- 如果沒有寫靜態構造函數,而類中包含帶有初始值設定的靜態成員,那么編譯器會自動生成默認的靜態構造函數。
- 如果靜態構造函數引發異常,運行時將不會再次調用該構造函數,并且在程序運行所在的應用程序域的生存期內,類型將保持未初始化。
構造函數格式為public+類名如果加上 static會報錯(靜態構造函數不能有訪問、型的對象,靜態構造函數只執行一次;
運行庫創建類實例或者首次訪問靜態成員之前,運行庫調用靜態構造函數;
靜態構造函數執行先于任何實例級別的構造函數;顯然也就無法使用this和base 來調用構造函數。
8.虛函數實現原理
每個虛函數都會有一個與之對應的虛函數表,該虛函數表的實質是一個指針數組,存放的是每一個對象的虛函數入口地址。對于一個派生類來說,他會繼承基類的虛函數表同時增加自己的虛函數入口地址,如果派生類重寫了基類的虛函數的話,那么繼承過來的虛函數入口地址將被派生類的重寫虛函數入口地址替代。那么在程序運行時會發生動態綁定,將父類指針綁定到實例化的對象實現多態。
9.指針和引用的區別
- 引用不能為空,即不存在對空對象的引用,指針可以為空,指向空對象。
- 引用必須初始化,指定對哪個對象的引用,指針不需要。
- 引用初始化后不能改變,指針可以改變所指對象的值。
- 引用訪問對象是直接訪問,指針訪問對象是間接訪問。
- 引用的大小是所引用對象的大小,指針的大小,是指針本身大小,通常是4字節。
- 引用沒有const,指針有const
- 引用和指針的+自增運算符意義不同。
- 引用不需要分配內存空間,指針需要。
10.C#中ref和out關鍵字有什么區別?
ref和out的作用
解決值類型和引用類型在函數內部改值或者重新申明能夠影響外部傳入的變量讓其也被修改。
使用
就是在申明參數的時候前面加上ref和out的關鍵字即可,傳入參數時同上。
區別
ref修飾參數,表示進行引用傳遞,
out修飾參數也表示進行引用傳遞,但傳遞的引用只為帶回返回值
ref又進又出 out不進只出
ref傳入的變量必須初始化但是在內部可改可不改。
out傳入的變量不用初始化但是在內部必須修改該值(必須賦值)。
11.請簡述關鍵字Sealed用在類聲明和函數聲明時的作用
關鍵字sealed,類聲明時可防止其他類繼承此類,在方法中聲明則可防止派生類重寫此方法。與override一起使用。
12.C#中委托和接口有什么區別?各用在什么場合?
委托介紹
委托是約束集合中的一個類,而不是一個方法,相當于一組方法列表的引用,可以便捷的使用委托對這個方法集合進行操作。委托是對函數指針的封裝。
委托和接口的區別
接口介紹
接口是約束類應該具備功能的集合,約束了類應該具備哪些功能,使類從復雜的邏輯中解脫出來,方便類的管理和拓展,同時解決類的單繼承問題。
使用情況
接口:無法繼承的場所、完全抽象的場所、多人協作的場所
委托:多由于事件的處理
C#中委托和事件的區別
委托和事件的區別
- 事件可以看做成委托中的一個變量。
- 事件是基于委托的存在,事件是委托的安全包裹 讓委托的使用更具有安全性。
委托可以用“=”來賦值,事件不可以。
委托可以在聲明它的類外部進行調用,而事件只能在類的內部進行調用。
委托是一個類型,事件修飾的是一個對象。
13. .Net與 Mono 的關系?
.Net是一個語言平臺,Mono為.Net提供集成開發環境,集成并實現了.NET的編譯器、CLR 和基礎類庫,使得.Net既可以運行在windows也可以運行于 linux,Unix,Mac OS 等。
14.請簡述GC(垃圾回收)產生的原因,并描述如何避免?
GC為了避免內存溢出而產生的回收機制
如何避免:
1)減少 new 產生對象的次數
2)使用公用的對象(靜態成員)
3)將 String 換為 StringBuilder
15.協變與逆變
協變(out):
和諧、自然的變化
里式替換原則中,父類容器可以裝載子類對象,子類可以轉換成父類。比如string轉object,感受是和諧的。
逆變(in):
逆常規、不正常的變化
里式替換原則中,父類容器可以裝載子類對象,但是子類對象不能裝載父類。所以父類轉換為子類,比如object轉string,感受是不和諧的。
協變和逆變是用來修飾泛型的,用于泛型中修飾字母,只有泛型接口和泛型委托能使用.
作用:
//1.返回值與參數//用out修飾的泛型,只能作為返回值delegate T Testout<out T>();//用in修飾的泛型,只能作為參數delegate T TestIn<in T>(T t);
16.重載和重寫的區別
- 封裝、繼承、多態所處位置不同,重載在同類中,重寫在父子類中。
- 定義方式不同,重載方法名相同參數列表不同,重寫方法名和參數列表都相同。
- 調用方式不同,重載使用相同對象以不同參數調用,重寫用不同對象以相同參數調用。
- 多態時機不同,重載時編譯時多態,重寫是運行時多態。
17.C#函數 Func(string a, string b)用 Lambda 表達式怎么寫?
(a,b) => {};
18.數列1,1,2,3,5,8,13…第 n 位數是多少?用 C#遞歸算法實現
public int CountNumber(int num) {if (num == 1 || num == 2) {return 1;} else {return CountNumber(num -1) + CountNumber(num-2);}}
19.冒泡排序(手寫代碼)
public static void BubblingSort(int[]array) {for (int i = 0; i < array.Length; i++){for (int j = array.Length - 1; j > 0; j--){if (array[j] < array[i]) {int temp = array[j];array[j] = array[j-1];array[j - 1] = temp;}}}}
C#中的排序方式有哪些?
選擇排序,冒泡排序,快速排序,插入排序,希爾排序,歸并排序
排序算法里,快速排序時間復雜度多少,穩不穩定?冒泡排序呢?插入排序呢?
(排序算法的時間復雜度,空間復雜度,和穩定性,常見的幾種排序算法) 快排復雜度是 nlogn,不穩定,插入排序和冒泡排序,復雜度都是 n^2都穩定。
“最近情緒不穩定,快(快速排序)些(希爾排序)選(直接選擇排序)堆(堆排序)朋友聊聊天”
20.C#中有哪些常用的容器類,各有什么特點。
List,HashTable,Dictionary,Stack,Queue
List:索引泛型容器 訪問速度快 修改速度慢
HashTable/Dictionary:散列表格式 查詢效率高 空間占用較大
Stack:后進先出
Queue:先進先出
21.C#中常規容器和泛型容器有什么區別,哪種效率高?
不帶泛型的容器需要裝箱和拆箱操作速度慢所以泛型容器效率更高數據類型更安全
22.有哪些常見的數值類?
簡單值類型–包括 整數類型、實數類型、字符類型、布爾類型
復合值類型–包括 結構類型、枚舉類型
23.反射的實現原理?
可以在加載程序運行時,動態獲取和加載程序集,并且可以獲取到程序集的信息反射即在運行期動態獲取類、對象、方法、對象數據等的一種重要手段
主要使用的類庫:System.Reflection
核心類:
1.Assembly描述了程序集
2.Type描述了類這種類型
3.ConstructorInfo描述了構造函數
4.MethodInfo描述了所有的方法
5.FieldInfo描述了類的字段
6.PropertyInfo描述類的屬性
//反射個人認為,就是得到程序集中的屬性和方法。
//實現步驟:
1,導入using System.Reflection;
2,Assembly.Load("程序集")加載程序集,返回類型是一個Assembly
3, foreach (Type type in assembly.GetTypes()){ string t = type.Name; } //得到程序集中所有類的名稱
4,Type type = assembly.GetType("程序集.類名");//獲取當前類的類型
5,Activator.CreateInstance(type); //創建此類型實例
6,MethodInfo mInfo = type.GetMethod("方法名");//獲取當前方法
7,mInfo.Invoke(null,方法參數);
通過以上核心類可在運行時動態獲取程序集中的類,并執行類構造產生類對象,動態獲取對象的字段或屬性值,更可以動態執行類方法和實例方法等。
反射面向對象體現
之前了解的面向對象是基于類實現,而反射中就是基于程序集實現,只不過把類再用程序集包裹了一下,封裝是把一些屬性方法封裝到一個類中,限制其數據修改的程度,那多加一層皮(程序集 ) 就是一個道理了,繼承多態就是和類一樣,把類換成程序集去理解。
- 優點:
允許在運行時發現并使用編譯時還不了解的類型以及成員。
- 缺點:
1.根據目標類型的字符串搜索掃描程序集的元數據的過程耗時。
2.反射調用方法或屬性比較耗時。(首先必須將實參打包成數組,在內部,反射必須將這些實參解包到線程棧上。可以使用多態避免反射操作)
通過反射去獲取對象的一個實例
反射可以直接訪問類的構造,直接通過getConstructor,去訪問這個構造函數,然后通過不同的參數列表,就可以具體的定位到哪一個構造的重載,通過這個方法,去得到類的實例,把對象就拿到了。
24.C#中unsafe關鍵字是用來做什么的?什么場合下使用?
非托管代碼才需要這個關鍵字一般用在帶指針操作的場合
25.請簡述ArrayList和 List的主要區別
ArrayList不帶泛型 數據類型丟失 需要裝箱拆箱 (不丟需)
List帶泛型 數據類型不丟失 不需要裝箱拆箱 (帶不不)
26.想要在for循環中刪除List(或者vector,都行)中的元素時,有可能出現什么問題,如何避免?
當刪除遍歷節點后面的節點時,會導致List.Count進行變化,刪除元素后,當根據i++,遍歷到刪除的節點會發生異常。
處理
可以從后往前元素元素,即刪除在訪問的前面
27.For,foreach,Enumerator.MoveNext的使用,與內存消耗情況
for循環可以通過索引依次進行遍歷,foreach和Enumerator.MoveNext通過迭代的方式進行遍歷。內存消耗上本質上并沒有太大的區別。但是在Unity中的Update中,一般不推薦使用foreach 因為會遺留內存垃圾。
28.函數中多次使用string的+=處理,會產生大量內存垃圾(垃圾碎片),有什么好的方法可以解決。
通過StringBuilder那進行append,這樣可以減少內存垃圾
29.當需要頻繁創建使用某個對象時,有什么好的程序設計方案來節省內存?
設計單例模式進行創建對象或者使用對象池
30.JIT和AOT區別
Just-In-Time -實時編譯
執行慢安裝快占空間小一點
Ahead-Of-Time -預先編譯
執行快安裝慢占內存占外存大
31.給定一個存放參數的數組,重新排列數組
void SortArray(Array arr){Array.Sort(arr);}
32.Foreach循環迭代時,若把其中的某個元素刪除,程序報錯,怎么找到那個元素?以及具體怎么處理這種情況?(注:Try…Catch捕捉異常,發送信息不可行)
foreach不能進行元素的刪除,因為迭代器會鎖定迭代的集合,解決方法:記錄找到索引或者key值,迭代結束后再進行刪除。
33.GameObject a=new GameObject() GameObject b=a 實例化出來了A,將A賦給B,現在將B刪除,問A還存在嗎?
存在,b刪除只是將它在棧中的內存刪除,而A對象本身是在堆中,所以A還存在
34.你擁有A塊錢,一瓶水B塊錢,每瓶水可以得到一個瓶蓋,每C個瓶蓋可以換一瓶水請寫出函數求解上面題目,上面題目ABC為參數
public static int Buy(int a,int b,int c) {return a/b + ForCap(c,a/b);}public static int ForCap(int c,int d) {if (d)???return 0;} else {return d/c + ForCap(c,d/c + d%c);}}
35.有一排開關,第一個人把所有的開關打開,第二個人按2的倍數的開關,第三個人按3的倍數的開關,以此類推,現在又n個開關,k個人,寫函數求最后等兩者的開關,輸入參數n和k
static void Main(string[] args) {int n = int.Parse(Console.ReadLine());int k = int.Parse(Console.ReadLine());Function(100,100);}static void Function(int n, int k) {int i, j = 0;bool[] a = new bool[1000]; //初始false:關燈,true:開燈 for (i = 1; i <= k; i++) //k個人for (j = 1; j <= n; j++) //n個燈if (j % i == 0)a[j] = !a[j]; //取反,false變true,原來開變關,關變開for (i = 1; i <= n; i++) //最后輸出a[i]的值就可以了if (a[i]) //燈亮著Console.WriteLine(i);}
36.數制轉換,將任意整數轉換成8進制形式
static void Main(string[] args) {int n;n =int.Parse(Console.ReadLine());Console.WriteLine("輸入的10進制為:{0}",n);Console.Write("轉換為8進制數為: ");d2o(n);}static void d2o(int n) {if (n > 7) {d2o(n / 8);}Console.Write(n%8);}
37.找出200以內的素數。
static void Main(string[] args) {int count = 0;for (int i = 1; i < 200; i++) {//外層循環:要判斷的數for (int j = 2; j <=i; j++){if (i % j == 0&& i!=j) {break;}if (j == i ) {//結束的條件:最后一個數還沒有被整除 count++;Console.WriteLine(i);}}}Console.WriteLine(count);}
38.打印楊輝三角形
public static void YHSJ(){int [][]a= new int[7][] ;a[0] = new int[1]; //a[0][0]=1;a[1] = new int[2] ;for (int i = 0; i < 7; i++) {a[i] = new int[i+1] ; a[i][0] =1;a[i][i]=1;if(i>1) {//求出中間的數據for(int j=1;ja[i][j]= a[i-1][j-1]+a[i-1][j];}}}for (int i=0; ifor (int k = 0; k < a.Length-1-i; k++) {Console.Write("");}for(int j=0;jConsole.Write(a[i][j] + "");}Console.WriteLine();}}
39.中國有句俗話“三天打魚兩天曬網”,某人從2000年1月1日起開始“三天打魚兩天曬網”,問這個人在今后的某天中“打魚”還是”曬網”
public static void Compute(){Console.WriteLine ((DateTime.Now - DateTime.Parse("2000-01-01")).Days%5<3?"打魚":"曬網");}
40.假設當前市場價一只雞10元,一只鴨12元5角。請寫一個函數ShowPrice,輸入參數分別為雞和鴨的個數(非負整型),功能為顯示出總價錢,精確到分。例如調用ShowPrice(5,10)后輸出175.00。請注意程序的可讀性和易于維護性。
static void ShowPrice(int num_chicken, int num_duck) {float totalPrice = 0.00f;float price_chicken = 10f;float price_duck = 12.5f;totalPrice = num_chicken * price_chicken + num_duck * price_duck;Console.WriteLine("總價錢為:{0:0.00}", totalPrice);}
41.請寫一個函數,用于返回n!(階乘)結果末尾連續0的個數,如GetZeroCount(5)返回1,因為5! = 120,末尾連續1個0
static void Main(string[] args) {int fac = Factorial(5);Console.WriteLine(CountZero(fac));}public static int Factorial(int n) {if (n == 1) {return 1;} else {return n * jiecheng(n - 1);}}//求連續的0的個數public static int CountZero(int num) {int result = 0; //最后的結果String numStr = num.ToString();for (int i = numStr.Length - 1; i >= 0; i--) {if (numStr[i] == '0') {result ++;} else {break;}}return result;}
42、C#中 委托和事件的區別
委托是一個類,該類內部維護著一個字段,指向一個方法。
事件可以被看作一個委托類型的變量,通過事件注冊、取消多個委托或方法。
通過委托執行方法
class Program
{static void Main(string[] args){Example example = new Example();example.Go(); Console.ReadKey();}}public class Example
{public delegate void DoSth(string str);internal void Go(){//聲明一個委托變量,并把已知方法作為其構造函數的參數DoSth d = new DoSth(Print); string str = "Hello,World"; //通過委托的靜態方法Invoke觸發委托 d.Invoke(str);} void Print(string str){Console.WriteLine(str);}}
以上在CLR運行時,委托DoSth實際上就一個類,該類有一個參數類型為方法的構造函數,并且提供了一個Invoke實例方法,用來觸發委托的執行。
- 委托DoSth定義了方法的參數和返回類型
- 通過委托DoSth的構造函數,可以把符合定義的方法賦值給委托
- 調用委托的實例方法Invoke執行了方法
但,實際上讓委托執行方法還有另外一種方式,那就是:委托變量(參數列表)
public class Example
{ public delegate void DoSth(object sender, EventArgs e); internal void Go() { //聲明一個委托變量,并把已知方法作為其構造函數的參數DoSth d = new DoSth(Print); object sender = 10; EventArgs e = new EventArgs(); d(sender, e); }void Print(object sender, EventArgs e) { Console.WriteLine(sender); } }
以上,
- 委托DoSth的參數列表和方法Print的參數列表還是保持一致
- 委托DoSth中的參數object sender通常用來表示動作的發起者,EventArgs e用來表示動作所帶的參數。
而實際上,委托變量(參數列表),事件就是采用這種形式執行方法的。
通過事件執行方法
public class Example
{ public delegate void DoSth(object sender, EventArgs e); public event DoSth myDoSth; internal void Go() { //聲明一個委托變量,并把已知方法作為其構造函數的參數DoSth d = new DoSth(Print); object sender = 10; EventArgs e = new EventArgs(); myDoSth += new DoSth(d); myDoSth(sender, e);}void Print(object sender, EventArgs e) { Console.WriteLine(sender); } }
以上,
- 聲明了事件myDoSth,事件的類型是DoSth這個委托
- 通過+=為事件注冊委托
- 通過DoSth委托的構造函數為事件注冊委托實例
- 采用委托變量(參數列表)這種形式,讓事件執行方法
而且,通過+=還可以為事件注冊多個委托。
public class Example
{ public delegate void DoSth(object sender, EventArgs e); public event DoSth myDoSth;internal void Go() { //聲明一個委托變量,并把已知方法作為其構造函數的參數 DoSth d = new DoSth(Print); DoSth d1 = new DoSth(Say); object sender = 10; EventArgs e = new EventArgs(); //為事件注冊多個委托 myDoSth += new DoSth(d); myDoSth += new DoSth(d1); myDoSth(sender, e); }void Print(object sender, EventArgs e) { Console.WriteLine(sender); }void Say(object sender, EventArgs e) { Console.WriteLine(sender); }
}
以上,通過+=為事件注冊1個或多個委托實例,實際上,還可以為事件直接注冊方法。
public class Example
{public delegate void DoSth(object sender, EventArgs e); public event DoSth myDoSth; internal void Go(){ object sender = 10; EventArgs e = new EventArgs(); //為事件注冊多個委托 myDoSth += Print; myDoSth += Say; myDoSth(sender, e); }void Print(object sender, EventArgs e) { Console.WriteLine(sender); }void Say(object sender, EventArgs e) {Console.WriteLine(sender); }}
通過EventHandler執行方法
先來看EventHandler的源代碼。
可見,EventHandler就是委托。現在就使用EventHandler來執行多個方法。
public class Example
{ public event EventHandler myEvent; internal void Go() { object sender = 10; EventArgs e = new EventArgs(); //為事件注冊多個委托 myEvent += Print; myEvent += Say;myEvent(sender, e); }void Print(object sender, EventArgs e) { Console.WriteLine(sender); }void Say(object sender, EventArgs e) {Console.WriteLine(sender); }
}
總結:
- 委托就是一個類,也可以實例化,通過委托的構造函數來把方法賦值給委托實例
- 觸發委托有2種方式: 委托實例.Invoke(參數列表),委托實例(參數列表)
- 事件可以看作是一個委托類型的變量
- 通過+=為事件注冊多個委托實例或多個方法
- 通過-=為事件注銷多個委托實例或多個方法
- EventHandler就是一個委托
1.事件的聲明只是在委托前面加一個event關鍵詞,雖然你可以定義一個public,但是有了event關鍵詞后編譯器始終會把這個委托聲明為private,然后添加1組add,remove方法。add對應+=,remove對應-=。這樣就導致事件只能用+=,-=來綁定方法或者取消綁定方法。而委托可以用=來賦值,當然委托也是可以用+=,-=來綁定方法的
2.委托可以在外部被其他對象調用,而且可以有返回值(返回最后一個注冊方法的返回值)。而事件不可以在外部調用,只能在聲明事件的類內部被調用。我們可以使用這個特性來實現觀察者模式。大概就是這么多。下面是一段測試代碼。
namespace delegateEvent
{public delegate string deleFun(string word);public class test{public event deleFun eventSay;public deleFun deleSay;public void doEventSay(string str){if (eventSay!=null)eventSay(str);}}class Program{static void Main(string[] args){test t = new test();t.eventSay += t_say;t.deleSay += t_say;t.deleSay += t_say2;//t.eventSay("eventSay"); 錯誤 事件不能在外部直接調用t.doEventSay("eventSay");//正確 事件只能在聲明的內部調用string str = t.deleSay("deleSay");//正確 委托可以在外部被調用 當然在內部調用也毫無壓力 而且還能有返回值(返回最后一個注冊的方法的返回值)Console.WriteLine(str);Console.Read();}static string t_say(string word){Console.WriteLine(word);return "return "+word;}static string t_say2(string word){Console.WriteLine(word);return "return " + word + " 2";}}
}
43、協同程序的執行代碼是什么?有何用處,有何缺點?
function Start() { // 協同程序WaitAndPrint在Start函數內執行,可以視同于它與Start函數同步執行.StartCoroutine(WaitAndPrint(2.0)); print ("Before WaitAndPrint Finishes " + Time.time );
}function WaitAndPrint (waitTime : float) {// 暫停執行waitTime秒yield WaitForSeconds (waitTime);print ("WaitAndPrint "+ Time.time );
}
作用:一個協同程序在執行過程中,可以在任意位置使用yield語句。yield的返回值控制何時恢復協同程序向下執行。協同程序在對象自有幀執行過程中堪稱優秀。協同程序在性能上沒有更多的開銷。
缺點:協同程序并非真線程,可能會發生堵塞。
45、什么是里氏代換元則?
里氏替換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。
通俗點:就是子類對象可以賦值給基類對象,基類對象不能賦值給子類對象
46、編寫一個函數,輸入一個32位整數,計算這個整數有多少個bit為1
uint BitCount (uint n)
{uint c = 0; // 計數器while (n > 0) {if ((n & 1) == 1) // 當前位是1++c; // 計數器加1n >>= 1; // 移位}return c;
}
47、簡述static和const關鍵字的作用
static 關鍵字至少有下列幾個作用:
(1)函數體內static 變量的作用范圍為該函數體,不同于auto 變量,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值;
(2)在模塊內的static 全局變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問;
(3)在模塊內的static 函數只可被這一模塊內的其它函數調用,這個函數的使用范圍被限制在聲明它的模塊內;
(4)在類中的static 成員變量屬于整個類所擁有,對類的所有對象只有一份拷貝;
(5)在類中的static 成員函數屬于整個類所擁有,這個函數不接收this 指針,因而只能訪問類的static 成員變量。
const 關鍵字至少有下列幾個作用:
(1)欲阻止一個變量被改變,可以使用const 關鍵字。在定義該const 變量時,通常需要對它進行初始化,因為以后就沒有機會再去改變它了;
(2)對指針來說,可以指定指針本身為const,也可以指定指針所指的數據為const,或二者同時指定為const;
(3)在一個函數聲明中,const 可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;
(4)對于類的成員函數,若指定其為const 類型,則表明其是一個常函數,不能修改類的成員變量;
(5)對于類的成員函數,有時候必須指定其返回值為const 類型,以使得其返回值不為“左值”。
48、計算 s = 1!+2!+3!+…+num!的代碼。num為輸入,s為輸出。(!代表階乘 3!= 1 * 2 * 3)
Console.ReadLine(num)
int s = 0;
for(int i = 1; i <= num; i++)
{s += JieCheng(num);
}
public int JieCheng(int num)
{if(num < 0){Console.WriteLine("error");return;}if(num <=1){return 1;}else {return num * JieCheng(num - 1)}
}
49、用你熟悉的語言從一個字符串中去掉相連的重復字符,例如原字符串“adffjkljaalkjhl”變為“adfjkljalkjhl”
int GetResult(char[] input, char[] output)
{ int i, j, k = 0; int flag; int length; if(input == NULL || output == NULL) { return -1; } length=strlen(input);//求數組的長度 for(i = 0; i<length; i++) { flag = 1; for(j = 0; j < i; j++) { if(output[j] == input [i]) flag = 0; } if(flag) output[k++] = input[i]; } printf("最終的字符串為:"); output[k] = '\0'; for(int m = 0; m < output.Length; m++){print (output [m]);} return 0;
}
50、在一個單鏈表中,如何判斷是否有環?
第一種,對鏈表遍歷的同時,記錄已訪問過的結點,如果遍歷過程中發現有結點是此前已經訪問過的結點,說明有環;第二種方法,快慢指針法,快指針每次前進兩個,慢指針每次前進一個,如果快慢指針相遇了,說明有環。
51、有限狀態機和行為樹的區別
附錄:
C#常用的數據結構詳解 :Array,ArrayList,List,LinkedList,Queue,Stack,Dictionary,t>
1、數組
特點:
數組存儲在連續的內存上。 數組的內容都是相同類型。 數組可以直接通過下標訪問。
數組創建
- int size = 10;
- int[] test = new int[size];
創建一個新的數組時將在 CLR 托管堆中分配一塊連續的內存空間,來盛放數量為size,類型為所聲明類型的數組元素。
如果類型為值類型,則將會有size個未裝箱的該類型的值被創建。
如果類型為引用類型,則將會有size個相應類型的引用被創建。
優點:
由于是在連續內存上存儲的,所以它的索引速度非常快,訪問一個元素的時間是恒定的也就是說與數組的元素數量無關,而且賦值與修改元素也很簡單。
string[] test2 = new string[3];**//賦值**test2[0] = "chen";
test2[1] = "j";
test2[2] = "d";**//修改**test2[0] = "chenjd";
缺點
- 由于是連續存儲,所以在兩個元素之間插入新的元素就變得不方便。
- 聲明一個新的數組時,必須指定其長度,這就會存在一個潛在的問題,那就是當我們聲明的長度過長時,顯然會浪費內存,當我們聲明長度過短的時候,則面臨這溢出的風險。
針對這種缺點,下面隆重推出ArrayList。
2、ArrayList:
為了解決數組創建時必須指定長度以及只能存放相同類型的缺點而推出的數據結構。ArrayList是System.Collections命名空間下的一部分,所以若要使用則必須引入System.Collections。正如上文所說,ArrayList解決了數組的一些缺點。
不必在聲明ArrayList時指定它的長度,這是由于ArrayList對象的長度是按照其中存儲的數據來動態增長與縮減的。
ArrayList可以存儲不同類型的元素。這是由于ArrayList會把它的元素都當做Object來處理。因而,加入不同類型的元素是允許的。
ArrayList的操作:
ArrayList test3 = new ArrayList();//新增數據
test3.Add("i");
test3.Add("j");
test3.Add("d");
test3.Add("is");
test3.Add(25);
test3[4] = 26;test3.RemoveAt(4);
缺點
那是因為ArrayList可以存儲不同類型數據的原因是由于把所有的類型都當做Object來做處理,也就是說ArrayList的元素其實都是Object類型的,辣么問題就來了。
- ArrayList不是類型安全的。因為把不同的類型都當做Object來做處理,很有可能會在使用ArrayList時發生類型不匹配的情況。
如上文所訴,數組存儲值類型時并未發生裝箱,但是ArrayList由于把所有類型都當做了Object,所以不可避免的當插入值類型時會發生裝箱操作,在索引取值時會發生拆箱操作。這能忍嗎?
注:為何說頻繁的沒有必要的裝箱和拆箱不能忍呢?
裝箱 (boxing):就是值類型實例到對象的轉換
拆箱:就是將引用類型轉換為值類型
//裝箱,將值類型轉成引用類型
int info = 1989;
object obj=(object)info; //拆箱,引用類型轉換成值類型
object obj = 1;
int info = (int)obj;
顯然,從原理上可以看出,裝箱時,生成的是全新的引用對象,這會有時間損耗,也就是造成效率降低。
那么為了解決ArrayList不安全類型與裝箱拆箱的缺點,所以出現了泛型 的概念,作為一種新的數組類型引入。也是工作中經常用到的數組類型。和ArrayList很相似,長度都可以靈活的改變,最大的不同在于在聲明List集合時,我們同時需要為其聲明List集合內數據的對象類型,這點又和Array很相似,其實List內部使用了Array來實現。
3、List (泛型)
List<string> test4 = new List<string>(); //新增數據test4.Add(“Fanyoy”); test4.Add(“Chenjd”); //修改數據 test4[1] = “murongxiaopifu”; //移除數據 test4.RemoveAt(0);
這么做最大的好處就是:
(1)即確保了類型安全;
(2)也取消了裝箱和拆箱的操作;
(3)它融合了Array可以快速訪問的優點以及ArrayList長度可以靈活變化的優點。
也就是鏈表了。
和上述的數組最大的不同之處
就是在于鏈表在內存存儲的排序上可能是不連續的。
這是由于鏈表是通過上一個元素指向下一個元素來排列的,所以可能不能通過下標來訪問。如圖
既然鏈表最大的特點就是存儲在內存的空間不一定連續,那么鏈表相對于數組最大優勢和劣勢就顯而易見了。
向鏈表中插入或刪除節點無需調整結構的容量。因為本身不是連續存儲而是靠各對象的指針所決定,所以添加元素和刪除元素都要比數組要有優勢。
鏈表適合在需要有序的排序的情境下增加新的元素,這里還拿數組做對比,例如要在數組中間某個位置增加新的元素,則可能需要移動移動很多元素,而對于鏈表而言可能只是若干元素的指向發生變化而已。
缺點
由于其在內存空間中不一定是連續排列,所以訪問時候無法利用下標,而是必須從頭結點開始,逐次遍歷下一個節點直到尋找到目標。所以當需要快速訪問對象時,數組無疑更有優勢。
綜上,鏈表適合元素數量不固定,需要經常增減節點的情況。
4、Queue
“先進先出”(FIFO—first in first out)的線性表。通過使用Enqueue和Dequeue這兩個方法來實現對 Queue 的存取。
默認情況下,Queue的初始容量為32, 增長因子為2.0。
當使用Enqueue時,會判斷隊列的長度是否足夠,若不足,則依據增長因子來增加容量,例如當為初始的2.0時,則隊列容量增長2倍。
5、Stack
后進先出順序(LIFO)的數據結構
默認容量為10。
使用pop和push來操作。
6、Dictionary<K,T>
提到字典就不得不說Hashtable哈希表以及Hashing(哈希,也有叫散列的),因為字典的實現方式就是哈希表的實現方式,只不過字典是類型安全的,也就是說當創建字典時,必須聲明key和item的類型,這是第一條字典與哈希表的區別。
哈希
簡單的說就是一種將任意長度的消息壓縮到某一固定長度,比如某學校的學生學號范圍從0000099999,總共5位數字,若每個數字都對應一個索引的話,那么就是100000個索引,但是如果我們使用后3位作為索引,那么索引的范圍就變成了000999了,當然會沖突的情況,這種情況就是哈希沖突(Hash Collisions)了。
回到Dictionary<K,T>
劣勢就是空間。
以空間換時間,通過更多的內存開銷來滿足我們對速度的追求。
在創建字典時,我們可以傳入一個容量值,但實際使用的容量并非該值。而是使用“不小于該值的最小質數來作為它使用的實際容量,最小是3。”,當有了實際容量之后,并非直接實現索引,而是通過創建額外的2個數組來實現間接的索引,即int[]buckets和Entry[]entries兩個數組(即buckets中保存的其實是entries數組的下標),這里就是第二條字典與哈希表的區別,還記得哈希沖突嗎?對,第二個區別就是處理哈希沖突的策略是不同的!
字典會采用額外的數據結構來處理哈希沖突,這就是剛才提到的數組之一buckets桶了,buckets的長度就是字典的真實長度,因為buckets就是字典每個位置的映射,然后buckets中的每個元素都是一個鏈表,用來存儲相同哈希的元素,然后再分配存儲空間。
因此,我們面臨的情況就是,即便我們新建了一個空的字典,那么伴隨而來的是2個長度為3的數組。所以當處理的數據不多時,還是慎重使用字典為好,很多情況下使用數組也是可以接受的。
愛你不跪的模樣
愛你對峙過絕望不肯哭一場
你將造你的城邦在廢墟之上