1 類和結構
類和結構可以看作是創建對象的模板。每個對象都包括數據,并提供處理和訪問數據的方法。類定義了每個對象(實例)包含什么樣的數據與功能
1.1
“封裝”有時被稱為面向對象的編程的第一個支柱或原則。根據封裝的原則,類或結構可以指定其每個成員對于該類或結構外部的代碼的可訪問性。可將無意在類或程序集外部使用的方法和變量隱藏起來,以減小編碼錯誤或遭惡意利用的可能性。
1.2 成員
所有方法、字段、常量、屬性和事件都必須在類型內部進行聲明;這些稱為類或結構的“成員”。與其他一些語言不同的是,C# 中沒有全局變量或方法。即使是作為程序入口點的 Main 方法也必須在類或結構內部進行聲明。下表列出了可在類或結構中聲明的所有不同種類的成員。
2 類與結構的區別
2.1值類型與引用類型
結構是值類型:值類型在堆棧上分配地址,所有的基類型都是結構類型,例如:int 對應System.int32 結構,string 對應 system.string 結構 ,通過使用結構可以創 建更多的值類型
類是引用類型:引用類型在堆上分配地址
堆棧的執行效率要比堆的執行效率高,可是堆棧的資源有限,不適合處理大的邏輯復雜的對象。所以結構處理作為基類型對待的小對象,而類處理某個商業邏輯
因為結構是值類型所以結構之間的賦值可以創建新的結構,而類是引用類型,類之間的賦值只是復制引用
注:
1.雖然結構與類的類型不一樣,可是他們的基類型都是對象(object),c#中所有類型的基類型都是object
2.雖然結構的初始化也使用了New 操作符可是結構對象依然分配在堆棧上而不是堆上,如果不使用“新建”(new),那么在初始化所有字段之前,字段將保持未賦值狀態,且對象不可用
2.2 繼承性
結構:不能從另外一個結構或者類繼承,本身也不能被繼承,雖然結構沒有明確的用sealed聲明,可是結構是隱式的sealed .
類:完全可擴展的,除非顯示的聲明sealed 否則類可以繼承其他類和接口,自身也能被繼承
注:雖然結構不能被繼承 可是結構能夠繼承接口,方法和類繼承接口一樣
2.3內部結構:
結構:
沒有默認的構造函數,但是可以添加構造函數
沒有析構函數
沒有 abstract 和 sealed(因為不能繼承)
不能有protected 修飾符
可以不使用new 初始化
在結構中初始化實例字段是錯誤的
類:
有默認的構造函數
有析構函數
可以使用 abstract 和 sealed
有protected 修飾符
必須使用new 初始化
3 如何選擇結構還是類
討論了結構與類的相同之處和差別之后,下面討論如何選擇使用結構還是類:
1. 堆棧的空間有限,對于大量的邏輯的對象,創建類要比創建結構好一些
2. 結構表示如點、矩形和顏色這樣的輕量對象,例如,如果聲明一個含有 1000 個點對象的數組,則將為引用每個對象分配附加的內存。在此情況下,結構的成本較低。
3. 在表現抽象和多級別的對象層次時,類是最好的選擇
4. 大多數情況下該類型只是一些數據時,結構時最佳的選擇
4 類成員
類中的數據和函數稱為類的成員。類的主要成員包括兩種類型,即描述狀態的數據成員和描述操作的函數成員。
4.1 數據成員
4.1.1 字段
字段就是在類內定義的成員變量,用來存儲描述類的特征的值
類或結構可以擁有實例字段或靜態字段,或同時擁有兩者。實例字段特定于類型的實例。如果您擁有類 T 和實例字段 F,可以創建類型 T 的兩個對象,并修改每個對象中 F 的值,這不影響另一對象中的該值。相比之下,靜態字段屬于類本身,在該類的所有實例中共享。從實例 A 所做的更改將立刻呈現在實例 B 和 C 上(如果它們訪問該字段)。
字段可標記為 public、private、protected、internal 或 protected internal
注意:通常應僅為具有私有或受保護可訪問性的變量使用字段。您的類向客戶端代碼公開的數據應通過方法、屬性和索引器提供。通過使用這些構造間接訪問內部字段,可以針對無效的輸入值提供防護。
4.1.2 常量
常量是在編譯時已知并在程序的生存期內不發生更改的不可變值。常量使用 const 修飾符進行聲明。只有 C# 內置類型(System.Object 除外)可以聲明為 const
用戶定義的類型(包括類、結構和數組)不能為 const。應使用 readonly 修飾符創建在運行時初始化一次即不可再更改的類、結構或數組。
常量可標記為 public、private、protected、internal 或 protectedinternal。這些訪問修飾符定義類的用戶訪問該常量的方式。
因為常量值對該類型的所有實例是相同的,所以常量被當作 static 字段一樣訪問。不使用 static 關鍵字聲明常量。未包含在定義常量的類中的表達式必須使用類名、一個句點和常量名來訪問該常量
4.2 函數成員
函數成員提供了操作類中數據的功能,包括方法,屬性,構造函數與終結器,運算符以及索引器。
方法:方法是與某個類相關的函數。它可以是實例方法也可以靜態方法。
屬性:屬性是這樣的成員:它們提供靈活的機制來讀取、編寫或計算私有字段的值。可以像使用公共數據成員一樣使用屬性,但實際上它們是稱作“訪問器”的特殊方法。這使得可以輕松訪問數據,此外還有助于提高方法的安全性和靈活性。
構造函數:任何時候,只要創建類或結構,就會調用它的構造函數。類或結構可能有多個接受不同參數的構造函數。構造函數使得程序員可設置默認值、限制實例化以及編寫靈活且便于閱讀的代碼。
如果沒有為對象提供構造函數,則默認情況下 C# 將創建一個構造函數,該構造函數實例化對象,并將成員變量設置為下表中列出的默認值。靜態類和結構也可以有構造函數。
在 C# 中不允許使用未初始化的變量。
值類型 | 默認值 |
---|---|
false | |
0 | |
'\0' | |
0.0M | |
0.0D | |
表達式 (E)0 產生的值,其中 E 為 enum 標識符。 | |
0.0F | |
0 | |
0L | |
0 | |
0 | |
將所有的值類型字段設置為默認值并將所有的引用類型字段設置為 null 時產生的值。 | |
0 | |
0 | |
0 |
下例顯示了由默認構造函數返回的值類型的默認值。默認構造函數是通過 new 運算符來調用的
int myInt = new int();
以上語句同下列語句效果相同:int myInt = 0;
析構函數 析構函數用于析構類的實例。
- 不能在結構中定義析構函數。只能對類使用析構函數。
- 一個類只能有一個析構函數。
- 無法繼承或重載析構函數。
- 無法調用析構函數。它們是被自動調用的。
- 析構函數既沒有修飾符,也沒有參數。
程序員無法控制何時調用析構函數,因為這是由垃圾回收器決定的。垃圾回收器檢查是否存在應用程序不再使用的對象。如果垃圾回收器認為某個對象符合析構,則調用析構函數(如果有)并回收用來存儲此對象的內存。程序退出時也會調用析構函數。
運算符
C# 中,“運算符”是一個術語或符號,它接受一個或多個表達式(即“操作數”)作為輸入并返回值。接受一個操作數的運算符稱為“一元”運算符,例如增量運算符 (++) 或 new。接受兩個操作數的運算符稱為“二元”運算符,例如算術運算符 +、-、*、/。條件運算符 ?: 接受三個操作數,是 C# 中唯一的三元運算符。
索引器
索引器允許類或結構的實例就像數組一樣進行索引。索引器類似于屬性,不同之處在于它們的訪問器采用參數。
使用索引器可以用類似于數組的方式為對象建立索引。
get 訪問器返回值。set 訪問器分配值。
this 關鍵字用于定義索引器。
value 關鍵字用于定義由 set 索引器分配的值。
索引器不必根據整數值進行索引,由您決定如何定義特定的查找機制。
索引器可被重載。
索引器可以有多個形參,例如當訪問二維數組時。
4.2.1 方法
通過指定方法的訪問級別(例如 public 或 private)、可選修飾符(例如 abstract 或 sealed)、返回值、名稱和任何方法參數,可以在類或結構中聲明方法。這些部分統稱為方法的“簽名”。
[modifiers] retunr_type MehodName({paramaters})
{
// method body
}
方法重載
方法重載是讓類以統一的方式處理不同類型數據的一種手段。在C#中,語法規定同一個類中兩個或兩個以上的方法可以用同一個名字,如果出現這種情況,那么該方法就被稱為重載方法.
當一個重載方法被調用時,C#回根據調用該方法的參數自動調用具體的方法來執行.對于方法的使用者來講,這種技術是非常有用的。
決定方法是否構成重載有以下幾個條件:
◆ 在同一個類中;
◆ 方法名相同;
◆ 參數列表不同。
◆兩個方法不能僅在返回類型上有區別
◆兩個方法不能僅根據參數是否聲明為ref或out來區分
屬性(property)
屬性是對現實世界中實體特征的抽象,提供了對類或對象性質的訪問。類的屬性所描述的是狀態信息,在類的某個實例中,屬性的值表示該對象的狀態值。
屬性是字段的自然擴展,它們都是具有關聯類型的命名成員,而且訪問字段和屬性的語法是相同的。然而,與字段不同,屬性不表示存儲位置。
屬性有訪問器(accessor),這些訪問器指定在它們的值被讀取或寫入時需執行的語句。因此屬性提供了一種機制,它把讀取和寫入對象的某些性質與一些操作關聯起來。
它們甚至還可以對此類性質進行計算。因此,屬性被稱為聰明的字段。
屬性(property)
-充分體現了對象的封裝性:不直接操作類的數據內容,而是通過訪問器進行訪問,即借助于get和set對屬性的值進行讀寫;另一方面還可以對數據的訪問屬性進行控制(當然也可以通過對普通域加readonly關鍵字來實現。
-設計原則:屬性封裝了對域的操作。把要訪問的域設為private,通過屬性中的get和set操作對域進行設置或訪問。
-不能把屬性作為引用類型或輸出參數來進行傳遞。
-get方法沒有參數;set方法有一個隱含的參數value。除了使用了abstract修飾符的抽象屬性,每個訪問器的執行體中只有分號“;”外,其他的所有屬性的get訪問器都通過return來讀取屬性的值,set訪問器都通過value來設置屬性的值。
-采用間接方式來訪問對象的屬性(間接調用get、set方法):對象.屬性 = 值(調用set),變量 = 對象.屬性(調用get)。
-在屬性的訪問聲明中:
只有set訪問器,表明該屬性是只寫的。
只有get訪問器,表明該屬性是只讀的。
既有set訪問器,又有get訪問器,表明該屬性是可讀可寫的。
屬性的聲明形式如下:
『特性』
『修飾符』【類型】 【屬性名】
{
『get { 【get訪問器體】 }』
『set { 【set訪問器體】 }』
}
特性』和『修飾符』都是可選的,適用于字段和方法的所有特性和修飾符都適用于屬性,最常用的就是訪問修飾符。也可以使用static修飾符。當屬性聲明包含static修飾符時,
稱該屬性為靜態屬性(static property)。當不存在static修飾符時,稱該屬性為實例屬性(instance property)。
靜態屬性不與特定實例相關聯,因此在靜態屬性的訪問器內引用this會導致編譯時錯誤。
實例屬性與類的一個給定實例相關聯,并且可以在屬性的訪問器內通過this來訪問該實例。
類的屬性稱為智能字段,類的索引器稱為智能數組。由于類本身作數組使用,所以用
this作索引器的名稱,索引器有索引參數值。例:
using System;
using System.Collections;class MyListBox
{
protected ArrayList data = new ArrayList();
public object this[int idx] //this作索引器名稱,idx是索引參數
{
get
{
if (idx > -1 && idx < data.Count)
{
return data[idx];
}
else
{
return null;
}
}
set
{
if (idx > -1 && idx < data.Count)
{
data[idx] = value;
}
else if (idx = data.Count)
{
data.Add(value);
}
else
{
//拋出一個異常
}
}
}
}}
自動實現的屬性
如果屬性的get和set訪問器中沒有任何邏輯,就可以使用自動實現的屬性。這種屬性會自動實現基礎成員變量public string Name
{
get
{
return _name
}
set
{
_name=value
}
}這段代碼和下面這行代碼是一樣的
public string Nmae{get;set;}
不需要聲明private string _name. 但自動實現的屬性必須有兩個訪問器
構造函數
構造函數可以初始類,函數重載可以實現多態。
構造函數和析構函數是類的特殊方法,分別用來初始化類的實例和銷毀類的實例。
構造函數(constructor)用于執行類的實例的初始化。每個類都有構造函數。即使沒有為類聲明構造函數,編譯器也會自動地為類提供一個默認的構造函數。
構造函數的名稱與類名相同,其基本特征為:
— 構造函數不聲明返回類型(甚至也不能使用void),也不能返回值。
— 一般地,構造函數總是public 類型的。private 類型的構造函數表明類不能被實例化,通常用于只含有靜態成員的類。
— 在構造函數中不要做對類的實例進行初始化以外的事情,也不能被顯式地調用。
下面的代碼說明了構造函數的聲明方式:
// Dog.cs
// 構造對象時將執行構造函數
using System;
public class Dog
{
public string name;
public Dog() //聲明構造函數
{
name ="未知";
Console.WriteLine("Dog():Dog類已被初始化。");
}
public void Bark()
{
Console.WriteLine("汪汪!");
}
public static void Main()
{
DogmyDog = new Dog(); // 會調用構造函數
Console.WriteLine("myDog的名字為“{0}”,叫聲為:",myDog.name);
myDog.Bark();
}
}
在訪問一個類的時候,系統將首先執行構造函數中的語句。構造函數的功能是創建對象,使對象的狀態合法化。
在從構造函數返回之前,對象都是不確定的,不能用于執行任何操作;只有在構造函數執行完成之后,存放對象的內存塊中才存放這一個類的實例。
使用構造函數
構造函數是在創建給定類型的對象時執行的類方法。構造函數具有與類相同的名稱,它通常初始化新對象的數據成員。
在下面的示例中,定義了一個具有一個簡單的構造函數,名為 Taxi 的類。然后使用 new 運算符來實例化該類。在為新對象分配內存之后,new 運算符立即調用 Taxi 構造函數。
public class Taxi
{
public bool isInitialized;
public Taxi()
{
isInitialized = true;
}
}
class TestTaxi
{
static void Main()
{
Taxi t = new Taxi();
System.Console.WriteLine(t.isInitialized);
}
}不帶參數的構造函數稱為“默認構造函數”。無論何時,只要使用 new 運算符實例化對象,并且不為 new 提供任何參數,就會調用默認構造函數
除非類是 static 的,否則 C# 編譯器將為無構造函數的類提供一個公共的默認構造函數,以便該類可以實例化。
實例構造函數
實例構造函數用于創建和初始化實例。創建新對象時將調用類構造函數
class CoOrds
{
public int x, y;
// constructor
public CoOrds()
{
x = 0;
y = 0;
}
}
無論何時創建基于 CoOrds 類的對象,都會調用此構造函數。諸如此類不帶參數的構造函數稱為“默認構造函數”。然而,提供其他構造函數通常十分有用。
例如,可以向 CoOrds 類添加構造函數,以便可以為數據成員指定初始值:
// A constructor with two arguments:
public CoOrds(int x, int y)
{
this.x = x;
this.y = y;
}
這樣便可以用默認或特定的初始值創建 CoOrd 對象,如下所示:CoOrds p1 = new CoOrds();
CoOrds p2 = new CoOrds(5, 3);
如果類沒有默認構造函數,將自動生成一個構造函數,并且將用默認值來初始化對象字段
私有構造函數
私有構造函數是一種特殊的實例構造函數。它通常用在只包含靜態成員的類中。如果類具有一個或多個私有構造函數而沒有公共構造函數,
則不允許其他類(除了嵌套類)創建該類的實例。例如:
lass NLog
{
// Private Constructor:
private NLog() { }
public static double e = System.Math.E; //2.71828...
}聲明空構造函數可阻止自動生成默認構造函數。注意,如果您不對構造函數使用訪問修飾符,則在默認情況下它仍為私有構造函數。
但是,通常顯式地使用 private 修飾符來清楚地表明該類不能被實例化。
當沒有實例字段或實例方法(如 Math 類)時或者當調用方法以獲得類的實例時,私有構造函數可用于阻止創建類的實例。
如果類中的所有方法都是靜態的,可考慮使整個類成為靜態的。
靜態構造函數
靜態構造函數用于初始化任何靜態數據,或用于執行僅需執行一次的特定操作。在創建第一個實例或引用任何靜態成員之前,將自動調用靜態構造函數
class SimpleClass
{
// Static constructor
static SimpleClass()
{
//...
}
}靜態構造函數具有以下特點:
靜態構造函數既沒有訪問修飾符,也沒有參數。
在創建第一個實例或引用任何靜態成員之前,將自動調用靜態構造函數來初始化類。
無法直接調用靜態構造函數。
在程序中,用戶無法控制何時執行靜態構造函數。
靜態構造函數的典型用途是:當類使用日志文件時,將使用這種構造函數向日志文件中寫入項。
- 靜態構造函數在為非托管代碼創建包裝類時也很有用,此時該構造函數可以調用 LoadLibrary 方法。
public class Bus
{
// Static constructor:
static Bus()
{
System.Console.WriteLine("The static constructor invoked.");
}
public static void Drive()
{
System.Console.WriteLine("The Drive method invoked.");
}
}class TestBus
{
static void Main()
{
Bus.Drive();
}
}