Unity3D學習之路 - C#學習筆記(二)


51.棧和堆是存放變量與常量的地方,它們有截然不同的生命期語義。

52.棧是用來存放局部變量和參數的內存塊,當一個函數被調用和退出時,棧就會邏輯增長和減小,考慮下面的函數:

1 static int Factorial( int n )
2 {
3 if ( 0 == n )
4 {
5 return 1;
6 }
7
8 return ( n * Factorial(n-1) );
9 }

這是一個遞歸函數,也就說它會自己調用自己。每次函數被調用時,就會有一個新的int參數被分配在棧上,而當函數退出時,int參數被釋放。

53.堆是用來存放對象(也就是引用類型的實例)的地方。無論何時當一個對象被創建時,它被分配在堆上,並且一個對該對象的引用將被返回。運行時有一個垃圾收集器,會定期的釋放在堆上的對象,所以你的計算機不會耗盡內存。只要一個對象沒有對它自身的任何引用,那么該對象就可以被垃圾收集器釋放。堆也可以存放靜態字段和常量,與在堆上分配的對象不同的是,他們不會被垃圾收集器釋放,而是直到應用程序結束才會銷毀。

54.注意:不能像C++那樣顯示的釋放對象,一個未被引用的對象最終會被垃圾收集器釋放。

55.C#執行明確的分配政策。在實踐中,這意味着除了一個不安全的上下文以外,不可能訪問到未初始化的內存。明確的分配政策有下面3個含義:

1.局部變量在讀取前必須指定一個值。

2.當一個函數被調用時,必須提供函數需要的參數。(除非為默認參數)

3.所有的其他變量(如數組元素)在運行時會自動初始化。

56.所有類型的實例都有一個默認值,預定義類型的默認值就是內存中每一位都是0,引用類型的默認值是null,數值類型和枚舉類型的默認值是0,char類型的默認值是'\0',bool類型的默認值是false。可以使用default關鍵字來獲取任意類型的默認值。

57.可以使用ref和out修飾符來控制如何傳遞參數,如下:

58.默認情況下,C#中參數的傳遞方式是值傳遞。也就是說將值類型的實例傳遞給函數時,將創建一份值的副本。將引用類型的實例傳遞給函數時,將創建一份引用的副本,而不是所引用的對象的副本。舉例如下:

 1 static void Foo( int n )
2 {
3 ++n;
4 Console.WriteLine( n );
5 }

6
7 static void Main(string[] args)
8 {
9 int x = 10;
10 Foo( x ); // 創建一份x的副本
11 Console.WriteLine( x ); // x仍舊是10,因為Foo函數改變的是副本
12 }

13 ////////////////////////////////////////////////////////////////////////////
14

15 static void Foo( StringBuilder strBuilder )
16 {
17 // strBuilder與sb引用同一個對象
18 strBuilder.Append( "Reference Copy" );

19
20 // strBuilder是引用的副本,所以改變它不會影響實參
21 strBuilder = null;

22 }
23
24 static void Main(string[] args)
25 {
26 StringBuilder sb = new StringBuilder();
27 Foo( sb );
28 Console.WriteLine( sb.ToString() ); // Reference Copy
29 }

59.C#提供了ref修飾符來進行引用傳遞,例如:

 1 static void Foo( ref int n )
2 {
3 ++n;
4 Console.WriteLine( n );
5 }
6
7 static void Main(string[] args)
8 {
9 int x = 10;
10 Foo( ref x ); // 引用傳遞,也就是說形參n與實參x現在引用同一塊內存
11 Console.WriteLine( x ); // x現在是11
12 }

60.out修飾符與ref修飾符相似,除了:

1.傳遞到函數之前無需指定值。

2.函數返回之前必須被指定值。

out修飾符通常用於從函數獲取多返回值的情形。

61.params修飾符可以用於一個函數的最后一個參數上,以表明該函數接受任意數量的特定類型的參數,但必須聲明為數組形式,如:

1 static int Sum (params int[] ints)
2 {
3 int sum = 0;
4 for (int i = 0; i < ints.Length; i++) sum += ints[i];
5 return sum;
6 }
7
8 // 可以這樣調用Sum函數
9 Console.WriteLine ( Sum (1, 2, 3, 4) ); // 10

62.從C#4.0開始,提供了默認參數,當一個參數在聲明時被指定了一個默認值時,它就是默認參數,如void Foo (int x = 23){ ... }。當調用函數時,默認參數可以被省略,如Foo();此時聲明時指定的默認值將被傳遞給函數。

