“類”[編程術語]

類(Class)是面向對象程式設計(OOP,Object-Oriented Programming)實現信息封裝的基礎。類是一種用戶定義類型,也稱類類型。每個類包含數據說明和一組運算元據或傳遞訊息的函式。類的實例稱為對象。

介紹

一個簡單的類圖 一個簡單的類圖

類是面向對象程式設計中的概念,是面向對象編程的基礎。

類的實質是 一種數據類型,類似於int、char等基本類型,不同的是它是一種複雜的數據類型。因為它的本質是類型,而不是數據,所以不存在於記憶體中,不能被直接操作,只有被實例化為對象時,才會變得可操作。

類是 對現實生活中一類具有共同特徵的事物的抽象。如果一個程式里提供的類型與套用中的概念有直接的對應,這個程式就會更容易理解,也更容易修改。一組經過很好選擇的用戶定義的類會使程式更簡潔。此外,它還能使各種形式的代碼分析更容易進行。特別地,它還會使編譯器有可能檢查對象的非法使用。

類的內部封裝了方法,用於操作自身的成員。類是對某種對象的定義,具有行為(be-havior),它描述一個對象能夠做什麼以及做的方法(method),它們是可以對這個對象進行操作的程式和過程。它包含有關對象行為方式的信息,包括它的名稱、方法、屬性和事件。

類的構成包括數據成員和成員函式。數據成員對應類的屬性,類的數據成員也是一種數據類型,並不需要分配記憶體。成員函式則用於操作類的各項屬性,是一個類具有的特有的操作,比如“學生”可以“上課”,而“水果”則不能。類和外界發生互動的操作稱為接口。

用法

定義一個類

公有成員、私有成員、保護成員均包含數據成員和成員函式兩部分,彼此沒有順序之分。一個public/private/protected關鍵字下可以跟多個成員,直到下一個public/private/protected關鍵字。如果成員前面沒有public/private/protected關鍵字,默認為私有成員。

結尾部分的分號必不可少,否則會發生語法錯誤。

無論公有成員、私有成員還是保護成員,彼此之間都可以訪問。比如公有的成員函式可以操作保護的數據成員,也可以調用私有的成員函式。

類的數據成員是類型,所以不能被賦值,聲明數據成員和聲明普通變數的格式相同,比如“int n;”。

1.

公有成員、私有成員、保護成員均包含數據成員和成員函式兩部分,彼此沒有順序之分。一個public/private/protected關鍵字下可以跟多個成員,直到下一個public/private/protected關鍵字。如果成員前面沒有public/private/protected關鍵字,默認為私有成員。

2.

結尾部分的分號必不可少,否則會發生語法錯誤。

3.

無論公有成員、私有成員還是保護成員,彼此之間都可以訪問。比如公有的成員函式可以操作保護的數據成員,也可以調用私有的成員函式。

4.

類的數據成員是類型,所以不能被賦值,聲明數據成員和聲明普通變數的格式相同,比如“int n;”。

成員函式的實現

成員函式可以在類內實現,也可以在類外實現。內部實現的成員函式被默認為加上了inline;外部實現的成員函式必須加上域操作符,即“類名::成員函式”。

構造函式與析構函式

構造函式和析構函式是特殊的成員函式,和普通成員函式不同的地方在於:

函式名固定

構造函式和析構函式的函式名必須是類名。

聲明格式不同

構造函式和析構函式沒有返回值,連空返回值——void也沒有。

構造函式的聲明形式:類名(參數列表);

析構函式的聲明形式:~類名();

重載的特殊性

構造函式和普通成員函式一樣可以被重載,析構函式不可以重載,只能是空參數。

調用過程不同

構造函式和析構函式不能被顯式地調用,只能由編譯器自動調用。

1.

函式名固定

構造函式和析構函式的函式名必須是類名。

2.

聲明格式不同

構造函式和析構函式沒有返回值,連空返回值——void也沒有。

構造函式的聲明形式:類名(參數列表);

析構函式的聲明形式:~類名();

3.

重載的特殊性

