網頁標題: 16 字串與二進位資料 (1): 基礎字串處理

Warning: fopen(/home/crazy/www/cmsb/bcj/has_read.php): failed to open stream: Permission denied in /home/crazy/www/compose/reading.php on line 2077

Warning: fputs() expects parameter 1 to be resource, bool given in /home/crazy/www/compose/reading.php on line 2079

Warning: fclose() expects parameter 1 to be resource, bool given in /home/crazy/www/compose/reading.php on line 2080
 
﹗﹗﹗觀看留言:此文章已經有1則留言 ﹗﹗﹗


 本單元介紹字串與二進位資料的處理,其實電腦裡的資料都是二進位 (binary), 經由不同的解釋方式變成資料型態裡的整數、浮點數、字串等等。本文先介紹基礎字串處理,因為它較直覺,且應用機會甚多,幾乎每個程式都需要處理字串。二進位資料的處理,就是字串處理概念的延伸。之後會再介紹正規表達式 (regular expression), 以及二進位資料的處理、、字串與二進位資料間的轉換等。

 其實在過去使用 MsgBox 輸出或以 InputBox 輸入,所交換的資料都是「字串」,當把一個表達式計算結果賦予 MsgBox 的視窗標題、內容參數,或者計算結果間進行連接 (&) 操作等場合,計算結果都會先被隱含轉型成字串,才繼續應有的操作。AutoIt 裡,把任何表達式代入 String() 函式當參數,可以明確要求轉型為字串,如 String(7) 將傳回字串 "7".

 「字串」由一串連續的「字元」構成,一個「字元」可能是一個中文字、一個半形或全形的英數符號、希臘字母、日文假名等。每個字元在電腦裡都有自己專屬的編號,稱為 Unicode, 在一個考慮有多國語言用戶的系統中,通常採用 Unicode 做為程式內部暫存字元、字串的方式。


 既然是連續的字元,字串就很類似「字元的一維陣列」,會有長度,也可以跟真正的字元一維陣列間轉換,如下例。

 StringLen 求出字串長度,也就是字串裡有幾個字元,空字串 ("") 的長度是 0, 字串的最大長度是 2147483647 = 231-1 個字元。範例中 $str1 由五個字母組成,長度即為 5. 注意,此處「長度」並不一定等於打字在 .txt 檔儲存後的位元組數,詳情請見後面對於「編碼」的解釋。

 StringToASCIIArray 概念上將字串轉成字元陣列,不過陣列裡的「字元」是以它的「編號」來儲存的。此例輸出的 [77, 69, 76, 79, 78] 分別為大寫 MELON 五個字母的 Unicode 值。有興趣的讀者可以把它改成中文或其他符號看看答案。之後,有了編碼的知識,更可以指定編碼,讓它對同樣輸入字串輸出不一樣的陣列。

 StringCompare 用來比較兩個字串,雖然前面提過可用關係運算符號來比較,但此函式針對比較細節上有較大彈性。前二個參數就是被比較二個字串,第三個可選參數用來挑選比較方式,共三種。預設為 $STR_NOCASESENSE, 大小寫視為一樣的比較方式,$STR_CASESENSE 表示大小寫視為不同,$STR_NOCASESENSEBASIC 也是大小寫視為一樣,但實作較有效率。三個常數都來自 StringConstants.au3, 這個檔案裡也定義之後許多本文介紹函式所用的常數。

 比較結果以 0 為基準分成三類。恰為 0 表示兩字串「相等」,正值表示 $str1 「大於」 $str2, 負值則是「小於」。此處的大於、小於跟比較運算符號在字串的作用原則相同。可以這樣記住 StringCompare 的回傳值:它就像 $str1 減去 $str2, 當雙方一樣答案即為 0, $str1 較大則答案是正的,較小則答案為負。

 範例中二個字串只差在大小寫,所以兩次不分大小寫的比較都得答案 0. 區分大小寫該次比較得負,因為小寫字母的 Unicode 編號比大寫字母大,所以 $str2 比 $str1 大。

 另外有個函式 StringFromASCIIArray 未被示範,它是 StringToASCIIArray 的反向操作,把整數陣列每個元素解釋成 Unicode 編號轉出對應字元再組成字串。


 很多場合,對字串的評估不是整體,而是針對其中感興趣的某部份。對於一部份的字串稱之為「子字串」,這個稱呼是相對的,必須有一個完整字串 $str1, 才會說字串 $str2 是 $str1 的子字串。更嚴謹地說,「子字串」是原本字串裡任何地方截取下來的一段,如果截取範圍是整個字串本身,截取出來的整個字串也算它的「子字串」。

 注意:空字串是任何字串的子字串,也就是截取範圍是 0 個字元時的結果。

 下例示範二種簡單的子字串產生方式,一是以字串中的字元位置為依據,二是根據條件直接「拆散」整個字串而產生元素都是子字串的陣列。

 StringLeft, StringRight 跟 StringMid 都是依照「位置」來決定截取範圍。StringLeft 截取字串左起指定長度的部份做為子字串,在姓名衷取左邊一個字就是「姓」,StringRight 則從右取,姓名中右取二個字就是「名」。這二個函式第二個參數都是要截取子字串的長度。

 StringMid 是根據位置決定範圍的一般狀況,第二個參數是截取範圍由左邊算起的位置,第三個參數是要截取的長度。計算「位置」十,注意要把最左邊的字當成位置 1, 範例中指定位置 2 就是落在「小」,第二個字的位置。

 如果想訪問字串 $name 裡每個「字元」,除了 StringToASCIIArray, 也可用 StringMid, 在迴圈 For $i = 1 To StringLen($name) 中,取出 StringMid($name, $i, 1) 當作一個字元。在此,「字元」被當成長度 1 的子字串。

 StringSplit 直接把原本字串拆成多個子字串,形成陣列而回傳。第二個參數是用來分開每個子字串的依據,第三個可選參數控制函式行為。第二個參數「分隔符」是一個字串,可能每個字都是分隔依據,或者整段字串的出線財會造成原本字串拆解,實際行為由第三個參數決定。

 註:分隔符在字串中的比對是區分大小寫的。

 如果將分隔符設為「小」,「王小明」會被拆成「王」跟「明」,形成陣列 ["王", "明"], 然而根據之前提過的陣列使用習慣,AutoIt 預設會在陣列第一個元素放置意義上回傳的陣列長度,所以沒有指定第三個參數,會收到 [2, "王", "明"]. $STR_NOCOUNT 定義在 StringConstants.au3, 要求 StringSplit 不要多出這第一個整數元素。

 範例中分隔符設為空字串是 StringSplit 的特別用法,會直接把原本字串變成每個元素都是「字元」的陣列,從印出的陣列可知,三個元素都是長度 1 的字串,也就是所謂的「字元」。有興趣的讀者可以挑戰用 StringSplit 訪問字串裡所有字元。另一個極端狀況,分隔符不是原本字串的子字串,則原字串將全數保留在陣列中傳回,也就是陣列除了記錄長度的元素之外就剩下一個元素裡裝著整串輸入字串。

 第三個參數中未示範的選項是 $STR_CHRSPLIT 與 $STR_ENTIRESPLIT, 前者是預設,分隔符內每個字都當分隔依據,後者表示整串分隔符才是分隔依據,不過對於長度未達 2 的分隔符來說,這個選項沒有意義。該選項可和 $STR_NOCOUNT 配合用 BitOR 做獨立的選擇組合。

 以下再示範子字串的搜尋。有些時候子字串的出現代表這個字串含有某些特徵,比如 DNA 序列的分析。例子中由 internationalization 這個單字中找出 tion 字根及四個 n 出現的位置。

 StringInStr 有六個參數,不過範例只著重於前二個與第四、五個參數。前二個參數分別是被搜尋的字串及要搜尋的子字串。第三個可選參數指定是否區分大小寫,填寫規則跟 StringCompare 相同,預設也是無區分大小寫。其他參數稍後再逐一介紹。

 StringInStr 傳回子字串第一個字元出現在被搜尋字串中的位置,依然以第一個字為位置 1, 如果沒有找稻子字串則傳回 0. 第一個 MsgBox 會印出 8, 就是 internationalization 在位置 8 開始出現 tion 子字串。這樣的回傳值設計還有個優點,可以直接把它當布林判斷來用,比如 If StringInStr($word, "tion") Then 的寫法,要是 tion 子字串有出現,就因為 If 收到非 0 的整數值,轉為布林值的 True 而執行下去,反之則整數 0 被轉為 False 而越過 If 下面的敘述。

 StringInStr 第四個參數指定子字串出現的次數,如第二個 MsgBox 要求印出第二次出現的 tion 在哪裡,答案是 17 不是 8. 要是故意指定 3 以上超出 tion 出現的次數,就會當成「沒有找到」而傳回 0. 要是這個參數指定為負值,則變成從右邊找起,以本例而言給 -1 將傳回 17.

 接下來以 While 迴圈示範如何找出所有子字串出現的地方。這段要搜尋的子字串變成 n, 共出現四次。首先示範有追蹤次數的循環搜尋,追蹤次數用的變數 $i 從 1 開始,當成 StringInStr 的第四個參數,然後每圈增加 1. 要是 $pos 接到的傳回值是 0 表示結束而離開迴圈,否則就印出搜尋到的位置 $pos. 於是,四次 n 的出現就會依序被印出。有興趣的讀者可以把 StringInStr 第四個參數改成 -$i 看看,讓它把四個位置倒著印。

 但有時候我們也不會這麼在意目前第幾次搜尋了,就利用第五個參數來指定從字串中哪裡開始搜尋,比如指定 3 就只在 ternationalization 裡搜尋 n, 第一次就會搜尋到位置 6 的 n, 雖然在 ternationalization 裡 n 出現的位置是 4, 但是傳回的位置仍以原本輸入字串 internationalization 為準。

 每次呼叫 StringInStr 都以 $pos + 1 填入第五個參數,這樣做的用意是「跳過」上次出現的 n, 找下次 n 出現位置。傳回結果又存入 $pos, 就是下次 n 的位置。$pos 初始為 0 的用意,就是當首次執行 StringInStr 時 $pos + 1 恰好就是 1, 從 internationalization 的頭開始找。因此,如果被搜尋的子字串改成長度 4 的 tion, $pos 就要從 -3 開始,每次呼叫 StringInStr 都用 $pos + 4 開始找下一個 tion. 有興趣的讀者可以自己推理任意長度的子字串對應的 $pos 初始值與 StringInStr 第五個參數的寫法。

 StringInStr 第六個參數要跟第五個參數合作使用,如果第五、第六個參數分別是 $p 與 $q, 則等價於把 StringMid("internationalization", $p, $q) 的片斷拿去搜尋,傳回結果仍是基於整個字串而非片段的位置,這種用法能有效界定字串被搜尋的範圍,也就是「在子字串中找子字串」了。


 下一個主題是字串的格式化處理,有意義的字串通常也有一定的格式,比如日期、時間、地址等。格式化處理又包含二個項目,格式的比對跟修改,而「格式」的定義也可以分為簡單的規則跟「正規表達式」兩種方法。「比對」型函式都會傳回布林值,表示參數指定的字串是否符合函式所檢查的特徵,「修改」型函式會把輸入字串依據函式本身指定的功能跟其他參數指定的特徵做出修改後的結果並傳回。

 基於簡單規則的格式比對、修改的功能,多為文字敘述就能理解其意思,因此下面即以表格列出相關函式及其功能。不過有三個函式,分別是 StringFormat, StringReplace, 與 StringStripWS 將特別說明其用法。

