• 沒有找到結果。

第一章第一章第一章第一章

N/A
N/A
Protected

Academic year: 2022

Share "第一章第一章第一章第一章"

Copied!
53
0
0

加載中.... (立即查看全文)

全文

(1)

第一章 第一章

第一章 第一章 C#語言基礎 語言基礎 語言基礎 語言基礎

本章介紹 C#語言的基礎知識,希望具有 C 語言的讀者能夠基本掌握 C#語言,以此為基礎,

能夠進一步學習用 C#語言編寫 Window 應用程序和 Web 應用程序。當然僅靠一章的內容就完 全掌握 C#語言是不可能的,如需進一步學習 C#語言,還需要認真閱讀有關 C#語言的專著。

1.1 C#語言特點 語言特點 語言特點 語言特點

Microsoft.NET(以下簡稱·NET)框架是微軟提出的新一代 Web 軟件開發模型,C#語言是·NET 框架中新一代的開發工具。C#語言是一種現代、物件導向的語言,它簡化了 C++語言在類、命 名空問、方法多載和異常處理等方面的操作,它摒棄了 C++的複雜性,更易使用,更少出錯。

它使用組件編程,和 VB 一樣容易使用。C#語法和 C++和 JAVA 語法非常相似,如果讀者用過 C++和 JAVA,學習 C#語言應是比較輕鬆的。

用 C#語言編寫的源碼程式,必須用 C#語言編譯器將 C#源碼程式編譯為中間語言(Microsoft Intermediate Language, MSIL)代碼,形成副檔名為 exe 或 dll 文件。中間語言代碼不是 CPU 可執 行的機器碼,在程序執行時,必須由通用語言執行環境(Common Language Runtime,CLR)中的 即時編譯器(Just In Time,JIT)將中間語言代碼翻譯為 CPU 可執行的機器碼,由 CPU 執行。CLR 為 C#語言中間語言代碼執行提供了一種執行時環境,C#語言的 CLR 和 JAVA 語言的虛擬機類 似。這種執行方法使執行速度變慢,但帶來其它一些好處,主要有:

 通用語言規範(Common Language Specification,CLS):.NET 系統包括如下語言:C#、C++、

VB、J#,他們都遵守通用語言規範。任何遵守通用語言規範的語言源程序,都可編譯為相 同的中間語言代碼,由 CLR 負責執行。只要為其它操作系統編製相應的 CLR,中問語言代 碼也可在其它系統中執行。

 自動記憶體管理:CLR 內建垃圾收集器,當變數實例的生命周期結束時,垃圾收集器負責 收回不被使用的實例佔用的記憶體空間。不必像 C 和 C++語言,用語句在堆積中建立的實 例,必須用語句釋放實例佔用的記憶體空間。也就是說,CLR 具有自動記憶體管理功能。

 跨語言處理:由於任何遵守通用語言規範的語言源程序都可編譯為相同的中間語言代碼,

不同語言設計的組件,可以互相通用,可以從其它語言定義的類衍生出本語言的新類。由 於中間語言代碼由 CLR 負責執行,因此異常處理方法是一致的,這在調試一種語言調用另 一種語言的子程序時,顯得特別方便。

 增加安全:C#語言不支援指標,一切對記憶體的存取者都必須通過物件的參照變數來實 現,只允許存取記憶體中允許存取的部分,這就防止病毒程序使用非法指標存取私有成 員。也避免指標的誤操作產生的錯誤。CLR 執行中間語言代碼需要對中間語言代碼的安全 性、完整性進行驗證,防止病毒對中間語言代碼的修改。

 版本支援:系統中的組件或動態聯接庫可能要升級,由於這些組件或動態聯接庫都要在註 冊表中註冊,由此可能帶來一系列問題,例如,安裝新程序時自動安裝新組件替換舊組件,

有可能使某些必須使用舊組件才可以執行的程序,使用新組件執行不了。在.NET 中這些組

(2)

件或動態聯接庫不必在註冊表中註冊,每個程序都可以使用自帶的組件或動態聯接庫,只 要把這些組件或動態聯接庫放到執行程序所在文件夾的子文件夾 bin 中,執行程序就自動 使用在 bin 文件夾中的組件或動態聯接庫。由於不需要在註冊表中註冊,軟件的安裝也變 得容易了,一般將執行程序及庫文件拷貝到指定文件夾中就可以了。

 完全物件導向:不像 C++語言,暨支援程序導向程序設計,又支援物件導件程序設計,C#

語言是完全物件導向的,在 C#中不再存在全域函數、全域變數,所有的函數、變數和常數 部必須定義在類中,避免了命名衝突。C#語言不支援多重繼承。

1.2 編寫主控台應用程序 編寫主控台應用程序 編寫主控台應用程序 編寫主控台應用程序

1.2.1 使用 使用 使用 使用 SDK 命令列工具編寫主控台程序 命令列工具編寫主控台程序 命令列工具編寫主控台程序 命令列工具編寫主控台程序

第一個程式總是非常簡單的,程序首先讓用戶通過鍵盤輸入自己的名字,然後程式在螢幕 上印出一條歡迎訊息,程式碼是這樣的:

using System; //導入命名空間。//為C#語言加註解方法,註解到本行結束 class Welcome //類定義,類的概念見下一節

{ /* 註解開始,和C語言註解用法相同 註解結束*/

static void Main() //主程序,程序入口函數,必須在一個類中定義 {

Console.WriteLine("請鍵入你的姓名:"); //主控台輸出字串 Console.ReadLine(); //從鍵盤讀入資料,鍵入Enter結束 Console.WriteLine("歡迎!");

} }

可以用任意一種文字編輯軟件完成上述程式碼的編寫,然後把文件存檔,假設文件名叫做 welcome.cs,C#源碼檔案是以 cs 作為文件的副檔名。和 C 語言相同,C#語言是區分大小寫的。

高級語言總是依賴於許多在程式外部預定義的變數和函數。在 C 成 C++中這些定義一般放到頭 文件中,用#include 語句來導入這個標頭檔,而在 C#語言中使用 using 語句導入命名空間,using System 語句意義是導入 System 命名空間,C#中的 using 語句的用途與 C++中的#include 語句的 用途基本類似,用於導入預定義的變數和函數,這樣在自己的程式中就可以自由地使用這些變 數和函數。如果沒有導入命名空間的話,我們該怎麼辦呢?程序還能保持正確嗎?答案是肯定 的,那樣的話,我們就必須把程式碼改寫成下面的樣子:

class Welcome {

static void Main() {

(3)

System.Console.WriteLine("請鍵入你的姓名:");

System.Console.ReadLine();

System.Console.WriteLine("歡迎!");

} }

也就是在每個 Console 前加上一個前綴 System,這個小圓點表示 Console 是作為 System 的成員 而存在的。C#中拋棄了 C 和 C++中繁雜且極易出錯的運算子,如 :: 和 -> 等,C#中的複合 名字一律通過 · 來連接。System 是 .Net 平台框架提供的最基本的命名空間之一,有關命名 空間的詳細使用方法將在以後詳細介紹,這裡只要學會怎樣導入命名空間就足夠了。

程序的第二行宣告了一個類,類的名字叫做 Welcome。C#程序中每個變數或函數都必須屬 於一個類,包括主函數 Main(),不能像 C 或 C++那樣建立全域變數。C#語言程序總是從 Main() 方法開始執行,一個程序中不允許出現兩個或兩行以上的 Main()方法。請牢記 C#中 Main()方 法必須被包含在一個類中,Main 的第一個字母必須大寫,必須是一個靜態方法,也就是 Main() 方法必須使用 static修飾。static void Main()是類 Welcome 中定義的主函數,靜態方法意義見以 後章節。

程序所進行的輸入輸出功能是通過 Console 類來完成的,Console 類是在命名空間 System 中已經定義好的一個類。Console 有兩個最基本的方法 WriteLine 和 ReadLine。ReadLine 表示從 輸入設備輸入資料,WriteLine 則用於在輸出設備上輸出資料。

如果在電腦上安裝了 Visual Studio.Net,則可以在集成開發環境中直接選擇快捷鍵或選單 命令編譯並執行源文件。如果您不具備這個條件,那麼至少需要安裝 Microsoft.Net 這樣才能 夠執行 C#語言程序。Microsoft.Net Framework SDK 中內置了 C#的編譯器 csc.exe,下面讓我們 使用這個微軟提供的命令列編譯器時對程序 welcome.cs 進行編譯。假設已經將文件保存在 d:\Csharp目錄下,啟功命令列提示符,在螢幕上輸入一行命令: d: 鍵入 Enter,鍵入命令:

C:\Windows\Microsoft.Net\Framework\v3.5\csc welcome.cs

