More Effective C 學習記錄2

2021-10-07 04:17:15 字數 4190 閱讀 7937

基礎

多型:多型性是指用乙個名字定義不同的函式,這些函式執行不同的操作,這樣就可以用同乙個函式呼叫不同內容的函式,從而,可以用同樣的介面訪問功能不同的函式,即「乙個介面,多種方法」。

在c++中,多型分為靜態多型性和動態多型性。這就涉及到聯編的概念。

聯編也分為靜態聯編和動態聯編。源程式經過編譯、鏈結,成為可執行檔案的過程是把可執行**聯編在一起的過程,其中在執行之前就完成的聯編就是靜態聯編(也可稱為靜態繫結),而在執行時才完成的聯編為動態聯編(動態繫結)。

靜態聯編:函式過載,運算子過載等操作在執行前即可確定執行的需要在靜態聯編中處理。

動態聯編:虛函式等函式重寫,需要在執行時才確定執行內容的需要進行在動態聯編處理。

因此,靜態多型多為函式過載,泛型程式設計等,動態聯編多為虛函式重寫

順便記錄關於函式重寫,過載,重定義的概念。

函式重寫:不在同乙個作用域,即必須發生在父類及子類之間。函式名字、返回值及引數必須相同,且父類中函式必須帶有virtual關鍵字,即必須是虛函式。

函式重定義:不在同一作用域,即發生在父類及子類之間。函式名字相同,返回值可以不同,且根據引數分為兩種:

1.引數相同,父類函式必須不能為虛函式,即不可帶有virtual關鍵字。父類函式被隱藏。

2.引數不同,無論是不是虛函式,父類函式被隱藏。

多型細節

談及到多型,重點是動態多型,即使用虛函式重寫。

虛函式:即在類的成員函式前面加virtual關鍵字。虛函式又分為純虛函式和半虛函式。

純虛函式:父類中虛函式不新增函式實現,且在括號後新增等於零。且在子類中必須重寫。含有純虛函式的類稱為抽象類。抽象類不能有例項化物件,更加像乙個介面。

半虛函式:父類中新增函式實現,子類可以重寫也可以不重寫。

動態多型發生條件:

1.必須是繼承關係。

2.父類中必須存在虛函式。

3.通過父類的指標或者引用呼叫虛函式。

class

people

virtual

void

show()

int age;};

class

student

:public people

student

(int age,

int ***=1)

:people

(age)

virtual

void

show()

private

:int ***;};

intmain()

//結論

student show

student age 10

student show

student age 30

people show

people age 20

虛函式的實現原理:

虛函式主要是通過一張虛函式表來實現的。vtable,主要是虛函式的位址表。

乙個類在記憶體中的分配:首先類只會分配變數記憶體,對於成員函式,存放在**區,對於成員函式的不同物件呼叫依靠this指標,詳細不在記錄,所以如果乙個類的物件建立,那麼記憶體只會分配成員變數,如果是子類,會先分配父類成員變數記憶體,再分配子類特有成員變數記憶體。而對於含有虛函式的父類,建立父類物件時,會額外分配乙個虛函式表指標,再分配成員變數記憶體。建立子類物件時,同樣會將父類記憶體分配完後,再分配自己的變數。

因此,虛函式實現原理是通過修改虛函式表中的存放函式指標,首先,如果父類中有多個虛函式,這些虛函式會按照順序將函式入口位址寫入到虛函式表中,其次,如果子類含有特有虛函式,也會按照順序新增到虛函式表中。

那麼編譯器是如何利用虛表指標與虛表來實現多型的呢?是這樣的,當建立乙個含有虛函式的父類的物件時,編譯器在物件構造時將虛表指標指向父類的虛函式;同樣,當建立子類的物件時,編譯器在建構函式裡將虛表指標(子類只有乙個虛表指標,它來自父類)指向子類的虛表(這個虛表裡面的虛函式入口位址是子類的)。

關於虛函式實現原理詳細可看:

類虛函式記憶體分布

虛函式表解析

正文

以上就是多型基礎內容,現在說明為什麼不要以多型方式處理陣列。

