String是存儲字符串的數據類型。
Delphi中字符串類型原理介紹
Delphi中字符串的操作很簡單,但幕後的情況卻相當復雜。Pascal傳統的字符串操作方法不同於Windows,吸收了C語言的字符串操作方法。32位Delphi增加了長字符串類型,功能強大,是Delphi默認的字符串類型。
字符串類型在TurboPascal和Borland公司的16位Delphi中,傳統的字符串類型是壹個字符序列,序列的頭是壹個長度字節,表示當前字符串的長度。因為只有壹個字節用於表示字符串的長度,所以字符串不能超過255個字符。這種長度限制給字符串操作帶來不便,因為每個字符串的長度必須是固定的(最大值為255),當然妳也可以聲明更短的字符串來節省存儲空間。
字符串類型類似於數組類型。實際上,壹個字符串幾乎就是壹個字符類型的數組,所以妳可以用[]符號訪問字符串中的字符這壹事實充分說明了上述觀點。
為了克服傳統Pascal字符串的局限性,32位Delphi增加了對長字符串的支持。因此* * *有三種類型的字符串:
ShortString短弦類型也是上面提到的傳統Pascal弦類型。這種字符串最多只能有255個字符,和16位Delphi中的字符串壹樣。短字符串中的每個字符都屬於
ANSIChar類型(標準字符類型)。
ANSIString long string類型是新添加的可變長度字符串類型。這種字符串是內存動態分配的,使用了引用計數,使用了寫時復制技術。此類字符串的長度沒有限制(最多可存儲20億個字符!),其字符類型也是ANSIChar類型。
WideString長字符串類型類似於ANSIString類型,只是它基於WideChar字符類型,後者是壹種雙字節Unicode字符。
使用長字符串
如果壹個字符串被簡單地定義為字符串,那麽它可能是壹個短字符串,也可能是壹個ANSI長字符串,這取決於$H編譯指令的值,$H+ (exact province)代表壹個長字符串(ANSIString類型)。長字符串是Delphi庫中控件使用的字符串。
Delphi長字符串基於引用計數機制,通過引用計數來跟蹤內存中引用同壹字符串的字符串變量,當字符串不再被使用時,即引用計數為零時,釋放內存。
如果妳想增加壹個字符串的長度,而這個字符串的相鄰位置又沒有空閑內存,也就是說,在同壹個存儲單元中沒有這個字符串的擴展空間,那麽這個字符串就必須完全復制到另壹個存儲單元中。當這種情況發生時,Delphi運行時支持程序將以完全透明的方式為字符串重新分配內存。為了有效地分配所需的存儲空間,可以使用SetLength過程來設置字符串的最大長度,例如:
SetLength (String1,200);
SetLength進程只完成壹個內存請求,並不實際分配內存。它只是預留了未來需要的內存,而實際上並沒有使用這個內存。這項技術起源於Windows操作系統,現在正被
Delphi用於動態分配內存。例如,當您請求壹個大型陣列時,系統會保留陣列內存,但不會將內存分配給該陣列。
壹般不需要設置字符串的長度,但是當妳需要將壹個長字符串作為參數傳遞給壹個API函數時(類型轉換後),就必須使用SetLength來為字符串預留內存空間,這個我後面會解釋。
看壹下內存中的字符串
為了幫助妳更好的理解字符串的內存管理細節,我寫了壹個簡單的例子StrRef。在程序中,我聲明了兩個完整的字符串:Str1和Str2。當按下第壹個按鈕時,程序將壹個字符串常量賦給第壹個變量,然後將第壹個變量賦給第二個變量:
str 1:= ' Hello ';
str 2:= str 1;
除了字符串操作之外,該程序還使用以下StringStatus函數在列表框中顯示字符串的內部狀態:
函數string status(const Str:string):string;
開始
結果:= '地址:'+ IntToStr (Integer (Str)) +
,長度:'+ IntToStr (Length (Str)) +
,引用:'+inttostr(pinteger(integer(str)-8)^)+
,值:'+Str;
結束;
在StringStatus函數中,傳遞帶有常量參數的字符串非常重要。通過復制(value參數)傳遞會產生副作用,因為在函數執行過程中會產生壹個對字符串的額外引用;相反,通過引用(var)或常量(const)參數傳遞不會產生這種情況。因為此示例不希望字符串被修改,所以選擇了常量參數。為了獲得壹個字符串的內存地址(這有助於識別字符串的實際內容,也有助於觀察兩個不同的字符串變量是否引用同壹個內存區域),我通過類型映射將字符串類型強制轉換為整數。字符串實際上是壹個引用,也就是指針:字符串變量保存的是字符串的實際內存地址。
為了提取引用計數信息,我利用了壹個鮮為人知的事實:字符串長度和引用計數信息實際存儲在字符串中,在實際內容和字符串變量所指向的內存位置的前面,它的負偏移量對於字符串長度是-4(用Length函數很容易得到這個值),對於引用計數是-8。
但是壹定要記住,以上關於offset的內部信息在未來的Delphi版本中可能會有變化,很難保證沒有寫入Delphi官方文檔的特性在未來保持不變。
通過運行這個例子,妳會看到這兩個字符串有相同的內容,相同的內存位置和引用計數2,如圖7.1中列表框頂部所示。現在,如果妳改變其中壹個字符串的值,更新後的字符串的內存地址也會改變。這是寫入時復制技術的結果。
第二個按鈕(Change)的OnClick事件代碼如下,結果顯示在列表框7.1的第二部分:
過程TFormStrRef。BtnChangeClick(發件人:to object);
開始
str 1[2]:= ' a ';
列表框1。items . Add(' str 1[2]:= ' ' a ' ');
列表框1。items . Add(' str 1-'+string status(str 1));
列表框1。items . Add(' str 2-'+string status(str 2));
結束;
請註意,BtnChangeClick只能在執行BtnAssignClick之後執行。所以程序啟動後第二個按鈕不能使用(按鈕的Enabled屬性設置為false);第壹種方法結束後,激活第二個按鈕。您可以自由地擴展這個示例,並使用StringStatus函數在其他情況下探索長字符串的特征。
動態分配可以使用任何函數來分配內存。事實上,系統最終調用GetMem。其他New、AllocMem、SetLength等。只需要做壹些除了調用GetMem之外的初始化,比如清空內存。釋放可以通過Dispose或者FreeMem來完成,系統最終會調用FreeMem。Dispose相當於Finalize(p)。FreeMem(p);
Finalize的作用僅僅是自動釋放結構或數組中的字符串和動態數組,而FreeMem則是直接釋放指針指向的內存,例如:
類型
TMyRec =記錄
名稱:字符串;
x,Y:整數;
結束;
pmy rec = ^tmyrec;
定義變量
MyRec:pmy rec;
開始
新(MyRec);//編譯器會根據MyRec的大小自動計算出要分配的內存量,然後生成代碼調用GetMem並清除其中的Name字段。
邁雷克。name:= str 1+str 2;
處置(MyRec);//除了調用FreeMem釋放MyRec結構的內存,還會自動清空Name使用的內存(如果Name指向的字符串引用計數= 1);
//FreeMem(my rec);& lt-如果直接調用FreeMem釋放MyRec,會造成內存泄漏,因為MyRec指向的字符串。名稱未發布(參考計數-1)。
結束;
由於delphi對字符串內存管理的特殊性,有很多技巧可以充分利用其優勢生成非常高效的代碼,比如使用TList保存string(不是TStringList),壹般的做法是在TList中保存壹個PString指針。Items[i],所以需要重新分配壹塊內存,復制原來的字符串。在數據量很大的情況下效率很低,但如果充分利用string的引用計數和強制類型轉換技巧,可以直接將string保存為TList中的指針。項目[i]:例如:
定義變量
list:TList;
GlobalString1,GlobalString2:字符串;
...
程序測試;
定義變量
tmp:字符串;
開始
tmp:= global string 1+global string 2;
列表。Add(指針(tmp));//將tmp作為指針保存到列表中。
{由於tmp會在測試過程結束時自動釋放,如果直接退出,列表中會保存壹個無效指針,所以需要欺騙編譯器認為tmp已經釋放,相當於在不改變tmp引用計數(目前為1)的情況下,執行壹個相當於tmp: = ' '的語句。因為直接tmp := ' '會修改引用計數,可能會釋放內存,所以我們使用強制類型轉換將tmp轉換為整數,並將這個整數設置為0(即nil)。這個語句完全等價於指針(tmp):= nil;只是個人喜好。我喜歡用整數(tmp) := 0。
}
整數(tmp):= 0;
結束;
1.string是Delphi編譯器預定義或內置的,是Delphi的壹個基本數據類型,而PChar只是壹個指向以零結尾的字符串的指針。
2.字符串的內存是在堆中分配的,字符串變量實際上是指向以零結尾的字符串的指針。同時,它還具有引用計數的功能,並且它保存字符串本身的長度,當引用計數為零時自動釋放所占用的空間。
3.將壹個字符串賦給另壹個字符串只是簡單的指針賦值,不產生復制動作,只是增加了該字符串的引用計數;
4.將PCCAR變量類型賦給string變量類型會產生真正的復制動作,即PCCAR指向的整個字符串都會被復制到為string分配的內存中;
5.將string賦給PCCAR變量類型只是將string的指針值賦給PCCAR變量類型,string的引用計數不會因為這個操作而改變,因為在這種情況下PCCAR將依賴於string。當string的引用計數為零時,PCCAR很可能會指向壹個無效的內存地址,所以在程序中必須小心處理這種情況。
6.PCCar的運算速度比string快很多,但是PCCar是壹種落後的管理string的方式,string以高效的管理取勝。PCCar的存在只是為了兼容早期的類型和操作系統(調用Windows API時經常用到),所以建議正常使用string。