如果一切正常 welcome.cs 文件將被編譯,編譯後產生可執行文件 Welcome.exe。可以在命 令提示符視窗執行可執行檔 Welcome.exe,螢幕上出現一行文字提示您輸入姓名:請鍵入你的 姓名,輸入任意文字並按下 Enter 鍵,螢幕上印出歡迎訊息:歡迎!

注意,和我們使用過的絕大多數編譯器不同,在 C#中編譯器只執行編譯這個過程,而在 C 和 C++中要經過編譯和鏈接兩個階段。換言之 C#源碼檔並不被編譯為目標檔.obj,而是直接產 生成可執行文件.exe 或動態鏈接庫.dll,C#編譯器中不需要包含鏈接器。

1.2.1 使用 使用 使用 使用 Visual Studio.Net 建立 建立 建立 建立主 主 主 主控台程序 控台程序 控台程序 控台程序

(1) 執行 Visual Studio.Net 程序,出現如圖 1.2.2A 界面。

(2) 單擊新建專案按鈕,出現如圖 1.2.2B 對話框。在專案類型(P)編輯框中選擇 Visual C#,在範 本(T)編輯框中選擇主控制台應用程序,名稱(N)編輯框中鍵入 e1,在位置(L)編輯框中鍵入 D:\Csharp,必須預先產生文件夾 D:\Csharp。也可以單擊瀏覽按鈕,在打開文件對話框中選 擇文件夾,單擊確定按鈕,產生專案。出現如圖 1.2.2C 界面。編寫一行應用程序,可能包

(4)

含多個文件,才能生成可執行文件,所有這些文件的集合叫做一個專案。

(3) 修改 e1.cs 文件如下,有陰影部分是新增加的語句,其餘是集成環境自動生成的。

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace e1 {

class Program {

static void Main(string[] args) {

Console.WriteLine("請鍵入你的姓名:");

System.Console.ReadLine();

System.Console.WriteLine("歡迎!");

System.Console.Read();

(5)

} } }

(4) 按 CTRL+F5 鍵,執行程序,如右圖,與第 1.2.1 節執行效果相同。螢幕上出現一行文字,

提示你輸入姓名,請鍵入你的姓,輸入任意字元並按下 Enter 鍵,螢幕將印出歡迎訊息:

歡迎!鍵入 Enter 退出程序。

1.3 類的基本概 類的基本概 類的基本概 類的基本概念 念 念 念

C#語言是一種現代、物件導向的語言,物件導向對程序設計方法提出了一個全新的慨念,·

它的主要思想是將資料(資料成員)及處理這些資料的相應方法(函數成員)封裝到類中,類的實 例則稱為對象或物件(object),這就是我們常說的封裝性。

1.3.1 類的 類的 類的 類的基本概念 基本概念 基本概念 基本概念

類可以認為是對結構的擴充,它和 C 中的結構最大的不同是:類中不但可以包括資料,還 包括處理這些資料的函數。類是對資料和處理資料的方法(函數)的封裝。類是對某一類具有相 同特性和行為的事物的描述。例如,定義一個描述個人情況的類 Person 如下:

using System;

class Person //類的定義,class是保留字,表示定義一個類,Person是類名 {

private string name = "張三"; //類的資料成員宣告 private int age = 12; //private表示私有資料成員

public void Display() //類的方法(函數)宣告,顯示姓名和年齡 {

Console.WriteLine("姓名:{0},年齡:{1}", name, age);

}

public void SetName(string PersonName) //修改姓名為方法(函數) {

name = PersonName;

}

public void setAge(int PersonAge) {

age = PersonAge;

} }

Console.WriteLine("姓名:{0},年齡:{1}", name, age)的意義是將第二個參數變數 name 變為字

(6)

串填到{0}位置,將第三個參數變數 age 變為字串填到{1}位置,將第一個參數表示的字串在顯 示器上輸出。

大家注意,這裡我們實際定義了一個新的資料型別,為用戶自己定義的資料型別,是對個 人的特性和行為的描述,型別名為 Person,它和 int、char 等一樣,為一種資料型別。用定義 新資料型別 Person 類的方法把資料和處理資料的函數封裝起來。類的宣告格式如下:

屬性 類修飾字 class 類名 { 類體 }

其中,關鍵字 class、類名和類體是必須的,其它項是可選項,類修飾字包括 new、public、

protected、internal、private、abstract 和 sealed,這些類修飾字以後介紹,類體用於定義類的 成員。

1.3.2 類成員的存取控制 類成員的存取控制 類成員的存取控制 類成員的存取控制

一般希望類中一些資料不被隨意修改,只能按指定方法修改,既隱蔽一些資料,同時這些 函數也不希望被其它類程序調用,只能在類內部使用。如何解決這個問題呢?可用存取權限控 制字,常用的存取權限控制字如下:private(私有)、public(公有)。在資料成員或函數成員前增 加存取權限控制字,可以指定該資料成員或函數成員的存取權限。

私有資料成員只能被類內部的函數使用和修改,私有函數成員只能被類內部的其它函數調 用。類的公有函數成員可以被類的外部程序調用,類的公有資料成員可以被類的外部程序直接 使用修改。公有函數實際是一個類和外部通訊的介面,外部函數通過調用公有函數,按照預先 設定好的方法修改類的私有成員。對於上述例子,name 和 age 是私有資料成員,只能通過公 有函數 SetName()和 SetAge()修改,即它們只能按指定方法修改。

這裡再一次解釋一下封裝,它有兩個意義,第一是把資料和處理資料的方法同時定義在類 中。第二是用存取權限控制字使資料隱蔽。

1.3.3 類的對象 類的對象 類的對象 類的對象

Person 類僅是一個用戶新定義的資料型別,由它可以生成 Person 類的實例,C#語言叫對 象(或翻譯為物件)。用如下方法宣告類的對象:Person OnePerson=new Person();,此語句的意 義是建立 Person 類對象,返回對象地址指派給 Person 類變數 OnePerson。也可以分兩步產 生 Person 類的對象:Person OnePerson;OnePerson = new Person();OnePerson 雖然儲存的是 Person 類對象地址,但不是 C 中的指標,不能像指標那樣可以進行加減運算,也不能轉換為 其它型別地址,它是參照型別變數,只能參照(引用)Person 對象,具體意義參見以後章節。和 C、C++不同,C#只能用此種方法生成類對象。

在程序中,可以用「OnePerson.方法名」或「OnePerson.資料成員名」存取對象的成員。

例如:OnePerson.Display(),公用資料成員也可以這樣存取。注意,C# 語言中不包括 C++語言 中的->符號。

(7)

1.3.4 類的 類的 類的 類的建構函數 建構函數 建構函數和 建構函數 和 和解構 和 解構 解構函數 解構 函數 函數 函數

在建立類的對象時,需做一些初始化工作,例如對資料成員初始化。這些可以用建構函數 來完成。每當用 new 生成類的對象時,自動調用類的建構函數。因此,可以把初始化的工作 放到建構函數中完成。建構函數和類名相同,沒有返回值。例如可以定義 Person 類的建構函 數如下:

//類的建構函數,函數名和類同名,無返回值。

public Person(string Name, int Age) {

name = Name;

age = Age;

}

當用 Person OnePerson=new Person("張五",20)語句生成 Person 類對象時,將自動調用 以上建構函數。請注意如何把參數傳遞給建構函數。

變數和類的對象都有生命期,生命期結束,這些變數和對象就要被撤銷。類的對象被撤銷 時,將自動調用解構函數。一些善後工作可放在解構函數中完成。解構函數的名字為「~類名」, 無返回型別,也無參數。Person 類的解構函數為~Person()。C#中類解構函數不能顯式地被調 用,它是被垃圾收集器撤銷不被使用的對象時自動調用的。

1.3.5 類的 類的 類的建構函數 類的 建構函數 建構函數的 建構函數 的 的多載 的 多載 多載 多載

在 C#語言中,同一個類中的函數,如果函數名相同,而參數型別或個數不同,認為是不 同的函數,這叫函數多載。僅返回值不同,不能看作不同的函數。這樣,可以在類定義中,定 義多個建構函數,名字相同,參數型別或個數不同。根據生成類的對象方法不同,調用不同的 建構函數。例如可以定義 Person 類沒有參數的建構函數如下:

//類的建構函數,函數名和類同名,無返回型別。

public Person() {

name = "張三"; age = 12;

}

用語句 Person OnePerson=new Person("李四", 30)生成對象時,將調用有參數的建構函數,

而用語句 Person OnePerson=new Person()生成對象時,調用無參數的建構函數。由於解構函數 無參數,因此,解構函數不能多載。

(8)

1.3.6 使用 使用 使用 使用 Person 類的完整的例子 類的完整的例子 類的完整的例子 類的完整的例子

下邊用一個完整的例子說明 Person 類的使用:(VisualStudio.Net 編譯通過) using System;

