複製鏈接
請複製以下鏈接發送給好友

(編程術語)

鎖定
類(Class)是面向對象程序設計(OOP,Object-Oriented Programming)實現信息封裝的基礎。類是一種用户定義的引用數據類型,也稱類類型。每個類包含數據説明和一組操作數據或傳遞消息的函數。類的實例稱為對象。
中文名
外文名
Class
類    別
一種引用數據類型
本    質
數據類型
拼    音
lèi
五    筆
odu [1] 
實    例
對象

基本信息

一個簡單的類圖 一個簡單的類圖
類是面嚮對象語言的程序設計中的概念,是面向對象編程的基礎。
類的實質是一種引用數據類型,類似於 byte、short、intchar)、longfloat、double 等基本數據類型,不同的是它是一種複雜的數據類型。因為它的本質是數據類型,而不是數據,所以不存在於內存中,不能被直接操作,只有被實例化為對象時,才會變得可操作。
類是對現實生活中一類具有共同特徵的事物的抽象。如果一個程序裏提供的數據類型與應用中的概念有直接的對應,這個程序就會更容易理解,也更容易修改。一組經過很好選擇的用户定義的類會使程序更簡潔。此外,它還能使各種形式的代碼分析更容易進行。特別地,它還會使編譯器有可能檢查對象的非法使用。 [2] 
類的內部封裝了屬性和方法,用於操作自身的成員。類是對某種對象的定義,具有行為(behavior),它描述一個對象能夠做什麼以及做的方法(method),它們是可以對這個對象進行操作的程序和過程。它包含有關對象行為方式的信息,包括它的名稱、屬性、方法和事件。
類的構成包括成員屬性和成員方法(數據成員和成員函數)。數據成員對應類的屬性,類的數據成員也是一種數據類型,並不需要分配內存。成員函數則用於操作類的各項屬性,是一個類具有的特有的操作,比如“學生”可以“上課”,而“水果”則不能。類和外界發生交互的操作稱為接口。

特性

類的三大特性

  1. 封裝性將數據和操作封裝為一個有機的整體,由於類中私有成員都是隱藏的,只向外部提供有限的接口,所以能夠保證內部的高內聚性和與外部的低耦合性。用者不必瞭解具體的實現細節,而只是要通過外部接口,以特定的訪問權限來使用類的成員,能夠增強安全性和簡化編程。
  2. 繼承性更符合認知規律,使程序更易於理解,同時節省不必要的重複代碼。
  3. 多態性是指同一操作作用於不同對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向父類(基類)的指針,來調用實現子類(派生類)中的方法。

類與結構體的區別

在 C++、C# 語言中,classstruct 都可以定義一個類,它們的區別如下:
  1. 從職能觀點來看,class 表現為行為;而 struct 常用於存儲數據。
  2. C# 中,class 是引用類型,繼承自 System.Object 類;struct 是值類型,繼承自 System.ValueType 類,不具多態性。但是注意,System.ValueType 是個引用類型。
  3. class 支持繼承,可以繼承自類和接口;而 struct 沒有繼承性,struct 不能從 class 繼承,也不能作為 class 的基類,但 struct 支持接口繼承
  4. class 可以聲明無參構造函數,可以聲明有參構造函數;而 struct 只能聲明帶參數構造函數,且不能聲明無參構造函數。因此,struct 沒有自定義的默認無參構造函數,默認無參構造函數只是簡單地把所有值初始化為它們的 0 等價值。
  5. Java / C# 中,實例化時,class 要使用 new 關鍵字;而 struct 可以不使用 new 關鍵字,如果不以 new 來實例化 struct,則其所有的字段將處於未分配狀態,直到所有字段完成初始化,否則引用未賦值的字段會導致編譯錯誤。
  6. class 可實現抽象類abstract),可以聲明抽象方法(抽象函數);而 struct 為抽象,也不能聲明抽象函數
  7. class 可以聲明 protected 成員、virtual 成員、sealed 成員和 override 成員;而 struct 不可以,但是值得注意的是,struct 可以重載 System.Object 的 3 個虛方法,equals( )、toString( ) 和 getHashTable( )。
  8. class 的對象複製分為淺拷貝深拷貝,必須經過特別的方法來完成複製;而 struct 創建的對象複製簡單,可以直接以等號連接即可。
  9. class 實例由垃圾回收機制來保證內存的回收處理;而 struct 變量使用完後立即自動解除內存分配
  10. 作為參數傳遞時,class 變量是以按址方式傳遞;而 struct 變量是以按值方式傳遞的。
