char, array of char, PChar都是簡單類型,簡單類型當然通用性好,這個很容易理解,只要保證記憶體布局一樣就可以了,在這個前提下如果有必要可以採取手段欺騙編譯器的強型別檢查。Delphi為什麼提出string類型?肯定有它的道理。什麼道理?用C的同志們都知道處理什麼類型都沒有處理字串類型費勁,Delphi作為如此優秀的RAD工具自然要想辦法解決這個問題。事實上,如果你能充分的理解string,你就會讚歎Delphi的精妙了。
處理字串時候最惱人的問題之一就是記憶體的分配與釋放,如果你用char數組(array [0..l] of char等同於C中的char[l+1]),則記憶體被靜態分配,這種情況主要用在定寬字元串中。但是對於最常見的變長字串可就費勁了。string解決這個問題的手段是,由編譯器通過引用計數和長度資訊自動維護字串記憶體地區。事實上,當把一個string類型的變數當成字元指標看的時候,他的記憶體布局是這樣的:
s: string = 'CSDN ';
則有PChar(s)^ = 'C ',(PChar(s)+1)^= 'S ',……(PChar(s)+4)^=#0。
且:PInteger(Integer(Pointer(s))-1*sizeof(DWORD))^=length(s)
和:PInteger(Integer(Pointer(s))-2*sizeof(DWORD))^=refcount(s)
可視化一下就是:
[RefCount: DWORD][Length: DWORD]^[ 'C '][ 'S '][ 'D '][ 'N '][#0]
(^處即為s所指記憶體位址)
可以看到string在s之後的記憶體布局同PChar完全一致,都是ASCIIZ標準的字串,因此任何string類型的變數都可以通過強制類型轉換的文法欺騙編譯器的強型別檢查而作為PChar直接使用,如: PChar(s)。而且由於字串的長度存放在位移-4處,因此求字串長度的時候速度極快,因為length(s) = PInteger(Integer(Pointer(s))-4)^,不用像PChar和array那樣從頭數到尾直到#0。那引用計數是幹哈的呢?首先要明確一點,Delphi對於string是從堆中自動分配和釋放記憶體,分配好說,但是什麼時候釋放呢?如果你瞭解COM的引用計數原理就知道,通過維護refCount系統就能夠確定可以安全釋放對象的時機了。因為Delphi對於string類型的字串使用了copy-on-write技術,因此如果簡單的兩個string都包含相同的內容,則實際上記憶體中僅有一份拷貝。如:
s1 := 'I love Delphi '; // line 1
s2 := s1; // line 2
s1 := s1 + '! '; // line 3
在第一行,Delphi將為s1分配記憶體並設定好負位移處的長度資訊與引用計數(初始為1);在第二行,事實上沒有任何的記憶體配置操作,s2簡單的被賦予s1的指標資訊,同時他們指向的字串的引用計數+1(事實上Delphi程式內部還有一張表來記錄這些引用資訊和變數執行個體的關係);在第三行,s1進行了改變,根據內部的邏輯規則,Delphi將自動分配新的記憶體並實際拷貝s1+ '! '的內容到新的記憶體中,然後改變s1的地址和s2指向字串的引用計數就搞定了。這就是所謂的copy-on-write。你說了,這沒什麼用啊,哪兒這麼多直接的賦值啊?其實像返回字串的函數這類情況都能發揮copy-on-write的好處,經濟、快速。
綜合一下,在Delphi編程中應該儘可能的使用string類型簡化代碼、減少出錯並提高程式的運行效率。在調用C或者其他應用PChar的場合可以直接拿PChar(string)來用,不存在效率問題(因為只是強制轉換而已)。字元數組主要用於定寬字串和定義結構時候用,不過只有下標為0且數群組成員類型為char的array才和PChar相容。