namespace e1 //定義以下程式碼所屬命名空間,意義見以後章節 {

class Person {

private String name = "張三";//類的資料成員宣告 private int age = 12;

//類的方法(函數)宣告,顯示姓名和年齡 public void Display()

{

Console.WriteLine("姓名: {0}, 年齡:{1}", name, age);

}

//指定修改姓名的方法(函數)

public void SetName(string PersonName) {

name = PersonName;

}

//指定修改年齡的方法(函數)

public void SetAge(int PersonAge) {

age = PersonAge;

}

//建構函數,函數名和類同名,無返回值

public Person(string Name, int Age) {

name = Name;

age = Age;

}

//類的建構函數多載 public Person() {

name = "田七"; age = 12;

}

(9)

class Class1 {

static void Main(string[] args) {

Person OnePerson = new Person("李四", 30); //生成類的對象 OnePerson.Display();

/*下句錯誤,在其它類(Class1類)中,

不能直接修改Person類中的私有成員。*/

//OnePerson.name = "王五";

/* 只能通過Person類中公有方法SetName 修改Person類中的私有成員name。*/

OnePerson.SetName("王五");

OnePerson.SetAge(40);

OnePerson.Display();

OnePerson = new Person();

OnePerson.Display();

} } } }

鍵入 CTRL+F5 執行後,顯示的效果是:

姓名: 李四, 年齡:30 姓名: 王五, 年齡:40 姓名: 田七, 年齡:12

1.4 C#的 的 的 的資料 資料 資料 資料型別 型別 型別 型別

從大的方面來分,C# 語言的資料型別可以分為三種:實值型別、參照型別、指標(pointer) 型別,指標型別僅用於非安全程式碼中。本節重點討論實值型別和參照型別。

1.4.1 實值型別 實值型別 實值型別 實值型別和 和 和參照 和 參照 參照型別 參照 型別 型別區別 型別 區別 區別 區別

在 C# 語言中,實值型別變數儲存的是資料型別所代表的實際資料,實值型別變數的值(或 實例)儲存在堆疊(Stack)中,指派語句是傳遞變數的值。參照型別(例如類就是參照型別)的實 例,也稱為對象,不存在堆疊中,而儲存在受管的堆積(Managed Heap)中,堆積實際上是計算 機系統中的空閒記憶體。參照型別變數的值儲存在堆疊中,但儲存的不是參照型別對象,而是 儲存參照型別對象的參照(地址),和指標所代表的地址不同,參照所代表的地址不能被修改,

(10)

也不能轉換為其它型別地址,它是參照型別變數,只能參照指定類對象,參照型別變數指派語 句是傳遞對象的地址。見下例:

using System;

class MyClass//類為參照型別 {

public int a = 0;

}

class Test {

static void Main() {

f1();

}

static public void f1() {

int v1 = 1;//實值型別變數v1,其值儲存在堆疊(Stack)中 int v2 = v1;//將v1的值(為)傳遞給v2,v2 = 1,v1值不變。

v2 = 2;//v2 = 2,v1 值不變。

MyClass r1 = new MyClass();//參照變數r1儲存MyClass類對象的地址 MyClass r2 = r1;//r1和r2都代表是同一個MyClass類對象

r2.a = 2;//和語句r1.a = 2 等價 }

}

儲存在堆疊中的變數,當其生命期結束,自動被撤銷,例如,v1 儲存在堆疊中,v1 和函 數 f1 同生命期,退出函數 f1,此時 v1 不存在了。但在堆積中的對象不能自動被撤銷。因此 C 和 C++語言,在堆積中建立的對象,不使用時必須用語句釋放對象佔用的儲存空間。.NET 系 統 CLR 內建垃圾收集器,當對象的參照變數被撤銷,表示對象的生命期結束,垃圾收集器負 責收回不被使用的對象佔用的儲存空間。例如,上例中參照變數 r1 及 r2 是 MyClass 類對象 的參照,儲存在堆疊中,退出函數 f1,此時 r1 和 r2 都不存在了,在堆積中的 MyClass 類對 象也就被垃圾收集器撤銷。也就是說,CLR 具有自動記憶體管理功能。

1.4.2 實值型別 實值型別 實值型別變數 實值型別 變數 變數分類 變數 分類 分類 分類

C#語言實值型別可以分為以下幾種:

 簡單型別(Simple types)

簡單型別中包括:數值型別和布爾型別(bool)。數值型別又細分為:整數型別、字元型別 (char)、浮點數型別和十進制型別(decimal)。

 結構型別(Struct types)

(11)

 列舉型別(Enumeration types)

C#語言實值型別變數無論如何定義,總是實值型別變數,不會變為參照型別變數。

1.4.3 結構 結構 結構 結構型別 型別 型別 型別

結構型別和類一樣,可以宣告建構函數、資料成員、方法、屬性等。結構和類的最根本的 區別是結構是實值型別,類是參照型別。和類不同,結構不能從另外一個結構或者類衍生,本 身也不能被繼承,因此不能定義抽像結構,結構成員也不能被存取權限控制字 protected 修飾,

也不能用 virtual 和 abstract 修飾結構方法。在結構中不能定義解構函數。但結構有預設的無 參數建構函數。

雖然結構不能從類和結構衍生,可是結構能夠繼承介面,結構繼承介面的方法和類繼承介 面的方法基本一致。下面例子定義一個點結構 point:

using System;

struct point//結構定義 {

public int x, y;//結構中也可以宣告建構函數(必須有參數)和方法,變數不能賦初值 }

class Test {

static void Main() {

point P1;

P1.x = 166;

P1.y = 111;

point P2;

P2 = P1;//值傳遞,使P2.x = 166,P2.y = 111

point P3 = new point();//用new 生成結構變數P3,P3 仍為實值型別變數 } //用new 生成結構變數P3 僅表示調用預設的建構函數,使x=y=0。

}

1.4.4 簡單 簡單 簡單型別 簡單 型別 型別 型別

簡單型別也是結構型別,因此有建構函數、資料成員、方法、屬性等,因此下列語句 int i

= int.MaxValue; string s = i.ToString()是正確的。即使一個常數,C#也會生成結構型別的實例,因 此也可以使用結構型別的方法,例如:string s = 13.ToString()是正確的。簡單型別包括:整數型 別、字元型別、布爾型別、浮點數型別、十進制型別。見下表:

(12)

保留字 System 命名空 間中的名字

位元

組數 取值範圍 範例

sbyte System.Sbyte 1 –128~127 byte System.Byte 1 0~255

short System.Int16 2 –32768~32767 ushort System.UInt16 2 0~65535

int System.Int32 4 –2147483648~2147483647 uint System.UInt32 4 0~4292967295

long System.Int64 8 –9223372036854775808 long x = 36L;

ulong System.UInt64 8 0~18446744073709551615 ulong x = 36UL;

char System.Char 2 0~65535 char x = '中';

float System.Single 4 3.4E-38~3.4E+38 float x = 23.56F;

double System.Double 8 1.7E-308~1.7E+308 double x = 23.56D;

bool System.Boolean 1 (true, false) bool x = true, y = false;

decimal System.Decimal 16 正負 1.0×10−28到 7.9×1028之間 decimal x = 1.68M;

C#簡單型別使用方法和 C、C++中相應的資料型別基本一致。需要注意的是:

 和 C 語言不同,無論在何種系統中,C# 每種資料型別所佔位元組(byte)數是固定的。

 字元型別採用 Unicode 字元集,一個 Unicode 標準字元長度為 16 位。

 整數型別不能被隱式轉換為字元型別(char),例如 char c1 = 10 是錯誤的,必須寫成:char c1 = (char)10、char c = 'A'、char c = '\x0032'、char c = '\u0032'。

 布爾型別有兩個值:false,true。不能認為整數 0 是 false,其它值是 true。bool x = 1 是錯 誤的,不存在這種寫法,只能寫成 x = true 或 x = false。