構造函式和普通成員函式一樣可以被重載,析構函式不可以重載,只能是空參數。

4.

調用過程不同

構造函式和析構函式不能被顯式地調用,只能由編譯器自動調用。

構造函式用於創建類的對象,任何創建對象的行為,都會導致構造函式被調用。析構函式和構造函式的功能相反,析構函式用於銷毀對象,當類的對象超出作用域被銷毀時,析構函式被調用。

即使顯式地定義構造函式和析構函式,也還是會有默認的構造函式和析構函式,函式內部無任何語句,不執行任何操作。默認構造函式是無參數的。需要注意的是,一旦顯式定義任意形參的構造函式,默認構造函式都不會生成,即只有沒有定義構造函式的類才存在默認構造函式。

一般情況下,默認的構造函式和析構函式可以滿足功能需要,然而當需要重載構造函式,或是需要動態分配資源的時候,就不得不定義自己的構造函式甚至析構函式了。

拷貝構造函式是特殊的構造函式,在複製對象時被調用,定義的格式為“類名(類名& 參數名)”。拷貝構造函式也存在默認的,但很多情況下都需要重載。

類的實例化

就像聲明某種類型的變數一樣,聲明一個類類型的對象,就是類的實例化,會涉及到必要的記憶體分配。

不同語言中類的實例化形式是不同的。

C++

類名 對象名(參數列表);

如果沒有參數,括弧必須省略,即“類名 對象名;”,自動調用構造函式。特殊地,參數可以是類的對象,此時會自動調用拷貝構造函式。

Java/C#

類名 對象名 = new 類名(參數列表);

括弧不能省略。

1.

C++

類名 對象名(參數列表);

如果沒有參數,括弧必須省略,即“類名 對象名;”,自動調用構造函式。特殊地,參數可以是類的對象,此時會自動調用拷貝構造函式。

2.

Java/C#

類名 對象名 = new 類名(參數列表);

括弧不能省略。

對象可以訪問類的成員,但並不是所有成員都可以被訪問,能否訪問取決於聲明該成員時所用的關鍵字(public/protected/private)。具體規則如下:

類的公有成員可以被該類,其派生類和類實例化的對象訪問。

類的保護成員可以被該類及其派生類訪問,不可以被該類的對象訪問。

類的私有成員可以被該類訪問,不可以被派生類及其該類的對象訪問。

1.

類的公有成員可以被該類,其派生類和類實例化的對象訪問。

2.

類的保護成員可以被該類及其派生類訪問,不可以被該類的對象訪問。

3.

類的私有成員可以被該類訪問,不可以被派生類及其該類的對象訪問。

派生與繼承

子類即是繼承而來的類,父類即是被繼承的類,或者稱之為基類。

public修飾的為公有繼承,private修飾的為私有繼承,protected修飾的為保護繼承。

父類可以只有一個,也可以有多個。只有一個父類稱為單繼承,多個父類稱為多繼承。C++支持多繼承的機制,Java則只具有單繼承功能,並增加了“接口”的概念,一個類可以實現多個接口。

如果不標明繼承方式,默認為私有繼承。

1.

子類即是繼承而來的類,父類即是被繼承的類,或者稱之為基類。

2.

public修飾的為公有繼承,private修飾的為私有繼承,protected修飾的為保護繼承。

3.

父類可以只有一個,也可以有多個。只有一個父類稱為單繼承,多個父類稱為多繼承。C++支持多繼承的機制,Java則只具有單繼承功能,並增加了“接口”的概念,一個類可以實現多個接口。

4.

如果不標明繼承方式,默認為私有繼承。

派生和繼承是類的重要特性,繼承是由抽象到具體的體現。通過繼承,子類可以使用父類的成員。

但要注意的是,派生和繼承在帶來方便的同時,也會使類與類之間的關係變得複雜,尤其是涉及到私有繼承和保護繼承時,類中成員的關係可能會變得難以理解。所以在涉及類時,儘量避免過多層次的繼承,私有繼承和保護繼承的使用也要慎重。

