一,C#中結構 在C#中可以使用struct關鍵字來定義一個結構,級別與類是一致的,寫在命名空間下面。 1)結構中可以定義屬性,字段,方法和構造函數。示例代碼如下: 復制代碼 //定義結構 struct Point { //定義字段 private int x; //封裝字段 public int X { get { return x; } set { x = value; } } //定義方法 public void Result() { } //定義構造函數 public Point(int n) { this.x = n; //Console.WriteLine(n); } } 復制代碼 那麼,聲明類與結構的區別有哪些呢? ①無論如何,C#編譯器都會為結構生成無參數的構造函數; 當我們顯式的定義無參數的構造函數,編譯時會報錯,結果如下: 編譯器告訴我們,結構不能包含顯式的無參數的構造函數 但是這樣編寫代碼時,編譯器卻不報錯,代碼如下: //這裡可以調用無參數的構造函數 Point p = new Point(); Console.WriteLine(p.GetType()); 運行結果如下: 雖然結構不能顯式的聲明無參數的構造函數,但是程序員卻可以顯式的調用結構的無參數的構造函數,說明C#編譯器無論如何都會為結構生成無參數的構造函數。 ②結構中的字段不能賦初始值; ③在結構的構造函數中必須要對結構體的每一個字段賦值; 當我們不聲明顯式的構造函數時,可以不對成員字段賦值,但是一旦聲明了構造函數,就要對所有的成員字段賦值 對所有的成員字段賦值,代碼如下: //定義構造函數 public Point(int n) { this.x = n; //Console.WriteLine(n); } ④在構造函數中對屬性賦值不認為對字段賦值,屬性不一定去操作字段; 所以在構造函數中我們對字段賦初始值的時候,正確的代碼應該是 復制代碼 //定義構造函數 public Point(int n) { //正確的可以對字段賦初始值 this.x = n; //在構造函數中對屬性賦值,但是不一定操作字段 this.X = n; //Console.WriteLine(n); } 復制代碼 2)結構體的數值類型問題 C#中的結構是值類型,它的對象和成員字段是分配在棧中的,如下圖: 那麼當我們寫了如下的代碼,內存中發生了什麼呢? //這裡可以調用無參數的構造函數 Point p = new Point(); //為p的屬性賦值 p.X = 100; //將p賦值給Point新的對象p1 Point p1 = p; Point p1=p發生了什麼呢?情況如下: 聲明結構體對象可以不使用“new”關鍵字如果不使用“new”關鍵字聲明結構體對象,因為沒有調用構造函數,這個時候結構體對象是沒有值的。而結構的構造函數必須為結構的所有字段賦值,所以通過"new"關鍵字創建結構體對象的時候,這個對象被構造函數初始化就有默認的初始值了。實例代碼如下: 復制代碼 class Program { static void Main(string[] args) { //沒有辦法調用默認的構造函初始化 Point p; Console.WriteLine(p); //會調用默認的構造函數對的Point對象初始化 Point p1 = new Point(); Console.WriteLine(p1); Console.ReadKey(); } } //定義結構 struct Point { //定義時賦初始值,編譯器會報錯 private int x; } 復制代碼 編譯的時候會報錯: 3)結構體不能使用自動屬性 在第一篇文章我寫自動屬性的時候,反編譯源代碼,知道自動屬性,會生成一個默認字段。而在結構的構造函數中需要對每一個字段賦值,但是編譯器不知道這個字段的名字。所以,沒有辦法使用自動屬性。 那麼什麼時候定義類,什麼時候定義結構體呢? 首先我們都知道的是,棧的訪問速度相對於堆是比較快的。但是棧的空間相對於堆來說是比較小的。 ①當我們要表示一個輕量級的對象,就可以定義結構體,提高訪問速度。 ②根據傳值的影響來選擇,當要傳遞的引用就定義類,當要傳遞的是“拷貝”就定義結構體。 二,關於GC(.NET的垃圾回收) 1)分配在棧中的空間變量,一旦出了該變量的作用域就會被CLR立即回收;如下代碼: //定義值類型的n當,程序出了main函數後n在棧中占用的空間就會被CLR立即回收 static void Main(string[] args) { int n = 5; Console.WriteLine(n); } 2)分配在堆裡面的對象,當沒有任何變量的引用時,這個對象就會被標記為垃圾對象,等待垃圾回收器的回收; GC會定時清理堆空間中的垃圾對象,這個時間頻率是程序員無法控制的,是由CLR決定的。所以,當一個對象被標記為垃圾對象的時候,不一定會被立即回收。 3)析構函數 在回收垃圾對象的時候,析構函數被GC自動調用。主要是執行一些清理善後工作。 析構函數沒有訪問修飾符,不能有你參數,使用“~”來修飾。 如下面的代碼示例: 復制代碼 class Program { //定義值類型的n當,程序出了main函數後n在棧中占用的空間就會被CLR立即回收 static void Main(string[] args) { int n = 5; OperateFile operate = new OperateFile(); operate.FileWrite(); //執行完寫操作後,會調用該類的析構函數,釋放對文件對象的控制 //Console.WriteLine(n); } } //定義操作硬盤上文件上的類 class OperateFile { //定義寫文件的方法 public void FileWrite() { } //定義調用該類結束後,所要執行的動作 ~OperateFile() { //釋放對操作文件對象的控制 } } 復制代碼 三,靜態成員和實例成員的區別: 靜態成員是需要通過static關鍵字來修飾的,而實例成員不用static關鍵字修飾。他們區別如下代碼: 復制代碼 class Program { static void Main(string[] args) { //靜態成員屬於類,可以直接通過“類名.靜態成員”的方式訪問 Person.Run(); //實例成員屬於對象,需要通過“對象名.實例成員”來訪問 Person p = new Person(); p.Sing(); } } class Person { //靜態成員變量 private static int nAge; //實例成員變量 private string strName; public static void Run() { Console.WriteLine("我會奔跑!"); } public void Sing() { Console.WriteLine("我會唱歌"); } } 復制代碼 當類第一次被加載的時候(就是該類第一次被加載到內存當中),該類下面的所有靜態的成員都會被加載。實例成員有多少對象,就會創建多少對象。 而靜態成員只被加載到靜態存儲區,只被創建一次,且直到程序退出時才會被釋放。 看下面的代碼: 復制代碼 class Program { static void Main(string[] args) { Person p = new Person(); Person p1 = new Person(); Person p2 = new Person(); } } class Person { //靜態成員變量 private static int nAge; //實例成員變量 private string strName; public static void Run() { Console.WriteLine("我會奔跑!"); } public void Sing() { Console.WriteLine("我會唱歌"); } } 復制代碼 那麼在內存中發生了什麼呢?如下圖: 由上面顯然可知,定義靜態的成員是可以影響程序的執行效率的。那麼什麼時候定義靜態的成員變量呢? ①變量需要被共享的時候②方法需要被反復的調用的時候 2)在靜態方法中不能直接調用實例成員。 當類第一次被加載的時候,靜態成員已經被加載到靜態存儲區,此時類的對象還有可能能沒有創建,所以靜態方法中不能調用類成員字段。實例代碼如下: this和base關鍵字都不能在靜態方法中使用。 ②可以創建類的對象指明對象的成員在靜態方法中操作,代碼如下: public static void Run() { Person p = new Person(); p.strName = "強子"; Console.WriteLine("我會奔跑!"); } ③在實例成員中肯定可以調用靜態方法,因為這個時候靜態成員肯定存在,代碼如下: 復制代碼 public static void Run() { Person p = new Person(); p.strName = "強子"; Console.WriteLine("我會奔跑!"); } public void Sing() { //實例方法被調用的時候,對象實例一定會被創建,所以可以在實例方法中訪問實例的字段 this.strName = "子強"; strName = "子強"; //調用靜態成員 Run(); Console.WriteLine("我會唱歌"); } 復制代碼 靜態成員和實例成員的對比: ①生命周期不一樣 靜態成員只有在程序結束時才會釋放,而實例成員沒有對象引用時就會釋放 ②內存中存儲的位置不一樣 靜態成員存放在靜態存儲區,實例成員在托管堆中。 四,靜態類 ①靜態類被static關鍵字修飾 //定義兩個靜態類 static class Person { } internal static class Cat { } ②靜態類中只能生命靜態的成員變量,否則會報錯(因為訪問該實例成員的時候,類的對象可能還沒有被創建) ③靜態類中不能有實例的構造函數(如果有實例的構造函數,則該靜態類能被實例化,都是靜態成員,沒有實例成員被調用) 正確的聲明方法: 復制代碼 static class Person { //private int nAge; private static string strName; static Person() { } } 復制代碼 ④靜態類不能被繼承,反編譯剛才的兩個類,結果如下: 會發現靜態類的本質是一個抽象密封類,所以不能被繼承和實例化。所以,靜態類的構造函數,不能有訪問修飾符 2)那麼什麼時候聲明靜態類呢? 如果這個類下面的所有成員的都需要被共享,可以把這個類聲明為靜態類。 且在一般對象中不能聲明靜態類型的變量(訪問該靜態變量時,可能該對象還沒有被創建)。 3)靜態類的構造函數 靜態類可以有靜態的構造函數(且所有類都可以有靜態的構造函數),如下代碼: 復制代碼 class Program { static void Main(string[] args) { Cat c; Cat c1 = new Cat(); Console.ReadKey(); } } class Cat { private int n; public string strName; //實例構造函數 public Cat() { Console.WriteLine("看誰先執行2"); } //靜態構造函數 static Cat() { Console.WriteLine("看誰先執行1"); } } 復制代碼 執行結果如下: 由此我們可以知道,靜態的構造函數會先於實例構造函數執行 且 //不執行靜態構造函數 Cat c; 當我們在Main()函數中添加如下的代碼是: 復制代碼 static void Main(string[] args) { //不執行靜態構造函數 Cat c; Cat c1 = new Cat(); Cat c2 = new Cat(); Console.ReadKey(); } 復制代碼