 十進制型別(decimal)也是浮點數型別,只是精度比較高,一般用於財政金融計算。

1.4.5 列舉 列舉 列舉 列舉型別 型別 型別 型別

C#列舉型別使用方法和 C、C++中的列舉型別基本一致。見下例:

using System;

class Class1 {

enum Days { Sat, Sun, Mon, Tue, Wed, Thu, Fri };

//使用Visual Studio.Net,enum語句添加在[STAThread]前邊 static void Main(string[] args)

{

Days day = Days.Tue;

int x = (int)Days.Tue;//x=2

Console.WriteLine("day={0}, x={1}", day, x);

(13)

//顯示結果為:day=Tue, x=3 }

}

在此列舉型別 Days 中,每個元素的預設的型別為 int,其中 Sat=0,Sun=1,Mon=2,

Tue=3,依此類推。也可以直接給列舉元素指派值。例如:

enum Days{Sat=1,Sun,Mon,Tue,Wed,Thu,Fri,Sat};

在此列舉中,Sat=1,Sun=2,Mon=3,Tue=4,Wed=5,等等。和 C、C++中不同,C#列舉 元素型別可以是 byte、sbyte 、short、ushort、int、uint、long 和 ulong 型別,但不能是 char 型別。見下例:

enum Days:byte{Sun,Mon,Tue,Wed,Thu,Fri,Sat};//元素為 byte 型別

1.4.6 實值型別 實值型別 實值型別的初值和 實值型別 的初值和 的初值和預設的 的初值和 預設的 預設的建構函數 預設的 建構函數 建構函數 建構函數

所有變數都要求必須有初值,如沒有指派值,採用預設值。對於簡單型別,sbyte、byte、

short、ushort、int、uint、long 和 ulong 預設值為 0,char 型別預設值是(char)0,float 為 0.0f,

double 為 0.0d,decimal 為 0.0m,bool 為 false,列舉型別為 0,在結構型別和類中,資料 成員的實值型別變數設置為預設值,參照型別變數設置為 null。

可以顯式的指派值,例如 int i = 0。而對於複雜結構型別,其中的每個資料成員都按此種 方法指派值,顯得過於麻煩。由於實值型別都是結構型別,可用 new 語句調用其建構函數初 始化實值型別變數,例如:int j = new int()。請注意,用 new 語句並不是把 int 變數變為參照 變數,j 仍是實值型別變數,這裡 new 僅僅是調用其建構函數。所有的實值型別都有預設的 的無參數的建構函數,其功能就是為該實值型別賦初值為預設值。對於自定義結構型別,由於 已有預設的的無參數的建構函數,不能再定義無參數的建構函數,但可以定義有參數的建構函 數。

1.4.7 參照 參照 參照 參照型別 型別 型別分類 型別 分類 分類 分類

C#語言中參照型別可以分為以下幾種:

 類:C#語言中預定義了一些類:對象類(object 類)、陣列類、字串類等。當然,程式設計 師可以定義其它類。

 介面。

 委派。

C#語言參照型別變數無論如何定義,總是參照型別變數,不會變為實值型別變數。C# 語 言參照型別對象一般用運算子 new 建立,用參照型別變數引用該對象。本節僅介紹對象型別 (object 型別)、字串型別、陣列。其它型別在其它節中介紹。

(14)

1.4.8 對象 對象 對象 對象類 類 類(object class) 類

C#中的所有型別(包括數值型別)都直接或間接地以 object 類為基類。對象類(object 類)是所 有其它類的基類。任何一個類定義,如果不指定基類,預設 object 為基類。繼承和基類的概念 見以後章節。C#語言規定,基類的參照變數可以引用衍生類的對象(注意,衍生類的參照變數 不可以引用基類的對象),因此,對一個 object 的變數可以賦予任何型別的值:

int x = 25;

object obj1;

obj1 = x;

object obj2 = 'A';

object 關鍵字是在命名空間 System 中定義的,是類 System.Object 的別名。

1.4.9 陣列 陣列 陣列類 陣列 類 類 類

在進行批次處理資料的時候,要用到陣列。陣列是一組型別相同的有序資料。陣列按照陣 列名、資料元素的型別和維數來進行描述。C#語言中陣列是類 System.Array 類對象,比如宣告 一個整數型陣列:int[] arr = new int[5];,實際上產生了一個陣列類對象,arr 是這個對象的參照 (地址)。

在 C#中陣列可以是一維的,也可以是多維的,同樣也支援陣列的陣列,即陣列的元素還 是陣列。一維陣列最為普遍,用的也最多。我們先看一個一維陣列的例子:

using System;

class Test {

static void Main() {

int[] arr = new int[3];//用new運算子建立一個個元素的一維陣列

for (int i = 0; i < arr.Length; i++)/* arr.Length是陣列類變數,

表示陣列元素個數*/

arr[i] = i * i;//陣列元素指派初值,arr[i]表示第i個元素的值 for (int i = 0; i < arr.Length; i++)//陣列第一個元素的下標為 Console.WriteLine("arr[{0}] = {1}", i, arr[i]);

} }

這段程式碼產生了一個 int 型別 3 個元素的一維陣列,初始化後逐項輸出。其中 arr.Length 表示陣列元素的個數。注意陣列定義不能寫為 C 語言格式:int arr[]。程序的輸出為:

arr[0] = 0 arr[1] = 1

(15)

arr[2] = 4

上面的例子中使用的是一維陣列,下面介紹多維陣列:

string[] a1;//一維 string 陣列類參照變數 a1 string[,] a2;//二維 string 陣列類參照變數 a2 a2 = new string[2,3];

a2[1,2] = "abc";

string[,,] a3;//三維 string 陣列類參照變數 a3 string[][] j2;//陣列的陣列,即陣列的元素還是陣列 string[][][][] j3;

在陣列宣告的時候,可以對陣列元素進行指派值。看下面的例子:

int[] a1 = new int[]{1,2,3};//一維陣列,有 3 個元素。

int[] a2 = new int[3]{1,2,3};//此格式也正確

int[] a3 = {1,2,3};//相當於 int[] a3=new int[]{1,2,3};

int[,] a4 = new int[,]{{1,2,3},{4,5,6}};//二維陣列,a4[1,1]=5

int[][] j2 = new int[3][];//定義陣列 j2,有三個元素,每個元素都是一個陣列 j2[0] = new int[]{1,2,3};//定義第一個元素,是一個陣列

j2[1] = new int[]{1, 2, 3, 4, 5, 6};//每個元素的陣列可以不等長 j2[2] = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};

1.4.10 字串 字串 字串類 字串 類 類(string 類 類 類 類 類)

C#還定義了一個基本的類 string,專門用於對字串的操作。這個類也是在命名空間 System 中定義的,是類 System.String 的別名。字串應用非常廣泛,在 string 類的定義中封裝了許多 方法,下面的一些語句展示了 string 類的一些典型用法:

 字串定義

string s;//定義一個字串參照型別變數 s

s = "Zhang";//字串參照型別變數 s 指向字串"Zhang"

string FirstName = "Ming";

string LastName = "Zhang";

string Name = FirstName+" "+LastName;//運算子+已被多載 string SameName = Name;

char[] s2 = {'計','算','機','科','學'};

string s3 = new String(s2);

 字串搜索

string s = "ABC科學";

int i = s.IndexOf("科");

搜索"科"在字串中的位置,因第一個字元索引為 0,所以"A"索引為 0,"科"索引為 3,因 此這裡 i = 3,如沒有此字串 i = –1。注意 C#中,ASCII 和漢字都用 2 個位元組表示。

(16)

 字串比較函數

string s1 = "abc";

string s2 = "abc";

int n = string.Compare(s1,s2);//n = 0

n 值得到 0 表示兩個字串相同,n 值小於零,表示 s1 < s2,n 值大於零,表示 s1 > s2。此 方法區分大小寫。也可用如下辦法比較字串:

string s1 = "abc";

string s = "abc";

string s2 = "不相同";

if(s == s1)//還可用!=。雖然 String 是參照型別,但此寫法仍可比較兩個字串的值 s2 = "相同";

 判斷是否為空字串 string s = "";

string s1 = "不空";

if(s.Length == 0) s1 = "空";

 得到子字串或字元

string s = "取子字串";

string sb = s.Substring(2,2);//從索引為 2 開始取 2 個字元,Sb="字串",s 內容不變 char sb1 = s[0];//sb1='取'

Console.WriteLine(sb1);//顯示:取

 字串刪除函數

string s = "取子字串";

string sb = s.Remove(0,2);//從索引為 0 開始刪除 2 個字元,Sb="字串",s 內容不變

 插入字串

string s = "計算機科學";

string s1 = s.Insert(3, "軟件");//s1="計算機軟件科學",s 內容不變

 字串替換函數

string s = "計算機科學";

string s1 = s.Replace("計算機","軟件");//s1="軟件科學",s 內容不變

 把 string 轉換為字元陣列 string s = "計算機科學";

char[] s2 = s.ToCharArray(0, S.Length);//屬性 Length 為字串類對象的長度

 其它資料型別轉換為字串 int i = 9;

string s8 = i.ToString();//s8="9"

float n = 1.9f;

string s9 = n.ToString();//s8="1.9"

(17)

其它資料型別都可用此方法轉換為字串類對象

 大小寫轉換

string s = "AaBbCc";

string s1 = s.ToLower();//把字元轉換為小寫,s 內容不變 string s2 = s.ToUpper();//把字元轉換為大寫,s 內容不變