繼承來的成員和自身聲明的成員並無本質區別,也有公有成員、私有成員、保護成員之分。繼承時,父類中成員類型(公有成員/私有成員/保護成員)和繼承方式(公有繼承/私有繼承/保護繼承)不同,情況不同。可以歸納為:

三種類型的繼承,父類的成員均被子類繼承(之前的百科關於這點的描述是錯誤的),只是由類實例化的對象對其繼承的成員的訪問許可權會有所變化。三種不同方式的繼承,描述的是子類實例化對象對其成員的訪問許可權,並非是描述子類時,子類對繼承自父類的成員的訪問許可權。

公有繼承

繼承自父類的成員保持不變。

私有繼承

繼承自父類的成員全部變為私有成員。

保護繼承

繼承自父類的公有成員變為保護成員,其餘不變。

1.

公有繼承

繼承自父類的成員保持不變。

2.

私有繼承

繼承自父類的成員全部變為私有成員。

3.

保護繼承

繼承自父類的公有成員變為保護成員,其餘不變。

操作符重載

操作符重載必須在類中進行,重載操作符可以使操作符對在類中的語義發生變化。除了. ,.* ,:: ,? : 、sizeof、typeid這幾個運算符不能重載之外,大部分運算符都能被重載。但要注意,重載操作符並不能改變操作符的優先權和結合律,而且從認知規律上講,重載的操作符功能必須與原意相近,否則很難被人理解。

操作符重載是函式,在使用該操作符時被調用。操作符重載函式的聲明形式:返回值 operator操作符(參數列表);

友元

友元可以是函式,被稱為友元函式;也可以是類,被稱為友元類。

通常,類中的私有成員只能被自身使用,無法被它的對象訪問。因此,另一個類即便可以使用該類的對象,也無法訪問該類的私有成員,通過定義友元的方法可以做到這一點。

友元就是在一個類中“再次聲明”另一個類的成員函式或是另一個類,被“再次聲明”的成員函式或類可以訪問該類的私有成員。這種“再次聲明”並不是普通的聲明,格式為:friend 函式/類名;

顯然,友元會破壞類的封裝性,使本該隱藏的成員暴露出來,因此應當謹慎使用。

組合

繼承可以描述“交通工具”和“公車”的關係,卻無法描述“公車”和“車輪”的關係。

大多數“車輪”具有的特性是“公車”所不具有的。比如說“車輪”具有“重量”,而“公車”的“重量”則是另一個含義。而通過私有成員、保護成員機制控制這些成員的繼承性,會使繼承變得複雜而難以理解。而且

繼承來的數據成員只有一個,而一輛“公車”卻有四個“車輪”,四個“車輪”的“重量”。

1.

大多數“車輪”具有的特性是“公車”所不具有的。比如說“車輪”具有“重量”,而“公車”的“重量”則是另一個含義。而通過私有成員、保護成員機制控制這些成員的繼承性,會使繼承變得複雜而難以理解。而且

2.

繼承來的數據成員只有一個,而一輛“公車”卻有四個“車輪”,四個“車輪”的“重量”。

引入組合的概念,“公車”完全可以由“車輪”、“方向盤”、“車身”等類組合而來。方法就是將類當成其他的數據類型一樣,在另一個類中定義該類類型的數據成員。

抽象類

並不是所有種類的事物都可以被實例化,換而言之,有的種類只是一種抽象概念,現實中並沒有實際存在的對應物。比如:假設所有的動物都會叫,我們可以定義一個類——“動物”,定義類中的一個成員函式——“叫”,我們知道貓的叫聲,也知道狗的叫聲,然而“動物”是如何“叫”的?——我們根本無法實現它。所以,我們引入了抽象類的概念,抽象類是無法被實例化的,無法聲明抽象類的對象。

通常,用 abstract 修飾的類是抽象類;C++中包含純虛函式的類是抽象類;Java中含有抽象方法的類是抽象類;繼承了純虛函式而沒有實現它的類也是抽象類。

抽象類只能被用作基類,作用體現在:

約束派生類必須實現的成員函式或方法。