上面已經說明,子類繼承父類後,記憶體可能會擴充,如果子類存在特殊成員變數,那麼子類物件記憶體肯定要比父類物件記憶體要大。因此在陣列操作中,如果發生多型行為,可能會因為記憶體不匹配發生錯誤指向,產生不可預期的結果。

class

people

;class

student

:public people

void

print

(const people array,

int num)

}people bpeo[10]

;student bstu[10]

;print

(bpeo,10)

;//指標指向正確

print

(bstu,10)

;//指標指向錯誤

分析:對於print函式而言,array[i] 就相當於 (array+i);通過查詢位址然後進行解引用,而array+i的位址計算,肯定是通過array的型別來計算的,即 isizeof(array);但是在函式引數中,array的型別為people,因此array+i 肯定是按照 isizeof(people)計算的,而本意是通過多型來列印資訊的話,array+i 應該是isizeof(student),因為student與people的記憶體可能不同,從而導致array[i]可能不是正確的第i個物件,而是指標指向錯誤,導致發生不可預計的結果。

基礎

c++編譯器會提供預設建構函式

如果提供了有參建構函式,編譯器只提供拷貝建構函式。

如果提供了拷貝建構函式,編譯器不會提供建構函式

正文

凡是可以合理的從無到有生成物件的class都應該提供預設建構函式。而 必須依靠外來資訊才能生成物件的class盡量不必提供預設建構函式。

class

people

~people()

}class

student

~student()

int age;

}people p[10]

;//正確

student s[10]

;//錯誤

分析:

因為student提供了有參建構函式,編譯器不會再分配預設溝站函式,因此直接定義10個student的陣列,會呼叫無參建構函式,而在類中無法找到無參建構函式,又存在有參建構函式,編譯器不會再分配預設建構函式,因此就會報錯。

解決此項問題有三種方法:

1.直接宣告引數,缺點:大量陣列時都需要提供引數,繁瑣

student s=

2.定義指標陣列,而不是物件陣列,缺點:多使用了指標記憶體,且需要釋放指標

student* ps[10]

;student* ps1 =

new student*[10

];ps[i]

=new

student(1

);..

..

3.提前將陣列記憶體分配,然後在這片記憶體上構造物件,缺點:提前分配記憶體的方法不常用,維護**不方便,關於operator new操作主要看條款8

void

*rawmem =

operator

new(

10*sizeof

(student));

student* bs =

static_cast

>

(rawmem)

;new

(&bs[i]

)student(1

);

由此可見,三種方法都不是特別方便。

如果每次都會將預設建構函式提供,那麼就必須提供乙個預設值來初始化,因此,就會存在乙個非法值,這就導致在類中成員函式處理時需要考慮非法值的存在,從而產生異常等操作,新增了**量及維護成本,如果模擬較簡單還可以,複雜的話反而將程式便複雜,因此不提倡提供預設建構函式。

結論:如果不提供預設建構函式,可能在定義陣列時會報錯,但是我們可以提前預知這個錯誤,且可以通過上面三個方法來規避,但是如果提供預設建構函式,**量會增加,複雜異常情況會產生,因此不建議提供預設建構函式。

More Effective C 《基礎議題》

1 指標 指標是乙個變數,只不過這個變數儲存的是乙個位址,指向記憶體的乙個儲存單元 而引用跟原來的變數實質上是同乙個東西,只不過是原變數的乙個別名而已。如 int a 1 int p a int a 1 int b a 上面定義了乙個整形變數和乙個指標變數p,該指標變數指向a的儲存單元,即p的值是a...

More Effective C 擴充套件方法

c 3.0中增加了許多新特性.其中.擴充套件方法允許我們在不修改原有 的基礎上擴充套件類 介面的功能.有技巧的運用該特性.能寫出有效率 易維護 美觀的 c 2.0提供了許多泛型與容器的介面和類.最常見的如icomparable常常需要新建乙個comparable來進行排序比較等等.但其預設只有乙個c...

more effective C 條款四解讀

深刻考慮是否需要給類提供乙個預設建構函式 有一些類擁有乙個預設建構函式是合理的需求,比如string比如容器 linked list vector 預設建構函式可以把他們初始化為空容器。但是有一些類我們最好還是不要提供預設建構函式。比如乙個equipment類 class equipment 對於這...