 刪除所有的空格

string s = "A bc ";

s.Trim();//刪除所有的空格

string 類其它方法的使用請用幫助系統查看,方法是打開 Visual Studio.Net 的程式碼編輯 器,鍵入 string,將游標移到鍵入的字串 string 上,然後按 F1 鍵。

1.4.11 型別 型別 型別 型別轉換 轉換 轉換 轉換

在編寫 C# 語言程式時,經常會碰到型別轉換問題。例如整數和浮點數相加,C#會進行隱 式轉換。詳細記住那些型別資料可以轉換為其它型別資料,是不可能的,也是不必要的。程式 設計師應記住型別轉換的一些基本原則,編譯器在轉換發生問題時,會給提示。C# 語言中型 別轉換分為:隱式轉換、顯示轉換、裝箱(boxing)和拆箱(unboxing)等三種。

一、隱式轉換

隱式轉換就是系統預設的的、不需要加以宣告就可以進行的轉換。例如從 int 型別轉換到 long 型別就是一種隱式轉換。在隱式轉換過程中,轉換一般不會失敗,轉換過程中也不會導致 訊息丟失。例如:

int i = 10;

long l = i;

二、顯示轉換

顯式型別轉換,又叫強制型別轉換。與隱式轉換正好相反,顯式轉換需要明確地指定轉換 型別,顯示轉換可能導致訊息丟失。下面的例子把長整數型變數顯式轉換為整數型:

long l = 5000;

int i = (int)l;//如果超過 int 取值範圍,將產生異常 三、裝箱(boxing)和拆箱(unboxing)

裝箱(boxing)和拆箱(unboxing)是 C#語言型別系統提出的核心概念,裝箱是實值型別轉換為 object(對象)型別,拆箱是 object(對象)型別轉換為實值型別。有了裝箱和拆箱的概念,對任何 型別的變數來說,最終我們都可以看作是 object 型別。

1. 裝箱操作

把一個實值型別變數裝箱也就是產生一個 object 對象,並將這個實值型別變數的值複製給 這個 object 對象。例如:

int i = 10;

object obj = i;//隱式裝箱操作,obj 為產生的 object 對象的參照。

我們也可以用顯式的方法來進行裝箱操作,例如:

(18)

int i =10;

object obj = object(i);//顯式裝箱操作

實值型別的值裝箱後,實值型別變數的值不變,僅將這個實值型別變數的值複製給這個 object 對象。我們看一下下面的程式:

using System class Test {

public static void Main() {

int n = 200;

object o = n;

o = 201;//不能改變 n

Console.WriteLine("{0}, {1}", n, o);

} }

輸出結果為:200, 201。這就證明了實值型別變數 n 和 object 類對象 o 都獨立存在著。

2. 拆箱操作

和裝箱操作正好相反,拆箱操作是指將一個對象型別顯式地轉換成一個實值型別。拆箱的 過程分為兩步:首先檢查這個 object 對象,看它是否為給定的實值型別的裝箱值,然後,把這 個對象的值複製給實值型別的變數。我們舉個例子來看看一個對象拆箱的過程:

int i = 10;

object obj = i;

int j = (int)obj;//拆箱操作

可以看出拆箱過程正好是裝箱過程的逆過程,必須注意裝箱操作和拆箱操作必須遵循型別 兼容的原則。

3. 裝箱和拆箱的使用 定義如下函數:

void Display(Object o)//注意,o 為 Object 型別 {

int x=(int)o;//拆箱

System.Console.WriteLine("{0},{1}",x,o);

}

調用此函數:int y = 20; Display(y);在此利用了裝箱概念,形參被實參替換:Object o = y,

也就是說,函數的參數是 Object 型別,可以將任意型別實參傳遞給函數。

1.5 運算 運算 運算 運算子 子 子 子

C#語言和 C 語言的運算子用法基本一致。以下重點講解二者之間不一致部分。

(19)

1.5.1 運算 運算 運算子 運算 子 子分類 子 分類 分類 分類

與 C 語言一樣,如果按照運算子所作用的運算元個數來分,C#語言的運算子可以分為以下 幾種類型:

 一元運算子:一元運算子作用於一個運算元,例如:–x、++x、x--等。

 二元運算子:二元運算子對兩個運算元進行運算,例如:x+y。

 三元運算子:三元運算子只有一個:x? y:z。

C#語言運算子的詳細分類及運算子從高到低的優先級順序見下表。

分類 運算子 結合性

初級 (x) x.y f(x) a[x] x++ x–– new typeof sizeof checked unchecked 左 單元 + – ! ~ ++x ––x (T)x 左

乘法等 * / % 左

加法等 + – 左

移位 << >> 左

關係 < > <= >= is as 左

相等 == != 右

邏輯與 & 左

邏輯異或 ^ 左

邏輯或 | 左

條件與 && 左

條件或 || 左

條件 ?: 右

指派等 = *= /= %= += –= <<= >>= &= ^= |= 右

1.5.2 測試 測試 測試 測試運算子 運算子 運算子 is 運算子

is運算子用於動態地檢查表達式是否為指定型別。使用格式為:e is T,其中 e 是一個表達 式,T 是一個型別,該式判斷 e 是否為 T 型別,返回值是一個布爾值。例子:

using System;

class Test {

public static void Main() {

Console.WriteLine(1 is int);

Console.WriteLine(1 is float);

Console.WriteLine(1.0f is float);

(20)

Console.WriteLine(1.0d is double);

} }

輸出為:

True False True True

1.5.3 typeof 運算子 運算子 運算子 運算子

typeof運算子用於獲得指定型別在 System 命名空間中定義的型別名字,例如:

using System;

class Test {

static void Main() {

Console.WriteLine(typeof(int));

Console.WriteLine(typeof(System.Int32));

Console.WriteLine(typeof(string));

Console.WriteLine(typeof(double[]));

} }

產生如下輸出,由輸出可知 int 和 System.int32 是同一型別。

System.Int32 System.Int32 System.String System.Double[]

1.5.4 溢出檢查 溢出檢查 溢出檢查 溢出檢查運算子 運算子 運算子 checked 和 運算子 和 和 和 unchecked

在進行整數型算術運算(如+、-、*、/等)或從一種整數型顯式轉換到另一種整數型時,有 可能出現運算結果超出這個結果所屬型別值域的情況,這種情況稱之為溢出。整數型算術運算 表達式可以用 checked 或 unchecked 溢出檢查運算子,決定在編譯和執行時是否對表達式溢出 進行檢查。如果表達式不使用溢出檢查運算子或使用了 checked 運算子,常數表達式溢出,在 編譯時將產生錯誤,表達式中包含變數,程序執行時執行該表達式產生溢出,將產生異常提示 訊息。而使用了 unchecked 運算子的表達式語句,即使表達式產生溢出,編譯和執行時都不會 產生錯誤提示。但這往往會出現一些不可預期的結果,所以使用 unchecked 運算子要小心。

(21)

下面的例子說明了 checked 和 unchecked運算子的用法:

using System;

class Class1 {

static void Main(string[] args) {

const int x = int.MaxValue;

unchecked//不檢查溢出 {

int z = x*2;//編譯時不產生編譯錯誤,z = –2 Console.WriteLine("z={0}", z);//顯示–2 }

checked//檢查溢出 {

int z1 = (x*2);//編譯時會產生編譯錯誤 Console.WriteLine("z = {0}", z1);

} } }

1.5.5 new 運算子 運算子 運算子 運算子

new運算子可以產生實值型別變數、參照型別對象,同時自動調用建構函數。例如:

int x = new int();//用 new 產生整數型變數 x,調用預設的建構函數 Person C1 = new Person ();//用 new 建立的 Person 類對象。

// Person型別變數 C1 是對象的參照

int[] arr=new int[2];//陣列也是類,產生陣列類對象,arr 是陣列對象的參照

需注意的是,int x = new int()語句將自動調用 int 結構不帶參數的建構函數,給 x 賦初值 0,

x 仍是實值型別變數,不會變為參照型別變數。

1.5.6 運算子 運算子 運算子 運算子的優先級 的優先級 的優先級 的優先級

當一個表達式包含多種運算子時,運算子的優先級控制著運算子求值的順序。例如,表達 式 x+y*z按照 x+(y*z)順序求值,因為*運算子比+運算子有更高的優先級。這和數學運算中的先 乘除後加減是一致的。1.5.1 節中的表總結了所有運算子從高到低的優先級順序。

當兩個有相同優先級的運算子對運算元進行運算時,例如 x+y–z,運算子按照出現的順序 由左至右執行,x+y–z 按(x+y)–z 進行求值。指派運算子按照右接合的原則,即操作按照從右向 左的順序執行。如 x=y=z按照 x=(y=z)進行求值。建議在寫表達式的時候,如果無法確定運算子

(22)

的實際順序,則盡量採用括號來保證運算的順序,這樣也使得程式一目瞭然,而且自己在編程 時能夠思路清晰。

1.6 程序控制語句 程序控制語句 程序控制語句 程序控制語句

C#語言控制語句和 C 基本相同,使用方法基本一致。C#語言控制語句包括:if 語句、swith 語句、while 語句、do…while 語句、for 語句、foreach 語句、break 語句、continue 語句、goto 語句、return 語句、異常處理語句等,其中 foreach 語句和異常語句是 C# 語言新增加控制語 句。

本節首先介紹一下這些語句和 C 語言的不同點,然後介紹 C# 語言新增的控制語句。

1.6.1 和 和 和 和 C 語言的不同點 語言的不同點 語言的不同點 語言的不同點

