- Visual C# 2008開發技術詳解
- 李容等編著
- 782字
- 2018-12-27 11:19:36
第3章 C#面向對象基礎
面向對象編程的英文簡稱是OOP(Object Oriented Programming),該項技術是目前運用最廣泛的程序化設計方法,幾乎已經完全取代了過去的面向過程編程。C#從一誕生開始,就是為面向對象編程所準備的。類是面向對象編程的核心部件,它描述了一組具有相同特性和行為的對象。基于面向對象的應用程序,就是由幾個或幾十個甚至更多的類組成,且類之間總是保持著或多或少的關系。類其實也是數據類型,所以在面向對象編程中程序員可以自定義數據類型,這和面向過程編程是有本質區別的。
本章主要內容:
● 類的定義
● 類的數據成員和函數成員
● 事件和委托
● 接口的使用
● 面向對象的三大特征
● 其他面向對象的主題
3.1 類的基本概念
在C#中,類可以看成是一種數據結構,它自身封裝了數據成員和函數成員等。其中數據成員包括字段、常量和域等,而函數成員主要包括方法、屬性、事件、索引器和操作符等。本節將對類的結構和用法進行詳細說明。
3.1.1 C#中的類定義
在C#中,用class關鍵字來定義類,基本結構如下所示。
[attributes] [modifiers] class identifier [:base-list] { class-body }
其中attributes表示附加的聲明信息,modifiers表示類的訪問修飾符,identifier表示類的名稱,base-list表示繼承的基類和實現的接口的列表,基類在前,接口在后,且用逗號隔開。
在C#中,類的訪問修飾符主要分為public、private、protected和internal。它們的具體用法如下所示。
public:它具有最高的訪問級別,對訪問公共成員沒有限制; private:它的訪問級別最低,僅限于它的包含類; protected:能在它的包含類或包含類的派生類中訪問; internal:只能在同一程序集的文件中。
原則上,一個類只能使用一種訪問修飾符,但有一個特例需要注意,具體如下所示。
protected internal:僅限于從包含類派生的當前程序集或類型。
還有一些修飾符能與以上修飾符相結合,得到一些特殊的限制,比如abstract和sealed等,如下所示。
public abstract:可以在任何地方訪問,但不能實例化,只能繼承; public sealed:可以在任何地方訪問,只能實例化,不能派生。
3.1.2 字段
字段實際上相當于類的變量,它在類中的應用十分廣泛,看一個簡單的例子,如下面代碼所示。
public class Car { public string Name; public string Color; public double Price; } Car car = new Car(); car.Name="BMW"; car.Color="White"; car.Price=80000.00;
在上例中,定義了一個Car類,它包含了三個字段,分別為Name、Color和Price。通過在類的外部對該類進行實例化,對類的字段進行賦值。這里需要注意的是,類中的字段也必須要定義成public類型才能在類的外部被訪問。
3.1.3 常量
常量在類中所處的地位和字段差不多,只是它不可變而已。通常,定義常量用關鍵字const,如下面代碼所示。
public const int age = 25;
3.1.4 域
域的聲明過程和字段比較相似,但它們之間有一個很重要的區別,即域只能聲明在類的內部,而不能聲明在類的方法的內部。域分為實例域和靜態域,實例域只能通過類的實例進行調用,而靜態域可以直接通過類名進行調用。下面看一個例子,代碼如下。
class Student { public static int count = 37; //聲明域 static void Main(string[] args) { Console.WriteLine("學生人數為:"+Student.count.ToString()); Console.ReadKey(); } }
運行結果如圖3-1所示。

圖3-1 運行結果圖
以上代碼聲明了一個靜態域count,因為是靜態的,所以它能直接用類名進行調用。此外,域也可以分為公有域和私有域。公有域可以在類的外部被修改,而私有域(也稱為只讀域)不能在類的外部被修改,聲明私有域的關鍵字是readonly,如下面的例子所示。
class Student { public readonly int count = 37; //聲明私有域 static void Main(string[] args) { Student sd = new Student(); Console.WriteLine("學生人數為:"+sd.count++); Console.ReadKey(); } }
如果試圖運行該程序,會出現如圖3-2所示的錯誤信息。

圖3-2 錯誤列表
這是因為域count是私有域,用readonly關鍵字標識的私有域不能在外部被修改。如果去掉readonly關鍵字,則count變為公有域,此時運行后輸出結果如圖3-3所示。

圖3-3 運行結果
3.1.5 類的方法
在C#中,方法的定義與其他語言一樣,包括三個部分,分別為訪問修飾符、輸入參數和返回類型。方法的訪問修飾符的類型和類的差不多,如表3-1所示。
表3-1 方法修飾符

類的方法被創建以后,必須要被調用才有意義。下面的代碼是調用類的方法的例子。
public abstract class Compute { public virtual int Method(int x, int y) { return x + y; } } public class Use:Compute { public override int Method(int x, int y) { return base.Method(x, y); } static void Main(string[] args) { Use use = new Use(); Console.WriteLine(use.Method(2,3)); Console.ReadKey(); } }
上面的代碼中首先定義一個抽象類Compute,它只能被繼承,此外它包含了一個虛擬方法Method(),該方法只能在它的包含類的派生類中被重寫后才能使用。然后定義Compute類的派生類Use,并且在Use類中重寫Method()方法。最后通過實例化Use類,輸出結果,如圖3-4所示。

圖3-4 運行結果
方法被調用時,參數傳遞十分重要。同其他編程語言一樣,C#中方法的參數傳遞也分為值傳遞和引用傳遞。它們之間的區別比較難以理解,下面通過例子來說明。
public class Use { public int Method1(int x) { x=3*x; return x; } public int Method2(ref int x) { x=3*x; return x; } static void Main(string[] args) { Use use = new Use(); int x = 2; Console.WriteLine("輸出結果是:" + use.Method2(ref x)); //輸出引用傳遞 //的結果 Console.WriteLine("輸出結果是:"+use.Method1(x)); //輸出值傳遞的結果 Console.ReadKey(); }
以上代碼主要定義了兩個方法,Method1()是值參數類型,而Method2()引用參數類型,最后輸出它們的結果,如圖3-5所示。

圖3-5 運行結果
此時,將兩個輸出語句的順序顛倒,具體如下所示。
Console.WriteLine("輸出結果是:"+use.Method1(x)); //輸出值傳遞的結果 Console.WriteLine("輸出結果是:" + use.Method2(ref x)); //輸出引用傳遞的結果
此時再看輸出結果,如圖3-6所示。

圖3-6 運行結果
比較以上兩個輸出結果,發現并不相同。原因是引用參數使方法引用的是原來的變量,而值參數引用的僅是原來變量的副本。交換輸出語句前的程序中,因為先調用了引用參數傳遞,所以更改了原來的變量,使x從2變成了6,所以值傳遞的結果為18。相反,當值傳遞先被調用時,它并沒有改變原來的變量,所以兩個輸出結果均為6。
3.1.6 類的屬性
類的屬性提供比較靈活的機制來讀取、編寫或計算私有字段的值,可以像使用公有數據成員一樣使用屬性。屬性必須要由訪問器進行讀寫,它的一般聲明格式如下所示。
[attributes] [modifiers] type identifier { declaration }
其中attributes表示附加的聲明信息,modifiers表示修飾符,和類的方法的修飾符差不多,type表示屬性類型,declaration表示對屬性的讀寫操作。如下面的例子所示。
class Fruit { private string name; public string Name { get { return name; } set { name = value; } } static void Main(string[] args) { Fruit fr = new Fruit(); fr.Name = "Apple"; Console.WriteLine("這種水果為:"+fr.Name); Console.ReadKey(); } }
運行結果如圖3-7所示。

圖3-7 運行結果
屬性的一個重要特點是含有get和set訪問器,set用于寫,get用于讀。它們也可以缺省,只含有get訪問器的屬性為只讀屬性,只含有set訪問器的屬性為只寫屬性。將上面的代碼替換為如下所示代碼。
class Fruit { private string name; public string Name { get { return name; } } static void Main(string[] args) { Fruit fr = new Fruit(); fr.name = "Apple"; Console.WriteLine("這種水果為:"+fr.Name); Console.ReadKey(); } }
此時的Name為只讀屬性,所以必須要對name進行賦值才能正確輸出結果,輸出結果與圖3-7一致。
3.1.7 類的索引器
索引器是C#所特有的類成員,它的主要作用是對象能向數組一樣被方便地引用。索引器的聲明與屬性的聲明比較類似,如下所示。
Public type this[index] { get { //代碼 } set { //代碼 } }
索引器具有以下特點。
(1)索引器沒有具體的名字,需要用this關鍵字對對象進行索引。this關鍵字指向被訪問成員所在的當前實例,可以在構造函數和實例方法中實現對成員的訪問,但不能訪問靜態成員。
(2)索引器不能定義為靜態的。
(3)索引器的參數index只能是傳值類型,不能出現ref和out關鍵字。
下面是一個關于索引器的例子。
class Student { public string[] Name = new string[10]; public Student() { for (int i = 0; i < 10; i++) { Name[i] = i.ToString(); } } public string this[int index] //創建索引器 { get { string str; if (index >= 0 && index < 10) { str = Name[index]; } else { str = "null"; } return str; } set { if (index >= 0 && index < 10) { Name[index] = value; } } } static void Main(string[] args) { Student sd = new Student(); sd[3] = "zhangwei"; sd[4] = "weiyi"; sd[5] = "lirong"; for (int i = 0; i < 12; i++) { Console.WriteLine(sd[i]); } Console.ReadKey(); } }
上面的代碼通過索引器實現了對Name數組的訪問,運行結果如圖3-8所示。

圖3-8 運行結果
3.1.8 類的構造函數和析構函數
類的構造函數能被編譯器自動執行,它具有以下特點。
(1)構造函數必須與類同名。
(2)構造函數不能有返回類型。
(3)當訪問一個類時,它的構造函數最先被執行。
(4)一個類可以有多個構造函數,如果沒有定義構造函數,系統會自動生成一個默認的構造函數。
構造函數又分為實例構造函數和靜態構造函數,其區別如表3-2所示。
表3-2 實例構造函數與靜態構造函數

下面通過例子進行說明,示例代碼如下。
class Student { public static int x; public int y; public int z; public int m; public int n; //構造函數一 public Student() { y = 2; z = 2; } //構造函數二 public Student(int m, int n) { this.m = m; this.n = n; } //構造函數三 static Student() { x = 5; } static void Main(string[] args) { Console.WriteLine("x="+Student.x); Student sd1 = new Student(); Console.WriteLine("y={0}, z={1}", sd1.y, sd1.z); Student sd2 = new Student(3,3); Console.WriteLine("m={0}, n={1}", sd2.m, sd2.n); Console.ReadKey(); } }
本例共為Student類創建了三個構造函數,其中構造函數三是靜態構造函數。例子運行結果如圖3-9所示。

圖3-9 運行結果
類的析構函數與構造函數的過程剛剛相反,它主要用于銷毀類的實例。它不能帶有參數,不能含有修飾符,不能被調用,且它也必須與類同名。為了區別于構造函數,通常在前面加符號“~”。其語法如下所示。
class Student { …… ~Student() { …… } }
析構函數通常會被自動執行,只有一些非常特殊的情況下才需要被用到,比如非托管資源的清理。
3.1.9 事件
事件相關知識的內容太多,在本章的后面部分將用單獨一節進行講解。
3.2 Visual Studio中的類向導
在Visual Studio 2008(以下簡稱VS2008)中,提供了創建類和類的成員的快捷方式,在本節中將通過例子進行詳細說明。其創建步驟如下。
(1)打開VS2008,在D:\C#\ch3目錄下建立名為“ClassWizard”的控制臺應用程序。打開“解決方案資源管理器”界面,右鍵單擊當前工程“ClassWizard”,選擇“添加”—“新建項”命令,添加新類Book.cs,如圖3-10所示。

圖3-10 添加類
(2)選擇“視圖”—“類視圖”菜單,如圖3-11所示。

圖3-11 類視圖
此時可以看出當前工程的類的結構圖,包含兩個類,Book類和Program類,其中Book類是剛被創建的,而Program類是隨工程一起被創建的。在圖3-11中,下半部分窗格主要是顯示被選中類的具體成員。因為Book類是剛被創建的,所以它不含有任何成員。
(3)現在開始為Book類添加成員,在圖3-11中,右鍵單擊“Book”,選擇“查看類關系圖”命令,彈出如圖3-12所示的對話框。右鍵單擊空白處,選擇“添加”命令,其菜單如圖3-13所示。

圖3-12 類的關系圖

圖3-13 添加類的成員
(4)為Book類添加一個bookname字段,其屬性設置如圖3-14所示;然后添加一個Price屬性,如圖3-15所示。

圖3-14 添加字段

圖3-15 添加屬性
(5)接著再添加方法Count,如圖3-16所示。

圖3-16 添加方法
(6)添加完以上成員后,在“解決方案資源管理器”下打開Book.cs,代碼如下所示。
class Book { private string[] bookname; public double Price { get { throw new System.NotImplementedException(); } set { } } public double Count() { throw new System.NotImplementedException(); } }
從以上代碼可以看出,bookname字段、Price屬性和Count方法已經添加到Book類中。其中.NotImplementedException()表示無法實現請求的方法或操作時引發的異常。將上面的代碼替換為如下所示代碼。
class Book { //字段 public string[] bookname = new string[4]; public double price; //屬性 public double Price { get { return price; } set { price = value; } } //方法 public double Count(double Price) { Price = Price * 0.8; return Price; } }
在Main()函數中添加如下代碼。
Book book = new Book(); book.bookname[0] = "C# Programming"; book.bookname[1] = "C++ Programming"; book.bookname[2] = "C Programming"; book.bookname[3] = "Java Programming"; for (int i = 0; i < 4; i++) { Console.WriteLine("書名:" + book.bookname[i]); Console.WriteLine("原價:"); string s = Console.ReadLine(); Console.WriteLine("打折后的價格:"); Console.WriteLine(book.Count(Convert.ToDouble(s))); } Console.ReadKey();
程序運行結果如圖3-17所示。

圖3-17 程序運行結果
本例主要是通過VS2008中的類向導實現了類成員的添加,完成的功能是將Book類中的書名,書的原價和書打折后的價格輸出。
3.3 事件和委托
事件是C#中的又一個重要概念,在發生其他類或對象需要關注的事情時,本類或對象可以通過事件來通知它們。發送事件的類稱為事件的發送者,而接收事件的類稱為事件的訂閱戶。
3.3.1 委托
委托是事件應用過程中必不可少的一個環節,委托首先是在Visual J++中提出的,后來被C#引用。如果一個類需要調用另一個類的方法,可以有三種方式,即實例方式、靜態方式和委托方式。應用委托調用方法的流程如圖3-18所示。

圖3-18 委托使用流程圖
下面通過一個例子來說明委托的具體用法。打開VS2008,在D:\C#\ch3目錄下建立名為DelegateTest的控制臺應用程序。打開工程,添加如下代碼。
class Test { public delegate void Mydelegate(string str); //聲明委托 public int s = 3; public void Method1(string str) //方法一 { s = 5; } public void Method2(string str) //方法二 { s = 7; } public void Call(Mydelegate d, string str) //調用委托中的方法 { d(str); Console.WriteLine(str); } static void Main(string[] args) { Test test = new Test(); Mydelegate d1 = new Mydelegate(test.Method1); //在委托中包含方法一 Mydelegate d2 = new Mydelegate(test.Method2); //在委托中包含方法二 test.Call(d2, "成功調用了委托"); Console.WriteLine("輸出結果:"+test.s.ToString()); Console.ReadKey(); } }
代碼說明如下。
(1)首先需要定義委托,使用的關鍵字是delegate,代碼如下所示。
public delegate void Mydelegate(string str); //聲明委托
它的返回類型和參數必須要和它調用的方法的返回類型和參數相匹配。
(2)接下來是定義該委托需要調用的方法,本例定義了兩個方法,用于對字段s進行不同的修改,如下所示。
public void Method1(string str) //方法一 { s = 5; } public void Method2(string str) //方法二 { s = 7; }
(3)創建委托對象,對聲明的方法進行包含,如下所示。
Test test = new Test(); Mydelegate d1 = new Mydelegate(test.Method1); //在委托中包含方法一 Mydelegate d2 = new Mydelegate(test.Method2); //在委托中包含方法二
(4)最后即是完成方法的調用,如下所示。
public void Call(Mydelegate d, string str) //調用委托中的方法 { d(str); Console.WriteLine(str); }
這里使用了兩個參數,第一個是創建的委托對象,它能直接通過要調用方法的參數完成對方法的調用,第二個參數是調用的方法的參數。程序的運行結果如圖3-19所示。

圖3-19 運行結果
3.3.2 委托的事件處理程序
前面提到,事件需要訂閱者,當事件發生時,訂閱者會給出相應的事件處理程序。事件處理程序本身是簡單的函數形式,它的參數和返回類型必須和調用它的委托相匹配。委托在這里的作用是包含事件處理程序,當事件被觸發時,通過委托來執行事件處理程序。下面通過例子進行說明,主要功能是通過在Class2中定義并觸發事件,在Class1中調用事件處理程序。
打開VS2008,在D:\C#\ch3目錄下建立名為EventTest的控制臺應用程序,打開工程,添加如下所示代碼。
namespace EventTest { //繼承 public class MyEventArgs : EventArgs { public string str; } //聲明委托對象 public delegate void MyEventHandler1(object sender, MyEventArgs e); class Class1 { //創建委托對象并包含事件處理函數 public Class1(Class2 class2) { MyEventHandler1 mh1 = new MyEventHandler1(Method1); //訂閱事件 class2.Event1 += mh1; } //事件處理函數 public void Method1(object sender, MyEventArgs e) { Console.WriteLine("事件處理結果:"+e.str); } } //通過委托來調用被包含的方法 class Class2 { //定義事件 public event MyEventHandler1 Event1; //觸發事件 public void mEvent1(MyEventArgs e) { if (Event1 ! = null) { Event1(this, e); } } } class Class3 { static void Main(string[] args) { Class2 class2 = new Class2(); Class1 class1 = new Class1(class2); MyEventArgs e1 = new MyEventArgs(); e1.str = "aaa"; class2.mEvent1(e1); Console.ReadKey(); } } }
代碼說明如下。
(1)首先還是需要定義委托,事件中的委托和普通委托有一定的區別,它的參數形式比較固定,如下所示。
public delegate void MyEventHandler1(object sender, MyEventArgs e);
事件中的委托具有兩個參數,第一個參數表示事件的觸發對象,第二個參數表示處理事件的對象,它是從EventArgs類繼承而來,EventArgs類包含很多事件處理對象類,在GUI中比較常見,如鼠標事件處理類MouseEventArgs。本例的MyEventArgs是從EventArgs中繼承而來(有關繼承的知識將會在本章后面部分講解),如下所示。
public class MyEventArgs : EventArgs { public string str; }
(2)接下來需要在Class2中完成事件的聲明和觸發,如下所示。
class Class2 { //定義事件 public event MyEventHandler1 Event1; public void mEvent1(MyEventArgs e) { if (Event1 ! = null) { //觸發事件 Event1(this, e); } } }
(3)在Class1類中定閱事件并編寫事件處理程序,如下所示。
class Class1 { //創建委托對象并包含事件處理函數 public Class1(Class2 class2) { MyEventHandler1 mh1 = new MyEventHandler1(Method1); //訂閱事件 class2.Event1 += mh1; } //事件處理函數 public void Method1(object sender, MyEventArgs e) { Console.WriteLine("事件處理結果:"+e.str); } }
在Class1中完成了Class2的Event1事件的訂閱,以上工作由委托mh1完成。相應的事件處理代碼包含在Method1()中。
(4)最后,輸出事件處理結果,如下所示。
Class2 class2 = new Class2(); Class1 class1 = new Class1(class2); MyEventArgs e1 = new MyEventArgs(); e1.str = "aaa"; class2.mEvent1(e1); Console.ReadKey();
程序的運行結果如圖3-20所示。

圖3-20 運行結果
3.3.3 委托中的GUI事件
從上一節的例子可以看出,事件的應用步驟是比較復雜的。在.NET Framework中,運用事件最多的部分是GUI控件,比如Button的Click(單擊)事件。是不是每應用一次事件就必須做出與上例相似的操作?答案是否定的。在GUI事件的應用中,.NET Framework已經完成了大量的工作,下面通過例子進行說明。
注意:以下例子會用到控件知識,不懂控件的讀者可先跳過這部分。
打開VS2008,在D:\C#\ch3目錄下建立名為GUIEventTest的Windows應用程序,打開工程,為當前窗體添加一個Button控件。右鍵單擊該Button控件對象,選擇“屬性”,轉到“屬性”的“事件”窗口,如圖3-21所示。

圖3-21 事件窗口
從圖3-21可以看出,Button包含了很多事件,雙擊Click事件,窗口中顯示的代碼如下所示。
private void button1_Click(object sender, EventArgs e) { }
button1_Click()為事件處理函數,它具有兩個參數,這在前面已經講過。需要注意的是它使用的事件處理對象EventArgs類。此時,打開Form1.Designer.cs,在有關Button的設計器代碼中有如下語句。
this.button1.Click += new System.EventHandler(this.button1_Click);
可見,編譯器已經訂閱好了該事件,讀者需要做的工作僅是向button1_Click()中添加程序執行代碼。比如添加如下所示代碼。
this.Text = "GUI事件";
完成對當前窗體標題的修改,運行程序,結果如圖3-22所示。

圖3-22 運行結果
本節主要介紹了C#中的事件處理機制及委托的用法。C#中的事件主要分為兩類,一類是GUI事件,.NET Framework為這類事件的應用做了大量工作,它的使用較簡單;另一類是非GUI事件,它的事件聲明、訂閱和處理都需要讀者自己編寫,使用比較復雜,但在一些場合它是很必要的。總的來說,事件具有以下特點。
(1)事件的發送者決定何時發送事件,事件的訂閱者決定執行何種操作來響應事件。
(2)一個事件可以同時有多個訂閱者,一個訂閱者可以響應多個事件。
(3)沒有訂閱者的事件不會被調用。
(4)具有多個訂閱者的事件被觸發時,會同步調用多個事件處理程序。
(5)在.NET Framework中,事件是基于EventHandler委托和EventArgs基類的。
通過本節的學習,讀者應該對事件有了一個比較初步的認識,在以后的章節中具體的應用場合還會繼續講解事件。
3.4 面向對象的特征
面向對象主要具有三大特征,即繼承、多態和封裝。正因為這些機制的存在,才使得應用程序變得更為簡單和豐富多彩。本節將對以上三個特征進行詳細介紹,此外還會提到面向對象中另一個重要知識點——重載。
3.4.1 繼承
繼承是指一個類A能利用另一個類B的資源(包括屬性和方法等),其中B類被稱為基類(或父類),而A類被稱為派生類(或子類)。繼承的使用語法如下所示。
public class A { public A{} } public B:A { public B{} }
類的繼承用符號“:”進行標識,以上代碼定義了B類繼承于A類。如果B類繼承于A類,那么B類是否能訪問A類中的全部成員?答案是否定的。這也就是下面要說明的有關派生類在基類中的訪問權限問題。
(1)大多數而并非所有類都可以作為基類被繼承,比如帶有sealed修飾符的密封類不能被繼承。
(2)基類中只有兩種成員能被派生類訪問,包括public和protected類型的成員。其中,protected類型是專為派生類設計的,該類型的成員只能在派生類中進行訪問。
(3)在派生類中可以修改基類中的以下成員,包括虛擬成員(virtual)和抽象成員(abstract)。其中對虛擬成員的修改是在派生類中重寫該成員的執行代碼;而對于抽象成員而言,它在基類中,沒有執行代碼,需要在派生類中進行添加。
可能有些讀者對于以上的講解感覺比較抽象,下面通過一個簡單的例子進行說明。
打開VS2008,在D:\C#\ch3目錄下建立名為InheritTest的控制臺應用程序。首先為當前工程添加一個基類ClassF,代碼如下所示。
class ClassF { //公有字段 public string name="zhangwei"; public string age; //公有屬性 public string Age { get { return age; } set { age = value; } } //虛擬方法 public virtual double Income(double time) { double income = time * 100.0 + 2000.0; return income; } }
基類ClassF中,定義了一個公有字段,一個公有屬性和一個虛擬方法。下面是派生類ClassS的代碼。
class ClassS:ClassF { //重寫虛擬方法 public override double Income(double time) { double income = time * 100.0 + 3000.0; return income; } static void Main(string[] args) { ClassS cs = new ClassS(); Console.WriteLine("姓名:"); //繼承公有字段 Console.WriteLine(cs.name); Console.WriteLine("工齡:"); //繼承公有屬性 cs.Age= Console.ReadLine(); Console.WriteLine("工資:"); //繼承虛擬方法 Console.WriteLine(cs.Income(Convert.ToDouble(cs.Age)).ToString()); Console.ReadKey(); } }
在派生類ClassS中,調用了ClassF類中的公有字段name、公有屬性Age并重寫了虛擬方法Income()。此處,需要注意的是重寫虛擬方法需要用到override關鍵字。運行程序,結果如圖3-23所示。

圖3-23 運行結果
3.4.2 多態
多態是面向對象的又一個重要特征,它主要是指同一操作(如方法)作用于不同的類的實例,將產生不同的結果。多態主要是通過在派生類中對基類中的成員進行替換或重定義完成。下面通過例子進行說明。
打開VS2008,在D:\C#\ch3目錄下建立名為PolymorphismTest的控制臺應用程序。打開工程,添加如下代碼。
//基類 class ClassF { public virtual void Out() { Console.WriteLine("調用了基類中的方法!"); } } //派生類1 class ClassS1:ClassF { public override void Out() { Console.WriteLine("調用了派生類1中的方法!"); } } //派生類2 class ClassS2:ClassF { public override void Out() { Console.WriteLine("調用了派生類2中的方法!"); } } //輸出結果 class Test { static void Main(string[] args) { ClassF[] cf = new ClassF[3]; cf[0] = new ClassF(); cf[1] = new ClassS1(); cf[2] = new ClassS2(); foreach (ClassF c in cf) { c.Out(); } Console.ReadKey(); } }
上面代碼演示了多態性的實現過程,通過在兩個派生類中重寫基類中的虛方法實現。運行結果如圖3-24所示。

圖3-24 運行結果
如果要在派生類中隱藏基類中的非虛成員,可以使用new關鍵字。代碼如下所示。
class ClassF { public void Out() { Console.WriteLine("調用了基類中的方法!"); } } class ClassS1:ClassF { public new void Out() { Console.WriteLine("調用了派生類1中的方法!"); } } class Test { static void Main(string[] args) { ClassS1 cs = new ClassS1(); Console.WriteLine(cs.Out().ToString()); //調用派生類中的方法 Console.WriteLine(((ClassF)cs).Out().ToString()); //調用基類中的方法 Console.ReadKey(); } }
運行結果如圖3-25所示。

圖3-25 運行結果
上面代碼同樣也實現了多態的功能。多態本身理論比較難,本節僅通過兩個例子對簡單的多態應用進行了說明,多態的主要作用是提高代碼的重用性和簡化程序結構。
3.4.3 封裝
封裝是指將對象的信息進行隱藏,只是提供一個訪問接口,使它的使用者無法看到對象的具體信息。在類中,通過不同的修飾符能讓類的成員實現公開或隱藏。通過這些修飾符,類實現了很好的封裝。封裝的主要用途是防止數據受到意外的破壞,代碼如下所示。
class Test { private int a; public int wr() { return a; } public void rd(int value) { a = value; } } class Program { static void Main(string[] args) { Test ts = new Test(); ts.rd(3); ts.wr(); } }
上面的代碼中,使Test類中的私有字段被訪問,但又很好地保護了它的數據不被破壞。封裝的內容遠不止這些,但基本思想是一致的。讀者應該注意對封裝思想的學習,這樣才能很好地應用封裝的操作。
3.4.4 重載
重載是面向對象中除三大特征外的又一個重要知識點,它是指在類中同名成員的不同定義。它的主要作用是使程序邏輯更加清晰。重載主要包括方法重載和運算符重載,本節將通過例子對這兩者進行詳細介紹。
3.4.5 方法重載
方法重載是C#中運用最廣泛的一種重載方式,它是指在類中建立名稱相同但參數不同的方法。方法重載主要是為了解決操作同一類對象需要使用不同方法的問題,如計算一類圖形的面積。圖形中包括矩形、圓和橢圓,它們的面積計算公式是不同的,這里就需要用到重載的概念。下面舉例進行說明。
打開VS2008,在D:\C#\ch3目錄下建立名為Overload1Test的控制臺應用程序。打開工程,添加如下代碼。
class Area { //計算矩形面積 public int Count(int x, int y) { return x * y; } //計算圓面積 public double Count(double r) { return Math.PI * r * r; } //計算橢圓面積 public double Count(double a, double b) { return Math.PI * a * b; } static void Main(string[] args) { Area area =new Area(); Console.WriteLine("矩形面積為:" + area.Count(4, 6)); Console.WriteLine("圓的面積為:"+area.Count(3.4)); Console.WriteLine("橢圓的面積為:"+area.Count(2.5,3.6)); Console.ReadKey(); } }

圖3-26 運行結果
在Area類中分別定義了計算三種圖形面積的方法,三種方法具有相同的名稱,不同的參數和返回類型,實現了方法的重載。程序運行結果如圖3-26所示。
3.4.6 運算符重載
運算符重載主要是為了在類中擴展運算符的功能,以完成一些特殊的操作。重載運算符需要用到operator關鍵字。下面通過例子進行說明。
打開VS2008,在D:\C#\ch3目錄下建立名為Overload2test的控制臺應用程序。打開工程,添加如下代碼。
class Test { public int real; public int img; public Test(int real, int img) { this.real = real; this.img = img; } public static Test operator +(Test x, Test y) { return new Test(x.real+y.real, x.img+y.img); } static void Main(string[] args) { Test t1 = new Test(2,3); Test t2 = new Test(4,5); Test t3 = t1 + t2; Console.WriteLine("該復數的實部為:" + t3.real); Console.WriteLine("該復數的虛部為:" + t3.img); Console.ReadKey(); } }
這是一個實現復數加法的經典例子,通過在Test類中重載“+”運算符實現了復數的加法。運行結果如圖3-27所示。

圖3-27 運行結果
在運算符重載的使用過程中,應注意以下情況。
(1)并非所有運算符都能被重載,不能被重載的運算符包括“=”、“? :”、“->”、“new”、“is”、“sizeof”和“typeof”。
(2)重載運算符不能改變原來運算符的優先級和操作數。
(3)比較運算符必須成對重載,如“==”和“! =”, “>”和“<”等。
(4)重載運算符可由派生類繼承。
3.5 接口
接口是面向對象中的又一個重要概念,它用于定義類或結構的行為特征。接口包含事件、方法、屬性和索引器4種成員,它只包含這些成員的簽名而不包含實現,這一點和抽象類比較相似;而且接口不能包含字段,且它的所有成員都必須是公開的。
3.5.1 接口的聲明
接口的聲明需要采用interface關鍵字,如下所示。
interface MyInterface { void MyMethod(); string MyProperty { get; } }
在接口MyInterface中,定義了一個方法成員和一個屬性。細心的讀者可能會發現以上定義中,接口成員沒有采用修飾符,如果試圖添加任何修飾符,均會出現“修飾符無效”的錯誤。因為在接口的定義中規定了所有接口成員必須是公開的。
3.5.2 接口的使用
前面提到,接口只能包含成員的簽名,不能包含成員的實現。接口成員必須要在繼承該接口中的類中才能實現。下面對MyInterface接口中的MyMethod()方法進行實現,代碼如下所示。
class Program:Interface1 { public void MyMethod() { Console.WriteLine("實現了該接口的方法!"); } static void Main(string[] args) { Program pg = new Program(); pg.MyMethod(); Console.ReadKey(); } }
運行結果,如圖3-28所示。

圖3-28 運行結果
上面代碼中,實現了接口MyInterface中的MyMethod()方法并完成輸出。在使用接口時,應注意以下問題。
(1)接口自身不能被實例化,需要在繼承它的類中才能使用。
(2)接口不能包含字段。
(3)接口不能包含靜態成員。
(4)接口成員默認是public類型的,不能在接口成員前面加任何修飾符。
(5)類和結構可以從多個接口繼承。
(6)接口本身也可以從其他接口繼承,它的繼承機制和類的繼承機制一樣。
3.6 面向對象的其他主題
本章前面部分以類為重點介紹了面向對象的相關知識,但僅有類是不夠的,面向對象還有一些其他主題,比如命名空間等。在本節中,將對面向對象中其他一些主題進行介紹,以幫助讀者更好地理解面向對象的本質。
3.6.1 命名空間
在.NET Framework中,一個命名空間就是一個邏輯的命名系統,它用于指定一個范圍,并在該范圍組織代碼(包括類、接口、結構體和枚舉等)。命名空間在前面的代碼中已經多次用到,如果需要使用包含在命名空間中的類,則需要使用using指令包含該命名空間,如下所示。
using System; …… Console.WriteLine("命名空間的用法!");
System是命名空間,Console是該命名空間中的一個類。因為包含了System命名空間,所以才能使用它里面的類。同樣,也可以用如下方式訪問Console類,如下所示。
System.Console.WriteLine("命名空間的用法!");
這里直接使用了顯示的命名空間前綴來完成Console類的訪問。這是一種不推崇的方法,因為它會增加代碼的復雜性。命名空間前面不能加任何修飾符,它的關鍵字是namespace。在命名空間的內部,也可以包含命名空間,稱為命名空間的嵌套,如下所示。
namespace NameSpaceA { namespace NameSpaceB { class Program { } } }
NameSpaceB嵌套在NameSpaceA命名空間中,下面為等效代碼。
namespace NameSpaceA.NameSpaceB { class Program { } }
如果一個命名空間的名字很長,但又必須要使用多次,.NET Framwork中提供了一種命名空間別名的方式來簡化。如下所示。
using ns=NameSpaceName;
在需要使用該命名空間的地方,可以直接用ns代替。為了幫助讀者能更好地理解命名空間的用法,下面給出一個完整的例子進行說明,代碼如下所示。
using nsp = NameSpaceA.NameSpaceB.NameSpaceC; //使用命名空間別名 //定義嵌套命名空間 namespace NameSpaceA { namespace NameSpaceB { namespace NameSpaceC { public class cs { public void output() { Console.WriteLine("命名空間的學習!"); } } } } } //調用嵌套命名空間中的類成員 namespace ns { class Program { static void Main(string[] args) { nsp.cs c = new nsp.cs(); c.output(); Console.ReadKey(); } } }
上面的代碼主要實現了命名空間的嵌套,命名空間的別名及使用命名空間中包含的類成員,運行結果如圖3-29所示。

圖3-29 運行結果
3.6.2 程序集
程序集是.NET Framework應用程序的基本構造塊,當生成C#應用程序時,VS會在當前工程的Debug目錄下生成可移植可執行的文件,通常是.exe或.dll文件。在較大的項目中,程序集的作用是十分明顯的。項目經理可以把項目劃分成幾個單獨的模塊,由不同的人員進行開發,然各自生成程序集,最后通過一定的方式將這些程序集組合起來即可。
程序集具有以下特點。
(1)程序集以.exe或.dll格式的文件存在。
(2)能在多個應用程序之間實現程序集的共享。
(3)在單個應用程序中可以使用程序集的兩個版本。
3.6.3 類庫
在.NET Framework中,類庫是由命名空間組成,同時又是類、接口和值類型組成的庫,這些庫能對系統功能進行訪問,是建立.NET Framework應用程序、組件和控件的基礎。.NET Framework中包含了大量的系統類庫供用戶使用,調用這些類庫時,系統會自動添加,只需用using指令包含類庫提供的命名空間即可,比如前面經常使用的System命名空間。
但系統提供的類庫有時候并不能完全滿足用戶的要求,此時就需要自定義類庫。下面通過例子說明類庫的編寫和調用。
打開VS2008,在D:\C#\ch3目錄下建立名為ClassLibraryTest的類庫程序。打開工程,添加如下代碼。
namespace ClassLibraryTest { public class Test { public int Add(int x, int y) { return x + y; } public int Dec(int x, int y) { return x - y; } public int mul(int x, int y) { return x * y; } public int dev(int x, int y) { return x / y; } } }
在ClassLibraryTest命名空間的Test類中定義了4個方法,分別用于計算兩個整數加、減、乘和除的結果。選擇“生成”—“生成解決方案”命令,或者按F6鍵將以上代碼生成類庫。此時轉到當前工程的Debug目錄下,可以看到名為ClassLibraryTest.dll的動態鏈接庫文件。下面需要做的工作是調用該類庫,主要分為以下幾個步驟。
(1)打開VS2008,在D:\C#\ch3目錄下建立名為ClTest的控制臺應用程序,打開工程,首先需要對該類庫進行引用。選擇“項目”—“添加引用”—“瀏覽”標簽,轉到該類庫工程的Debug目錄,如圖3-30所示。

圖3-30 添加引用
(2)轉到“解決方案資源管理器”界面上,可以看到該類庫已經被添加,如圖3-31所示。

圖3-31 添加類庫
(3)需要包含該類庫中的命名空間,如下所示。
using ClassLibraryTest;
(4)在當前工程中添加如下代碼。
namespace Cltest { class Program { static void Main(string[] args) { Test ts = new Test(); Console.WriteLine("兩個數相加的結果為:" + ts.Add(6, 3)); Console.WriteLine("兩個數相減的結果為:" + ts.Dec(6, 3)); Console.WriteLine("兩個數相乘的結果為:" + ts.mul(6, 3)); Console.WriteLine("兩個數相除的結果為:" + ts.dev(6, 3)); Console.ReadKey(); } } }
(5)運行程序,結果如圖3-32所示。

圖3-32 運行結果
3.7 小結
本章主要介紹了面向對象技術的基本內容。首先是類的相關知識的介紹,類是面向對象技術中最基礎也是最重要的內容,本章分別從類的定義、類的訪問權限和類的成員等方面對類的用法進行了說明。其中采用public、private、protected和abstract等修飾符設置了類的不同訪問權限;類的成員包括常量、字段、屬性、索引器、方法和事件等,本章通過例子對以上成員進行了詳細的說明。接下來介紹了面向對象的三大特征——繼承、多態和封裝,它們是面向對象技術的核心部分。在有關接口的內容中簡單介紹了接口的聲明和使用。最后介紹了面向對象技術中的其他一些主題,包括命名空間、程序集和類庫等。
- Big Data Analytics with Hadoop 3
- 腦動力:Linux指令速查效率手冊
- 大學計算機基礎:基礎理論篇
- Learning Social Media Analytics with R
- Visual C# 2008開發技術實例詳解
- DevOps:Continuous Delivery,Integration,and Deployment with DevOps
- 可編程序控制器應用實訓(三菱機型)
- C語言寶典
- Android游戲開發案例與關鍵技術
- 網絡安全技術及應用
- Hands-On Data Warehousing with Azure Data Factory
- Apache源代碼全景分析(第1卷):體系結構與核心模塊
- 寒江獨釣:Windows內核安全編程
- Building Google Cloud Platform Solutions
- 智能制造系統及關鍵使能技術