不同派生類中同名的成員函式實現不同,體現了多態性。

1.

約束派生類必須實現的成員函式或方法。

2.

不同派生類中同名的成員函式實現不同,體現了多態性。

靜態成員

用static修飾的成員是靜態成員,可以是成員函式或數據成員。靜態成員是所有對象共有的,只分配一次記憶體,產生新的對象時不會產生副本。

靜態數據成員的初始化必須在類外進行,使用靜態成員時必須使用類名和域操作符。

特性

類的三大特性

封裝性

將數據和操作封裝為一個有機的整體,由於類中私有成員都是隱藏的,只向外部提供有限的接口,所以能夠保證內部的高內聚性和與外部的低耦合性。用者不必了解具體的實現細節,而只是要通過外部接口,以特定的訪問許可權來使用類的成員,能夠增強安全性和簡化編程。

繼承性

繼承性更符合認知規律,使程式更易於理解,同時節省不必要的重複代碼。

多態性

同一操作作用於不同對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向基類的指針,來調用實現派生類中的方法。

1.

封裝性

將數據和操作封裝為一個有機的整體,由於類中私有成員都是隱藏的,只向外部提供有限的接口,所以能夠保證內部的高內聚性和與外部的低耦合性。用者不必了解具體的實現細節,而只是要通過外部接口,以特定的訪問許可權來使用類的成員,能夠增強安全性和簡化編程。

2.

繼承性

繼承性更符合認知規律,使程式更易於理解,同時節省不必要的重複代碼。

3.

多態性

同一操作作用於不同對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向基類的指針,來調用實現派生類中的方法。

與結構體的區別

在C++、C#語言中,class和struct都可以定義一個類,它們的區別如下:

C#中,class是引用類型,繼承自System.Object類;struct是值類型,繼承自System.ValueType類,不具多態性。但是注意,System.ValueType是個引用類型。

從職能觀點來看,class表現為行為;而struct常用於存儲數據。

class支持繼承,可以繼承自類和接口;而struct沒有繼承性,struct不能從class繼承,也不能作為class的基類,但struct支持接口繼承。

class可以聲明無參構造函式,可以聲明析構函式;而struct只能聲明帶參數構造函式,且不能聲明析構函式。因此,struct沒有自定義的默認無參構造函式,默認無參構造器只是簡單地把所有值初始化為它們的0等價值。

Java/C#中,實例化時,class要使用new關鍵字;而struct可以不使用new關鍵字,如果不以new來實例化struct,則其所有的欄位將處於未分配狀態,直到所有欄位完成初始化,否則引用未賦值的欄位會導致編譯錯誤。

class可以實現抽象類(abstract),可以聲明抽象函式;而struct為抽象,也不能聲明抽象函式。

class可以聲明protected成員、virtual成員、sealed成員和override成員;而struct不可以,但是值得注意的是,struct可以重載System.Object的3個虛方法,Equals()、ToString()和 GetHashTable()。

class的對象複製分為淺拷貝和深拷貝,必須經過特別的方法來完成複製;而struct創建的對象複製簡單,可以直接以等號連線即可。

class實例由垃圾回收機制來保證記憶體的回收處理;而struct變數使用完後立即自動解除記憶體分配。

作為參數傳遞時,class變數是以按址方式傳遞;而struct變數是以按值方式傳遞的。

1.

C#中,class是引用類型,繼承自System.Object類;struct是值類型,繼承自System.ValueType類,不具多態性。但是注意,System.ValueType是個引用類型。

2.

從職能觀點來看,class表現為行為;而struct常用於存儲數據。

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:

實現一個主要用於存儲數據的結構時,可以考慮struct。

struct變數占有堆疊的空間,因此只適用於數據量相對小的場合。

struct數組具有更高的效率。

1.

實現一個主要用於存儲數據的結構時,可以考慮struct。

2.

struct變數占有堆疊的空間,因此只適用於數據量相對小的場合。

3.

struct數組具有更高的效率。

示例

類的復用

相關詞條

熱門詞條

聯絡我們