1. 程式人生 > >工作中能用到的基礎知識總結(二)

工作中能用到的基礎知識總結(二)

protected 構造函數 blog 繼承鏈 附加 調用 初始化 傳統 -s

簡介

繼承、封裝和多態是面向對象編程的重要特性。要想運用好,就必須熟悉這三種特性,本篇說說我對封裝、繼承和多態相關的知識總結。

知識點

一、訪問修飾符

C#中類及類型成員修飾符有五類:public,private,protected,internal,protected internal。

  1. public:類及類型成員的修飾符(任何地方該類都可以被訪問到);
  2. private:類型成員的修飾符(只能在同一個類或方法中使用,即使是類的實例也不能訪問它的私有成員);
  3. protected:類型成員的修飾符(可以被子類繼承,並可被訪問,類的實例不能訪問它的私有成員);
  4. internal:類及類型成員的修飾符(只有在同一程序集內,才可以被訪問到);
  5. protected internal:同一程序集或繼承的子類可訪問(protected or internal)。

可以放在class前面修飾類的修飾符關鍵字只有public和internal,如果沒寫任何關鍵字,默認是internal。類型成員前如果沒寫任何關鍵字,默認為private。

二、封裝詳解

面向對象編程,最主要的就是對象。每個對象就是一個封裝,封裝就是對象的可變部分信息進行隱藏,只是提供穩定的部分的訪問接口,使它的使用者無法看到對象的具體信息,

而對象本身包含能進行操作所需的所有信息,不必依賴其他對象來完成自己的操作。eg:在用類實現某個邏輯的時候,類就是一個單獨功能塊,實現功能的具體代碼就是可變的部分,

而public的方法或者屬性則是穩定的部分。

封裝好處

  1. 使用者只需要了解如何通過類的接口使用類,而不用關心類的內部數據結構和數據組織方法。
  2. 良好的封裝能夠減少耦合。
  3. 類內部的實現可以自由的修改,而對外接口不改變情況下不影響其他類。
  4. 類具有了簡潔清晰的對外接口,降低了使用者的學習過程。

封裝實現

通過對屬性的讀和寫來保護類中的域。屬性具有兩種操作get和set。get用來返回屬性域的值。set通過value這個變量來給屬性域賦值。屬性可以設為只讀的(read-only),

只需屬性只具有一個set操作;屬性也可以是只寫的(write-only),只需屬性只具有一個get操作。使用屬性的好處在於對象的使用者可以用一條語句來操作內部的數據。

三、繼承相關的關鍵字

與繼承相關的關鍵字有:base,virtual,override,abstract,new,sealed。

  • base 關鍵字

可從子類中訪問基類中非private關鍵字修飾的非抽象成員

指定創建子類實例時調用帶有相應參數的基類的構造函數(將先調用基類的相同參數的構造函數);

不能在靜態方法中使用base關鍵字。

  • virtual關鍵字

在基類中表示指定一個虛方法(有方法體)或屬性,該方法或屬性可以在子類中被override重寫,new棄用,sealed隱藏或者直接繼承。

  • abstract關鍵字

在基類中表示指定抽象類或抽象方法(沒有實現體的虛方法)。

如果是抽象方法方法,必須被子類用override關鍵字來覆寫,抽象類不能夠實例化;如果類中包含抽象方法,那麽類就必須定義為抽象類,不論是否還包含一般方法。

  • override關鍵字

在子類中重寫父類的虛方法( 被virtual或 override 修飾)或抽象方法(被abstract或override修飾)。

  • new關鍵字

顯式隱藏從基類繼承的成員,在子類中修飾基類中有或無virtual聲明的方法

如果在基類中有與子類同名同參的方法時,會隱式在子類前添加一個new關鍵字;當當前子類再派生孫子類的時候,派生的孫子類繼承子類new關鍵字修飾的方法(非孫子類實例化賦值給爺爺類情況);new還可以與virtual一起使用。