函式名稱公能描述
StringAddCR在字串的全部 Line Feed (@LF) 前加上 Carriage Return (@CR)
StringIsAlNum檢查字串是否僅包含字母或數字
StringIsAlpha檢查字串是否僅包含字母
StringIsASCII檢查字串是否僅包含 ASCII 字元,也就是 Unicode 0 到 127
StringIsDigit檢查字符串是否僅包含數字
StringIsFloat檢查字串是否可以轉為浮點數
StringIsInt檢查字串是否可以轉為整數
StringIsLower檢查字串是否僅包含小寫字母
StringIsSpace檢查字串是否僅包含空白字元
StringIsUpper檢查字串是否僅包含大寫字母
StringIsXDigit檢查字串是否僅包含十六進制數字字元 (0-9, A-F)
StringLower字串包含字母的部分都轉成小寫
StringStripCR刪除字串的所有 Carriage Return (@CR)
StringTrimLeft刪除字串左起指定數量的字元
StringTrimRight刪除字串右起指定數量的字元
StringUpper字串包含字母的部分都轉成大寫

 下面有幾點備註:

  1. 全形的英文字母跟數字也會通過 StringIs... 函式的檢查。
  2. 別的國家比如歐洲國家、希臘的語言字母,也會通過 StringIsAlpha 等字母相關的函式檢查,其大小寫也能以 StringIsLower, StringIsUpper 檢查。
  3. 「空白字元」的範圍請見函式連結內的說明,並非所有 Unicode 定義為空白的字元都惠通過檢查。

 「最基本的輸入輸出」提到過 StringFormat 的功能、用法與優點,在此更深入介紹其「格式」的寫法。StringFormat 第一個參數是代表格式的字串,包含一般字串內容跟可變的內容,後面可以跟隨最多 32 個參數,這些參數的值會根據「格式」的指定填入它的可變內容區裡。

 「格式」的內容分成三種情形討論,一般狀況、反斜線跳脫字元跟 % 可變部份。只要不是反斜線跳脫字元跟 % 可變部分的話,其他字元都會直接複製到輸出字串,即一般狀況。反斜線跳脫字元包含 \n (line feed), \r (carriage return) 與 \t (tab), 寫兩個反斜線 \\ 也會被當成「跳脫」自己而等同只有一個反斜線,故此類字元就是這四個。也就是說,這個字串 "\t" 在別的地方是反斜線跟 t 兩個字元,但在 StringFormat 的「格式」裡只當做一個 tab. 不過,要是故意寫 \m 這樣並沒列入跳脫字元的狀況,StringFormat 就會當它是反斜線跟 m, 而不會亂解釋或給予錯誤提醒。

 % 可變部分的寫法是 %[flags][width][.precision]type, 其中有四個部份。一定要寫的是 type, 他表示這個變數將被轉為何種型態,詳細列表可從 StringFormat 的連結裡查閱,較常用的如 %d, %f, %s 分別表示整數、浮點數、字串。浮點數 %f 就是一般所見的小數點寫法,另有二種格式是 %e 跟 %g, %e 是「科學記號」寫法,比如 123000000.1 會被印成 1.230000e+008, 表示 1.23×108. %g 就是 %f 和 %e 選比較短的那種寫法。

 其他可寫可不寫的部分最常用的該是 width, 表示印出「至少」多少字元,要是不足就補空白,如假設參數值是 2016 但格式指定 %6d, 則二個空白會被補上以滿足 width 的限制,要是參數值給 1234567, 則七個數字都被保留在輸出字串而不會少。

 flags 有五種,詳細請見 StringFormat 的參考網頁,比較常用的是半形減號 - 代表「靠左對齊」,也就是有指定寬度又遇到輸出內容不夠長時,輸出從左邊開始,不夠長的部分在右邊補空白,而預設是空白補在左邊。另外 0 表示補零而非空白,比如 %03d 是 flags 指定 0, 寬度指定 3 的有號整數,若要印出 12 則它會印成 012, 剛才未指定靠左對齊因此補零在左邊,但如果補零又指定靠左對齊像 %-03d 會讓輸出變 120 就完全錯誤,所以靠左對齊的話補零就被忽略,照補空白。

 .precision 表示「精確度」,浮點數才需要指定,是指浮點數的數值部份小數點後面要幾位小數。比如 3.14159 用 %.2f 印出來是 3.14, %.4f 印出來是 3.1416, 四捨五入到第四位小數。寬度的指定也必須超過要求的精確度才有意義,以 %.4f 為例,由於印出來是 3.1416, 至少要六位數字,寫 %5.4f 就沒有意義,而 %8.4f 就會有些數值較小的輸出前面被追加空白以滿足寬度。

 注意:如果要在字串中寫 % 必須自我跳脫,也就是「格式」字串寫 %% 實際只印出一個 % 字元。

 再來是 StringReplace, 將字串中特定子字串取代為指定的內容,好像記事本的「取代」功能,不過 StringReplace 也可以針對指定位置的內容來替換。以下提供三個範例:

 StringReplace 有五個參數,第一個是要被替換的字串,第二個參數可以指定子字串或位置,第三個參數是要換成怎樣的內容,後面二個可選參數分別是替換次數與區分大小寫。

 Demo1 指定搜尋 ing 並換成 ed. Demo2 指定把 in 換成兩個底線,但是加入第四個參數,限制只換一次,所以只有字首的 in 被替換。Demo3 指定從位置 9 開始替換,就從第 9 個字母開始往後取代,因為指定替換成兩個底線,所以第 9, 10 個字母被替換,有興趣的讀者可以試著替換成更長內容,另外,這種用法時兩個可選參數均無作用。

 最後是 StringStripWS, 它除了輸入的字串外有第二個參數,指定要被清除的狀況。$STR_STRIPLEADING 和 $STR_STRIPTRAILING 分別要求它清除字串頭跟尾的空白字元,$STR_STRIPSPACES 會把字串中間有二個以上空白字元的地方縮減為一個空白,以上三個選項可用 BitOR 進行多選。指定 $STR_STRIPALL 會把整個字串所有空白字元都去掉,且會蓋過前面三個選項的效果,所以也可以單獨指定它。


 如果有興趣的讀者,可以去看看 String.au3 裡面的函式,比較好理解的有三個:

  1. _StringBetween: 挑出夾在兩特定字串中間的文字,筆如夾在一對括號或引號之間的引文。使用範例如 _StringBetween("介紹「字串」與「二進位」", "「", "」"), 第一個參數是輸入資料,後二個參數指定「介於哪兩者間」。滿足條件的經常不只一個地方,所以傳回的是一個陣列,每個元素就是被夾在第二、三個參數指定的夾注前後記號之間,以上例而言傳回陣列即是 ["字串", "二進位"].
  2. _StringExplode: 把字串分成子字串。行為方向與前二個參數跟 StringSplit 一樣,但是它的第三個參數用來限制傳回的陣列大小。它以整個分隔字串為依據來切開,不像 StringSplit 能彈性調整以分隔字串整串或每個字元為依據。然而 _StringExplode 能控制傳回陣列大小,第三個參數為 $n 時就會最多拆開 $n 次,形成 $n+1 個子字串,可用在只有前 $n 次的分隔字串有效時,比如「根據第一個逗號拆成兩段」。
  3. _StringRepeat: 把字串重複指定次數。比如 _StringRepeat("a", 3) 將傳回 "aaa", 有時候不想寫很多個字串連接記號 & 就用它吧。

回 · 我的 AutoIt 學習筆記 這一篇文章封面


本文張貼者:Bo-Cheng Jhan〔張貼時間:民國106年1月3日(星期二)21點55分 | 更新次數 #1 | 最後更新:民國106年1月18日(星期三)0點47分〕

部落格首頁


學習的故鄉首頁
本站公告:〔您越需要我們,我們就越有創意〕 本站說明書:〔發現故鄉還有改進的地方,請來信告訴原丁們〕
觀察應用學習點數 :〔咱的故鄉有您的參與,會使我們有更大的發揮空間,展現更豐富精彩的學習畫面〕 〔期待藉由無障礙網頁設計,能讓視障小朋友更愛看書、更愛寫作且更愛學習〕:盲用電腦「心得分享」
〔為了讓我們有乾淨的學習環境,請勿任意在本站散播商業廣告與不合法文件或聯結〕:本站宣示