大一程設-下
東華大學
東華大學資管系
基本程式概念
資管經驗分享
複製建構子可謂一下最大大魔王,我覺得很會打這個不太重要,但他想傳遞的概念非常重要,是很重要的思維!
今天透過宣告類別實體化物件的時候,根據需求我們可能會要複製某個物件的資料。
我們先不管實例,直接先用簡單的類別說明。
可以看到對於 a1 跟 a2 而言,name 這個屬性各自隸屬不同的記憶體區塊,畢竟是兩個不同物件的屬性,在第 22 行我們做了一個宣告即 assign 的動作把 a1 的值 copy 一份給 a2,所以你才能看到上圖的輸出。
而因為是複製值,所以 name 沒有占用同一塊記憶體。當第 22 行修改了 a1 的 name 沒有影響到 a2 的 name。
而像第 22 行這樣一個宣告即 assign 的 copy 動作,其實就已經觸發了 copy constructor 了。
copy constructor 又稱複製建構子,顧名思義,他是一種建構子,還記得建構子是物件初始化的天生狀態,我們可以透過自定義的建構子來初始化物件,而既然稱為複製建構子,想必有一個想要複製的東西,而又稱建構子,所以是把某個東西複製過來,成為另一個東西天生的狀態。
簡單的說,copy constructor 就是想要做到把 A 物件的內容複製一份給 B 物件這樣的動作。
而因為 copy constructor 也是一種建構子,所以他其實也是在做物件實體化的動作。
Orange
Copy Constructor 與類別同名,參數只有一個,必須要是同類別的物件,是一種 function。
C++ 預設每一個類別其實都有一個預設的 copy constructor,如果我們沒定義他,他會自己運作,所以我們可以自己實作他。
而預設的 copy constructor 被呼叫的時候,會把資料都複製一份出來。
大概會長像下面這樣
為何要 call-by reference?
為何要 const?
請看第四行,根據參數,我們知道是一個 Animal 物件,呼叫 Copy Constructor。
第四行跟第五行的寫法,只要你是在宣告的時候這樣寫,會產生一樣的效果,都會呼叫 copy constructor。
但第六行的寫法,如果今天是單純的 assign,並不等於第四行跟第五行,下面都會說到,但你可以先記起來。
倘若今天沒有在類別內撰寫 copy constructor,你的程式若執行 copy 的動作的時候,會有預設的 copy constructor 被執行,詳細可以看 reference。
根據前面的說明,我們已經知道 Copy Constructor 用來複製內容,那既然預設的已經會幫我們做複製了,我們還有必要來覆寫(親自定義)他嗎?
答案是有的,我們先來說總結。
所以會造成刪除同一塊記憶體的情況。
想必無法理解,在真的講到上面的情況前,我們先看一個小例子,再回頭講這個比較難的。
上上個例子我們示範了非指標的變數複製的結果,會發現名字的改變並不會相互影響,但如果今天變數換成指標變數的時候,結果就完全大不相同了哦,我們看一下輸出。
如果覺得圖片不清楚,可以右鍵圖片「在新分頁中開啟圖片」看完整大小。
按照上上個例子的做法,不是應該年齡也不會互相影響嗎? 怎麼我改 a1 的 age 結果兩個人的 age 都變成 25 了?
這就跟指標變數本身的特性有關,「指標變數存的是位址」,在做Animal a2 = a1;
的時候,我們複製的是值,因此我們是把上圖的 0xbd1530
這個位址丟進去 a2 的指標變數 age 裡面,所以現在兩個物件的 age 都指向同一個地方,進而我只要修改一個人的 age 最後指向的地方的值,另一個人就會跟著被改變。
這是非常糟糕的,也就是因為這個緣故,如果類別的屬性有指標變數,我們就必須親手撰寫 copy constructor,因為預設的 copy constructor 會發生上面這樣的情況。
為了避免上面的情況,我們親自來撰寫 copy constructor 來處理這個問題吧!
首先我們知道 Animal 的 age 屬性是指標變數,所以我們必須要給一塊記憶體放到這個指標變數裡面(不是指標變數本身的記憶體哦,是他要指向的那個整數所在的地方),但這個記憶體要從哪來呢? 目前並沒有配置的記憶體阿,所以我們要自己動態的配置出來。
先貼上完整的程式碼,細部的說明就往下看吧。
細部說一下每個 function。
user-defined constructor:
如同上面說的,因為 age 現在是指標變數,因此我們需要分配一塊記憶體給他來存(切記不是 age 變數本身的記憶體位址!!),我們 new 出來的是紅色的這塊。要先 new 出記憶體,才能把參數 a 的數字拿來存。
copy constructor:
這樣當 a2 被 copy constructor 實體化的時候,會把 a1 當作參數丟進來,因為實體化的時候 age 並沒有被指派記憶體,因此我們 new 一個給他,最後再把值賦予 a2 的 age。
Destructor,又稱解構子,負責來把物件的記憶體給回收,跟動態陣列的思維很像,你必須要來把你手動配置的記憶體給回收掉,如果你類別今天都沒有 new 出來的東西,可以不撰寫沒關係。
但很明顯的,上例有一個指標變數,我們也有親自 new 記憶體出來,接下來就來介紹一下解構子吧。
解構子與類別同名,要在最前面加上 ~
,且沒有參數,不用呼叫他,系統會根據物件的存活週期的最後來呼叫執行裡面的內容,釋放掉記憶體。
以我們上面寫的例子來說,Animal 物件都是在 main 裡面,所以他們的存活週期就是 main 要結束之前,才會呼叫 destructor,至於其他的存活週期以前已經教過摟。
記憶體的回收在 C++ 內一直都是很重要的,雖然大多新的語言不用我們去擔心這些,但是保有這邊理論的思維帶去開發,你會變得更謹慎,更成熟穩重。
但 Destructor 如果因為寫程式的不小心,其實會引發刪掉同樣一塊記憶體的問題,我們放到下一篇筆記的最後跟大家說。
接下來要來講與 copy constructor 一定要搭配在一起的東西 - Assignment Operator Overloading。
這邊的思維是非常重要的,可能要花點心思理解。