63.除了使用位置指定參數,還可以使用名稱指定參數,如void Foo (int x, int y){ ... }; Foo(x:1, y:2); 以名稱指定參數可以使用任意順序,如Foo(y:2, x:1)和Foo(x:1, y:2)是等價的。也可以混合使用位置和名稱指定參數,但是以名稱指定參數必須出現在最后,如Foo(1, y:2); 以名稱指定參數與默認參數一起使用時顯得特別有用,如void Bar (int a=0, int b=0, int c=0, int d=0) { ... } 就可以使用以下方式只提供一個值給d:Bar( d:3 );

64.通常有在聲明變量的同時初始化的情況,此時,如果編譯器能夠從初始化表達式中推斷出類型的話,那么就可以使用var來代替類型聲明,如var x = 5;等價於int x = 5; 這稱為隱式類型變量,它是靜態類型,如下面的語句將產生編譯錯誤:var x = 5; x = "string"; // 編譯錯誤,x是int類型

65.一個賦值表達式使用=運算符將一個表達式的結果賦值給一個變量,如int x = x * 5; 賦值表達式能夠和其他表達式組合,如y = 5 * (x = 2); 初始化多個變量時,這種方式特別有用:a = b = c = d = 0; 復合賦值運算符是組合賦值運算符與另一個運算符的語法簡寫,如x *= 2;等價於x = x * 2;

66.當一個表達式包含多個運算符時,優先級和結合律決定了計算順序。高優先級的運算符將在低優先級的運算符之前計算。當運算符的優先級相同時,運算符的結合律決定了運算順序。如1+2*3的計算順序為1+(2*3),因為*運算符的優先級高於+運算符。結合律分為左結合與右結合,左結合就是計算時從左至右,如8/4/2的計算順序為(8/4)/2,因為/運算符是左結合。右結合就是計算時從右至左,如x=y=3,先將3賦值給y,然后將表達式y=3的結果(即3)賦值給x。

67.下表以優先級從高至低的順序列出了C#中的運算符,相同副標題下的運算符具有相同的優先級:

68.一條聲明語句聲明了一個新的變量,在聲明變量時可以選擇是否使用表達式初始化該變量,聲明語句以分號結尾。可以在一條聲明語句中聲明多個同類型的變量,當中以逗號分隔,如int a = 5, b = 6; 一個常量的聲明與變量的聲明類似,除了常量在聲明后不能被改變,以及必須在聲明時進行初始化。

69.一個局部變量的生命期為當前語句塊,不能在當前語句塊或任何嵌套的語句塊中聲明另一個同名局部變量。

70.表達式語句意味着表達式"做了"某些事情,如下:

賦值或修改一個變量

實例化一個對象

調用一個函數

一個沒有做上面任何事情的表達式是非法的語句,如string s = "foo"; s.Length; // 非法的語句。 當調用一個構造函數或者一個有返回值的函數的時候,並不是必須使用該結果的,如new StringBuilder();是合法的語句。

71.在switch語句中只能使用能夠被靜態求值的表達式,類型被限制在內建的整型,string類型和枚舉類型。在每一個case從句的最后,必須使用一些跳轉語句類型明確說明接下去該如何執行,可選的跳轉語句如下:

break - 跳轉到switch語句的結尾

goto case x - 跳轉到另一個case從句

goto default - 跳轉到default從句

任何其他的跳轉語句 - 也就是return,throw,continue或goto標簽

當多個值需要執行相同的代碼時,可以依次列出:

 1 switch (cardNumber)
2 {
3 case 13:
4 case 12:
5 case 11:
6 Console.WriteLine ("Face card");
7 break;
8 default:
9 Console.WriteLine ("Plain card");
10 break;
11 }

72.foreach語句迭代一個可枚舉對象中的每一個元素,C#中大多數表示元素集合或元素列表的類型都是可枚舉的,比如數組和字符串,如:

1 foreach ( char c in "foreach" )
2 {
3 Console.Write( c );
4 }

73.跳轉語句包括break, continue, goto, return以及throw。break語句結束一個迭代語句或switch語句的執行。continue語句放棄循環中剩余的語句,立即開始下一次迭代。goto語句跳轉到一個標簽(使用冒號后綴表示)執行。return語句結束函數的執行,如果函數具有返回類型,那么必須返回一個該類型的表達式。

74.命名空間是一個域,在該域中的類型名稱必須是唯一的。命名空間不受成員訪問權限(private, internal, public等等)的影響。沒有定義在任何命名空間的類型就說處於全局命名空間。

