.NET Core CSharp初級篇 1-3
本節內容為面向對象初級教程
類
簡介
面向對象是整個C#中最核心最有特色的一個模塊了,它很好的詮釋了程序與現實世界的聯系。
面向對象的三大特征:繼承、多態、封裝;繼承的含義可以理解為集合中的包含關系,例如人類屬於動物類的一個分支,這就是一種繼承。多態的理解就可以是人的呼吸用肺,鯉魚使用鰓,這就是一種同種操作對應不同的實現。封裝可以理解為一堆零件可以組成一個手機,這個過程就叫做封裝。而將電腦顯卡等拆下來組裝成另一台電腦,則屬於類的拆箱裝箱。
封裝一個類的好處在哪里呢?我舉一個例子:
首先,我們考察一個常見的生活實例來進行說明,例如每當發工資的日子小王都來到 ATM 機
前,用工資卡取走一筆錢為女朋友買禮物,從這個很帥的動作,可以得出以下的結論:
- 小王和 ATM 機之間,以銀行卡進行交互。要取錢,請交卡。
- 小王並不知道 ATM 機將錢放在什么地方,取款機如何計算錢款,又如何通過銀行卡返回小王
所要數目的錢。對小王來說,ATM 就是一個黑匣子,只能等着取錢;而對銀行來說,ATM 機就
像銀行自己的一份子,是安全、可靠、健壯的員工。 - 小王要想取到自己的錢,必須遵守 ATM 機的對外約定。他的任何違反約定的行為都被視為不
軌,例如欲以磚頭砸開取錢,用公交卡冒名取錢,盜卡取錢都將面臨法律風險,所以小王只能
安分守己地過着月光族的日子。
那么小王和 ATM 機的故事,能給我們什么樣的啟示?對應上面的 3 條結論,我們的分析如下: - 小王以工資卡和 ATM 機交互信息,ATM 機的入卡口就是 ATM 機提供的對外接口,磚頭是塞不
進去的,公交卡放進去也沒有用。 - ATM 機在內部完成身份驗證、余額查詢、計算取款等各項服務,具體的操作對用戶小王是不
可見的,對銀行來說這種封閉的操作帶來了安全性和可靠性保障。 - 小王和 ATM 機之間遵守了銀行規定、國家法律這樣的協約。這些協約和法律,就掛在 ATM 機旁邊的牆上。
具體來說,封裝隱藏了類內部的具體實現細節,對外則
提供統一訪問接口,來操作內部數據成員。這樣實現的好處是實現了 UI 分離,程序員不需要知道
類內部的具體實現,只需按照接口協議進行控制即可。同時對類內部來說,封裝保證了類內部成
員的安全性和可靠性。在上例中,ATM 機可以看做封裝了各種取款操作的類,取款、驗證的操作
對類 ATM 來說,都在內部完成。而 ATM 類還提供了與小王交互的統一接口,並以文檔形式——
法律法規,規定了接口的規范與協定來保證服務的正常運行
類屬於在堆分布的變量,意味着它的大小是不固定的。可以動態的進行調節。
創建與實例化類
類的創建非常的簡單,實例化也非常的簡單,創建類就是把一個具體的事物抽象化,實例化就是將抽象化的類給轉換成具象化的對象。例如我們定義一個People類,內含若干個變量;
//定義類使用class關鍵字
class People
{
public string Name;
public int Age;
}
//實例化類
People p = new People();
或許你還看不太懂這些,別急,請繼續往下看。
修飾符
訪問控制修飾符
- public:對訪問沒有任何限制,屬於最高級別的訪問權限
- private:私有權限,最低級別的訪問,只能在聲明的代碼段(類)中使用。
- protected:保護權限,只有繼承了該類才可以使用。
- internal:僅包含當前程序集使用
- protect internal:同一個程序集的類和其派生的類可以使用
這樣說或許過於抽象,我們這樣來解釋吧,一個程序就類似一個公司,public就好比是董事長、CEO一類的權限,擁有着最高級別的訪問;protected你可以理解為部門經理,它的下屬就是繼承該部門,下屬可以訪問父類(部門)的資源,但不可以訪問其他部門的protected資源,體現為一種縱向的權限控制。Internal類似與考勤部門,無論該部門是否屬於考勤部門領導,考勤部門都可以管轄其他部門,體現為一種橫向的權限控制。Protected internal則是具有兩種屬性。
可選修飾符
- static(可用於類內成員):靜態的,表示只被創建一次,屬於所有對象公用的變量
- sealed:密封類,禁止類被繼承
- abstract:抽象類,要求類被繼承,並且不能實例化
- virtual(不能用於類):表示可以被重寫
- readonly(用於字段):表示該字段只讀
- const(用於字段):表示常量
- extern(用於函數):表示該函數由外部實現
- async(用於函數):表示該函數為異步函數
函數
函數也被稱為方法,是包含一系列語句的代碼塊。封裝了類的行為,提供了類的對外表現。用於將封裝的內部細節以公有方法提供對外接口,從而實現與外部的交互與響應。例如,從上面屬性的分析我們可知,實際上對屬
性的讀寫就是通過方法來實現的。因此,對外交互的方法,通常實現為 public。程序通過調用該方法並指定任何所需的方法參數使語句得以執行。 在 C# 中,每個執行的指令均在方法的上下文中執行。
函數的構成由訪問控制關鍵字+修飾符+返回值+函數名稱+函數參數+函數體,如果一個類內函數或者其他成員使用了static關鍵字,則可以不實例化類對其進行調用,因為使用了static標明的成員,屬於全體該類對象共有。例如下面這個例子:
class Man
{
static void GettingOld()
{
//life - 1s
}
public void Eat()
{
}
}
//靜態方法可以直接調用
Man.GettingOld();
Man man = new Man();
man.Eat();
函數的使用如下,其中x,y稱為形參,x1,y1稱為實參,通常對形參的操作並不會影響到實參
public static int Add(int x,int y)
{
x++;y++;
return x+y;
}
int x1 =5;
int y1 =6;
Add(x1,y1);
//帶有默認參數
public static int Add(int x,int y=4)
{
}
//不定參數
public void Add(params object[] a)
{
}
當然不是所有的方法都被實現為 public,否則類內部的實現豈不是全部暴露在外。必須對對外
的行為與內部操作行為加以區分。因此,通常將在內部的操作全部以 private 方式來實現,而將需要與外部交互的方法實現為 public,這樣既保證了對內部數據的隱藏與保護,又實現了類的對外交互。例如在 ATM 類中,對錢的計算、用戶驗證這些方法涉及銀行的關鍵數據與安全數據的保護問題,必須以 private 方法來實現,以隱藏對用戶不透明的操作,而只提供返回錢款這一 public 方法接口即可。在封裝原則中,有效地保護內部數據和有效地暴露外部行為一樣關鍵。
函數的重載與重寫
重載函數表示使用同一個函數名,通過參數的不同,從而實現使用同一個名稱可以選擇調用多種函數。例如實現兩個數字的相加,傳入的有可能是整型參數也有可能是浮點型參數,因此,我們選擇使用重載函數去實現。以下是一個重載的例子;
public static int Add(int x,int y)
{
return x+y;
}
public static double Add(double x,double y)
{
return x+y;
}
Add(1,2);//調用第一個Add
Add(1.1,2.2);//調用第二個函數
通過調用函數時傳入的參數不同,就可以很簡單的用不同方法實現。
函數重寫則多出現在面向對象的多態性中,這里我不會很詳細的講解,在后面會有一個詳細的講解。重寫就可以理解為,人呼吸用肺,大部分魚類呼吸用鰓,呼吸這個函數就是在兩個類中被重寫過(即實現方法在不同的類中)。具體的實現我會在后一步進行講解
需要注意的是,重載需要在參數上有本質的區別,例如個數、類型不同,重寫則需要方法可以被重寫,使用override關鍵字表明重寫的函數
類中重要的兩個函數
構造函數:構造函數是一種特殊的函數,它的簽名和類名一致,並且沒有返回值。它可以接受任意個參數。當類被實例化的時候,對應的構造函數會被調用。可以說,對象是通過調用類的構造函數進行創建。如果不指定構造函數,C#會自動調用默認的無參構造函數。如果重載了構造函數,並且傳入了指定參數,則會調用對應的構造函數。
析構函數:類似與構造函數,但是調用是在GC(垃圾回收器)回收類對象的時候自動調用,通常無需去重寫。例如:
class A
{
//默認無參構造函數
public A()
{
}
public A(int a)
{
}
// 析構函數
~A()
{
}
}
A a = new A(1);//調用第二個構造函數
字段和屬性
(此處補充IL代碼)
字段:類中具體實現存儲數據的變量,你可以理解為各個零件。通常而言,字段不會對外進行開發訪問權限。例如:幼兒園讀書的小朋友類,里面有一個Age(年齡)字段。假設一個人實例化,我們給年齡賦值上1000。這符合常理嗎?顯然是不符合的。那么我們就要使用屬性進行控制輸入的變量。
屬性:屬性不存儲數據,通常定義為 public,表示類的對外成員。屬性具有可讀、可寫屬性,通過 get 和 set 訪問器來實現其讀寫控制。但是如果你使用默認的屬性實現方法,例如public string a {get;set;},C#會自動的為你隱式生成一個私有的字段a。屬性本質上是作為外部訪問字段的一個媒介、橋梁,也稱之為接口。通常來說,我們會將字段定義為私有的,將屬性定義為公有的,通過屬性去返回和設置其中的值。在這里,我們涉及到了兩個從未見過的關鍵字——get,set。
get訪問器:get訪問器本質上是一個返回值為屬性類型的函數,你可以使用dnSpy進行反編譯查看。你一般需要在get中返回你需要訪問的變量。
set訪問器:使用value關鍵字接受外界傳來的參數並且賦值給你的字段,本質上也只是一個函數,當你對屬性賦值的時候,就會調用他的set控制塊內的代碼
class A
{
private int a;
public int A
{
get{return a;}
set
{
if(value>5)
{
a=value;
}
}
}
//自動生成一個b字段
public int B{get;set;}
}
選看 函數參數修飾符
對C#了解的人都知道,實參到形參的傳遞時存在一個數據備份的過程,因此我們對形參的操作本質上是對備份的變量進行操作,並不會影響到實參的值。但是在C#中,可以對形參進行修飾,使傳入的變量按內存地址進行傳入,這樣就可以實現改變實參的值了。
ref 關鍵字
ref關鍵字的要求有以下幾點:
- 被調用函數的參數和調用時都必須使用ref標記參數
- 傳遞到 ref 參數的參數必須初始化,否則程序會報錯
例如:
static void Main(string[] args)
{
int a = 10;
int b = 20;
Test(ref a,ref b);
Console.WriteLine("a:{0},b:{1}", a, b);
}
static void Test(ref int a, ref int b)
{
a = a+b;
b = 6;
}
out 關鍵字
out關鍵字的要求有以下幾點:
-
方法定義和調用方法都必須顯式使用 out關鍵字
-
out關鍵字無法將參數值傳遞到out參數所在的方法中,只能傳遞參數的引用,所以out參數的參數值初始化必須在其方法內進行,否則程序會報錯
static void Main(string[] args)
{
int a=100;
int b;
Test(out a, out b);
Console.WriteLine("a:{0},b:{1}", a, b);
}
static void Test(out int a, out int b)
{
a = 1+2;
b = 1;
}
in關鍵字
in關鍵字的要求有:
- 它讓形參成為實參的別名,這必須是變量。 換而言之,對形參執行的任何操作都是對實參執行的。
- in 參數無法通過調用的方法進行修改。 out 參數必須由調用的方法進行修改,這些修改在調用上下文中是可觀察的,而 ref 參數是可以修改的
int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument); // value is still 44
void InArgExample(in int number)
{
// Uncomment the following line to see error CS8331
//number = 19;
}
練習題
-
請試着創建一個圓類(Circular),要求封裝圓周率和半徑(或直徑),並且定義一個含有一個參數的構造函數,傳入的是半徑(直徑)。並寫入計算周長和面積的函數。
-
定義一個用戶類,要求用戶名和密碼不可以被訪問,只允許設置,並且密碼小於6位需要輸出相應提示並且不進行賦值要求重新賦值。
前往Github獲取更多本節資料(PPT,實例代碼)
如果我的教程幫到了您,希望您動動小手,在GitHub給我一個star
Reference
《你必須知到的.NET》