 與 C 不同,if 語句、while 語句、do…while 語句、for 語句中的判斷語句,一定要用布爾 表達式,不能認為 0 為 false,其它數為 true。

 switch 語句不再支援遍歷,C 和 C++語言允許 switch 語句中 case 標籤後不出現 break 語句,

但 C#不允許這樣,它要求每個 case 標籤項後使用 break 語句或 goto 跳轉語句,即不允許 從一個 case 自動遍歷到其它 case,否則編譯時將報錯。switch 語句的控制型別,即其中控 制表達式的資料型別可以是 sbyte、byte、short、ushort、uint、long、ulong、char、string 或列舉型別。每個 case 標籤中的常數表達式必須屬於或能隱式轉換成控制型別。如果有 兩個或兩個以上 case 標籤中的常數表達式值相同,編譯時將會報錯。執行 switch 語句,

首先計算 switch 表達式,然後與 case 後的常數表達式的值進行比較,執行第一個與之匹 配的 case分支下的語句。如果沒有 case 常數表達式的值與之匹配,則執行 default 分支下 的語句,如果沒有 default 語句,則退出 switch 語句。switch 語句中可以沒有 default 語句,

但最多只能有一個 default 語句。見下例:

using System;

class class1 {

static void Main() {

System.Console.WriteLine("請輸入要計算天數的月份");

string s = System.Console.ReadLine();

string s1 = "";

switch(s) {

case "1": case "3": case "5":

case "7": case "8": case "10":

case "12"://共用一條語句

(23)

s1 = "31"; break;

case "2":

s1 = "28"; break;

case "4": case "6": case "9":

goto case "11";//goto語句僅為說明問題,無此必要 case "11":

s1 = "30"; break;

default:

s1 = "輸入錯誤"; break;

}

System.Console.WriteLine(s1);

} }

1.6.2 foreach 語句 語句 語句 語句

foreach 語句是 C#語言新引入的語句,C 和 C++中沒有這個語句,它借用 Visual Basic 中的 foreach 語句。語句的格式為:

foreach(型別 變數名 in 表達式) 循環語句

其中表達式必須是一個陣列或其它集合型別,每一次循環從陣列或其它集合中逐一取出資料,

指派給指定型別的變數,該變數可以在循環語句中使用、處理,但不允許修改變數,該變數的 指定型別必須和表達式所代表的陣列或其它集合中的資料型別一致。例子:

using System;

class Test() {

public static void Main() {

int[] list = {10, 20, 30, 40};//陣列 foreach(int m in list)

Console.WriteLine("{0}", m);

} }

對於一維陣列,foreach 語句循環順序是從下標為 0 的元素開始一直到陣列的最後一個元 素。對於多維陣列,元素下標的遞增是從最右邊那一維開始的。同樣 break 和 continue 可以出 現在 foreach 語句中,功能不變。

(24)

1.6.3 異常語句 異常語句 異常語句 異常語句

在編寫程序時,不僅要關心程序的正常操作,還應該考慮到程序執行時可能發生的各類不 可預期的事件,比如用戶輸入錯誤、記憶體不夠、磁碟出錯、網路資源不可用、資料庫無法使 用等,所有這些錯誤被稱作異常或例外(exception),不能因為這些異常使程序執行產生問題。

各種程序設計語言經常採用異常處理語句來解決這類異常問題。

C#提供了一種處理系統級錯誤和應用程序級錯誤的結構化的、統一的、型別安全的方法。

C#異常語句包含 try 子句、catch 子句和 finally 子句。try 子句中包含可能產生異常的語句,該 子句自動捕捉執行這些語句過程中發生的異常。catch 子句中包含了對不同異常的處理程式 碼,可以包含多個 catch 子句,每個 catch 子句中包含了一個異常型別,這個異常型別必須是 System.Exception 類或它的衍生類參照變數,該語句只捕捉該型別的異常。可以有一個通用異 常型別的 catch 子句,該 catch 子句一般在事先不能確定會發生什麼樣的異常的情況下使用,

也就是可以捕捉任意型別的異常。一個異常語句中只能有一個通用異常型別的 catch 子句,而 且如果有的話,該 catch 子句必須排在其它 catch 子句的後面。無論是否產生異常,子句 finally 一定被執行,在 finally 子句中可以增加一些必須執行的語句。

異常語句捕捉和處理異常的機理是:當 try 子句中的程式碼產生異常時,按照 catch 子句 的順序查找異常型別。如果找到,執行該 catch 子句中的異常處理語句。如果沒有找到,執行 通用異常型別的 catch 子句中的異常處理語句。由於異常的處理是按照 catch 子句出現的順序 逐一檢查 catch 子句,因此 catch 子句出現的順序是很重要的。無論是否產生異常,一定執行 finally 子句中的語句。異常語句中不必一定包含所有三個子句,因此異常語句可以有以下三種 可能的形式:

 try-catch 語句,可以有多個 catch 語句

 try-finally 語句

 try-catch-finally 語句,可以有多個 catch 語句 請看下邊的例子:

1. try-catch-finally 語句 using System

using System.IO//使用文件必須引用的命名空間 public class Example

{

public static void Main() {

StreamReader sr = null;//必須賦予初值 null,否則編譯不能通過 try

{

sr = File.OpenText("d:\\csarp\\test.txt");//可能產生異常 string s;

(25)

while(sr.Peek() != –1) {

s = sr.ReadLine();//可能產生異常 }

Console.WriteLine(s);

}

catch(DirectoryNotFoundException e)//無指定目錄異常 {

Console.WriteLine(e.Message);

}

catch(FileNotFoundException e)//無指定文件異常 {

Console.WriteLine("文件"+e.FileName+"未被發現");

}

catch(Exception e)//其它所有異常 {

Console.WriteLine("處理失敗:{0}",e.Message);

}

finally {

if(sr != null) sr.Close();

} } }

2. try-finally 語句

上例中,其實可以不用 catch 語句,在 finally 子句中把文件關閉,提示用戶是否正確打開 了文件,請讀者自己完成。

3. try-catch 語句

請讀者把上例修改為使用 try-catch 結構,注意在每個 catch 語句中都要關閉文件。

1.7 類的繼承 類的繼承 類的繼承 類的繼承

在 1.3節,定義了一個描述個人情況的類 Person,如果我們需要定義一個僱員類,當然可 以從頭開始定義僱員類 Employee。但這樣不能利用 Person 類中已定義的函數和資料。比較好 的方法是,以 Person 類為基礎(基類),衍生出一個僱員類 Employee,僱員類 Employee 繼承了 Person 類的資料成員和函數成員,既 Person 類的資料成員和函數成員成為 Employee 類的成 員。這個 Employee 類稱為以 Person 類為基類的衍生類,這是 C#給我們提出的方法。C#用繼承 的方法,實現程式碼的重用。

(26)

1.7.1 衍生 衍生 衍生 衍生類的 類的 類的宣告 類的 宣告 宣告格式 宣告 格式 格式 格式

衍生類的宣告格式如下:

屬性 類修飾符 class 衍生類名 : 基類名 { 類體 } 僱員類 Employee 定義如下:

class Employee : Person//Person類是基類 {

private string department;//部門,新增資料成員 private decimal salary;//工資,新增資料成員

public Employee(string Name,int Age,string D,decimal S) : base(Name,Age) {

/*注意 base 的第一種用法,根據參數調用指定基類建構函數,

注意參數的傳遞 */

department = D;

salary = S;

public new void Display()//覆蓋基類 Display()方法, //注意用 new,本例不可用 override {

base.Display();//存取基類被覆蓋的方法,base 的第二種用法 Console.WriteLine("部門:{0} 工資:{1}",department,salary);

} } }

修改主函數如下:

class Class1 {

static void Main(string[] args) {

Employee OneEmployee = new Employee("李四",30,"計算機系",2000);

OneEmployee.Display();

} }

Employee 類繼承了基類 Person 的方法 SetName()、SetAge(),資料成員 name 和 age,即 認為基類 Person 的這些成員也是 Employee 類的成員,但不能繼承建構函數和解構函數。添加 了新的資料成員 department 和 salary。覆蓋了方法 Display()。請注意,雖然 Employee 類繼承 了基類 Person 的 name 和 age,但由於它們是基類的私有成員,Employee 類中新增或覆蓋的方 法不能直接修改 name 和 age,只能通過基類原有的公有方法 SetName()和 SetAge()修改。如果

