PL真有意思(七):數據抽象和麪向對象

前言

在以前的名字、做用域那篇提到模塊類型,它使程序員能夠從一個給定抽象出發,經過實例化產生多個實例;再後面是類,它使程序員能夠定義一族相關的抽象。java

在這一篇裏,咱們會來看一下面向對象程序設計及其三個基本概念、動態方法約束、多重繼承等等程序員

面向對象程序設計

隨着軟件變得愈來愈複雜,數據抽象已經變成了軟件工程中最重要的部分。由模塊和模塊類型提供的這種抽象至少帶來了以下三個好處:數組

  • 它能夠減小程序員必須同時考慮的細節量,減小了人的概念負擔
  • 它起到一種故障遏制做用,能夠防止程序員以不適當的方式使用程序的各類部件。也限制了查找程序錯誤必須考慮的程序的部分
  • 它爲程序部件之間的獨立性提供了一個重要的層次,使得將程序的構造分配到各個單獨的部分更加容易,在修改部件的內部實現就能夠避免改動使用它們的外部代碼

可是很顯然,第三點在實踐中卻很難作到。由於或許咱們有一個原先作好的模塊幾乎具備當前某個新應用所需的所有性質,可是並不徹底適用。假如咱們有一個隊列抽象,可是須要的倒是可以在兩端插入刪除,那麼就不徹底符合了安全

面向對象的程序設計能夠看做是這個方向上的一種努力,它使咱們可以更容易的擴展或精化現有抽象的方式定義新抽象,也就是繼承。函數

封裝和繼承

封裝機使程序員能夠將數據和操做它們的子程序組織在一塊兒,對抽象的用戶隱藏起各類可有可無的實現細節。設計

隨着繼承的引入,面嚮對象語言不但要支持基於模塊的語言的做用域規則,還須要處理另一些問題,好比基類中的私有成員對於派生類的方法應該是可見的嗎?基類中的公用成員在派生類中也老是公用的嗎?指針

在C++中可見性規則背後的基本原則能夠總結以下code

  • 任何一個類均可以限制其成員的可見性。只要該類聲明在做用域中,其公用成員就都是可見的。私用成員只在本類的方法中可見。保護成員在本類及其派生類中可見對象

  • 派生類能夠顯式其基類成員的可見性,可是不能提高它們的可見性。基類私用成員在派生類中根本不可見。公用基類的保護成員和公用成員,在派生類中仍然分別是保護的和公用的成員。繼承

  • 若是一個派生類經過將基類聲明的protected或private而限制了基類成員的可見性,那麼在這個派生類中還能夠一個一個恢復基類成員的可見性。

嵌套類

許多語言都容許類聲明的嵌套。這帶來了一個直接的問題:若是Inner是Outer的成員,那麼Inner的方法可以看到Outer的成員嗎?

在C++和C#中,只容許訪問外層類的靜態成員。而Java中則更復雜,它容許嵌套類訪問外層類的任意成員。所以內層類的每一個實例都必須屬於外層類的一個實例。

初始化和終結處理

咱們將對象的生存期定義爲它佔據空間並所以能保存數據的那段時間。大多數面向對象的語言都提供了某種特殊機制,用以在對象的生存期開始時自動作初始化。也有幾種語言提供了相似的析構函數機制,用於在對象生存期結束時自動來終結它。

構造函數的選擇

C++、Java和C#都容許程序爲一個類指定多個構造函數。多個構造函數的行爲方式就像是重載的子程序,必須能根據其參數的個數和類型區分它們。

執行順序

C++強調每一個對象都要在使用前初始化,進一步說,若是該對象的類派生自另外一個類,C++強調必須在調用派生類的構造函數以前調用基類的構造函數。以保證派生類不會看到它所繼承的域處於不一致的狀態。

在Java中

super(args);

super關鍵字用於引用當前類的基類。若是沒有這種對super的調用,Java編譯器就會自動插入一個對基類的無參構造函數的調用。

廢料收集

當一個C++對象被銷燬時,首先會調用它所在派生類的析構函數,而後按照與派生類相反的順序調用各基類的析構函數。在C++中,析構函數最多見的用途就是手工釋放存儲空間。

可是如今的許多語言都提供了自動廢料收集

動態方法約束

假設咱們如今有三個類:

class Persopn {}
class Student : public Persion {}
class Professor : public Persion {}

Student s;
Professor p;

Persopn *x = &s;
Persopn *y = &p

如今假設三個類都有一個print_mailing_label方法,那麼對x,y調用這個方法將會調用的是基類的Persion的方法,仍是根據如今變量引用的s、p的類型來作選擇呢?

第一種選擇是靜態方法約束,而第二種方法是動態方法約束。動態方法約束是面向對象程序設計的核心概念

虛方法和非虛方法

C++和C#默認使用靜態方法約束,可是程序員能夠將特定的方法標記爲virtual,要求對它使用動態約束。對虛方法的調用將在運行時根據對象的類而不是引用的類型指派適當的方法實現

抽象類

在大多數面嚮對象語言中,基類中均可以不給出virtual方法的體。在Java和C#中,作這件事的方法是將類或沒有體的方法都標記爲abstract

不管用什麼語法形式,若是一個類中包含了至少一個抽象方法,這個類就稱爲是抽象的。咱們不可以聲明抽象類的對象

成員查找

對於靜態方法約束,編譯器總能夠基於所引用的變量的類型肯定應該調用相應的方法的哪一個版本。然而,對於動態約束,被引用或指針變量所引用的對象中就必須包含足夠的信息,使編譯器生成的代碼可以在運行時找到正確的方法版本。

最多見的實現方式是用記錄的形式表示每一個對象,這種記錄中第一個域是一個指針,指向該對象的類的虛方法表。虛表也就是一個數組,其中的第i個項指明該對象的第i個虛方法的代碼地址。同一個類的全部對象共享同一個虛表。

多態性

動態方法約束將多態性引入到指望某個基類foo的對象引用的全部代碼中。只要派生類的對象支持這個基類的操做,這些代碼對於基類的任何派生類的對象均可以很好的工做。

有人可能會認爲,有了繼承和動態方法約束後就再也不須要泛型了,但實際狀況並不是如此,爲了訪問這些派生類的特殊內容,就必須進行強制轉換,而且獲得的代碼仍然是不安全的,可是泛型可以解決這些問題

多重繼承

有些時候,讓一個派生類繼承多個基類的特徵也是很是有用的。例如咱們須要一個學生類,又但願可以方便進行增長刪除,那麼就可能但願從Person類和鏈表類派生出一個類來。

C++和Python都有多重繼承。Java、C#則只提供了一種受限的多重繼承方式。

總結

在這一篇的一開始咱們指出了面向對象程序設計的三大基本概念:封裝、繼承和多態。在以後咱們討論了對象的初始化和終結操做、動態方法約束和多重繼承。

相關文章
相關標籤/搜索