<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 第七章 ## 字符串操作 Delphi 中字符串的操作很簡單,但幕后情況卻相當復雜。Pascal 傳統的字符串操作方法與Windows 不同,Windows吸取了C語言的字符串操作方法。32位Delphi中增加了長字符串類型,該類型功能強大,是Delphi 確省的字符串類型。 ### 字符串類型 在Borland公司的Turbo Pascal和16位Delphi中,傳統的字符串類型是一個字符序列,序列的頭部是一個長度字節,指示當前字符串的長度。由于只用一個字節來表示字符串的長度,所以字符串不能超過255個字符。這一長度限制為字符串操作帶來不便,因為每個字符串必須定長(確省最大值為255),當然你也可以聲明更短的字符串以節約存儲空間。 字符串類型與數組類型相似。實際上一個字符串差不多就是一個字符類型的數組,因為用[]符號,你就能訪問字符串中的字符,這一事實充分說明了上述觀點。 為克服傳統Pascal 字符串的局限性,32位Delphi增加了對長字符串的支持。這樣共有三種字符串類型: ShortString 短字符串類型也就是前面所述的傳統 Pascal 字符串類型。這類字符串最多只能有255個字符,與16位Delphi中的字符串相同。短字符串中的每個字符都屬于ANSIChar 類型(標準字符類型)。 ANSIString長字符串類型就是新增的可變長字符串類型。這類字符串的內存動態分配,引用計數,并使用了更新前拷貝(copy--on-write)技術。這類字符串長度沒有限制(可以存儲多達20億個字符!),其字符類型也是ANSIChar 類型。 WideString 長字符串類型與ANSIString 類型相似,只是它基于WideChar 字符類型,WideChar 字符為雙字節Unicode 字符。 ### 使用長字符串 如果只簡單地用String定義字符串,那么該字符串可能是短字符串也可能是ANSI長字符串,這取決于$H 編譯指令的值,$H+(確省)代表長字符串(ANSIString 類型)。長字符串是Delphi 庫中控件使用的字符串。 Delphi 長字符串基于引用計數機制,通過引用計數追蹤內存中引用同一字符串的字符串變量,當字符串不再使用時,也就是說引用計數為零時,釋放內存。 如果你要增加字符串的長度,而該字符串鄰近又沒有空閑的內存,即在同一存儲單元字符串已沒有擴展的余地,這時字符串必須被完整地拷貝到另一個存儲單元。當這種情況發生時,Delphi運行時間支持程序會以完全透明的方式為字符串重新分配內存。為了有效地分配所需的存儲空間,你可以用SetLength 過程設定字符串的最大長度值: SetLength (String1, 200); SetLength 過程只是完成一個內存請求,并沒有實際分配內存。它只是把將來所需的內存預留出來,實際上并沒有使用這段內存。這一技術源于Windows 操作系統,現被Delphi用來動態分配內存。例如,當你請求一個很大的數組時,系統會將數組內存預留出來,但并沒有把內存分配給數組。 一般不需要設置字符串的長度,不過當需要把長字符串作為參數傳遞給API 函數時(經過類型轉換后),你必須用SetLength 為該字符串預留內存空間,這一點我會在后面進行說明。 ### 看一看內存中的字符串 為了幫你更好地理解字符串的內存管理細節,我寫了一個簡例StrRef 。在程序中我聲明了兩個全程字符串:Str1 和 Str2,當按下第一個按鈕時,程序把一個字符串常量賦給第一個變量,然后把第一個變量賦給第二個: Str1 := 'Hello'; Str2 := Str1; 除了字符串操作外,程序還用下面的StringStatus 函數在一個列表框中顯示字符串的內部狀態: function StringStatus (const Str: string): string; begin Result := 'Address: ' + IntToStr (Integer (Str)) + ', Length: ' + IntToStr (Length (Str)) + ', References: ' + IntToStr (PInteger (Integer (Str) - 8)^) + ', Value: ' + Str; end; 在StringStatus 函數中,用常量參數傳遞字符串至關重要。用拷貝方式(值參)傳遞會引起副作用,因為函數執行過程中會產生一個對字符串的額外引用;與此相反,通過引用(var)或常量(const)參數傳遞不會產生這種情況。由于本例不希望字符串被修改,因此選用常量參數。 為獲取字符串內存地址(有利于識別串的實際內容也有助于觀察兩個不同的串變量是否引用了同一內存區),我通過類型映射把字符串類型強行轉換為整型。字符串實際上是引用,也就是指針:字符串變量保存的是字符串的實際內存地址。 為了提取引用計數信息,我利用了一個鮮為人知的事實:即字符串長度和引用計數信息實際上保存在字符串中, 位于實際內容和字符串變量所指的內存位置之前,其負偏移量對字符串長度來說是-4(用Length 函數很容易得到這個值),對引用記數來說是-8。 不過必須記住,以上關于偏移量的內部信息在未來的Delphi版本中可能會變,沒有寫入正式Delphi文檔的特性很難保證將來不變。 通過運行這個例子,你會看到兩個串內容相同、內存位置相同、引用記數為2,如圖7.1中列表框上部所示。現在,如果你改變其中一個字符串的值,那么更新后字符串的內存地址將會改變。這是copy-on-write技術的結果。 ![](https://box.kancloud.cn/37ae0072fd75f33a0482d7884149fe2f_481x252.png) **圖 7.1: 例StrRef顯示兩個串的內部狀態,包括當前引用計數** 第二個按鈕(Change)的OnClick 事件代碼如下,結果如圖7.1列表框第二部分所示: ~~~ procedure TFormStrRef.BtnChangeClick(Sender: TObject); begin Str1 [2] := 'a'; ListBox1.Items.Add ('Str1 [2] := ''a'''); ListBox1.Items.Add ('Str1 - ' + StringStatus (Str1)); ListBox1.Items.Add ('Str2 - ' + StringStatus (Str2)); end; ~~~ > 注意,BtnChangeClick 只能在執行完BtnAssignClick 后才能執行。為此,程序啟動后第二個按鈕不能用(按鈕的Enabled 屬性設成False);第一個方法結束后激活第二個按鈕。你可以自由地擴展這個例子,用StringStatus 函數探究其它情況下長字符串的特性。 ### Delphi 字符串與 Windows PChar字符串 長字符串為零終止串,這意味著長字符串完全與Windows使用的C語言零終止串兼容,這給長字符串使用帶來了便利。一個零終止串是一個字符序列,該序列以一個零字節(或null)結尾。零終止串在Delphi中可用下標從零開始的字符數組表示,C語言就是用這種數組類型定義字符串,因此零終止字符數組在Windows API 函數(基于C語言)中很常見。由于Pascal長字符串與C語言的零終止字符串完全兼容,因此當需要把字符串傳遞給Windows API 函數時,你可以直接把長字符串映射為PChar 類型。 下例把一個窗體的標題拷貝給PChar 字符串(用API 函數GetWindowText),然后再把它拷貝給按鈕的Caption 屬性,代碼如下: ~~~ procedure TForm1.Button1Click (Sender: TObject); var S1: String; begin SetLength (S1, 100); GetWindowText (Handle, PChar (S1), Length (S1)); Button1.Caption := S1; end; ~~~ 你可以在例LongStr 中找到這段代碼。注意:代碼中用SetLength函數為字符串分配內存,假如內存分配失敗,那么程序就會崩潰;如果你直接用PChar 類型傳遞值(而不是象以以上代碼那樣接受一個值),那么代碼會很簡單,因為不需要定義臨時字符串,也不需要初始化串。下面代碼把一個Label(標簽)控件的Caption 屬性作為參數傳遞給了API函數,只需要簡單地把屬性值映射為PChar類型: `SetWindowText (Handle, PChar (Label1.Caption));` 當需要把WideString 映射為Windows兼容類型時,你必須用PWideChar 代替PChar進行轉換,WideString常用于OLE和 COM 程序。 剛才展現了長字符串的優點,現在談談它的弊端。當你把長字符串轉換為PChar 類型時可能會引發一些問題,問題根本在于:轉換以后字符串及其內容將由你來負責,Delphi 不再管了。現在把上面Button1Click代碼稍作修改: ~~~ procedure TForm1.Button2Click(Sender: TObject); var S1: String; begin SetLength (S1, 100); GetWindowText (Handle, PChar (S1), Length (S1)); S1 := S1 + ' is the title'; // this won't work Button1.Caption := S1; end; ~~~ 程序編譯通過,但執行結果會令你驚訝,因為按鈕的標題并沒變,所加的常量字符串沒有添加到按鈕標題中。問題原因是Windows寫字符串時(在GetWindowText API調用中),Windows 沒有正確設置Pascal 長字符串的長度。Delphi 仍可以輸出該字符串,并能通過零終止符判斷字符串何時結束,但是如果你在零終止符后添加更多的字符,那么這些字符將被忽略。 怎么解決這個問題呢?解決方法是告訴系統把GetWindowText API函數返回的字符串再轉換成Pascal字符串。然而,如果你用以下代碼: `S1 := String (S1);` Delphi 系統將不予理睬,因為把一種類型轉換為它自己的類型是無用的操作。為獲得正確的Pascal 長字符串,需要你把字符串重新映射為一個PChar 字符串,然后讓Delphi 再把它轉回到字符串: `S1 := String (PChar (S1));` 實際上,你可以跳過字符串轉換(S1 := PChar (S1));, 因為在Delphi中Pchar轉換到string是自動執行的,最終代碼如下: ~~~ procedure TForm1.Button3Click(Sender: TObject); var S1: String; begin SetLength (S1, 100); GetWindowText (Handle, PChar (S1), Length (S1)); S1 := String (PChar (S1)); S1 := S1 + ' is the title'; Button3.Caption := S1; end; ~~~ 另一個辦法是用PChar 字符串的長度重新設定Delphi 字符串長度,可以這樣寫: `SetLength (S1, StrLen (PChar (S1)));` 在例LongStr中你可以看到三種方法的結果,分別由三個按鈕執行。如果只想訪問窗體標題,僅需要用到窗體對象本身的Caption 屬性,沒有必要寫這段迷糊人的代碼,這段代碼只是用來說明字符串轉換問題。當調用Windows API 函數時會遇到這種實際問題,那時你就不得不考慮這一復雜情況了。 ### 格式化字符串 使用加號(+)操作符和轉換函數(如IntToStr),你確實能把已有值組合成字符串,不過另有一種方法能格式化數字、貨幣值和其他字符串,這就是功能強大的Format 函數及其一族。 Format 函數參數包括:一個基本文本字符串、一些占位符(通常由%符號標出)和一個數值數組,數組中每個值對應一個占位符。例如,把兩個數字格式化為字符串的代碼如下: `Format ('First %d, Second %d', [n1, n2]);` 其中n1和n2是兩個整數值,第一個占位符由第一個值替代,第二個占位符由第二個值替代,以此類推。如果占位符輸出類型(由%符號后面的字母表示)與對應的參數類型不匹配,將產生一個運行時間錯誤,因此設置編譯時間類型檢查會有利于Format 函數的使用。 除了%d外,Format 函數還定義了許多占位符,見表7.1。這些占位符定義了相應數據類型的默認輸出,你可以用更深一層的格式化約束改變默認輸出,例如一個寬度約束決定了輸出中的字符個數,而精度約束決定了小數點的位數。例如 `Format ('%8d', [n1]);` 該句把數字n1轉換成有8個字符的字符串,并通過填充空白使文本右對齊,左對齊用減號(-) 。 **表 7.1: Format函數的占位符 ** |占位符 |說明 | |---|---| |d (decimal) |將整型值轉換為十進制數字字符串 | |x (hexadecimal) |將整型值轉換為十六進制數字字符串| |p (pointer) |將指針值轉換為十六進制數字字符串 | |s (string) |拷貝字符串、字符、或字符指針值到一個輸出字符串 | |e (exponential) |將浮點值轉換為指數表示的字符串| |f (floating point)| 將浮點值轉換為浮點表示的字符串 | |g (general)| 使用浮點或指數將浮點值轉換為最短的十進制字符串 | |n (number) |將浮點值轉換為帶千位分隔符的浮點值 | |m (money)| 將浮點值轉換為現金數量表示的字符串,轉換結果取決于地域設置,詳見Delphi幫助文件的Currency and date/time formatting variables主題 | 領會以上內容最好的辦法是你親自進行字符串格式化試驗。為了簡便起見,我寫了FmtTest 程序,它能將整數和浮點數轉換為格式化字符串。從圖7.2可見,程序窗體分為左右兩部分,左邊對應整型數字轉換,右邊對應浮點數轉換。 各部分的第一個編輯框顯示需要格式化為字符串的數值。第一個編輯框下方有一個按鈕,用來執行格式化操作并在消息框中顯示結果;緊接著第二個編輯框用于輸入格式化類型串。你也可以單擊ListBox 控件中的任一行,選擇預定義的格式化類型串,也可以自行輸入,每輸入一個新的格式化類型串,該類型串就會被添加到列表框中(注意,關閉程序就失去了添加的類型)。 ![](https://box.kancloud.cn/db0f725fafed31e66594e45cc81f4ff5_406x370.png) **圖 7.2: 程序 FmtTest 的浮點值輸出** 本例只簡單使用了不同的控制文本來產生輸出,下面列出了其中一個Show 按鈕事件代碼: ~~~ procedure TFormFmtTest.BtnIntClick(Sender: TObject); begin ShowMessage (Format (EditFmtInt.Text, [StrToInt (EditInt.Text)])); // if the item is not there, add it if ListBoxInt.Items.IndexOf (EditFmtInt.Text) < 0 then ListBoxInt.Items.Add (EditFmtInt.Text); end; ~~~ 這段代碼主要用EditFmtInt 編輯框的文本和EditInt 控件的值進行了格式化操作。如果格式化類型串沒有在列表框中列出,那么輸入的串會被添加到列表框中;如果用戶在列表框中進行點擊,代碼會把點擊的串移到編輯框中: ~~~ procedure TFormFmtTest.ListBoxIntClick(Sender: TObject); begin EditFmtInt.Text := ListBoxInt.Items [ ListBoxInt.ItemIndex]; end; ~~~ * * * * * ### 結束語 字符串是一種很常用的數據類型,盡管在很多情況下不理解字符串怎樣工作也能安全使用它們,不過通過本章,了解了字符串的內部運行機制之后,你就能更充分地利用字符串類型的強大功能。 Delphi用特殊的動態方式處理字符串內存,正如動態數組一樣,這將在下一章進行討論。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看