我們可以簡單的理解,class 是一個可以動的機器,有行為,有多態,有繼承;而 struct 就是個零件箱,組合了不同結構的零件。其實,class 和 struct 最本質的區別就在於 class 是引用類型內存分配於託管堆;而 struct 是值類型,內存分配於線程的堆棧上。由此差異,導致了上述所有的不同點。所以只有深刻的理解內存分配的相關內容,才能更好的駕馭。
當然,使用 class 基本可以替代 struct 的任何場合,class 後來居上。雖然在某些方面 struct 有性能方面的優勢,但是在面向對象編程裏,基本是 class 橫行的天下。
那麼,有人不免會提出,既然 class 幾乎可以完全替代 struct 來實現所有的功能,那麼 struct 還有存在的必要嗎……答案是,至少在以下情況下,鑑於性能上的考慮,我們應該考慮使用 struct 來代替 class:
  1. 實現一個主要用於存儲數據的結構時,可以考慮 struct。
  2. struct 變量佔有堆棧的空間,因此只適用於數據量相對小的場合。
  3. struct 數組具有更高的效率。

用法

定義一個類
class 類名 {
	public: 公有成員
	private: 私有成員
	protected: 保護成員
};
  1. 公有成員、私有成員、保護成員均包含數據成員成員函數兩部分,彼此沒有順序之分。一個 public / private / protected 關鍵字下可以跟多個成員,直到下一個 public / private / protected 關鍵字。如果成員前面沒有 public / private / protected 關鍵字,默認為私有成員。
  2. 結尾部分的分號必不可少,否則會發生語法錯誤。
  3. 無論公有成員、私有成員還是保護成員,彼此之間都可以訪問。比如公有的成員函數可以操作保護的數據成員,也可以調用私有的成員函數。
  4. 類的數據成員是類型,所以不能被賦值,聲明數據成員和聲明普通變量的格式相同,比如 “ int n; ”。
成員函數的實現
成員函數可以在類內實現,也可以在類外實現。內部實現的成員函數被默認為加上了 inline;外部實現的成員函數必須加上域操作符,即 “ 類名 :: 成員函數 ”。

構造函數與析構函數

構造函數析構函數是特殊的成員函數,和普通成員函數不同的地方在於:
  1. 函數名固定構造函數和析構函數的函數名必須是類名。
  2. 聲明格式不同構造函數和析構函數沒有返回值,連空返回值—— void 也沒有。構造函數的聲明形式:類名(參數列表);析構函數的聲明形式:~ 類名( );
  3. 重載的特殊性構造函數和普通成員函數一樣可以被重載,析構函數不可以重載,只能是空參數。
  4. 調用過程不同構造函數和析構函數不能被顯式地調用,只能由編譯器自動調用。
構造函數用於創建類的對象,任何創建對象的行為,都會導致構造函數被調用。析構函數和構造函數的功能相反,析構函數用於銷燬對象,當類的對象超出作用域被銷燬時,析構函數被調用。
即使顯式地定義構造函數和析構函數,也還是會有默認的構造函數和析構函數,函數內部無任何語句,不執行任何操作。默認構造函數是無參數的。需要注意的是,一旦顯式定義任意形參的構造函數,默認構造函數都不會生成,即只有沒有定義構造函數的類才存在默認構造函數。
一般情況下,默認的構造函數和析構函數可以滿足功能需要,然而當需要重載構造函數,或是需要動態分配資源的時候,就不得不定義自己的構造函數甚至析構函數了。
拷貝構造函數是特殊的構造函數,在複製對象時被調用,定義的格式為 “ 類名(類名 & 參數名)”。拷貝構造函數也存在默認的,但很多情況下都需要重載。

類的實例化

就像聲明某種類型的變量一樣,聲明一個類類型的對象,就是類的實例化,會涉及到必要的內存分配。
不同語言中類的實例化形式是不同的。
  1. C++ 類名 對象名(參數列表);如果沒有參數,括號必須省略,即 “ 類名 對象名;”,自動調用構造函數。特殊地,參數可以是類的對象,此時會自動調用拷貝構造函數
  2. Java / C# 類名 對象名 = new 類名(參數列表);括號不能省略。
對象可以訪問類的成員,但並不是所有成員都可以被訪問,能否訪問取決於聲明該成員時所用的關鍵字(public / protected / private)。具體規則如下:
  1. 類的公有成員可以被該類,其派生類和類實例化的對象訪問。
  2. 類的保護成員可以被該類及其派生類訪問,不可以被該類的對象訪問。
  3. 類的私有成員可以被該類訪問,不可以被派生類及其該類的對象訪問。

派生與繼承

class子類類名:
public父類類名,private父類類名,
protected父類類名
{
public:
公有成員
private:
私有成員
protected:
保護成員
};
  1. 子類即是繼承而來的類,父類即是被繼承的類,或者稱之為基類
  2. public 修飾的為公有繼承,private 修飾的為私有繼承protected 修飾的為保護繼承。
  3. 父類可以只有一個,也可以有多個。只有一個父類稱為單繼承,多個父類稱為多繼承。C++ 支持多繼承的機制,Java 則只具有單繼承功能,並增加了 “ 接口 ” 的概念,一個類可以實現多個接口。
  4. 如果不標明繼承方式,默認為私有繼承
