之前同事提問了一個問題,問題的描述是他用了TList當作容器來存放畫面上的Component,但是當他用TList.Clear的時候,明明TList.Count已經歸0了,畫面上的Component卻還在並沒有消失,這是為什麼?是TList比較特殊嗎?還是TList本身的缺陷嗎?

其實都不是XD,在講這個之前先要瞭解

  • 傳值(Call by value)
  • 傳參考(Call by reference)



傳值

讓我們一步一步來看看是如何運作的吧!


程式碼會先宣告兩個記憶體空間分別是a和b

接著將字串「ABC」賦予進去a的記憶體空間

將a的值賦予給b

最後我們將a的值從「ABC」修改為「DEF」


上面的步驟其實就是把a記憶體空間的值「ABC」複製到b的記憶體空間,由於我們a賦予給b的時候是傳值(Call by value),所以修改a的值並不會影響到b的值因此程式碼最後的結果就是

  • a=DEF
  • b=ABC
💡 基本型別如:string、integer、boolean、double等等的這些都是傳值哦!




傳參考

看完了傳值的過程後我們再來看看傳參考,我們需要先準備一個TTest物件並包含一個name屬性程式碼給我下下去!接著就來看看傳參考的是如何運作的,走起!



程式碼會先建立a和b的TTest物件記憶體空間


接著我們把a建構出來,並且賦予a.name」的值為Momo」
從上圖可以得知a的記憶體空間是0x01而這個位置參考到0x08位置的實際物件,這很饒舌我知道XD,這裡為了圖解方便將a的記憶體空間填入0x08(實際上會不會在0x01填入0x08我就不知道了XD)

a建構完成後,我們再將a賦予給b
到這裡會發現b的位置是0x02跟a的0x01位置不同而唯一相同的就是a和b都參考到0x08的物件位置

最後我們將b.name」的值修改為Momo Chen」
因為a和b都是參考到0x08所以當我們改變了b.name的值就等於改變a.name的值,以上就是物件參考的特性。




實際案例

理解了傳值」傳參考」之後回到文章一開始的問題:TList當作容器來存放畫面上的Component,這裡做一個範例來演示一下用TList存放三個TTest物件,然後再Clear會發生什麼事情呢?


宣告一個cmp是TList<TTest>的物件,然後把TList<TTest>建構起來


接著做迴圈添加三個TTest物件的項目

從上面的圖片可以知道TList添加的三個項目分別對應到3個不同的記憶體位置(0x011~0x013),而這三個項目參考到各自的TTest物件
💡 藍色數字為項目的索引
💡 綠色區塊為TTest物件


最後我們執行TList.Clear

有沒有發現好玩的地方了!沒有錯!雖然下了TList.Clear清除掉了TList的項目,但TList.Clear只會清除TList本身的位置,所以實際上物件還是在原本的位置而畫面上的物件當然不會消失囉(因為根本還在啊XD)!
💡 圖片最後我在三個物件的地方打上了問號,是因為TList.Clear後這三個物件會因為沒有對應的參考位置,因此他就永遠迷失在Delphi的程式裡面造成記憶體洩漏囉!




結論

經過上面的傳值和傳參考的分析後答案很明顯TList它並沒有特殊也沒有缺陷,它很認真的在做它的工作啊(不要再誤會TList大大了XD),只是我們對於物件的特性沒有這麼熟悉,所以在用法上就要特別的小心不然很有可能下了Clear後物件就從此迷失在記憶體裡面,嚴重一點也可能導致系統發生更多未知的問題呢!

💡 如果要存儲物件,這邊推薦一個解法,就是使用TList的孿生兄弟TObjectList,用它高興怎麼Clear就怎麼Clear哦XD
💡 注意建構TObjectList的時候不要把false帶進去,不然結果又會跟TList一樣哦!
⭕ cmp := TObjectList<TTest>.Create();
❌ cmp := TObjectList<TTest>.Create(false);