(27)

希望在 Employee 類中能直接修改 name 和 age,必須在基類中修改它們的屬性為 protected。

1.7.2 base 關鍵字 關鍵字 關鍵字 關鍵字

base 關鍵字用於從衍生類中存取基類成員,它有兩種基本用法:

 在定義衍生類的建構函數中,指明要調用的基類建構函數,由於基類可能有多個建構函 數,根據 base 後的參數型別和個數,指明要調用哪一個基類建構函數。參見上節僱員類 Employee 建構函數定義中的 base 的第一種用法。

 在衍生類的方法中調用基類中被衍生類覆蓋的方法。參見上節僱員類 Employee 的 Display() 方法定義中的 base 的第二種用法。

1.7.3 覆蓋基類成員 覆蓋基類成員 覆蓋基類成員 覆蓋基類成員

在衍生類中,通過宣告與基類完全相同新成員,可以覆蓋基類的同名成員,完全相同是指 函數型別、函數名、參數型別和個數都相同。如上例中的方法 Display()。衍生類覆蓋基類成員 不算錯誤,但會導致編譯器發出警告。如果增加 new 修飾符,表示認可覆蓋,編譯器不再發 出警告。請注意,覆蓋基類的同名成員,並不是移走基類成員,只是必須用如下格式存取基類 中被衍生類覆蓋的方法:base.Display()。

1.7.4 C#語言類繼承特點 語言類繼承特點 語言類繼承特點 語言類繼承特點

C#語言類繼承有如下特點:

 C#語言只允許單繼承,即衍生類只能有一個基類。

 C#語言繼承是可以傳遞的,如果 C 從 B 衍生,B 從 A 衍生,那麼 C 不但繼承 B 的成員,還 要繼承 A 中的成員。

 衍生類可以添加新成員,但不能刪除基類中的成員。

 衍生類不能繼承基類的建構函數、解構函數和事件。但能繼承基類的屬性。

 衍生類可以覆蓋基類的同名成員,如果在衍生類中覆蓋了基類同名成員,基類該成員在衍 生類中就不能被直接存取,只能通過「base.基類方法名」存取。

 衍生類對象也是其基類的對象,但基類對象卻不是其衍生類的對象。例如,前面定義的僱 員類 Employee 是 Person 類的衍生類,所有僱員都是人類,但很多人並不是僱員,可能 是學生,自由職業者,兒童等。因此 C#語言規定,基類的參照變數可以引用其衍生類對 象,但衍生類的參照變數不可以引用其基類對象。

1.8 類的成員 類的成員 類的成員 類的成員

由於 C#程式中每個變數或函數都必須屬於一個類或結構,不能像 C 或 C++那樣建立全域變 數,因此所有的變數或函數都是類或結構的成員。類的成員可以分為兩大類:類本身所宣告的

(28)

以及從基類中繼承來的。

1.8.1 類的成員類型 類的成員類型 類的成員類型 類的成員類型

類的成員包括以下類型:

 局部變數:在 for、switch 等語句中和類方法中定義的變數,只在指定範圍內有效。

 欄位:即類中的變數或常數,包括靜態欄位、實例欄位、常數和唯讀欄位。

 方法成員:包括靜態方法和實例方法。

 屬性:按屬性指定的 get 方法和 set 方法對欄位進行讀寫。屬性本質上是方法。

 事件:代表事件本身,同時聯繫事件和事件處理函數。

 索引子:允許像使用陣列那樣存取類中的資料成員。

 運算子多載:採用多載運算子的方式定義類中特有的操作。

 建構函數和解構函數。

包含有可執行程式碼的成員被認為是類中的函數成員,這些函數成員有方法、屬性、索引 子、運算子多載、建構函數和解構函數。

1.8.2 類成員 類成員 類成員 類成員存取 存取 存取修飾符 存取 修飾符 修飾符 修飾符

存取修飾符用於指定類成員的可存取性,C#存取修飾符有 private、protected、public 和 internal 四種。private 宣告私有成員,私有資料成員只能被類內部的函數使用和修改,私有函 數成員只能被類內部的函數調用。衍生類雖然繼承了基類私有成員,但不能直接存取它們,只 能通過基類的公有成員存取。protected 宣告保護成員,保護資料成員只能被類內部和衍生類 的函數使用和修改,保護函數成員只能被類內部和衍生類的函數調用。public 宣告公有成員,

類的公用函數成員可以被類的外部程序所調用,類的公用資料成員可以被類的外部程序直接使 用。公有函數實際是一個類和外部通訊的介面,外部函數通過調用公有函數,按照預先設定好 的方法修改類的私有成員和保護成員。internal 宣告內部成員,內部成員只能在同一程序集中 的文件中才是可以存取的,一般是同一個應用(Application)或庫(Library)。

1.9 類的 類的 類的 類的欄位 欄位 欄位 欄位和屬性 和屬性 和屬性 和屬性

類或結構中定義的變數和常數一般稱為「欄位」。屬性不是欄位,本質上是定義修改欄位 的方法,由於屬性和欄位的緊密關係,把它們放到一起敘述。

1.9.1 靜態 靜態 靜態 靜態欄位 欄位 欄位、 欄位 、 、實例 、 實例 實例欄位 實例 欄位 欄位、 欄位 、 、常數 、 常數 常數和 常數 和 和唯讀欄位 和 唯讀欄位 唯讀欄位 唯讀欄位

用修飾符 static 宣告的欄位為靜態欄位。不管包含該靜態欄位的類生成多少個對象或根本 無對象,該欄位都只有一個實例,靜態欄位不能被撤銷。必須採用如下方式引用靜態欄位:類 名.靜態欄位名。如果類中定義的欄位不使用修飾符 static,該欄位為實例欄位,每產生該類的

(29)

一個對象,在對象內產生一個該欄位實例,產生它的對象被撤銷,該欄位對象也被撤銷,實例 欄位採用如下方式引用:實例名.實例欄位名。用 const 修飾符宣告的欄位為常數,常數只能 在宣告中初始化,以後不能再修改。用 readonly 修飾符宣告的欄位為唯讀欄位,唯讀欄位是 特殊的實例欄位,它只能在欄位宣告中或建構函數中重新指派值,在其它任何地方都不能改變 唯讀欄位的值。例子:

public class Test {

public const int intMax = int.MaxValue;//常數,必須賦初值 public int x = 0;//實例欄位

public readonly int y = 0;//唯讀欄位 public static int cnt = 0;//靜態欄位 public Test(int x1, int y1)//建構函數 {

//intMax = 0;//錯誤,不能修改常數 x = x1;//在建構函數允許修改實例欄位 y = y1;//在建構函數允許修改唯讀欄位

cnt++;//每產生一個對象都調用建構函數,用此語句可以記錄對象的個數 }

public void Modify(int x1,int y1) {

//intMax = 0;//錯誤,不能修改常數 x = x1;

cnt = y1;

//y = 10;//不允許修改唯讀欄位 }

}

class Class1 {

static void Main(string[] args) {

Test T1 = new Test(100,200);

T1.x = 40;//引用實例欄位採用:實例名.實例欄位名 Test.cnt = 0;//引用靜態欄位採用:類名.靜態欄位名 int z = T1.y;//引用唯讀欄位

z = Test.intMax;//引用常數 }

}

(30)

1.9.2 屬性 屬性 屬性 屬性(Property)

C#語言支援組件編程,組件也是類,組件用屬性、方法、事件描述。屬性不是欄位,但必 然和類中的某個或某些欄位相聯繫,屬性定義了得到和修改相聯繫的欄位的方法。C#中的屬性 更充分地體現了對象的封裝性:不直接操作類的資料內容,而是通過存取器進行存取,借助於 get 和 set 方法對屬性的值進行讀寫。存取屬性值的語法形式和存取一個變數基本一樣,使存 取屬性就像存取變數一樣方便,符合習慣。

在類的基本概念一節中,定義一個描述個人情況的類 Person,其中欄位 name 和 age 是私 有欄位,記錄姓名和年齡,外部通過公有方法 SetName 和 SetAge 修改這兩個私有欄位。現在 用屬性來描述姓名和年齡。例子如下:

using System;

public class Person {

private string P_name = "張三";//P_name 是私有欄位 private int P_age = 12;//P_age是私有欄位

public void Display()//類的方法宣告,顯示姓名和年齡 {

Console.WriteLine("姓名:{0},年齡:{1}", P_name,P_age);

}

public string Name//定義屬性 Name {

get

{ return P_name; } set

{ P_name = value; } }

public int Age//定義屬性 Age {

get

{ return P_age; } set

{ P_age = value; } }

}