使用 new 關鍵字時,調用的是新的類成員而不是已被替換的基類成員。如果需要調用隱藏類成員,可以將派生類的實例強制轉換為基類的實例。

  • sealed關鍵字

密封類/方法/屬性。

修飾方法的時候,子類不能重寫父類該方法,且只能夠密封需重寫的方法;修飾類的時候,其他類無法再繼承該類;修飾屬性的時候,類的密封屬性不允許在派生類中被繼承,密封屬性的訪問器同樣也是密封的。

四、繼承詳解

對象初始的化順序

方便後續理解,在談繼承之前,先說說對象初始的化順序。在一個繼承鏈中,對象初始化順序為:先靜態化後實例化,先變量後構造函數,先子類後父類。eg:比如C類派生自B類,B類派生自A類。

那麽當實例化對象C的時候,實例化順序為C的靜態變量-》C的靜態構造函數-》C的變量-》B的靜態變量-》B的靜態構造函數-》B的變量-》A的靜態變量-》A的靜態構造函數-》A的變量-》

A的非靜態構造函數-》B的非靜態構造函數-》C的非靜態構造函數。如下圖:

技術分享

繼承就是在類之間建立一種相交關系,使得新定義的派生類的實例可以繼承已有的基類的成員:方法、域、屬性、事件、索引指示器。除了構造函數和析構函數,派生類隱式地繼承了直接基類的所有成員,

還可以加入新的特性或修改已有的特性建立起類的新層次。繼承是面向對象程序設計的主要特性之一,它使得所有子類的公共部分都在放在父類,使得代碼得到共享,避免了重復編碼;使得修改和擴展繼承而來的實現較為方便

但是要註意的是,繼承是強耦合關系的,更改父類,其子類都要隨之更改。在設計程序的時候,父類公共方法一定要設計好,勁量避免後期更改父類。

繼承遵循的規則

  1. 繼承是可傳遞的。如果C從B中派生,B又從A中派生,那麽C不僅繼承了B中聲明的成員,同樣也繼承了A中的成員。Object 類作為所有類的基類。
  2. 派生類應當是對基類的擴展。派生類可以添加新的成員,但不能除去已經繼承的成員的定義。
  3. 構造函數和析構函數不能被繼承。除此以外的其它成員,不論對它們定義了怎樣的訪問方式,都能被繼承。基類中成員的訪問方式只能決定派生類能否訪問它們。
  4. 派生類如果定義了與繼承而來的成員同名的新成員,就可以覆蓋已繼承的成員。但這並不因為這派生類刪除了這些成員,只是不能再訪問這些成員。
  5. 類可以定義虛方法、虛屬性以及虛索引指示器,它的派生類能夠重載這些成員,從而實現類可以展示出多態性。
  6. 派生類只能從一個類中繼承,可以通過接口實現多重繼承。

繼承實現方式

繼承實現方式分為三種:實現繼承、接口繼承和可視繼承。

  • 實現繼承:指使用基類的屬性和方法而無需額外編碼的能力。
  • 可視繼承:指子窗體(類)使用基窗體(類)的外觀和實現代碼的能力。
  • 接口繼承:指僅使用屬性和方法的名稱、但是子類必須提供實現的能力。C#不支持多重繼承,但是客觀世界出現多重繼承的情況又比較多。為了避免傳統的多重繼承給程序帶來的復雜性等問題,

    C# 提出了接口的概念。通過接口可以實現多重繼承的功能。

五、多態詳解

多態:同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向基類的指針,來調用實現派生類中的方法。通俗點講,就是:子類以父類的身份出現;子類在工作時以自己的方式實現父類功能;

子類以父類的身份出現時,子類特有的屬性和方法不可以使用。 eg:從某個基類繼承出多個對象,其基類有一個虛方法virtualFunc,然後其子類也有這個方法,但行為不同,然後這些子對象中的任何一個可以賦給其基類對象的引用,

