學弟學妹們,C#為啥學不好?是因為你沒真正理解物件導向的思想!

2021-04-29 17:00:02


7.1物件導向的基本概念

7.1.1 什麼是物件導向程式設計

物件=(演演算法+資料結構

OOP程式=(物件+物件+……)

程式就是許多物件在計算機中相繼表現自己,而物件則是一個個程式實體。

7.1.2 物件導向程式設計的特點

1. 封裝性

封裝: 是指將資料成員、屬性、事件和方法(統稱為成員)集合在一個整體裡的過程。
隱藏: 對內部細節隱藏保護的能力,類內的某些成員可以以對外隱藏的特性被保護起來。從而保證了類具有較好的獨立性,防止外部程式破壞類的內部資料,同時便於程式的維護和修改。

2. 繼承性

繼承: 利用現有類派生出新類的過程。
特點: 新類擁有原有類的特性,又增加了自身新的特性,設計程式時,只需對新增的內容或是對原內容的修改設計程式碼。
作用: 繼承性可簡化類和物件的建立工作量,增強程式碼的可重用性。
例如:有了人類,要定義學生類,只要增加一個學號屬性。

3. 多型性

多型性:不同的類進行同一操作可以有不同的行為。同樣的訊息被不同型別的物件接收時導致完全不同的行為。多型性允許每個物件以適合自身的方式去響應共同的訊息,不必為相同功能的操作作用於不同的物件而去特意識別。
例如,定義一個狗類和一個貓類,狗和貓都會叫(可定義方法bark),說明兩者在這方面可以進行相同的操作,然而,狗和貓叫的行為是截然不同的,因為狗叫是「汪汪」,而貓叫是「喵喵」 。

7.2 類

7.2.1 類的概念

類(class)是物件導向技術中最重要的一種資料結構,是指具有相同屬性和操作的一組物件的抽象集合,它支援資訊隱藏和封裝,進而支援對抽象資料型別(Abstract Data Type,ADT)的實現。

類包含:資料成員(常數和域)、函數成員(方法、屬性、事件、索引器、運運算元、範例建構函式、解構函式和靜態建構函式)和巢狀型別。

7.2.2 類的宣告

類的屬性集 類的修飾符 關鍵字class 類名 繼承方式 基礎類別名
{
}

如:定義汽車類

public class Car
{
		public string color;
		public string brand;
}	

C#中常用的類修飾符:

序號修飾符說明
1public表示不限制對該類的存取
2protected表示只能對其所在類和所在類的子類進行存取
3internal只有其所在類才能存取,不允許外部程式集使用該類
4private只有.NET中的應用程式或庫才能存取
5new僅允許在巢狀類宣告時使用,表明類中隱藏了由基礎類別中繼承而來的、與基礎類別中同名的成員
6abstract抽象類,不允許建立類的範例
7sealed密封類,不允許被繼承

7.2.3 類的成員

 區域性變數:在指定範圍內有效。
 欄位:即類中的變數或常數,包括靜態欄位、範例欄位、常數和唯讀欄位。
 方法成員:包括靜態方法和實體方法。
 屬性:按屬性指定的get方法和Set方法對欄位進行讀寫。屬性本質上是方法。
 事件:代表事件本身,同時聯絡事件和事件處理常式。
 索引指示器:允許像使用陣列那樣存取類中的資料成員。
 操作符過載:採用過載操作符的方法定義類中特有的操作。
 建構函式和解構函式。

包含可執行程式碼的成員被認為是類中的函數成員,這些函數成員有方法、屬性、索引指示器、操作符過載、建構函式和解構函式。

7.2.4 類成員存取修飾符

Private:私有資料成員只能被類內部的函數使用和修改,私有函數成員只能被類內部的函數呼叫。派生類雖然繼承了基礎類別私有成員,但不能直接存取它們,只能通過基礎類別的公有成員存取。

Protected:保護資料成員只能被類內部和派生類的函數使用和修改,保護函數成員只能被類內部和派生類的函數呼叫。

Public:類的公用函數成員可以被類的外部程式所呼叫,類的公用資料成員可以被類的外部程式直接使用。公有函數實際是一個類和外部通訊的介面,外部函數通過呼叫公有函數,按照預先設定好的方法修改類的私有成員和保護成員。

Internal:內部成員只能在同一程式集中的檔案中才是可以存取的,一般是同一個應用(Application)或庫(Library)。

7.3 物件

物件是類的範例,是OOP應用程式的一個組成部件。

汽車類是抽象的概念,它有顏色和品牌,是所有汽車的集合,是「類」;而一輛紅色賓士車則是汽車類的一個具體範例,是「物件」。

變數型別是一個類,變數也是一個物件。

物件的定義、範例化及存取

【例7-1】實現存取Car類的物件和物件資料狀態。

public class Car
{
		public  int number;		//號碼屬性
		public  string color;		//顏色屬性
		private string _brand;	//品牌域
		public Car()
        {
	   }
	   public string brand		//品牌屬性
	  {
		          get			//讀操作
{
     return this._brand;
}
set			//寫操作
{
    this._brand = value;
}
} 
}

7.4 建構函式和解構函式

建構函式和解構函式是類中比較特殊的兩種成員函數,主要用來對物件進行初始化和回收物件資源。

建構函式的名字和類名相同。解構函式和建構函式的名字相同,但解構函式要在名字前加一個波浪號(~)。

7.4.1 建構函式

 建構函式的名稱與類名相同;
 建構函式不宣告返回型別;
 建構函式通常是公有的(使用public存取限制修飾符宣告),如果宣告為保護的(protected)或私有的(private),則該構造
函數不能用於類的範例化;

 建構函式的程式碼中通常只進行物件初始化工作,而不應執行其他操作;
 建構函式在建立物件時被自動呼叫,不能像其他方法那樣顯式地呼叫建構函式。

【例7-2】使用建構函式對類Person的3個欄位進行初始化。

public class Person 
{ 
	public string m_name; 
	protected int m_age; 
	protected bool m_gender; 
	//建構函式 
	public Person() 
	{ 
		m_name = "Unknown"; 
		m_age = 0; 
		m_gender = false; 
	}
 }

一個類定義必須且至少有一個建構函式,如果定義類時,沒有宣告建構函式,系統會提供一個預設的建構函式,不帶任何引數的建構函式稱為預設建構函式。如果宣告了建構函式,系統將不再提供預設建構函式。

為了建立範例的方便,一個類可以有多個具有不同參數列的建構函式,即建構函式可以過載。

 public class Xyz {
	// 成員變數
	int x;
	public Xyz() {			//參數列為空的建構函式		
		x = 0;			// 預設建立物件
	}
	public Xyz(int i) {		//帶一個引數的建構函式		
		x = i;			// 使用引數建立物件
	}
}

7.4.2 解構函式

解構函式不接受任何引數,也不返回任何值;

 解構函式不能使用任何存取限制修飾符; 
 
 解構函式的程式碼中通常只進行銷燬物件的工作,而不應執行其他的操作; 

解構函式不能被繼承,也不能被顯式地呼叫。

public class Person 
{ 
	private string m_name; 
	private int m_age; 
	private bool m_gender; 
	//解構函式 
	 ~Person() 
	{ 
	}
}

7.5 方法

7.5.1 方法的宣告

方法是一種用於實現可以由物件或類執行的計算或操作的成員。類的方法主要是和類相關聯的動作,它是類的外部介面,對於那些私有的欄位來說,外部介面實現對它們的操作一般只能通過方法來實現。

語法格式:

存取修飾符 返回型別 方法名(參數列){ }

 方法的存取修飾符通常是public,以保證在類定義外部能夠呼叫該方法。

 方法的返回型別用於指定由該方法計算和返回的值的型別,可以是任何值型別或參照型別資料,如,int、string及前面定義的Person類。如果方法不返回一個值,則它的返回型別為void。

 方法名是一個合法的C#識別符號。

 參數列在一對圓括號中,指定呼叫該方法時需要使用的引數個數、各個引數的型別,引數之間以逗號分隔。

 實現特定功能的語句塊放在一對大括號中,叫方法體,「{」表示方法體的開始,「}」表示方法體的結束。

 如果方法有返回值,則方法體中必須包含一個return語句,以指定返回值,其型別必須和方法的返回型別相同。如果方法無返回值,在方法體中可以不包含return語句。

例如:無返回值的方法method:

public void method()
{  
	Console.Write("方法宣告");
} 

7.5.2 方法的引數

 在方法宣告中使用的引數叫形式引數(形參)

 在呼叫方法中使用的引數叫實際引數(實參)

 在呼叫方法時,引數傳遞就是將實參傳遞給形參的過程。

例如,某類定義中宣告方法時的形參如下:

public int IntMax(int a,int b){}

則宣告物件classmax後呼叫方法時的實參為:

   classmax.IntMax(x,y) 

 呼叫方法時,傳遞給方法的引數型別應該與方法定義的引數型別相同,或者是能夠隱式轉換為方法定義的引數型別。

 如果方法進行處理和更改數值等操作,有時需要傳遞引數值給方法並從方法獲得返回值。下面是引數值的4種常用情況。

  1. 值引數
  2. 參照引數
  3. 輸出引數
  4. 引數陣列

值引數

宣告時不帶修飾符的引數是值引數,一個值引數相當於一個區域性變數,初始值來自該方法呼叫時提供的相應的實參。引數按值的方式傳遞是指當把實參傳遞給形參時,是把實參的值複製(拷貝)給形參,實參和形參使用的是兩個不同記憶體中的值,所以這種引數傳遞方式的特點是形參的值發生改變時,不會影響實參的值,從而保證了實引資料的安全。 之所以叫做值型別,是因為傳遞的是物件的副本而不是物件本身。傳遞的是值,而不是同一個物件。

【例7-3】值引數傳遞。

      CzMath c = new CzMath(); 
      c.Swap(a, b); 
         ……
     class CzMath 
    {   //交換兩個數的值 
    public void Swap(double x, double y) 
         {  double temp = x; 
             x = y; 
             y = temp; 
  } 
   } 

程式中,方法Swap試圖通過一個臨時變數來交換a和b的值,但程式執行後的結果仍然是:

a = 100;
b = 150;

這是因為呼叫Swap方法時,首先將主方法Main中定義的變數a和b(實參)進行一次複製,而後傳遞給方法的形參,在方法的執行程式碼中所改變的只是複製的值,而不是實際引數的原始值,因此達不到交換數值的效果。

上面傳遞引數的方法叫做值傳遞值傳遞:

實參的值複製(拷貝)給形參,實參和形參使用的是兩個不同記憶體中的值,所以這種引數傳遞方式的特點是形參的值發生改變時,不會影響實參的值,從而保證了實引資料的安全。
如果希望改變實參的值,則需要另一種傳遞引數的方法參照傳遞。

參照引數

參照傳遞是指實參傳遞給形參時,不是將實參的值複製給形參,而是將實參的參照傳遞給形參,實參與形參使用的是一個記憶體中的值。這種引數傳遞方式的特點是形參的值發生改變時,同時也改變實參的值。

參照引數使用ref修飾符,告訴編譯器,實參與形參的傳遞方式是參照。

【例7-4】參照引數傳遞。

         ……
      CzMath c = new CzMath(); 
      c.Swap(ref a, b); 
         ……
     class CzMath 
    {   //交換兩個數的值 
         public void Swap(ref double x, double y) 
         {  double temp = x; 
             x = y; 
             y = temp; 
        } 
   } 
類物件引數總是按參照傳遞的,所以類物件引數傳遞不需要使用ref關鍵字。 

程式中,傳遞給方法的引數就不是實參的拷貝,而是指向實參的參照。所以當對形參進行修改時,相應的實參也發生了改變。程式執行的結果表明了數值交換的成功:

a = 150;
b = 100;

輸出引數

在傳遞的引數前加out關鍵字,即可將該傳遞引數設定為一個輸出引數。

與參照引數類似,輸出引數也不開闢新的記憶體區域。它與參照型引數的差別在於,呼叫方法前無需對變數進行初始化。輸出型引數用於傳遞方法返回的資料。

out修飾符後應跟隨與形參的型別相同的型別宣告。在方法返回後,傳遞的變數被認為經過了初始化。

【例7-5】輸出引數傳遞。

  ……

double ave; //ave未初始化
CzMath m = new CzMath();
m.Average(a, b, out ave);
……
class CzMath
{
public void Average(int x, int y, out double z)
{
z = Convert.ToDouble(x + y) / 2;
}
}

引數陣列

引數陣列必須用params修飾詞明確指定。

方法的引數型別也可以是陣列 。
public double Average(int[] array)
{    double ave = 0;
	      int sum = 0;
	      for(int i = 0; i < array.Length; i++)		
		sum += array[i];
	      ave = (double)sum / array.Length;
	      return ave;
}

public double Average(params int[] array)
{……}

呼叫Average方法的範例程式碼:

int[] a = { 1, 3, 3, 5, 7, 9 };
int x = Average(a);
int y = Average(1, 3, 5, 7, 9);
int z = Average(10, 20, 30);
如果在形參前面加上關鍵字params,該形參就成為了引數陣列。

public double Average(params int[] array)
{ double ave = 0;
int sum = 0;
for(int i = 0; i < array.Length; i++)
sum += array[i];
ave = (double)sum / array.Length;
return ave;
}

傳遞給陣列型引數的實參既可以是一個陣列,也可以是任意多個陣列元素型別的變數。

例如,呼叫Average的程式碼:

  int[] a = { 1, 3, 3, 5, 7, 9 };
  int x = Average(a);
  int y = Average(1, 3, 5, 7, 9);
  int z = Average(10, 20, 30); 

都是合法的 。

C#中對於引數陣列有著嚴格的規定:

  • 1 在方法的參數列中只允許出現一個引數陣列,而且在方法同時具有固定引數和引數陣列的情況下,引數陣列必須放在整個參數列的最後。
  • 2 引數陣列只允許是一維陣列。
  • 3 引數陣列不能同時作為參照引數或輸出引數。

【例7-6 】引數陣列的使用-計算陣列所有元素的平均值 。

7.5.3 靜態和非靜態方法

方法分為靜態方法(方法宣告中含有static修飾符 )和非靜態方法(方法宣告中沒有static修飾符 )。

1.靜態方法

(1)靜態方法不對特定範例進行操作,在靜態方法中參照this會導致編譯錯誤。

(2)C#中通過關鍵字static來定義靜態成員。和範例成員不同,使用靜態成員時,圓點連線符的前面不再是某個具體的物件變數,而是類的名稱。如Console.WriteLine和Console.ReadLine等方法就屬於靜態方法,使用時沒有建立哪個具體的Console類的範例,而是直接使用類本身。

例7-4類CzMath的Swap方法只用於交換兩個數值,而與具體的物件無關,因此可以將其定義為靜態方法,這樣在呼叫方法時就無需建立CzMath類的範例,

【例7-7 】靜態方法。

       ……
 // CzMath c = new CzMath(); 
 //c.Swap(ref a, b); 
 CzMath.Swap(ref a, ref b); 
     ……
 class CzMath 
{   //交換兩個數的值 
     public static void Swap(ref double x, double y) 
     {  double temp = x;  x = y; y = temp;  } 

}

方法分為靜態方法(方法宣告中含有static修飾符 )和非靜態方法(方法宣告中沒有static修飾符 )。

2.非靜態方法

非靜態方法是對類的某個給定的範例進行操作,而且可以用this來存取該方法。
例如程式例7-4中的

CzMath c = new CzMath(); 
c.Swap(ref a, ref b);

就是先範例化CzMath的物件c,然後用物件c去呼叫非靜態方法Swap()。

7.5.4 方法的過載

方法過載是指呼叫同一方法名,但各方法中引數的資料型別、個數或順序不同。類中有兩個以上的同名方法,但使用的引數型別、個數或順序不同,呼叫時,編譯器就可以判斷在哪種情況下呼叫哪種方法。

 public int MethodTest(int i, int j)
{……}
public int MethodTest(int i)
{……}
public string MethodTest(string s)
{……} 	

7.6 欄位和屬性

為了儲存類的範例的各種資料資訊,C#提供了兩種方法—欄位(或稱域、成員變數)和屬性。

屬性不是欄位,本質上是定義修改欄位的方法, 兩者密切相關。

7.6.1 欄位概念及用途

欄位也叫成員變數,表示儲存位置,用來儲存類的各種資料資訊。

欄位是 C#中不可缺少的一部分,代表一個與物件或類相關的變數或常數。

一個欄位宣告可以把一個或多個給定型別的欄位引入。

欄位的宣告非常簡單。例如:

 private int a;

7.6.2 欄位的宣告

欄位又可分為靜態欄位、範例欄位、常數和唯讀欄位。
例如:

private static int myvar;
private int myvar;
private const int myvar=5;
private readonly int myvar=6;

【例7-8】四種欄位的使用。

7.6.3 屬性的概念及用途

屬性不是欄位,但必然和類中的某個或某些欄位相聯絡。

屬性定義了得到和修改相聯絡的欄位的方法。

C#中的屬性更充分地體現了物件的封裝性:不直接操作類的資料內容,而是通過存取器進行存取,藉助於get和set方法對屬性的值進行讀寫。

當讀取屬性時,執行get存取器的程式碼塊;當向屬性分配一個新值時,執行set存取器的程式碼塊。

7.6.4 屬性的宣告及使用

屬性在類模組內是通過以下方式宣告的:指定欄位的存取級別,後面是屬性的型別,接下來是屬性的名稱,然後是宣告get存取器和/或set存取器的程式碼模組。

例如,在Student類中可以Name屬性來封裝對私有欄位name的存取:

public class Student
{
  private string name;	//欄位
  public string Name 		//屬性
  {
    get { return name; }
    set { name = value; }
  }
}

屬性

作為類的特殊函數成員,get和set存取函數需要包含在屬性宣告的內部,而函數宣告只需要寫出get和set關鍵字即可。其中get存取函數沒有引數,預設返回型別就是屬性的型別,表示屬性返回值;set存取函數的預設返回型別為void,且隱含了一個與屬性型別相同的引數value,表示要傳遞給屬性的值。

通過屬性來存取隱藏的欄位,例如:

Student s1 = new Student(「李明」);
Console.WriteLine(s1.Name);		//呼叫get存取函數存取name欄位
Console.WriteLine(「請輸入新姓名:」);
s1.Name = Console.ReadLine();	//呼叫set存取函數修改name欄位

屬性可以只包含一個存取函數,如只有get存取函數,那麼屬性的值不能被修改;如只有set存取函數,則表明屬性的值只能寫不能讀。
例如,希望Student物件在建立之後就不允許修改其姓名,那麼Name屬性的定義可以修改為:

public string Name 		//唯讀屬性
{
    get { return name; }    
}

屬性的典型用法是一個共有屬性對應封裝一個私有或保護欄位,但這並非強制要求。屬性本質上是方法,在其程式碼中可以進行各種控制和計算。
例如,在學生類中有個表示出生年份的私有欄位birthYear,那麼表示年齡的屬性Age的返回值應該是當前年份減去出生年份:

privage int birthYear;
public int Age
{
		get{ return DateTime.Now.Year – birthYear;}
}

注意:在C# 3.0中,提供了名為「自動屬性」特徵,它允許只寫出屬性及其存取函數的名稱,編譯就會自動生成所要封裝的欄位以及存取函數的執行程式碼。
例如,

public class Student
{
  public string Name	 { getset;}
}

其效果和下面傳統的定義方式是一樣的:

public class Student
{
  private string name;		//欄位
  public string Name 			//屬性
  {
    get { return name; }
    set { name = value; }
  }
}

7.7 繼承和多型

封裝、繼承和多型性是物件導向程式設計的3個基本要素

通過繼承,派生類能夠在增加新功能的同時,吸收現有類的資料和行為,從而提高軟體的可重用性。

而多型性使得程式能夠以統一的方式來處理基礎類別和派生類的物件行為,甚至是未來的派生類,從而提高系統的可延伸性和可維護性。

7.7.1 基礎類別和派生類

1.基礎類別和派生類

所謂繼承,是指在已有類的基礎上構造新的類,新類繼承了原有類的資料成員、屬性、方法和事件。原有的類稱為基礎類別,新類稱為派生類。

【例7-9】基礎類別和派生類的例子。

為避免層次結構過於複雜,C#中的類不支援多繼承,即不允許一個派生類繼承多個基礎類別,只有在類和介面之間可以實現多繼承。
.NET 類庫本身在構造過程中就充分利用了繼承技術。System.Object類是其他所有類的基礎類別,不僅如此,C#是完全物件導向的程式語言,其中所有的資料型別都是從類中衍生而來,因此 System.Object類也是其他所有資料型別的基礎類別。

存取基礎類別成員

2.存取基礎類別成員

存取基礎類別成員涉及隱藏基礎類別成員和base關鍵字的使用。

(1)隱藏基礎類別成員

大多數情況下,派生類不會一成不變地繼承基礎類別中的所有成員,如可能希望在某些欄位中儲存不同的資訊、在某些方法中執行不同的操作等。這時就需要通過new關鍵字來隱藏基礎類別中的成員。

例如例7-9中,可能會希望StudentLeader類的NewPrintInfo()方法輸出不同的內容(除了基礎類別的三個欄位之外,還需要輸出Duty),這時就可以將NewPrintInfo()方法定義為:

public new void PrintInfo()
{
      Console.WriteLine(No);
      Console.WriteLine(Name);
      Console.WriteLine(Score);
      Console.WriteLine(Duty);
 }

提示:隱藏基礎類別成員時所使用的new
關鍵字屬於一種修飾符,它和建立
物件時使用的new操作符是完全不同的。

這時稱派生類中重新定義的方法覆蓋了基礎類別中的同名方法。其中,new關鍵字放在存取限制修飾符的前後均可,但一定要在成員的型別說明之前。

(2) base關鍵字的使用

base關鍵字用來存取當前物件的基礎類別物件,進而呼叫基礎類別物件的成員。

例如,可以StudentLeader類的PrintInfo方法改寫成:

public new void PrintInfo()
{
      base.PrintInfo();      
      Console.WriteLine(Duty);
 }

這樣,派生類StudentLeader的PrintInfo()方法就呼叫了其隱藏的基礎類別方法。

base的另外一個用途是在派生類中呼叫基礎類別建構函式,例如,上例中StudentLeader類別建構函式可以改寫為:

public StudentLeader(string a, string b, int c,string d):base(a, b, c)
{
        Duty = d;
}

其中,base(a, b, c)表示呼叫基礎類別建構函式Student(a,b,c)。

7.7.2 多型

「多型性」 的含義:

同一事物在不同的條件下可以表現出不同的形態。
在物件之間進行通訊時非常有用,如一個物件傳送訊息到其他物件,它並不一定要知道接收訊息的物件屬於哪一類。接收到訊息之後,不同型別的物件可以做出不同的解釋,執行不同的操作,從而產生不同的結果。

1.虛擬方法和重寫方法
2.抽象類和抽象方法
3.密封類和密封方法

虛擬方法和重寫方法

派生類很少一成不變地去繼承基礎類別中的所有成員。

 一種情況是派生類中的方法成員可以隱藏基礎類別中同名的方法成員,這時通過關鍵字new對成員加 以修飾;
 另一種更為普遍和靈活的情況是將基礎類別的方法成員定義為虛擬方法,而在派生類中對虛擬方法進行重寫。
 後者的優勢在於它可以實現執行時的多型性,即程式可以在執行過程中確定應該呼叫哪一個方法成員。

基礎類別的虛擬方法通過關鍵字virtual進行定義,而派生類的重寫方法則通過關鍵字override進行定義。

【例7-10】修改例7-9,要求使用虛擬方法和重寫方法。

說明:
1.如果在派生類中使用override關鍵字定義了重寫方法,那麼也就允許該類自己的派生類繼續重寫這個方法,因此重寫方法預設也是一種虛 擬方法,但不能同時使用virtual和override修飾一個方法。

2.虛擬方法不能是私有的。而且在基礎類別和派生類中,對同一個虛擬方法和重寫方法的存取限制應當相同,即要麼都使用public修飾符,要麼都使用 protected修飾符。

過載和重寫

過載和重寫的區別:

  • 重寫是派生類具有和基礎類別同名的方法,即用派生類同名(可以同形參和返回值)重寫基礎類別的同名方法,所以也稱之為覆蓋,屬於執行時多型;
  • 過載是同一個類中具有同名的方法,其形參列表不能夠相同,屬於編譯時多型。

2.抽象類和抽象方法

基礎類別中的虛擬方法允許在派生類中進行重寫,並在呼叫時動態地決定是執行基礎類別的方法程式碼,還是執行哪一個派生類的方法程式碼。

C#中還可以為基礎類別定義抽象方法,它強制性地要求所有派生類必須重寫該方法。
抽象方法使用關鍵字abstract定義,並且不提供方法的執行體。
例如:

//抽象方法
public abstract void Area();

包含抽象方法的類必須是抽象類,它也需要使用關鍵字abstract加以定義。
例如:

public abstract class Shape 
{ 
		//類的成員定義... 
}

抽象類表達的是抽象的概念,它本身不與具體的物件相聯絡,其作用是為派生類提供一個公共的介面。例如「圖形」就可以是一個抽象類,因為每一個具體的圖形物件必然是其派生類的範例,如「四邊形」物件、「圓形」物件等;但「四邊形」不是一個抽象類,因為「四邊形」物件既可以是其派生的「平行四邊形」、「正方 形」等特殊四邊形物件,也可以是一般的四邊形物件。

對於抽象類,不允許建立類的範例, 例如在定義了抽象類Shape之後,下面的程式碼是錯誤的:

 Shpae s = new Shape(); 

抽象類之間也可以進行繼承。抽象類要求其所有派生類都繼承它的抽象方法;而如果派生類不是抽象類,它就必須重寫這些抽象方法並提供實現程式碼。

和虛擬方法類 似,派生類中對抽象方法的重寫也通過override關鍵字進行。抽象方法不能是私有的,而且抽象方法及其重寫方法的存取限制應當相同。最後,抽象方法不 能同時使用virtual關鍵字進行修飾。

【例7-11】抽象類和抽象方法。

public abstract class Shape
{	//定義Shape抽象類
    public const double PI = 3.14;	
    public abstract double area();	//抽象方法,不需要定義處理
}

3.密封類和密封方法
抽象類本身無法建立範例,而強制要求通過派生類實現功能。與之相反的是,在C#中還可以定義一種密封類,它不允許從中派生出其他的類。密封類通常位於類的繼承層次的最低層,或者是在其他一些不希望類被繼承的情況下使用。
密封類使用關鍵字sealed定義,例如:

public sealed class Circle : Shape 
{ 
		//類的成員定義...
 } 

4.說明

(1)有趣的是, 儘管密封類和抽象類是截然相反的兩個概念,但它們並不衝突:一個類可以同時被定義為密封類和抽象類,這意味著該類既不能被繼承、也不能被範例化。這隻出現在一種情況之下,那就是該類的所有成員均為靜態成員,Console類就是這樣的一個類。

(2)類似的,如果方法在定義中使用了關鍵字sealed,它就成為密封方法。與密封類的不同之處在於密封類是指不允許有派生類的類,而密封方法則是指不允許被重寫的方法。密封方法所在的類不一定是密封類(這一點與抽象方法不同),而如果該類存在派生類,那麼在派生類中就必須原封不動地繼承這個密封方法。此外, 密封方法本身也要求是一個重寫方法(即sealed和override必須在方法定義中同時出現)

例如,可在Rectangle類中將重寫 area()為密封的:

public sealed override double area()
{
     return (width * height);
}

這樣在Rectangle類的所有派生類中,就不允許重寫該方法的實現。如果要在派生類中定義同名的方法,就必須使用關鍵字new來隱藏基礎類別的方法。

public class square : rectangle
{
	public new double area()
	{
     	return (width * width);
	}
}

本章小結

1.類是物件導向程式設計的基本單元,是C#中最重要的一種資料結構。這種資料結構可以包含欄位成員、方法成員以及其他的巢狀型別。

2.建構函式、解構函式、屬性、索引指示器、事件和操作符都可以視為方法成員。類別建構函式用於物件的初始化,而解構函式用於物件的銷燬。

3.利用屬性提供的存取方法,可以隱藏資料處理的細節,更好地實現物件的封裝性。

4.繼承是物件導向的程式設計方法中實現可重用性的關鍵技術。

5.同一操作作用於不同的物件,可以有不同的解釋,產生不同的執行結果,這就是多型性。多型性通過派生類過載基礎類別中的虛擬方法來實現。

6.C#中還提供了抽象和密封的概念,給繼承和多型性的實現帶來了更大的靈活性。抽象類和介面都把方法成員交給派生類去實現。而密封類不允許被繼承,密封方法不允許被過載。