public class Test {

public static void Main()

(31)

{

Person OnePerson = new Person();

OnePerson.Name = "田七";//value="田七",通過 set 方法修改變數 P_Name string s = OnePerson.Name;//通過 get 方法得到變數 P_Name 值

OnePerson.Age = 20;//通過定義屬性,既保證了姓名和年齡按指定方法修改 int x = OnePerson.Age;//語法形式和修改、得到一個變數基本一致,符合習慣 OnePerson.Display();

} }

在屬性的存取宣告中,只有 set 存取器表明屬性的值只能進行設置而不能讀出,只有 get 存取器表明屬性的值是唯讀的不能改寫,同時具有 set 存取器和 get 存取器表明屬性的值的讀 寫都是允許的。

雖然屬性和欄位的語法比較類似,但由於屬性本質上是方法,因此不能把屬性當做變數那 樣使用,也不能把屬性作為傳參照參數或輸出參數來進行傳遞。

1.10 類的方法 類的方法 類的方法 類的方法

方法是類中用於執行計算或其它行為的成員。所有方法都必須定義在類或結構中。

1.10.1 方法的 方法的 方法的 方法的宣告 宣告 宣告 宣告

方法的宣告格式如下:

屬性 方法修飾符 返回型別 方法名 ( 形參列表 ) { 方法體 }

方法修飾符包括 new、public、protected、internal、private、static、virtual、sealed、override、

abstract 和 extern。這些修飾符有些已經介紹過,其它修飾符將逐一介紹。返回型別可以是任 何合法的 C#資料型別,也可以是 void,即無返回值。形參列表的格式為:(形參型別 形參 1, 形 參型別 形參 2, ...),可以有多個形參。不能使用 C 語言的形參格式。

1.10.2 方法參數的種類 方法參數的種類 方法參數的種類 方法參數的種類

C#語言的方法可以使用如下四種參數(請注意和參數型別的區別):

 傳值參數,不含任何修飾符。

 傳參照參數,以 ref 修飾符宣告。

 輸出參數,以 out 修飾符宣告。

 參量參數,以 params 修飾符宣告。

1. 傳值參數

當用傳值參數向方法傳遞參數時,程序複製實參的值,並且將此副本傳遞給該方法,被調 用的方法不會修改實參的值,所以使用傳值參數時,可以保證實參的值是安全的。如果參數型

(32)

別是參照型別,例如是類的參照變數,則副本中儲存的也是對象的參照,所以副本和實參引用 同一個對象,通過這個副本,可以修改實參所引用的對象中的資料成員。

2. 傳參照參數

有時在方法中,需要修改或得到方法外部的變數值,C 語言是向方法傳遞實參指標來達到 目的,而 C#語言是用傳參照參數。當用傳參照參數向方法傳遞實參時,程序將把實參的參照,

即實參在記憶體中的地址傳遞給方法,方法通過實參的參照(地址),修改或得到方法外部的變 數值。傳參照參數以 ref 修飾符宣告。注意在使用前,實參變數必須被設置初始值。

3. 輸出參數

為了把方法的運算結果保存到外部變數,因此需要知道外部變數的參照(地址)。輸出參數 用於向方法傳遞外部變數參照(地址),所以輸出參數也是傳參照參數,與傳參照參數的差別在 於調用方法前無需對變數進行初始化。在方法返回後,傳遞的變數被認為經過了初始化。傳值 參數、傳參照參數和輸出參數的用法見下例:

using System;

class g{ public int a = 0; }//類定義 class Class1

{

public static void F1(ref char i)//傳參照參數 { i = 'b';}

public static void F2(char i)//傳值參數,參數型別為實值型別 { i = 'd';}

public static void F3(out char i)//輸出參數 { i = 'e';}

public static void F4(string s)//傳值參數,參數型別為字串 { s = "xyz";}

public static void F5(g gg)//傳值參數,參數型別為參照型別 { gg.a = 20;}

public static void F6(ref string s)//傳參照參數,參數型別為字串 { s = "xyz";}

static void Main(string[] args) {

char a = 'c';

string s1 = "abc";

F2(a);//傳值參數,不能修改外部的 a

Console.WriteLine(a);//因 a 未被修改,顯示 c F1(ref a);//傳參照參數,函數修改外部的 a 的值 Console.WriteLine(a);//a被修改為 b,顯示 b char j;

F3(out j);//輸出參數,結果輸出到外部變數 j

(33)

Console.WriteLine(j);//顯示 e

F4(s1);//傳值參數,參數型別是字串,s1 為字串參照變數 Console.WriteLine(s1);//顯示:abc,字串 s1 不被修改 g g1 = new g();

F5(g1);//傳值參數,但實參是一個類參照型別變數

Console.WriteLine(g1.a.ToString());//顯示:20,修改對象資料 F6(ref s1);//傳參照參數,參數型別是字串,s1 為字串參照變數 Console.WriteLine(s1);//顯示:xyz,字串 s1 被修改

} } 4. 參量參數

參量參數使用 params修飾符,如果形參列中包含了參量參數,那麼它必須是參數表中最 後一個參數,參量參數只允許是一維陣列。比如 string[]和 string[][]型別都可以作為參量型參 數。最後,參量型參數不能再有 ref 和 out 修飾符。見下例:

using System;

class Class1 {

static void F(params int[] args)//參量參數,有 params 修飾符 {

Console.Write("Array contains {0} elements:", args.Length);

foreach (int i in args)

Console.Write(" {0}", i);

Console.WriteLine();

}

static void Main(string[] args) {

int[] a = {1, 2, 3};

F(a);//實參為陣列類參照變數 a

F(10, 20, 30, 40);//等價於 F(new int[] {60, 70, 80, 90});

F(new int[] {60, 70, 80, 90});//實參為陣列類參照 F();//等價於 F(new int[] {});

F(new int[] {});//實參為陣列類參照,陣列無元素 }

}

程序輸出

Array contains 3 elements: 1 2 3

Array contains 4 elements: 10 20 30 40 Array contains 4 elements: 60 70 80 90

(34)

Array contains 0 elements:

Array contains 0 elements:

方法的參數為陣列時也可以不使用 params,此種方法可以使用一維或多維陣列,見下例:

using System;

class Class1 {

static void F(int[,] args)//傳值參數,參數型別為陣列類參照變數,無 params 修飾 {

Console.Write("Array contains {0} elements:", args.Length);

foreach (int i in args)

Console.Write(" {0}", i);

Console.WriteLine();

}

static void Main(string[] args) {

int[,] a = {{1, 2, 3}, {4, 5, 6}};

F(a);//實參為陣列類參照變數 a

//F(10, 20, 30, 40);//此格式不能使用

F(new int[,] {{60, 70},{80, 90}});//實參為陣列類參照 //F();//此格式不能使用

//F(new int[,] {});//此格式不能使用 }

}

程序輸出

Array contains 3 elements: 1 2 3 4 5 6 Array contains 4 elements: 60 70 80 90

1.10.3 靜態方法和實例方法 靜態方法和實例方法 靜態方法和實例方法 靜態方法和實例方法

用修飾符 static 宣告的方法為靜態方法,不用修飾符 static 宣告的方法為實例方法。不管 類有否產生對象,類的靜態方法都可以被使用,使用格式為:類名.靜態方法名。靜態方法只 能使用該靜態方法所在類的靜態資料成員和靜態方法。這是因為使用靜態方法時,該靜態方法 所在類可能還沒有對象,即使有對象,由於用類名.靜態方法名方式調用靜態方法,靜態方法 沒有 this 指標來存放對象的地址,無法判定應存取哪個對象的資料成員。在類產生對象後,

實例方法才能被使用,使用格式為:對象名.實例方法名。實例方法可以使用該方法所在類的 所有靜態成員和實例成員。例子如下:

using System;

public class UseMethod

參考文獻

相關文件

第四章 直角座標與二元一次方程式.

前一章我們學過了一次函數,本章將繼續延伸到二次函數。二次函數的函數圖形為拋

前一章我們學過了一次函數,本章將繼續延伸到二次函數。二次函數的函數圖形為拋

5 這些國家和國際組織包括:國際勞工組織和聯合國教育、科學及文化組織(ILO &amp; UNESCO,2006) 、 歐盟(European Communities,2007)、挪威(Norway Ministry of

第四章: 中學報稅的設計 第五章: 初中諒程主建議 第六章: 高中諒我建議,..

關於宋代寺院的合法性,日本的高雄義堅先生《宋代教史の研究》第三章的第一節、竺

第一篇 國際安全與軍事情勢 第一章 國際安全環境 第二章 全球軍事情勢 第三章 亞洲軍事情勢 第四章 中共軍事情勢.. 第二篇

[r]