類和對象
類是對象的類型,對象是類的實例
成員變量:用來區分同一個類的不同對象,可以是各種類型(簡單類型。指針、引用、複合類型、其他類)
靜態成員:描述整個類的特徵;非靜態成員變量:描述不同對象的特徵
常量成員:必須在創建一個對象的時候初始化它的常量成員,且不能更改
成員函數:用來操作一個類的對象的函數
靜態成員函數:屬於整個類的成員函數
private 私有,把類的數據成員對外隱藏,使之從外部不可見,稱爲信息隱藏;成員函數是給外部的訪問接口,public 公有,外部通過公有成員函數才能訪問私有成員(則把對象的私有數據保護起來了,這種方式就是封裝)
構造函數
構造函數用於生成一個對象,析構函數用於銷燬一個對象,get函數用於獲取成員變量的值,set函數用於修改成員變量的值
聲明對象時,將自動調用構造函數:構造函數必須公有,函數名和類名一樣,沒有返回值,可以接收參數
默認構造函數:不是一定沒有參數,而是調用時不用傳參,有且只有一個默認構造函數,但是可以有多個重載的構造函數,靠不同的參數列表來區分
包括兩種情況:構造函數本身無參數、構造函數有參數但參數都有默認值
重載函數
函數名稱一樣,參數列表不同(參數類型或參數個數不同),僅僅返回值不同是不可以的
初始化列表
在構造函數的函數頭,對類的成員變量進行初始化,初始化方式【:成員變量(賦給成員變量的值) {}】。賦給成員變量的值是構造函數的形參或常數。其優勢:效率高,有些類型的成員變量只能用初始化列表初始化,如常量成員、引用類型
複合類
複合類的成員變量,是另一個類的對象
當不需要在成員函數裏修改成員變量的值時,加上const可以增加程序健壯性
析構函數
析構函數:收回構造函數時申請的資源,垃圾回收
兩種情況會調用析構函數:離開對象的作用域;銷燬對象刪除一個指向對象的指針。當一個類中有指針成員變量並且指向手動開闢的堆空間時,必須寫析構函數,用delete回收指針指向的內存空間,避免內存泄漏
特點:無返回值、無參、唯一、公有
淺拷貝
可以向一個函數傳遞對象作爲參數,對象也可以作爲函數的返回值,有傳值、傳引用、傳指針三種方式
爲了避免對象的拷貝,經常傳遞對象的引用,並且爲了避免被修改加上const(const & x)
如果傳值方式返回,系統將創建一個臨時對象,來接收返回值
淺拷貝:邏輯拷貝,把對象賦值給另一個對象,對象每個成員的值,將一對一拷貝到新的對象,僅適用於沒有指針成員的對象拷貝
二次刪除:如果對象含有指針成員變量,且指向堆上空間,將只拷貝指針成員變量本身的值,造成兩個對象指向同一堆上的內存空間,刪除對象時造成二次刪除
內存泄漏:如果上述情況中,對象原來含有指針成員變量且指向另一堆上空間,則賦值後,造成該內存空間泄漏,並且刪除時造成二次刪除
this指針
每個對象都隱式的包含了一個成員變量,this
this是一個指針,指向對象自己,其值是對象的地址
靜態成員
靜態局部變量:存放在靜態存儲區、在兩次函數調用期間保留原值僅在函數內部可見、可以被自動初始化爲0
靜態全局變量:作用域僅在變量所在的文件內
2、類的靜態成員:
2.1 靜態成員的值和某一個具體對象無關
2.2 靜態成員整個類只有一份拷貝,不需要每個對象都保留一份copy(非靜態成員,每個對象都有一個特定的值,每個對象都爲自己的非靜態成員,在內存中保留一個位置)
2.3 訪問方式 類名::靜態成員名
2.4 靜態成員不應該讓對象來調用(不推薦)
2.5 必須在類聲明之後,用::初始化(不在頭文件初始化)
3、靜態成員函數:
3.1 被類的所有對象共享,不是和某個特定對象而是和整個類相關聯
3.2 不可以使用this指針,因爲this指針是指向每一個具體對象的,所以this指針本身是非靜態成員變量
3.3 靜態成員函數只可以訪問靜態成員,而不能訪問非靜態成員,反之,非靜態成員可以訪問靜態成員
3.4 同樣有public/private,私有靜態成員函數不能在外部用類名或對象名訪問,只能從類內部被其他靜態成員函數訪問
3.5 導入類所在頭文件,就可以讓多個文件調用(用類名調用),大大減少代碼書寫量
深拷貝
1、操作符重載
1.1 讓同樣的操作符執行不同的操作,使用操作符操作非默認類型的操作數
1.2 const A & operator = (const A & x)
2、深拷貝
2.1 避免自賦值(if(this != &x))
-- 釋放掉指針成員已經指向的內存空間,避免內存泄漏
-- 爲指針成員開闢新堆內存空間
-- 向新內存空間拷貝內容
-- 返回*this,即返回新對象本身
3、重載==運算符,判斷兩個對象的每一對成員變量是否相等:bool = true;首先判斷長度是否相等,如果不等,bool = false;否則逐一比較對應元素是否相等:int i= 0;while (i < size && *(rep+i) == *(v.rep + i)){i++;} ;退出while循環後比較i和size,如果i < size,則不等
4、拷貝構造函數:通過拷貝一個已有對象來創建一個新的對象
1、聲明一個對象,並用另一個已經存在的對象初始化:
1.1把已經存在的對象作爲新聲明對象的構造函數的參數
1.2聲明一個對象,並用賦值表達式,用一個已有對象來初始化它
1.3用new運算符,在堆上創建對象,並用一個已有對象對其進行初始化時
2、傳值的方式,從函數返回一個對象:
2.1向函數傳參時,傳值調用時,形參是實參的拷貝
2.2從函數傳值返回對象時,創建一個臨時對象,接受返回值,會調用拷貝構造函數
三大件:拷貝構造函數、析構函數、賦值運算符的重載(實現深拷貝,避免二次刪除和內存泄漏)
如果拷貝構造函數的參數用傳值的方式,那麼當實參傳遞給拷貝構造函數的時候,拷貝構造函數的形參爲了複製實參,會再次調用拷貝構造函數,形成死循環,因此拷貝構造函數的參數必須傳引用
編譯器提供的析構函數,對於指針類型的成員變量,僅僅釋放指針本身的內存,不釋放指針指向的堆上內存,此時必須定義一個析構函數,用delete來釋放堆內存
臨時對象的系統開銷很大,傳值(包括參數、返回值)分別需要形參和返回值的臨時對象調用拷貝構造函數和析構函數,傳引用就顯得效率,傳參不需拷貝構造函數,返回時也不需要臨時變量和析構。
繼承
1、子類與父類
1.1 子類自動繼承父類所有成員(包括成員函數、成員變量),除了構造函數、析構函數和重載的賦值運算符。
1.2 子類在用父類所有功能的同時,也擴展了一些新的功能。即子類成員分爲兩部分,繼承自父類的部分和子類獨有的成員即子類自己擴展的成員。
1.3 子類也擁有父類的私有成員,但子類不能通過自己擴展的函數來訪問繼承自父類的私有成員,而必須通過繼承自父類的公有成員函數來訪問。
1.4 在子類中調用被覆蓋的父類版本的函數時,在函數名前加Base::。
1.5 面向對象最重要的目標:實現代碼複用。通過繼承子類自動擁有了父類代碼,大大減少了代碼書寫量,十分方便維護程序(只需修改父類代碼,子類就自動隨着更改)
2、覆蓋與重載
2.1 子類可以自己實現與父類成員函數原型相同(函數名、函數列表)的成員函數,稱爲覆蓋,覆蓋是函數重載的特例,覆蓋一定發生在繼承的過程中。
2.2 發生覆蓋時,根據調用者對象本身的類型來確定,無法根據參數列表確定。
2.3 ------
//1.重寫必須繼承,重載不用。
//2.重寫的方法名,參數數目相同,參數類型兼容,重載的方法名相同,參數列表不同。
//3.重寫的方法修飾符大於等於父類的方法,重載和修飾符無關。
//4.重寫不可以拋出父類沒有拋出的一般異常,可以拋出運行時異常
//重寫是子類的方法覆蓋父類的方法,要求方法名和參數都相同
//重載是在同一個類中的兩個或兩個以上的方法,擁有相同的方法名,但是參數卻不相同,方法體也不相同
一、重寫(override)
override是重寫(覆蓋)了一個方法,以實現不同的功能。一般是用於子類在繼承父類時,重寫(重新實現)父類中的方法。
重寫(覆蓋)的規則:
1、重寫方法的參數列表必須完全與被重寫的方法的相同,否則不能稱其爲重寫而是重載.
2、重寫方法的訪問修飾符一定要大於被重寫方法的訪問修飾符(public>protected>default>private)。
3、重寫的方法的返回值必須和被重寫的方法的返回一致;
4、重寫的方法所拋出的異常必須和被重寫方法的所拋出的異常一致,或者是其子類;
5、被重寫的方法不能爲private,否則在其子類中只是新定義了一個方法,並沒s有對其進行重寫。
6、靜態方法不能被重寫爲非靜態的方法(會編譯出錯)。
二、overload是重載,一般是用於在一個類內實現若干重載的方法,這些方法的名稱相同而參數形式不同。
重載的規則:
1、在使用重載時只能通過相同的方法名、不同的參數列表實現。不同的參數列表可以是不同的參數類型,不同的參數個數,不同的參數順序(參數類型必須不一樣);
2、不能通過訪問權限、返回類型、拋出的異常進行重載;
3、方法的異常類型和數目不會對重載造成影響;
重定義(隱藏)是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
a 如果派生類的函數和基類的函數同名,但是參數不同,此時,不管有無virtual,基類的函數被隱藏。
b 如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有vitual關鍵字,此時,基類的函數被隱藏。
重載和重寫的區別:
(1)範圍區別:重寫和被重寫的函數在不同的類中,必須是繼承,重載和被重載的函數在同一類中。
(2)參數區別:重寫與被重寫的函數參數列表一定相同,重載和被重載的函數參數列表一定不同。
(3)virtual的區別:重寫的基類必須要有virtual修飾,重載函數和被重載函數可以被virtual修飾,也可以沒有。
3、訪問修飾符
如果父類有protected類型成員,在子類中可以直接訪問,不需要藉助父類公有函數,但是protected類型成員對外界依然隱藏,對外就像private類型一樣
3.1 ------
public:
1、修飾class裏的成員,這些成員可以在class的內部被訪問,也可以在外部被訪問。
2、公有成員可以被繼承。
3、當一個class作爲父類被繼承時,可以用public來修飾。如下:
class Child: public Father
{ };
此時,父類中的public和protected成員都可以被子類繼承,並且修飾符與父類一樣。
protected:
1、修飾class裏的成員,這些成員可以在class的內部被訪問,也可以被友元類和友元方法訪問,不可以在外部被訪問。
2、保護型成員可以被繼承。
3、當一個class作爲父類被繼承時,可以用protected來修飾。如下:
class Child: protected Father
{ };
此時,父類中的public和protected成員都可以被子類繼承,並且修飾符都是protected。
private:
1、修飾class裏的成員,這些成員只可以在class的內部被訪問。
2、私有成員可以被繼承,但不能被子類訪問。
3、當一個class作爲父類被繼承時,可以用private來修飾。如下:
class Child: private Father
{ };
此時,父類中的public和protected成員都可以被子類繼承,並且修飾符都是private。
繼承的應用
當兩個類之間是IS-A的關係,即一個類是另一個類的特例,用繼承來實現,類之間的層級關係與它們所反映的真實世界裏的事物之間的關係是對應的,一般用公有繼承。
爲了實現代碼複用,也可以用繼承,兩個類之間未必存在內在聯繫。類A想用類B的成員,就把A聲明成B的子類,A就自動擁有B的成員函數(除了那三個)
類之間主要的兩種關係:組合:一個類的對象是另外一個類的成員變量,一個類擁有另一個類,HAS-A,有一個;;繼承:一個類是另外一個類的子類,IS-A,是一個
子類的三大件
1.1 子類構造函數中,調用父類構造函數,對來自父類的那部分成員進行初始化,再初始化自己擴展的成員
1.2 誰的成員,歸誰初始化,子類無需重複完成父類部分的工作
1.3 父類構造函數放在初始化列表
1.4 如果在子類的構造函數中,不顯式的調用父類構造函數,將自動調用父類的默認構造函數(前提是有默認構造函數)
1.5 如果在子類的構造函數內部,調用父類構造函數,會創建一個局部的Base對象,而子類對象自己的成員並沒有被參數初始化
1.6 析構函數無需參數,在子類的析構函數中,父類的析構函數將被自動調用
1.7 在拷貝構造函數裏,也是父類成員歸父類初始化
1.8 構造子類時,先父類後子類;析構子類時,先子類後父類
虛函數與多態
1、虛函數
1.1 當父類指針或引用指向子類對象,而子類中又覆蓋了父類的函數,希望用父類指針或引用調用到正確版本的成員函數,需要把該成員函數聲明爲虛函數
1.2 調用虛函數時,到底調用哪個版本,是根據調用該函數的對象本身的類型,而不是指向那個對象的指針或引用的類型,即對象的內存空間爲誰(父類or子類)開闢就調用誰的成員方法或成員變量
1.3 虛函數目的:父類指針或引用,不管指向父類還是子類,在調用覆蓋函數時,可以反映真實情況;有了虛函數,無需向下轉型,就可以正確的用父類的指針或引用調用到子類的函數
1.4 如果函數的參數是傳值方式,形參是父類對象,實參是子類對象,則在函數內部,用形參調用的成員函數,依然是父類版本。因爲傳值只是用子類對象給父類對象賦值,父類對象不是指向子類的引用或指針
1.5 如果一個虛函數被其他成員函數調用,子類的版本也會被正確調用
1.6 如果一個類有子類,則這個父類的析構函數必須是虛函數,即虛析構。如果不是虛析構,則當(用 delete)刪除一個指向子類對象的父類指針時,將調用父類版本的析構函數,子類只釋放了來自於父類的那部分成員變量,而沒有釋放子類擴展的成員變量,造成內存泄漏。
1.7 如果子類的成員函數是虛函數,則子類覆蓋後,不寫virtual也是虛函數。
1.8 虛函數被調用的時候,到底調用哪個版本,在編譯的時候無法確定,只有在執行時才能確定,稱爲動態綁定。之前的函數調用,是在編譯時就可以確定調用哪個版本的函數
1.9 動態綁定使得程序可以照顧到未來增加的代碼,比如創建一個新的子類,並在子類中覆蓋了父類的虛函數,用之前的父類指針依然可以正確的調用到新子類中的函數,而無需對舊有代碼進行修改。
2、抽象基類、純虛函數
2.1 純虛函數,沒有函數體,不需要實現,在子類中實現純虛函數的具體功能
2.2 擁有純虛函數的類,稱爲抽象類,抽象類提供了不同種類對象的一個通用接口。
2.3 不能創建抽象類的對象,因爲抽象類裏面的純虛函數沒有實現。
2.4 抽象類只能作爲基類使用,即抽象基類,如果想創建子類對象,必須實現抽象基類中的所有純虛函數,否則,子類依然是抽象類
2.5 不能單獨調用抽象類的構造函數,僅可以用於子類構造函數的初始化列表裏,用於初始化子類中繼承自父類的成員變量
2.6 抽象類不是必須有析構函數,一旦有,必須是虛析構
2.7 不能以傳值的方式,向一個函數傳遞抽象基類的參數。如果函數形參是抽象類,實參是子類,就相當於用子類對象,創建了一個臨時的抽象基類對象,後者是不允許的,所以必須以傳引用或指針的方式來傳參
3、多態:用父類的指針或引用指向子類的對象,在函數調用時可以調用到正確版本的函數
3.1 用一個父類的指針指向一個子類對象
3.2 用一個父類的指針當函數的形參,用這個指針可以接受到任何它的子類對象也包括他自己
3.3 在複合類,儘量飲用高層次的類(父類的指針)當做類的成員變量,這樣就可以通過它創建出它所對應的任何子類對象包括他自己
3.4 在容器中,可以聲明一個父類指針的容器,這時可以往容器中添加它所對應的任何子類對象包括他自己
4、多態的類別及實現方法
4.1 靜態多態:編譯時多態、函數的多態性,主要通過重載、模板、宏實現
4.2 動態多態:運行時多態、類的多態性,主要通過繼承和虛函數實現,(晚綁定、動態綁定)
4.3 封裝可以使得代碼模塊化,繼承可以擴展已存在的代碼,他們的目的都是爲了代碼重用。而多態的目的則是爲了接口重用。也就是說,不論傳遞過來的究竟是哪個類的對象,函數都能夠通過同一個接口調用到適應各自對象的實現方法。
描述內存分配方式以及它們的區別?
1) 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static 變量。
2) 在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集。
3) 從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc 或new 申請任意多少的內存,程序員自己負責在何時用free 或delete 釋放內存。動態內存的生存期由程序員決定,使用非常靈活,但問題也最多