4、類和封裝,4,類封裝
本學習主要參考Andrew Troelsen的C#與.NET4高級程序設計,這小節主要述說以下幾個東西:
1、構建支持任意數量的構造函數的定義明確的類類型。
2、類和分配對象的基本知識
3、封裝的作用
4、定義類屬性以及靜態成員、對象初始化語法、只讀字段、常亮和分部類的作用
C#類類型
.net平台最基本的編程結構就是類類型,正式的說,類是由字段數據(成員變量)以及操作這個數據的成員(構造函數、屬性、方法、事件等)所構成的自定義類型。總的來說,其中的字段數據用於表示類實例的‘狀態’(對象)。
C#這樣基於對象的語言的強大之處就在於,通過將數據和相關功能集合在類定義中,我們就可以仿照現實生活中的實體來設計軟件。(面相對象)類定義:
//關鍵字是class,類名是Car
class Car
{
//Car的狀態
public string petName;
public int currSpeed;
//Car的功能
public void PrintState()
{
Console.WriteLine("{0} is going {1} MPH.", petName, currSpeed);
}
public void SpeedUp(int delta)
{
currSpeed += delta;
}
}
說明,雖然上述我們用了兩個共有字段表示類的‘狀態’,但是類的字段很少定義為公有的,為了保護狀態數據完整,我們一般將字段定義為私有,並且通過類型屬性對數據提供受控制的訪問。
上面定義了成員變量,那麼描述其行為的就是成員,例如上述類中的一個SpeedUp()方法和一個PrintState()方法.
在Program類中寫入如下代碼:
static void Main(string[] args)
{
Console.WriteLine("****Fun with class types****");
Car myCar = new Car();
myCar.petName = "Henry";
myCar.currSpeed = 10;
for (int i = 0; i <= 10; i++)
{
myCar.SpeedUp(5);
myCar.PrintState();
}
Console.ReadLine();
}
當我們運行程序就看到類方法輸出的狀態數據。
new關鍵字:對象必須用new關鍵字來分配到內存中。new關鍵字主要是為了把引用付給對象。只有new之後才會把引用賦給對象,這個引用才會指向沒村中的有效類實例。
構造函數
如果希望在使用對象前就先給對象字段數據賦值,那麼就用到了構造函數。構造函數是類的特殊方法,在使用new關鍵字創建對象時被間接調用。然而和‘普通’方法不同,構造函數永遠不會有返回值,並且他的名字總是和需要構造的類的名字相同。
1.默認構造函數的作用:
每個C#類都提供了內建的默認構造函數,需要時可以重新定義。根據定義,默認構造函數不會接受任何參數。
在上類中更新如下方法:
public Car()
{
petName = "GoodGirl";
currSpeed = 10;
}
當我們創建對象之後,直接調用PrintState方法就會打印出來GoodGirl is going 10 MPH.
2.定義自定義的構造函數:
除了默認構造函數,我們還可以自定義不同的構造函數。讓構造函數彼此不同的是構造函數參數的個數和類型。當我們定義了具有同樣名字但參數數量和類型不同的方法時,就是重載方法。因此,我們自定義構造函數相當於是方法重載。比如除了上述的構造函數,我們還可以構造如下:
public Car(string p)
{
petName = p;
}
3.再談默認構造函數:
如果自定義了構造函數,那麼默認的構造函數就會自動從類中移除,不再有效。因此,如果希望對象用戶使用默認構造函數和自定義構造函數創建類型實例,就必須顯示重新定義默認構造函數。如下新建一個類所示:
class Motorcycle
{
public int intensity;
public void PopAWheely()
{
for (int i = 0; i <= intensity; i++)
{
Console.WriteLine("aaaa hhhhh!");
}
}
//顯示默認構造函數
public Motorcycle() { }
//自定義構造函數
public Motorcycle(int intensity)
{ this.intensity= intensity; }
}
this關鍵字的作用
C#支持this關鍵字來提供對當前類實例的訪問。this關鍵字可能的用途就是,解決了當傳入參數的名字和類型數據字段的名字相同時產生的作用域歧義。如上例所示,最後一個構造函數的裡面,this.intensity指的是類中定義的字段,而給他賦值的那個intensity則是構造函數的參數。
說明:在靜態成員的實現中實現this關鍵字,會產生編譯錯誤。我們知道,靜態成員在類(而不是對象)級別產生作用,因此在類級別沒有當前對象(也就沒有this)。
1.使用this進行串聯構造函數調用(簡化編程):
this關鍵字的另一種用法就是使用一項名為構造函數鏈的技術來設計類,當類定義了多個構造函數時,這個設計模式就會很有用。如果多個構造函數有相同的處理邏輯,但是參數不一樣,則很容易產生代碼冗余。這時候,this關鍵字串聯就非常有用,我們可以如下構造:就是讓一個接受最多參數個數的構造函數做朱構造函數,並且實現必須的驗證邏輯。
//默認構造函數
public Motorcycle() { }
//構造函數鏈
public Motorcycle(int intensity)
: this(intensity, "") { }
public Motorcycle(int intensity, string name)
{
if (intensity>10)
{
intensity = 10;
}
driverIntensity = intensity;
driverName = name;
}
2.構造函數流程:
我們要知道,在構造函數傳遞參數給指定的主構造函數(並且構造函數處理了數據)之後,調用者最初調用的構造函數還會執行所有剩余的代碼語句。(即走完構造函數中所有流程)。構造函數的邏輯流程如下:
3.再談可選參數:
之前提過,可選參數允許我們對傳入參數提供默認值,如果調用者希望使用這些默認值而不是使用自定義數據,就不必在單獨制定這些參數。盡管可選/命名參數靈巧的簡化了為給定類定義構造函數集的方式,但是要記住的是這種語法只能在C#2010中編譯並且只能在.net4下運行(以及更高版本)。可選參數構造函數諸如下面形式:
public Motorcycle(int intensity = 0, string name = "")
{
//處理邏輯
}
static關鍵字:
C#類(或結構)可以通過static關鍵字來定義許多靜態成員。如果這樣的話,這些成員就只能直接從類級別而不是對象引用調用。比如Console中的WriteLine()方法。
簡而言之,靜態方法被(類設計者)認為是非常普遍的項,並且不需要再調用成員時創建類型的實例。
1.定義靜態方法:
public static string A(){//處理邏輯};
靜態成員只能操作靜態數據或者調用類的靜態方法,如果你常事在靜態成員中使用非靜態類數據或調用非靜態類方法,就會收到編譯時錯誤。
2.定義靜態數據:
public static double currentNum=0.04;
不同於非靜態數據那樣類型每個對象都會維護字段的一個獨立副本,靜態字段只在內存中分配一次,之後所有的類型的對象操作的將會是同一個值。所以,要記住的是,靜態數據字段是所有對象共享的,因此如果你要定義一個所有對象都可以分享的數據點,就可以使用靜態成員。
3.定義靜態構造函數:
構造函數用於在創建對象時設置對象的數據值,因此,如果在實例級別的構造函數中賦值給靜態數據成員,你會驚奇的發現每次新建對象的時候,值都會重置。這種方法看起來違背了我們設計類的原則,但是如果需要在運行時獲取靜態數據的值的時候,將會非常有用。
簡而言之,靜態構造函數是特殊的構造函數,並且非常適用於初始化在編譯時未知的靜態數據的值。靜態構造函數特點如下:
4.定義靜態類:
如果直接在類級別應用static關鍵字,就不能使用new關鍵字來創建,並且只能包含static關鍵字標記的成員或字段。
說明:只包含靜態功能的類或結構通常稱為工具類,在設計工具類時,將類定義為靜態類是一個非常好的做法。
OOP的支柱
oop支柱,也就是面向對象語言的原則。封裝、繼承、多態。
1.封裝的作用:
封裝是將對象用戶不必了解的實現細節隱藏起來的一種語言能力。和封裝編程邏輯緊密相關的概念是數據保護。理想狀態下,對象的狀態數據應該使用private關鍵字來指定。這樣的話,外部世界就不能直接改變或獲取底層的值,這樣可以避免數據點被破壞。
2.繼承的作用:
繼承是指基於已有類定義來創建心累定義的語言能力。本質上,通過繼承,子類可以繼承基類(父類)核心的功能,並擴展基類的行為。
在oop中海油另一種形式的代碼重用:包含/委托模型(has-a關系)。這種重用的形式不是用來建立父類/子類關系的。它意味著一個類可以定義另一個類的成員變量,並向對象用戶間接公開它的功能。
3.多態的作用:
多態表示的是語言以同一種方式處理相關對象的能力。准確的說,這個面向對象語言的原則允許基類為所有的派生類定義一個成員集合(多態接口)。
C#訪問修飾符
C#的訪問修飾符主要有private、public、protected、internal、protected internal。作用如下表:
而C#成員類型的可修飾及默認修飾符如下表:
private、protected、protected internal訪問修飾符可以應用到嵌套類型上。嵌套類型是直接聲明在類或結構作用域中的類型。而非嵌套類上只能用public、internal修飾符定義。
C#封裝服務(OOP第一個支柱)
封裝的核心概念是,對象的內部數據不應該從對象實例直接訪問。如果調用者想改變對象的狀態,就要使用訪問方法(getter)和修改方法(setter)。在C#中,封裝是通過private、public、protected、internal關鍵字在語法級別上體現的。
封裝提供了一種保護狀態數據完整性的方法,與定義共有字段相比,應該定義更多的私有數據字段,這種字段可以由調用者間接地操作。定義私有字段的主要方式有以下兩種(黑盒編程):
1.使用傳統的訪問方法和修改方法執行封裝:
即用get方法和set方法。set方法可以改變當前實際狀態數據的值。定義如下:
class A
{
private string name;
//get
public string GetName()
{
return name;
}
//set
public void SetName()
{
//處理邏輯
}
}
2.使用.net屬性進行封裝:
盡管可以使用傳統的獲取方法和設置方法封裝這些字段數據,.net語言還是提倡使用屬性來強制數據封裝狀態數據。
C#屬性由屬性作用域中定義的get作用域(訪問方法)和set作用域(修改方法)構成 ,屬性通過返回值指定了它所封裝的數據類型。定義如下:其中set中的value標記用來表示調用者設置屬性時傳入的值。
private int id;
public int ID
{
get{return id;}
set{id = value;}
}
屬性的好處主要是,讓我們類型易於操作,因為屬性可以結合C#內部操作符進行使用。
3.使用類的屬性:
屬性,特別是屬性的set部分,常用語打包類的業務規則。在類中直接使用屬性更好一些,將會減少一些重復的檢查或者判斷。
4.屬性的內部表示:
許多程序員往往使用get_和set_前綴來命名傳統的訪問方法和修改方法。這種命名約定沒有問題,但是在底層,C#屬性也是使用相同的前綴在CIL代碼中我們可以清晰的看到。可以用ildasm.exe打開exe程序觀看。
說明,封裝字段數據時,.net基礎類庫總是使用類型屬性而不是傳統的訪問方法和修改方法,因此,如果想創建能與.net平台良好集成的自定義類,就不要定義傳統的訪問方法和修改方法。以避免和類中定義的方法名字產生雷同而產生編譯錯誤。
5.控制屬性的get/set語句的可見性級別:
在一些情況下,為獲取和設置邏輯指定唯一的可訪問性級別是有益的。為此,我們可以將可訪問性關鍵字作為合適的get或set關鍵字的前綴:
public string A
{
get{return a;}
protected set{a = value;}
}
6.只讀和只寫屬性:
當封裝數據時,可能希望配置一個只讀屬性,為此,可以忽略set塊。如果想要只寫屬性,則是忽略get塊。這樣,如果一個只讀屬性被試圖賦值的時候,就會產生編譯錯誤。
7.靜態屬性:
C#還支持靜態屬性,靜態屬性必須在靜態數據上操作。
C#喜歡用屬性來封裝數據,屬性的優點是,對象的用戶可以只使用一個命名項就能操作內部數據。
自動屬性
public int A{get;set;}
而自動屬性不允許創建只讀和只寫屬性,如public int A{get;}會產生編譯錯誤。
1.與自動屬性交互:
由於編譯器在編譯時才會定義私有的返回字段,所以定義自動屬性的類通常都需要使用屬性語法來獲取和設置實際的值。屬性語法:a.b="aa";
2.關於自動屬性和默認值:
你可以直接在代碼庫中使用封裝了數字或布爾數據的自動屬性,因為隱藏的返回字段將設置一個可以直接使用的安全默認值。但如果自動屬性包裝了另一個類變量,隱藏的私有引用類型的默認值也將設置為null。
public int A {get; set;}//隱藏的int返回字段設置為0 public Car B{get;set;}//隱藏的Car返回字段設置為null;
由於私有的返回字段是在編譯時創建的,所以你不能使用C#的字段初始化語法用new關鍵字直接分配引用類型,這項工作必須在類構造函數內部執行,以確保對象以安全的方式誕生。
class Car
{
public int A{get;set;}
public Car()
{
A=1;
}
}
對象初始化器語法
為了簡化新建對象的過程,C#提供了對象初始化器語法,語法組成為:大括號內部用逗號分隔的指定值列表:
Car car = new Car(){ petName="abc";}
對象初始化語法只是使用默認構造函數創建類變量並設置各個屬性狀態數據的語法的簡寫形式。
常量數據
const int a=100;
C#通過const關鍵字來定義常量(它在賦初始值後從未變過)。而類的常量字段是隱式靜態的。然而,我們可以在類型成員中定義和訪問局部常量。
注意,常量在定義時必須為其指定初始值。否則將產生編譯錯誤。
1.只讀字段:
public readonly double pi;
public Car()//car的構造函數
{ pi = 3.14}
和常量相似,只讀字段不能在賦初始值之後改變。然而,和常量不同的是,賦給只讀字段的值可以在運行時決定,因此在構造函數作用域中進行賦值是合法的(其他地方不行)。
2.靜態只讀字段:
和常量不同,只讀字段不是隱式靜態的。因此,要從類級別公開pi,就必須顯示使用static關鍵字:在上面添加static關鍵字,構造函數也需要是static的。這樣才能直到運行時才知道靜態只讀字段的值。
分部類型
利用partial關鍵字可以創建分部類。使用分部類將構造函數和字段數據轉移到全新的類文件中,這樣可以避免單個cs文件中代碼太多。
分部類的整個理念都是在設計時實現的,編譯應用程序後,程序集中只存在唯一的類。定義分部類唯一的要求是類型名稱必須相同,並且必須定義在相同的.net命名空間中。如果你用winform或者wpf就會發現許多的代碼隱藏文件就利用了這種分部類的方法。
partial class A
{}
小結
這一小節主要介紹了類類型的作用,以及其構造函數和其中的成員以及成員變量的各種表示和定義。除此之外,還接觸了封裝的細節。封裝這塊,我們學習了C#的訪問修飾符以及類型屬性、對象初始化語法以及分部類的作用。下一小節將學習繼承和多態來構建以一組相關的類。