C Primer讀書筆記梳理系列(三)

2021-09-21 07:30:04 字數 4365 閱讀 5812

在我看來這部分的內容,比較底層,也是比較難的,但是非常重要,面試過程中也經常遇到

拷貝控制操作

特殊的成員函式

控制類的行為

拷貝和移動建構函式

用同型別的另乙個物件初始化本物件時做什麼(class a(b))

拷貝和移動賦值運算子

將乙個物件賦予同型別的另乙個物件時做什麼(class a = b)

析構函式

此型別物件銷毀時做什麼

如果沒有,編譯器會生成這些函式,但是不一定是我們想要的

拷貝建構函式

合成的拷貝建構函式,簡單的拷貝複製

直接初始化與拷貝初始化區別:乙個簡單的普通函式匹配(建構函式或者拷貝建構函式),另乙個是拷貝建構函式

string dots(10

,'s');

//直接初始化

string s

(dots)

;//直接初始化

string s2 = dots;

//拷貝初始化

拷貝初始化會在定義變數時會用=會發生外,還有以下情況會使用拷貝初始化(值傳遞)

實參傳遞給非引用形參

返回型別為非引用

用花括號列表初始化陣列和聚合類:這裡就有初始化列表會少呼叫一次賦值運算子操作

對於此部分仍然還有疑問,與effective c++第四款解釋有所區別

當然需要注意的是,為什麼拷貝建構函式要使用引用傳遞,防止無線遞迴(值傳遞需要使用拷貝建構函式)

拷貝賦值運算子

注意:拷貝建構函式是在定義時用 = ,拷貝賦值是在賦值時

int a =0;

//拷貝建構函式

int b;

b = a;

//拷貝賦值運算子

賦值運算子通常返回左側物件的引用,為了可以連續賦值

析構函式

建構函式中,成員的初始化是在函式體執行之前完成的,順序是類**現的順序初始化

析構函式,首先執行函式體,然後銷毀成員,按初始化順序來逆序銷毀

隱式銷毀乙個內建指標型別的成員不會delete它所指向的物件(普通指標沒有析構函式),但智慧型指標是類型別,是有析構函式的,所以智慧型指標在析構階段會被自動銷毀。

什麼時候會呼叫析構函式

一般而言,析構函式,拷貝建構函式、拷貝賦值運算子,一旦乙個定義了,最好其他三個都全部自定義了

為什麼?

需要析構函式的時候也需要拷貝賦值操作

一般而言,如果成員變數含有指標變數時 ,一般會定義析構函式,如果不定義拷貝建構函式與建構函式與賦值運算子,這在函式中拷貝與賦值操作時,由於是淺拷貝,產生的副本只是簡單的對指標進行賦值(但指標指向的物件是同乙個),於是在釋放過程中該指標指向的物件釋放了兩次就會出現未定義錯誤。

class

hasptr

//看看這個建構函式

~hasptr()

//自定義析構函式};

//呼叫這個類,你能看出**錯了嗎?

hasptr f

(hasptr hp)

f函式返回時, hp和ret都會被銷毀,都會呼叫hasptr的析構函式,都會delete rer和hp中的指標成員,兩個指標指向同乙個物件,釋放兩次出現錯誤,如果是深拷貝就不會出現這種問題

使用 =default

如果定義拷貝建構函式,如果定義了,編譯器就不會生成,但是可以顯示通過將拷貝控制成員函式顯式要求編譯器生成合成的版本(且是內聯的),

阻止拷貝

如果乙個類不想讓我們的類物件被拷貝與賦值,如unique_ptr不允許拷貝與賦值,只需要在後面新增**=delete** ,delete可以用在任何函式,而default只能在合成的預設建構函式與拷貝控制成員。特別注意的是析構函式不能被定義為刪除的

struct nodtor

;nodtor nd;

//錯誤:無法定義該類物件

nodtor *p =

newnodtor()

;//正確:但是我們無法delete p

delete p;

//錯誤:nodtor的析構函式是刪除的

合成的拷貝控制成員可能是刪除的

