Delphi 中的字串——《Delphi6 開發人員指南》讀書筆記 (轉)

amyz發表於2007-11-25
Delphi 中的字串——《Delphi6 開發人員指南》讀書筆記 (轉)[@more@]

中的字串

——《Delphi6 開發人員指南》讀書筆記

Spacesoft【暗夜狂沙】

Delphi 對字串這個結構的支援是十分豐富的,不僅有Delphi 本身支援的string 型別,還支援和C 語言相容的字串陣列。那麼他們之間有什麼區別呢?本文試圖就此做一個詳細的剖析,並且試圖回答論壇上常見的幾個問題。

首先,我們要討論的是Delphi 中的string 到底是什麼東東,因為我們在OP 的語法參考手冊上知道,Delphi 有三種string : ShortString、AnsiString (就是相容BCB 的AnsiString 的那種)以及WString (用來支援Unicode 的,其他的和AnsiString 是完全一樣的)。在預設的情況下,將把string 解釋為AnsiString ,而需要的時候,可以設定使編譯器將string 解釋為ShortString(當然,我們很少見到人要這麼做,因為AnsiString 用得很好,而ShortString 只支援255 個字元,並且和C 語言的字串陣列不相容,這個我們後面會提到),對應的編譯器指示符號是預設的{$H+} 和 {$H-}。

那麼,這三種string 到底有什麼差別呢?差別在於他們的不同結構。

對於ShortString 來說,它在中的結構可以表示為:

strShort = record
  wLength: ;
  szBuf: array of Char;
end;

也就是說,對ShortString 的每次操作,不需要遍歷這個字串便可以取得這個字串的大小了。另外,這個字串陣列的最後是沒有一個#0 字元作為結尾的。因此ShortString 的陣列和C 語言的字串是不相容的。


對於AnsiString 來說,根據《Delphi6 開發人員指南》的描述,它的結構可以表示如下:

strAnsi = record
  nSize: Integer;
  nRef: Integer; 
  nLength: Integer;
  szBuf: array of Char;
end;

也就是說AnsiString 不僅僅儲存了本字串的長度,還儲存了對這個字串的引用數。因此,不同的AnsiString 事實上可能具有相同的實體地址,所以Delphi 的字串複製經常是驚人的。然而,當一個字串改變時怎麼辦呢?Delphi 將釋放對這個字串的引用(把nRefCount 減1),然後新建一個字串來裝載新的內容。

另外,與ShortString 不同的是,AnsiString 的字元陣列是以#0 結尾的,這樣szBuf 就可以被當成C 中的字元陣列了。估計這樣的設計是為了相容 。

WideString 和AnsiString 基本上是一樣的,所以我們討論的時候,僅僅討論AnsiString 和ShortString。

然後, 我們要說明的是,string 具有生存期管理特性。就是說,當string 超出作用域後,字串佔用的資源自動被釋放掉。這個機制是怎麼實現的呢?對於全域性變數,當然它的作用域就是它所在的Unit 的生存週期,那麼當然可以在finalization 段進行釋放了。而中的變數呢?編譯器就自動在整個函式的外面套上一個try ... finally 處理。這樣,不管什麼情況下,這些變數就總能釋放掉了。

下面一個問題是:假如現在有一個指標,它被指向一個string, 那麼它指向的東西是什麼呢?

答案是:指向szBuf 的開頭,並且ShortString 不支援這樣的強制轉換(這是很自然的,因為做這樣轉化的目的純粹就是為了和C 語言相容)。於是string[1] 就是這個字串的第一個字元。於是,“看起來”這個字串就只有那些字元了。於是,我們就可以用PChar() 強制型別轉化把一個string 轉化為一個PChar,然後傳到需要字元陣列作為引數的Win32 API 中,因為“看起來” 這個字串這個時候確實是一個標準的C 語言字串。甚至,我們可以這樣使用字串:

procedure Test();
var
  strBuf: string;
begin
  SetLength(strBuf, 255);  //千萬不要忘了先給你的字串申請足夠的空間,不然……嘿嘿,等著彈框兒吧^_^
  GetModuleFileName(0, PChar(strBuf), 255);
  ShowMessage(strBuf);
end;

我們知道,這個API 的原型是這樣的:

DWORD GetModuleFileName(
  HMODULE hModule,  // handle to module
  LPTSTR lpFilename,  // Pointer to a buffer that receives the fully-qualified path for the module
  DWORD nSize  // size of buffer
);

這個API居然以為我們真的根據它的要求傳了一個array of Char 進去!

透過類似這樣的機制,Delphi 使它的使用者在C 語言寫的API 原型中可以自由的穿行,一點都沒有“二等公民”的感覺,而且又可以享用Delphi 本身的資料型別帶來的方便,了不起!

現在我們來總結一下Delphi 中字串型別的特點:

1、與C 語言不同,不是依靠#0 字元做字串結束標誌,而是透過字串前面的數字來記錄字串的長度(儘管為了和C 語言相容,AnsiString 的字串實現確實是在結尾放了一個#0 字元的)
2、string 是具有生存期管理特性的,當string 超出作用域後,字串佔用的資源自動被釋放掉。
3、對字串進行強制型別轉化為PChar ,會得到一個字串指標,這個字串指標的用法和C 語言中的陣列是一樣的。
對於
  strBuf: string;

  SetLength(strBuf, 255);
你在使用時,可以把PChar(strBuf) 看作是C 語言裡的這樣一個東西:

  char strBuf[255];

記住,在上個宣告裡strBuf 是一個指標,它指向那個指標陣列的第一個字元的地址,而你做PChar(strBuf) 運算得來的指標就是這個東西。

在最後,解釋一下《開發人員指南》裡面引起過一段爭論的一句話:

“在練習將一個字串轉換為PChar型別時要小心,因為字串在超出其作用範圍時有自動回收的功能,因此當進行P:=PChar(Str)的賦值時,P的作用域(生存期)要比Str 長。”

這句話的意思是這樣的:字串型別是具有自動回收功能的,但是字串指標沒有。P:=PChar(Str)返回的指標可能在作用域之外使用,因此P 的生存週期可能比Str 要長。舉個例子來說明:


procedure getPChar(P: PChar);
var
  strTmp: string;
begin
  strTmp := 'aass';
  P := PChar(strTmp);
end;

這個時候,明顯返回的這個指標P 的作用域要大於strTmp,那麼這個過程結束的時候,strTmp已經被自動釋放掉了。這個過程的函式得到的P 事實上已經是一個懸掛指標,沒有意義了。作者的本意是提醒讀者注意防止這樣的情況發生。

參考文獻:

1、《Delphi6 開發人員指南》,Steve Teixeira 等著,龍勁松等譯,機械工業出版社,北京,年1月第一版

2、《 Pascal 語言參考手冊》

歡迎光臨作者的個人主頁:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-984617/,如需轉載,請註明出處,否則將追究法律責任。

相關文章