共找到6條詞條名為類的結果 展開

編程術語

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

介紹


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

用法


定義一個類
1
2
3
4
5
6
7
8
9
10
11
class類名
{
 
public:
公有成員
private:
私有成員
 
protected:
保護成員
};
● ● 公有成員、私有成員、保護成員均包含數據成員和成員函數兩部分,彼此沒有順序之分。一個public/private/protected關鍵字下可以跟多個成員,直到下一個public/private/protected關鍵字。如果成員前面沒有public/private/protected關鍵字,默認為私有成員。
● ● 結尾部分的分號必不可少,否則會發生語法錯誤。
● ● 無論公有成員、私有成員還是保護成員,彼此之間都可以訪問。比如公有的成員函數可以操作保護的數據成員,也可以調用私有的成員函數。
● ● 類的數據成員是類型,所以不能被賦值,聲明數據成員和聲明普通變數的格式相同,比如“int n;”。
成員函數的實現
成員函數可以在類內實現,也可以在類外實現。內部實現的成員函數被默認為加上了inline;外部實現的成員函數必須加上域操作符,即“類名::成員函數”。
構造函數與析構函數
構造函數和析構函數是特殊的成員函數,和普通成員函數不同的地方在於:
● ● 函數名固定
● ● 構造函數和析構函數的函數名必須是類名。
● ● 聲明格式不同
● ● 構造函數和析構函數沒有返回值,連空返回值——void也沒有。
● ● 構造函數的聲明形式:類名(參數列表);
● ● 析構函數的聲明形式:~類名();
● ● 重載的特殊性
● ● 構造函數和普通成員函數一樣可以被重載,析構函數不可以重載,只能是空參數。
● ● 調用過程不同
● ● 構造函數和析構函數不能被顯式地調用,只能由編譯器自動調用。
構造函數用於創建類的對象,任何創建對象的行為,都會導致構造函數被調用。析構函數和構造函數的功能相反,析構函數用於銷毀對象,當類的對象超出作用域被銷毀時,析構函數被調用。
即使顯式地定義構造函數和析構函數,也還是會有默認的構造函數和析構函數,函數內部無任何語句,不執行任何操作。默認構造函數是無參數的。需要注意的是,一旦顯式定義任意形參的構造函數,默認構造函數都不會生成,即只有沒有定義構造函數的類才存在默認構造函數。
一般情況下,默認的構造函數和析構函數可以滿足功能需要,然而當需要重載構造函數,或是需要動態分配資源的時候,就不得不定義自己的構造函數甚至析構函數了。
拷貝構造函數是特殊的構造函數,在複製對象時被調用,定義的格式為“類名(類名& 參數名)”。拷貝構造函數也存在默認的,但很多情況下都需要重載
類的實例化
就像聲明某種類型的變數一樣,聲明一個類類型的對象,就是類的實例化,會涉及到必要的內存分配。
不同語言中類的實例化形式是不同的。
● ● C++
● ● 類名 對象名(參數列表);
● ● 如果沒有參數,括弧必須省略,即“類名 對象名;”,自動調用構造函數。特殊地,參數可以是類的對象,此時會自動調用拷貝構造函數。
● ● Java/C#
● ● 類名 對象名 = new 類名(參數列表);
● ● 括弧不能省略。
對象可以訪問類的成員,但並不是所有成員都可以被訪問,能否訪問取決於聲明該成員時所用的關鍵字(public/protected/private)。具體規則如下:
● ● 類的公有成員可以被該類,其派生類和類實例化的對象訪問。
● ● 類的保護成員可以被該類及其派生類訪問,不可以被該類的對象訪問。
● ● 類的私有成員可以被該類訪問,不可以被派生類及其該類的對象訪問。
派生與繼承
1
2
3
4
5
6
7
8
9
10
11
class子類類名:
public父類類名,private父類類名,
protected父類類名
{
public:
公有成員
private:
私有成員
protected:
保護成員
};
● ● 子類即是繼承而來的類,父類即是被繼承的類,或者稱之為基類。
● ● public修飾的為公有繼承,private修飾的為私有繼承,protected修飾的為保護繼承。
● ● 父類可以只有一個,也可以有多個。只有一個父類稱為單繼承,多個父類稱為多繼承。C++支持多繼承的機制,Java則只具有單繼承功能,並增加了“介面”的概念,一個類可以實現多個介面。
● ● 如果不標明繼承方式,默認為私有繼承。
派生和繼承是類的重要特性,繼承是由抽象到具體的體現。通過繼承,子類可以使用父類的成員。
但要注意的是,派生和繼承在帶來方便的同時,也會使類與類之間的關係變得複雜,尤其是涉及到私有繼承和保護繼承時,類中成員的關係可能會變得難以理解。所以在涉及類時,盡量避免過多層次的繼承,私有繼承和保護繼承的使用也要慎重。
繼承來的成員和自身聲明的成員並無本質區別,也有公有成員、私有成員、保護成員之分。繼承時,父類中成員類型(公有成員/私有成員/保護成員)和繼承方式(公有繼承/私有繼承/保護繼承)不同,情況不同。可以歸納為:
三種類型的繼承,父類的成員均被子類繼承(之前的百科關於這點的描述是錯誤的),只是由類實例化的對象對其繼承的成員的訪問許可權會有所變化。三種不同方式的繼承,描述的是子類實例化對象對其成員的訪問許可權,並非是描述子類時,子類對繼承自父類的成員的訪問許可權。
● ● 公有繼承
● ● 繼承自父類的成員保持不變。
● ● 私有繼承
● ● 繼承自父類的成員全部變為私有成員。
● ● 保護繼承
● ● 繼承自父類的公有成員變為保護成員,其餘不變。
操作符重載
操作符重載必須在類中進行,重載操作符可以使操作符對在類中的語義發生變化。除了. ,.* ,:: ,? : 、sizeof、typeid這幾個運算符不能重載之外,大部分運算符都能被重載。但要注意,重載操作符並不能改變操作符的優先順序和結合律,而且從認知規律上講,重載的操作符功能必須與原意相近,否則很難被人理解。
操作符重載是函數,在使用該操作符時被調用。操作符重載函數的聲明形式:返回值 operator操作符(參數列表);
友元
友元可以是函數,被稱為友元函數;也可以是類,被稱為友元類。
通常,類中的私有成員只能被自身使用,無法被它的對象訪問。因此,另一個類即便可以使用該類的對象,也無法訪問該類的私有成員,通過定義友元的方法可以做到這一點。
友元就是在一個類中“再次聲明”另一個類的成員函數或是另一個類,被“再次聲明”的成員函數或類可以訪問該類的私有成員。這種“再次聲明”並不是普通的聲明,格式為:friend 函數/類名;
顯然,友元會破壞類的封裝性,使本該隱藏的成員暴露出來,因此應當謹慎使用。
組合
繼承可以描述“交通工具”和“公交車”的關係,卻無法描述“公交車”和“車輪”的關係。
● ● 大多數“車輪”具有的特性是“公交車”所不具有的。比如說“車輪”具有“重量”,而“公交車”的“重量”則是另一個含義。而通過私有成員、保護成員機制控制這些成員的繼承性,會使繼承變得複雜而難以理解。而且
● ● 繼承來的數據成員只有一個,而一輛“公交車”卻有四個“車輪”,四個“車輪”的“重量”。
引入組合的概念,“公交車”完全可以由“車輪”、“方向盤”、“車身”等類組合而來。方法就是將類當成其他的數據類型一樣,在另一個類中定義該類類型的數據成員。
並不是所有種類的事物都可以被實例化,換而言之,有的種類只是一種抽象概念,現實中並沒有實際存在的對應物。比如:假設所有的動物都會叫,我們可以定義一個類——“動物”,定義類中的一個成員函數——“叫”,我們知道貓的叫聲,也知道狗的叫聲,然而“動物”是如何“叫”的?——我們根本無法實現它。所以,我們引入了抽象類的概念,抽象類是無法被實例化的,無法聲明抽象類的對象。
通常,用 abstract 修飾的類是抽象類;C++中包含純虛函數的類是抽象類;Java中含有抽象方法的類是抽象類;繼承了純虛函數而沒有實現它的類也是抽象類。
抽象類只能被用作基類,作用體現在:
● ● 約束派生類必須實現的成員函數或方法。
● ● 不同派生類中同名的成員函數實現不同,體現了多態性。
靜態成員
用static修飾的成員是靜態成員,可以是成員函數或數據成員。靜態成員是所有對象共有的,只分配一次內存,產生新的對象時不會產生副本。
靜態數據成員的初始化必須在類外進行,使用靜態成員時必須使用類名和域操作符。

特性


類的三大特性
1.封裝性
將數據和操作封裝為一個有機的整體,由於類中私有成員都是隱藏的,只向外部提供有限的介面,所以能夠保證內部的高內聚性和與外部的低耦合性。用者不必了解具體的實現細節,而只是要通過外部介面,以特定的訪問許可權來使用類的成員,能夠增強安全性和簡化編程。
2.繼承性
繼承性更符合認知規律,使程序更易於理解,同時節省不必要的重複代碼。
3.多態性
同一操作作用於不同對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向基類的指針,來調用實現派生類中的方法。
與結構體的區別
在C++、C#語言中,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:
1.實現一個主要用於存儲數據的結構時,可以考慮struct。
2.struct變數佔有堆棧的空間,因此只適用於數據量相對小的場合。
3.struct數組具有更高的效率。

示例


類的復用