派生和繼承是類的重要特性,繼承是由抽象到具體的體現。通過繼承,子類可以使用父類的成員。
但要注意的是,派生和繼承在帶來方便的同時,也會使類與類之間的關係變得複雜,尤其是涉及到私有繼承和保護繼承時,類中成員的關係可能會變得難以理解。所以在涉及類時,儘量避免過多層次的繼承,私有繼承和保護繼承的使用也要慎重。
繼承來的成員和自身聲明的成員並無本質區別,也有公有成員、私有成員、保護成員之分。繼承時,父類中成員類型(公有成員 / 私有成員 / 保護成員)和繼承方式(公有繼承 / 私有繼承 / 保護繼承)不同,情況不同。可以歸納為:
三種類型的繼承,父類的成員均被子類繼承(之前的百科關於這點的描述是錯誤的),只是由類實例化的對象對其繼承的成員的訪問權限會有所變化。三種不同方式的繼承,描述的是子類實例化對象對其成員的訪問權限,並非是描述子類時,子類對繼承自父類的成員的訪問權限。
  1. 公有繼承繼承自父類的成員保持不變。
  2. 私有繼承繼承自父類的成員全部變為私有成員。
  3. 保護繼承繼承自父類的公有成員變為保護成員,其餘不變。

操作符重載

操作符重載必須在類中進行,重載操作符可以使操作符對在類中的語義發生變化。除了. ,.* ,:: ,? : 、sizeoftypeid這幾個運算符不能重載之外,大部分運算符都能被重載。但要注意,重載操作符並不能改變操作符的優先級結合律,而且從認知規律上講,重載的操作符功能必須與原意相近,否則很難被人理解。
操作符重載是函數,在使用該操作符時被調用。操作符重載函數的聲明形式:返回值 operator操作符(參數列表);

友元

友元可以是函數,被稱為友元函數;也可以是類,被稱為友元類
通常,類中的私有成員只能被自身使用,無法被它的對象訪問。因此,另一個類即便可以使用該類的對象,也無法訪問該類的私有成員,通過定義友元的方法可以做到這一點。
友元就是在一個類中“再次聲明”另一個類的成員函數或是另一個類,被“再次聲明”的成員函數或類可以訪問該類的私有成員。這種“再次聲明”並不是普通的聲明,格式為:friend 函數 / 類名;
顯然,友元會破壞類的封裝性,使本該隱藏的成員暴露出來,因此應當謹慎使用。

組合

繼承可以描述 “ 交通工具 ” 和 “ 公交車 ” 的關係,卻無法描述 “ 公交車 ” 和 “ 車輪 ” 的關係。
大多數 “ 車輪 ” 具有的特性是 “ 公交車 ” 所不具有的。比如説 “ 車輪 ” 具有 “ 重量 ”,而 “ 公交車 ” 的 “ 重量 ” 則是另一個含義。而通過私有成員、保護成員機制控制這些成員的繼承性,會使繼承變得複雜而難以理解。而且繼承來的數據成員只有一個,而一輛 “ 公交車 ” 卻有四個 “ 車輪 ”,四個 “ 車輪 ” 的 “ 重量 ”。
引入組合的概念,“ 公交車 ” 完全可以由 “ 車輪 ”、“ 方向盤 ”、“ 車身 ” 等類組合而來。方法就是將類當成其他的數據類型一樣,在另一個類中定義該類類型的數據成員。

抽象類

並不是所有種類的事物都可以被實例化,換而言之,有的種類只是一種抽象概念,現實中並沒有實際存在的對應物。比如:假設所有的動物都會叫,我們可以定義一個類——“ 動物 ”,定義類中的一個成員函數——“ 叫 ”,我們知道貓的叫聲,也知道狗的叫聲,然而 “ 動物 ” 是如何 “ 叫 ” 的——我們根本無法實現它。所以,我們引入了抽象類的概念,抽象類是無法被實例化的,無法聲明抽象類的對象。
通常,用 abstract 修飾的類是抽象類;C++ 中包含純虛函數的類是抽象類;Java 中含有抽象方法的類是抽象類;繼承了純虛函數而沒有實現它的類也是抽象類。
抽象類只能被用作基類,作用體現於:
  1. 約束派生類必須實現的成員函數或方法。
  2. 不同派生類中同名的成員函數實現不同,體現了多態性

靜態成員

用static修飾的成員是靜態成員,可以是成員函數或數據成員。靜態成員是所有對象共有的,只分配一次內存,產生新的對象時不會產生副本。
靜態數據成員的初始化必須在類外進行,使用靜態成員時必須使用類名和域操作符

示例

類的複用
class Animal
{

    private void beat()
{

System.out.println("");

    }

public void breath()
{
        
beat();

        System.out.println("")
       
}

}

class bird
{

//
privateAnimala;

public Bird(Animala)
{

        this.a=a;
    
}

    //
pubilc void breath()
{

        //
a.breath();
    
}
    
public void fly()
{

        System.out.println("")
     
}

}

class Wolf
{
    
//
private Animala;

}

參考資料
  • 1.      .漢典網[引用日期2020-01-14]
  • 2.    Bjarne Stroustrup.C++程序設計語言(譯名):機械工業出版社,2002:199-200