這樣其基類的對象就可以執行不同的操作了。實際上是在通過其基類來訪問其子對象的,要做的就是一個賦值操作。

註:當方法被調用時,無論對象是否被轉換成其父類,都只有位於對象繼承鏈最末端的方法實現被調用。也就是說,虛方法是按照其運行時類型而非編譯時類型進行動態綁定調用的。

支持的類型

多態是基於繼承的,繼承的多層次變化就是多態的一種表現形式。C#中支持兩種不同類型的多態:編譯時的多態性和運行時的多態性。

編譯時的多態性:通過重載(方法名必須相同;參數列表必須不相同;返回值類型可以不相同)來實現的。對於非虛的成員來說,系統在編譯時,根據傳遞的參數、返回的類型等信息決定實現何種操作。

運行時的多態性:直到系統運行時,才根據實際情況決定實現何種操作。C#中,運行時的多態性通過虛成員實現,即使用新的派生成員替換基成員或者可以重寫虛擬的基成員

使用新的派生成員替換基類的成員需要使用 new 關鍵字。如果基類定義了一個方法、字段或屬性,則 new 關鍵字用於在派生類中創建該方法、字段或屬性的新定義。重寫虛擬基類成員是通過使用override關鍵字來實現覆寫。

編譯時的多態性為我們提供了運行速度快的特點,而運行時的多態性則帶來了高度靈活和抽象的特點。

實現方式(摘計算機世界網)

接口多態性: 多個類可實現相同的“接口”,而單個類可以實現一個或多個接口。接口本質上是類需要如何響應的定義。接口描述類需要實現的方法、屬性和事件,以及每個成員需要接收和返回的參數類型,

但將這些成員的特定實現留給實現類去完成。 補充了C#不能多重繼承缺點。

繼承實現的多態性 :多個類可以從單個基類“繼承”。通過繼承,類在基類所在的同一實現中接收基類的所有方法、屬性和事件。這樣,便可根據需要來實現附加成員,而且可以重寫基成員以提供不同的實現。C# 通過繼承提供多態性。

對於小規模開發任務而言,這是一個功能強大的機制,但對於大規模系統,通常證明會存在問題。過分強調繼承驅動的多態性一般會導致資源大規模地從編碼轉移到設計,這對於縮短總的開發時間沒有任何幫助。 何時使用繼承驅動的多態性呢?

使用繼承首先是為了向現有基類添加功能。若從經過完全調試的基類框架開始,則程序員的工作效率將大大提高,方法可以增量地添加到基類而不中斷版本。當應用程序設計包含多個相關類,而對於某些通用函數,

這些相關類必須共享同樣的實現時,您也可能希望使用繼承。重疊功能可以在基類中實現,應用程序中使用的類可以從該基類中派生。抽象類合並繼承和實現的功能,這在需要二者之一的元素時可能很有用。

抽象類實現的多態性 :抽象類同時提供繼承和接口的元素。抽象類本身不能實例化,它必須被繼承。該類的部分或全部成員可能未實現,該實現由繼承類提供。已實現的成員仍可被重寫,並且繼承類仍可以實現附加接口或其他功能。

抽象類提供繼承和接口實現的功能。抽象類不能示例化,必須在繼承類中實現。它可以包含已實現的方法和屬性,但也可以包含未實現的過程,這些未實現過程必須在繼承類中實現。這使您得以在類的某些方法中提供不變級功能,

同時為其他過程保持靈活性選項打開。抽象類的另一個好處是:當要求組件的新版本時,可根據需要將附加方法添加到基類,但接口必須保持不變。 何時使用抽象類呢?當需要一組相關組件來包含一組具有相同功能的方法,

但同時要求在其他方法實現中具有靈活性時,可以使用抽象類。當預料可能出現版本問題時,抽象類也具有價值,因為基類比較靈活並易於被修改。

工作中能用到的基礎知識總結(二)