當然effective c++條例06 , 提到了如果不需要編譯器合成的拷貝控制成員,需要將其設定為私有,並且繼承這個類,這在boost庫中也使用了這種方法。

​ 這部分主要介紹兩個內容,通過定義不同的拷貝操作,使自定義的類的行為看起來像乙個值或者指標(難道就是傳說中的淺拷貝與深拷貝)

行為像值的類

為了像值,每個string指標指向那個string物件,都得有自己的乙份拷貝,為了實現這個目的,我們需要以下三個小工作:

定義乙個拷貝建構函式,完成string的拷貝,而不是拷貝指標

定義析構函式來釋放string

定義乙個賦值運算子來釋放當前的string,並從右側運算物件拷貝string

class

hasptr

//預設實參,列表初始化

hasptr

(const hasptr &p):ps

(new

string

(*p.ps)),

i(p.i)

//拷貝建構函式

hasptr&

operator=(

const hasptr &);

//賦值運算子宣告

~hasptr()

//析構函式

private

: string *ps;

int i;

};

類值拷貝運算子

當 a = b;

hasptr物件出現這樣的賦值

銷毀左側運算物件的資源,類似於析構函式

從右側運算物件拷貝資料,類似拷貝建構函式,為了保證乙個物件能為它本身賦值,我們先拷貝右邊的物件,在釋放左側資源,並更新指向新分配的string;

hasptr& hasptr::

operator=(

const hasptr &rhs)

行為像指標的類

你們可能覺得拷貝指標就行了,但是還需要釋放記憶體,當最後乙個指向string的hasptr銷毀時才能釋放記憶體,所以使用share_ptr,這裡不用智慧型指標,看看底層如何實現引用計數

class

hasptr

//預設實參,列表初始化

hasptr

(const hasptr &p):ps

(p.ps),i

(p.i)

,use

(p.use)

//拷貝建構函式,要遞增計數器

hasptr&

operator=(

const hasptr &);

//賦值運算子宣告

~hasptr()

//析構函式

}private

: string *ps;

int i;

size_t *use;

//引用計數};

//賦值運算子

hasptr& hasptr::

operator=(

const hasptr &rhs)

ps = rhs.ps;

//拷貝

i = rhs.i;

use = rhs.use;

return

*this

;//返回本物件

}

這個在effective c++裡面也有提及了

除了定義拷貝控制成員外,管理資源的類通常還會定義乙個swap函式交換兩個元素,對於需要重排元素的演算法很重要

如果物件很大,借助中間變數來交換物件會多次使用拷貝建構函式與賦值運算子,比較消耗時間,因此可以通過交換指標更划算

string *temp = v1.ps;

v1.ps = v2.ps;

v2.ps = temp;

為自己的類編寫swap函式

class

hasptr

;inline

void

swap

(hasptr &lhs, hasptr &rhs)

//宣告為內聯函式

則賦值運算子就可以使用swap了

hasptr& hasptr::

operator

=(hasptr rhs)

使用allocator類將記憶體分配和物件構造分離

C primer 讀書筆記

第2 章 變數和基本型別 1 變數直接初始化和變數 複製初始化 int ival 1024 direct initialization int ival 1024 copy initialization 初始化不是賦值 2 內建型別復 制初始化和直接初始化幾乎沒有區別 但 對類型別物件來 說,有些初...

C Primer讀書筆記

前些日子開始看 c primer 順便做一些筆記,既有書上的,也有自己理解的。因為剛學c 不久,筆下難免有謬誤之處,行文更是凌亂 所幸不是用來顯配的東西,發在linuxsir只是為了方便自己閱讀記憶,以防只顧上網忘了正事。書看了不到一半,所以大約才寫了一半,慢慢補充。const要注意的問題 1 下面...

C Primer讀書筆記

前些日子開始看 c primer 順便做一些筆記,既有書上的,也有自己理解的。因為剛學c 不久,筆下難免有謬誤之處,行文更是凌亂 所幸不是用來顯配的東西,發在linuxsir只是為了方便自己閱讀記憶,以防只顧上網忘了正事。書看了不到一半,所以大約才寫了一半,慢慢補充。const要注意的問題 1 下面...