75.可以使用完整合格名稱引用一個類型,也就是包括該類型定義所在的所有的命名空間。如:System.Security.Cryptography.RSA rsa = System.Security.Cryptography.RSA.Create(); 使用using指令導入命名空間后,就可以不需要使用完整合格名稱來引用一個類型,如:using System.Security.Cryptography; RSA rsa = RSA.Create();

76.在外層命名空間聲明的名稱可以在內層命名空間中直接使用,而不需要使用完整合格名稱,如

 1 namespace Outer
2 {
3 namespace Middle
4 {
5 class Class1 {}
6
7 namespace Inner
8 {
9 class Class2 : Class1 {}
10 }
11 }
12 }

如果想要使用同一命名空間層級下不同分支里的一個類型,只需要使用完整合格名稱的一部分即可:

 1 namespace MyTradingCompany
2 {
3 namespace Common
4 {
5 class ReportBase {}
6 }
7
8 namespace ManagementReporting
9 {
10 class SalesReport : Common.ReportBase {}
11 }
12 }

77.如果相同的類型名稱同時出現在內層和外層的命名空間中,那么內層的命名空間中的類型將替代外層的命名空間中的類型。如果想要使用外層的命名空間中的類型,需要使用包括命名空間的合格名稱。

78.導入命名空間可能會導致類型名稱沖突。相比導入整個命名空間,可以只導入需要的類型,並給類型一個別名,如

1 using PropertyInfo2 = System.Reflection.PropertyInfo;
2 class Program { PropertyInfo2 p; }

命名空間也可以給定別名,如

1 using R = System.Reflection;
2 class Program { R.PropertyInfo p; }

79.一個字段是一個類或結構體中的成員變量。一個字段可以使用readonly修飾符來防止它在構造后被修改,一個只讀字段只能在聲明時或構造函數中賦值。字段的初始化是可選的,未初始化的字段有默認值(0, \0, null, false),字段的初始化在構造函數之前,以他們聲明出現的順序執行。

80.一個類型可以重載方法(有多個相同名稱的方法),只要參數類型不同即可,如

1 void Foo (int x);
2 void Foo (double x);
3 void Foo (int x, float y);
4 void Foo (float x, int y);

81.一個類或結構體可以重載構造函數,通過使用this關鍵字,一個重載的構造函數可以調用另外一個重載的構造函數,如

1 public class Wine
2 {
3 public Wine (decimal price) {...}
4 public Wine (decimal price, int year)
5 : this (price) {...}
6 }

當一個構造函數調用另外一個構造函數時,被調用的構造函數先執行。

82.對於類來說,當且僅當沒有定義任何構造函數時,編譯器會自動生成一個無參的構造函數。對於結構體來說,無參的構造函數是結構體的內部結構,因此不能自己定義。結構體的無參的構造函數負責使用默認值初始化結構體中的每一個字段。

83.為了簡化對象的初始化,對象的可訪問的字段或屬性可以在構造后的單條語句中初始化,如

 1 public class Bunny
2 {
3 public string Name;
4 public bool LikesCarrots, LikesHumans;
5 public Bunny () {}
6 public Bunny (string n) { Name = n; }
7 }
8
9 // 可以像下面這樣初始化
10 Bunny b1 = new Bunny {
11 Name="Bo",
12 LikesCarrots = true,
13 LikesHumans = false
14 };
15
16 Bunny b2 = new Bunny ("Bo") {
17 LikesCarrots = true,
18 LikesHumans = false
19 };

84.屬性從外部看像字段,但在內部他們包含邏輯,像函數一樣。一個屬性像字段那樣聲明,但是附加了get/set塊,如

1 public class Stock
2 {
3 decimal currentPrice;
4 public decimal CurrentPrice // 屬性
5 {
6 get { return currentPrice; }
7 set { currentPrice = value; }
8 }
9 }

get和set表示屬性的訪問器,當屬性被讀取的時候,get訪問器就會運行,它必須返回與屬性類型相同的一個值。當屬性被賦值的時候,set訪問器就會被運行,它包含一個名稱為value的與屬性類型相同的隱式參數。一個屬性如果只定義了get訪問器,那么它就是只讀的。如果只定義了set訪問器,那么它就是只寫的。

85.大多數屬性的實現僅僅只是讀與寫一個與屬性同類型的私有字段,一個自動屬性聲明可以告訴編譯器提供這種實現,自動屬性聲明如下:

1 public class Stock
2 {
3 public decimal CurrentPrice { get; set; }
4 }

此時編譯器就會自動生成一個私有字段供屬性讀和寫,該字段的名稱是編譯器生成的,所以不能引用。

86.索引器提供了一種自然的語法來訪問一個類或結構體中的元素。寫一個索引器需要定義一個名稱為this的屬性,然后在方括號中指定參數:

