轉:c#中類和對象詳解 - 孔小爽 - 博客園 (cnblogs.com)
類 (class) 是最基礎的 C# 類型。類是一個數據結構,將狀態(字段)和操作(方法和其他函數成員)組合在一個單元中。
類為動態創建的類實例 (instance) 提供了定義,實例也稱為對象 (object)。
P:萬物皆對象,實例是對象中的一種,eg創建類的一個實例,這個實例叫實例對象
類支持繼承 (inheritance) 和多態性 (polymorphism),這是派生類 (derived class) 可用來擴展和專用化基類 (base class) 的機制。
類的屬性 類的修飾符 類的名稱 (該類實現的接口)
public class Point (,)
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
類的實例使用 new 運算符創建,該運算符為新的實例分配內存,調用構造函數初始化該實例,並返回對該實例的引用。下面的語句創建兩個 Point 對象,並將對這兩個對象的引用存儲在兩個變量中:
Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);
當不再使用對象時,該對象占用的內存將自動收回。在 C# 中,沒有必要也不可能顯式釋放分配給對象的內存。
-
成員:
類的成員或者是靜態成員 (static member),或者是實例成員 (instance member)。靜態成員屬於類,實例成員屬於對象(類的實例)。(P:類class不分靜態實例)
成員 |
說明 |
常量 |
與類關聯的常數值 |
字段 |
類的變量 |
方法 |
類可執行的計算和操作 |
屬性 |
與讀寫類的命名屬性相關聯的操作 |
索引器 |
與以數組方式索引類的實例相關聯的操作 |
事件 |
可由類生成的通知 |
運算符 |
類所支持的轉換和表達式運算符 |
構造函數 |
初始化類的實例或類本身所需的操作 |
析構函數 |
在永久丟棄類的實例之前執行的操作 |
類型 |
類所聲明的嵌套類型 |
-
可訪問性
類的每個成員都有關聯的可訪問性,它控制能夠訪問該成員的程序文本區域。有五種可能的可訪問性形式。下表概述了這些可訪問性。
可訪問性 |
含義 |
public |
訪問不受限制 |
protected |
訪問僅限於此類和從此類派生的類 |
internal |
訪問僅限於此程序 |
protected internal |
訪問僅限於此程序和從此類派生的類 |
private |
訪問僅限於此類 |
-
基類
類聲明可通過在類名稱后面加上一個冒號和基類的名稱來指定一個基類。省略基類的指定等同於從類型 object 派生。在下面的示例中,Point3D 的基類為 Point,而 Point 的基類為 object:
public class Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Point3D: Point
{
public int z;
public Point3D(int x, int y, int z): Point(x, y) {
this.z = z;
}
}
一個類繼承它的基類的成員。繼承意味着一個類隱式地包含其基類的所有成員,但基類的構造函數除外。派生類能夠在繼承基類的基礎上添加新的成員,但是它不能移除繼承成員的定義。在前面的示例中,Point3D 類從 Point 類繼承了 x 字段和 y 字段,每個 Point3D 實例都包含三個字段 x、y 和 z。
從某個類類型到它的任何基類類型存在隱式的轉換。因此,類類型的變量可以引用該類的實例或任何派生類的實例。例如,對於前面給定的類聲明,Point 類型的變量既可以引用 Point 也可以引用 Point3D:
Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);
-
字段
字段是與類或類的實例關聯的變量。
使用 static 修飾符聲明的字段定義了一個靜態字段 (static field)。一個靜態字段只標識一個存儲位置。對一個類無論創建了多少個實例,它的靜態字段永遠都只有一個副本。
不使用 static 修飾符聲明的字段定義了一個實例字段 (instance field)。類的每個實例都包含了該類的所有實例字段的一個單獨副本。
在下面的示例中,Color 類的每個實例都有實例字段 r、g 和 b 的單獨副本,但是 Black、White、Red、Green 和 Blue 靜態字段只存在一個副本:
public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);
private byte r, g, b;
public Color(byte r, byte g, byte b) {
this.r = r;
this.g = g;
this.b = b;
}
}
如上面的示例所示,可以使用 readonly 修飾符聲明只讀字段 (read-only field)。給 readonly 字段的賦值只能作為字段聲明的組成部分出現,或在同一類中的實例構造函數或靜態構造函數中出現。
-
方法
方法 (method) 是一種用於實現可以由對象或類執行的計算或操作的成員。靜態方法 (static method) 通過類來訪問。實例方法 (instance method) 通過類的實例來訪問。
方法具有一個參數 (parameter) 列表(可能為空),表示傳遞給該方法的值或變量引用;方法還具有一個返回類型 (return type),指定該方法計算和返回的值的類型。如果方法不返回值,則其返回類型為 void。
方法的簽名 (signature) 在聲明該方法的類中必須唯一。方法的簽名由方法的名稱及其參數的數目、修飾符和類型組成。方法的簽名不包含返回類型
-
靜態方法和實例方法
使用 static 修飾符聲明的方法為靜態方法 (static method)。靜態方法不對特定實例進行操作,並且只能訪問靜態成員。
不使用 static 修飾符聲明的方法為實例方法 (instance method)。實例方法對特定實例進行操作,並且能夠訪問靜態成員和實例成員。在調用實例方法的實例上,可以通過 this 顯式地訪問該實例。而在靜態方法中引用 this 是錯誤的。
下面的 Entity 類具有靜態成員和實例成員。
class Entity
{
static int nextSerialNo;
int serialNo;
public Entity() {
serialNo = nextSerialNo++;
}
public int GetSerialNo() {
return serialNo;
}
public static int GetNextSerialNo() {
return nextSerialNo;
}
public static void SetNextSerialNo(int value) {
nextSerialNo = value;
}
}
每個 Entity 實例都包含一個序號(並且假定這里省略了一些其他信息)。Entity 構造函數(類似於實例方法)使用下一個可用的序號初始化新的實例。由於該構造函數是一個實例成員,它既可以訪問 serialNo 實例字段,也可以訪問 nextSerialNo 靜態字段。
GetNextSerialNo 和 SetNextSerialNo 靜態方法只可以訪問 nextSerialNo 靜態字段,但是如果訪問 serialNo 實例字段就會產生錯誤。
下面的示例演示 Entity 類的使用。
using System;
class Test
{
static void Main() {
Entity.SetNextSerialNo(1000); //靜態方法的使用
Entity e1 = new Entity();
Entity e2 = new Entity();
Console.WriteLine(e1.GetSerialNo()); // Outputs "1000"
Console.WriteLine(e2.GetSerialNo()); // Outputs "1001"
Console.WriteLine(Entity.GetNextSerialNo()); // Outputs "1002"
}
}
注意:SetNextSerialNo 和 GetNextSerialNo 靜態方法是在類上調用的,而 GetSerialNo 實例方法是在該類的實例上調用的。
-
構造函數
C# 支持兩種構造函數:實例構造函數和靜態構造函數。實例構造函數 (instance constructor) 是實現初始化類實例所需操作的成員。靜態構造函數 (static constructor) 是一種用於在第一次加載類本身時實現其初始化所需操作的成員。
構造函數的聲明如同方法一樣,不過它沒有返回類型,並且它的名稱與其所屬的類的名稱相同。如果構造函數聲明包含 static 修飾符,則它聲明了一個靜態構造函數。否則,它聲明的是一個實例構造函數。
實例構造函數可以被重載。例如,List 類聲明了兩個實例構造函數,一個無參數,另一個接受一個 int 參數。實例構造函數使用 new 運算符進行調用。下面的語句分別使用 List 類的每個構造函數分配兩個 List 實例。
List list1 = new List();
List list2 = new List(10);
實例構造函數不同於其他成員,它是不能被繼承的。一個類除了其中實際聲明的實例構造函數外,沒有其他的實例構造函數。如果沒有為某個類提供任何實例構造函數,則將自動提供一個不帶參數的空的實例構造函數。
-
屬性
屬性 (propery) 是字段的自然擴展。屬性和字段都是命名的成員,都具有相關的類型,且用於訪問字段和屬性的語法也相同。然而,與字段不同,屬性不表示存儲位置。相反,屬性有訪問器 (accessor),這些訪問器指定在它們的值被讀取或寫入時需執行的語句。
屬性的聲明與字段類似,不同的是屬性聲明以位於定界符 { 和 } 之間的一個 get 訪問器和/或一個 set 訪問器結束,而不是以分號結束。同時具有 get 訪問器和 set 訪問器的屬性是讀寫屬性 (read-write property),只有 get 訪問器的屬性是只讀屬性 (read-only property),只有 set 訪問器的屬性是只寫屬性 (write-only property)。
get 訪問器相當於一個具有屬性類型返回值的無參數方法。除了作為賦值的目標,當在表達式中引用屬性時,將調用該屬性的 get 訪問器以計算該屬性的值。
set 訪問器相當於具有一個名為 value 的參數並且沒有返回類型的方法。當某個屬性作為賦值的目標被引用,或者作為 ++ 或 -- 的操作數被引用時,將調用 set 訪問器,並傳入提供新值的實參。(P:值被改變了)
List 類聲明了兩個屬性 Count 和 Capacity,它們分別是只讀屬性和讀寫屬性。下面是這些屬性的使用示例。
object[] items;
int count;
public int Count {
get { return count; }
}
public string Capacity {
get {
return items.Length;
}
set {
if (value < count) value = count;
if (value != items.Length) {
object[] newItems = new object[value];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
}
}
List names = new List();
names.Capacity = 100; // Invokes set accessor,value=100
int i = names.Count; // Invokes get accessor
int j = names.Capacity; // Invokes get accessor
與字段和方法相似,C# 同時支持實例屬性和靜態屬性。靜態屬性使用 static 修飾符聲明,而實例屬性的聲明不帶該修飾符。
屬性的訪問器可以是虛的。當屬性聲明包括 virtual、abstract 或 override 修飾符時,修飾符應用於該屬性的訪問器。
-
索引器
索引器 (indexer) 是這樣一個成員:它使對象能夠用與數組相同的方式進行索引。索引器的聲明與屬性類似,不同的是該成員的名稱是 this,后跟一個位於定界符 [ 和 ] 之間的參數列表。在索引器的訪問器中可以使用這些參數。與屬性類似,索引器可以是讀寫、只讀和只寫的,並且索引器的訪問器可以是虛的。
該 List 類聲明了單個讀寫索引器,該索引器接受一個 int 參數。該索引器使得通過 int 值對 List 實例進行索引成為可能。例如
List numbers = new List();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++) {
string s = (string)names[i];
names[i] = s.ToUpper();
}
索引器可以被重載,這意味着一個類可以聲明多個索引器,只要它們的參數的數量和類型不同即可。
-
事件
事件 (event) 是一種使類或對象能夠提供通知的成員。事件的聲明與字段類似,不同的是事件的聲明包含 event 關鍵字,並且類型必須是委托類型。
在聲明事件成員的類中,事件的行為就像委托類型的字段(前提是該事件不是抽象的並且未聲明訪問器)。該字段存儲對一個委托的引用,該委托表示已添加到該事件的事件處理程序。如果尚未添加事件處理程序,則該字段為 null。
List 類聲明了一個名為 Changed 的事件成員,它指示有一個新的項已被添加到列表中。Changed 事件由 OnChanged 虛方法引發,后者先檢查該事件是否為 null(表明沒有處理程序)。“引發一個事件”與“調用一個由該事件表示的委托”完全等效,因此沒有用於引發事件的特殊語言構造。
客戶端通過事件處理程序 (event handler) 來響應事件。事件處理程序使用 += 運算符添加,使用 -= 運算符移除。下面的示例向 List 類的 Changed 事件添加一個事件處理程序。
public event EventHandler Changed;
using System;
class Test
{
static int changeCount;
static void ListChanged(object sender, EventArgs e) {
changeCount++;
}
static void Main() {
List names = new List();
names.Changed += new EventHandler(ListChanged);
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
Console.WriteLine(changeCount); // Outputs "3"
}
}
對於要求控制事件的底層存儲的高級情形,事件聲明可以顯式提供 add 和 remove 訪問器,它們在某種程度上類似於屬性的 set 訪問器。
-
運算符
運算符 (operator) 是一種類成員,它定義了可應用於類實例的特定表達式運算符的含義。可以定義三種運算符:一元運算符、二元運算符和轉換運算符。所有運算符都必須聲明為 public 和 static。
List 類聲明了兩個運算符 operator == 和 operator !=,從而為將那些運算符應用於 List 實例的表達式賦予了新的含義。具體而言,上述運算符將兩個 List 實例的相等關系定義為逐一比較其中所包含的對象(使用所包含對象的 Equals 方法)。下面的示例使用 == 運算符比較兩個 List 實例。
public static bool operator ==(List a, List b) {
return Equals(a, b);
}
public static bool operator !=(List a, List b) {
return !Equals(a, b);
}
using System;
class Test
{
static void Main() {
List a = new List();
a.Add(1);
a.Add(2);
List b = new List();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b); // Outputs "True"
b.Add(3);
Console.WriteLine(a == b); // Outputs "False"
}
}
第一個 Console.WriteLine 輸出 True,原因是兩個列表包含的對象數目和值均相同。如果 List 未定義 operator ==,則第一個 Console.WriteLine 將輸出 False,原因是 a 和 b 引用的是不同的 List 實例。
-
析構函數
析構函數 (destructor) 是一種用於實現銷毀類實例所需操作的成員。析構函數不能帶參數,不能具有可訪問性修飾符,也不能被顯式調用。垃圾回收期間會自動調用所涉及實例的析構函數。
垃圾回收器在決定何時回收對象和運行析構函數方面允許有廣泛的自由度。具體而言,析構函數調用的時機並不是確定的,析構函數可能在任何線程上執行。由於這些以及其他原因,僅當沒有其他可行的解決方案時,才應在類中實現析構函數。
-
虛方法、重寫方法和抽象方法
若一個實例方法的聲明中含有 virtual 修飾符,則稱該方法為虛方法 (virtual method)。若其中沒有 virtual 修飾符,則稱該方法為非虛方法 (non-virtual method)。
在調用一個虛方法時,該調用所涉及的那個實例的運行時類型 (runtime type) 確定了要被調用的究竟是該方法的哪一個實現。在非虛方法調用中,實例的編譯時類型 (compile-time type) 是決定性因素。
虛方法可以在派生類中重寫 (override)。當某個實例方法聲明包括 override 修飾符時,該方法將重寫所繼承的具有相同簽名的虛方法。虛方法聲明用於引入新方法,而重寫方法聲明則用於使現有的繼承虛方法專用化(通過提供該方法的新實現)。
抽象 (abstract) 方法是沒有實現的虛方法。抽象方法使用 abstract 修飾符進行聲明,並且只有在同樣被聲明為 abstract 的類中才允許出現。抽象方法必須在每個非抽象派生類中重寫。
下面的示例聲明一個抽象類 Expression,它表示一個表達式樹節點;它有三個派生類 Constant、VariableReference 和 Operation,它們分別實現了常量、變量引用和算術運算的表達式樹節點。
using System;
using System.Collections;
public abstract class Expression
{
public abstract double Evaluate(Hashtable vars);
}
//常量
public class Constant: Expression
{
double value;
public Constant(double value) {
this.value = value;
}
public override double Evaluate(Hashtable vars) {
return value;
}
}
//變量
public class VariableReference: Expression
{
string name;
public VariableReference(string name) {
this.name = name;
}
public override double Evaluate(Hashtable vars) {
object value = vars[name];
if (value == null) {
throw new Exception("Unknown variable: " + name);
}
return Convert.ToDouble(value);
}
}
//操作符
public class Operation: Expression
{
Expression left;
char op;
Expression right;
public Operation(Expression left, char op, Expression right) {
this.left = left;
this.op = op;
this.right = right;
}
public override double Evaluate(Hashtable vars) {
double x = left.Evaluate(vars);
double y = right.Evaluate(vars);
switch (op) {
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;
}
throw new Exception("Unknown operator");
}
}
上面的四個類可用於為算術表達式建模。例如,使用這些類的實例,表達式 x + 3 可如下表示。
Expression e = new Operation(
new VariableReference("x"),
'+',
new Constant(3));
Expression 實例的 Evaluate 方法將被調用,以計算給定的表達式的值,從而產生一個 double 值。該方法接受一個包含變量名稱(作為哈希表項的鍵)和值(作為項的值)的 Hashtable 作為參數。Evaluate 方法是一個虛抽象方法,意味着非抽象派生類必須重寫該方法以提供具體的實現。
Constant 的 Evaluate 實現只是返回所存儲的常量。VariableReference 的實現在哈希表中查找變量名稱,並返回產生的值。Operation 的實現先對左操作數和右操作數求值(通過遞歸調用它們的 Evaluate 方法),然后執行給定的算術運算。
下面的程序使用 Expression 類,對於不同的 x 和 y 值,計算表達式 x * (y + 2) 的值。
using System;
using System.Collections;
class Test
{
static void Main() {
Expression e = new Operation(
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);
Hashtable vars = new Hashtable();
vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // Outputs "21"
vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // Outputs "16.5"
}
}