1 class Sentence
2 {
3 string[] words = "The quick brown fox".Split();
4 public string this [int wordNum] // 索引器
5 {
6 get { return words [wordNum]; }
7 set { words [wordNum] = value; }
8 }
9 }

一個索引器可以使用多個參數。每個類型可以定義多個索引器,只要它們的參數類型不同即可。如果省略set訪問器,那么索引器就是只讀的。

87.一個常量是一個無法改變其值的字段。一個常量在編譯時期被靜態求值,無論何時使用它,編譯器將用字面值替換它。一個常量使用關鍵字const進行聲明,聲明時必須被初始化:

1 public class Test
2 {
3 public const string Message = "Hello World";
4 }

88.靜態構造函數在每個類型上執行一次,而不像普通構造函數那樣在每個實例上執行一次。一個類型只能定義一個靜態構造函數,並且必須無參。當一個類型被使用之前,運行時會自動調用靜態構造函數,有2件事會觸發這種行為:實例化一個類型的對象以及訪問類型中的靜態成員。有個需要注意的地方:如果靜態構造函數引發一個未處理的異常,那么在應用程序的生命期內,該類型都無法使用。

89.一個類可以被標記為靜態的,表明它必須完全由靜態成員組成。

90.終結器(Finalizers)是只有類擁有的函數,該函數在垃圾收集器回收一個未被引用的對象的內存時被調用。終結器的語法是類名加上前綴~:

1 class Class1
2 {
3 ~Class1() { ... }
4 }

91.一個類只能有一個基類,但它自身可以作為許多類的基類。

92.引用是多態的,也就是說一個類型的變量可以引用它的子類對象。多態的實現基於這樣一個事實:子類擁有其基類的所有功能。

93.一個對象的引用能夠:

隱式的向上轉型到基類的引用

顯式的向下轉型到子類的引用

在兼容的引用類型之間向上轉型或向下轉型將執行引用轉換,引用轉換將創建一個指向同對象的引用。向上轉型總是成功的,而向下轉型是否成功取決於對象的類型是否合適。

94.一個向上轉型操作從子類引用創建一個基類引用。一個向下轉型操作從基類引用創建一個子類引用,一個向下轉型需要顯式指定因為可能會失敗,如果失敗,將拋出InvalidCastException異常:

House h = new House();
Asset a = h; // 向上轉型總是成功
Stock s = (Stock)a; // 向下轉型失敗: a不是Stock類型

95.as運算符執行向下轉型,如果轉型失敗,會返回null,而不是拋出異常。as運算符不能用於數值轉換。

96.is運算符用於測試是否一個引用轉換將會成功,換句話說,也就是是否一個對象派生自一個特定類(或實現了一個接口)。

97.函數,屬性,索引器以及事件都可以被聲明為virtual,被標記為virtual就能夠被想要提供特殊實現的子類覆蓋(overridden),子類通過使用override修飾符來實現覆蓋:

 1 public class Asset
2 {
3 public string Name;
4 public virtual decimal Liability { get { return 0; } }
5 }
6
7 public class House : Asset
8 {
9 public decimal Mortgage;
10 public override decimal Liability
11 { get { return Mortgage; } } // 覆蓋基類的屬性,提供特殊實現。使用override修飾符
12 }

虛方法和覆蓋方法的簽名,返回類型以及訪問權限必須一致。如果一個覆蓋方法想要調用基類的實現,那么可以使用base關鍵字。

98.一個被聲明為抽象(abstract)的類不能被實例化。抽象類可以定義抽象成員,抽象成員類似於虛成員,但他們不需要提供一個默認實現。子類必須提供抽象成員的實現,除非子類也被聲明為抽象類。

99.一個基類和一個子類可以定義相同的成員,例如:

 

1 public class A      { public int Counter = 1; }
2 public class B : A { public int Counter = 2; }

此時可以說B類中的Counter字段隱藏了A類中的Counter字段。編譯器會生成警告並且按如下的方法解決二義性:A類的引用對象綁定A.Counter,B類的引用對象綁定B.Counter。但是有時候,可能是故意想要隱藏成員,在這種情況下,可以使用new修飾符:

public class A     { public     int Counter = 1; }
public class B : A { public new int Counter = 2; }

new修飾符其實只是使得編譯器不再會生成警告,並且可以告訴其他程序員,這是故意的。

100.一個覆蓋的方法可以使用sealed關鍵字來密封它的實現,從而防止被其子類覆蓋。也可以密封類,從而隱式的密封所有的虛函數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM