【Lua 筆記】字串(String) - part 8 === 目錄(Table of Contents): [TOC] --- 由於有款遊戲叫做 CSO(Counter-Strike Online),內建模式創世者模式(Studio)新增使用 Lua 及其遊戲的 API,所以突發奇想製作這個筆記。 這個筆記會在一開始先著重純粹的程式設計自學,在最後的章節才會與 CSO 遊戲 API 進行應用。 ※本文篇幅較長,十分複雜,請小心閱讀,請務必收藏!!※ 字串(String) --- 字串(String)是由許多字元(Character)所組成的。 字元(Character):中文字、英文字母(a-z)、底線等等任何符號皆是一種字元。 字串可以用以下三種方式表示: * 成對單引號括起來('String') * 成對雙引號括起來("String") * [[ ]] 裡面放字串 -> 多行字串 第三種方式舉例: ```lua= a = [[ Hi, this is a string aaaaaaaa ]] print(a) ``` 輸出結果: ``` Hi, this is a string aaaaaaaa ``` ### 取得字串長度的另一種方式 --- `string.len(裡面放字串)`,實際上這個方式與用 `#` 這個運算子的方式並沒有什麼區別,但 `string.len()` 的可讀性會比用 `#` 更高。 以下是用兩種方式的比較: ```lua= a = [[ Hi, this is a string aaaaaaaa ]] print(string.len(a)) --> 30 ``` ```lua= a = [[ Hi, this is a string aaaaaaaa ]] print(#a) --> 30 ``` 也可以用 utf8.len 函数: ```lua= local myString = "Hello, 世界!" -- utf8.len() local length1 = utf8.len(myString) print(length1) -- 输出 10 -- string.len() local length2 = string.len(myString) print(length2) -- 输出 14 ``` 註:utf8 是一種編碼,主要針對 Unicode 進行編碼,詳細具體內容筆者推薦看這篇文章:[UTF-8](https://openhome.cc/Gossip/Encoding/UTF8.html) 可以看出當我們字串是中文字時,使用 string.len()(註:使用 `#` 的效果同屬於 string.len())便會使字串長度輸出不準確,這是因為中文字編碼的關係。 string.len() 跟 # 回傳的會是 ASCII 編碼字串的長度,而 utf8.len() 包含中文字與英文。 所以歸結出一個結論: * 一般英文字母(ASCII)用 string.len() 或 # * 中文字與英文字母混合用 utf8.len() ### 跳脫字元(Escape Character) --- 跳脫字元(Escape Character),又稱轉義字元(陸譯)、逃逸字元、逸出字元。是一種能夠針對字串進行特殊操作的字元,例如原本不能在字串裡面顯示單雙引號的字元,所以透過反斜線(`\`)結合單雙引號形成一個跳脫字元,就能夠變成一種能夠出現在字串內的字元。 話說這麼多,跳脫字元其實本身還是一個字元(廢話XD),總之他就是一種特殊的字元,能讓字串的操作變得更加多元、方便。 所以要使用跳脫字元之前,必須要有一個反斜線存在(`\`)。 以下有個表格是關於跳脫字元的使用(表格來源:[Lua 字符串 | 菜鸟教程](https://www.runoob.com/lua/lua-strings.html)): | 跳脫字元 | 意義 | ASCII碼(十進位) | | -------- | -------- | -------- | | \a | 響鈴(BEL:bell) | 007 | | \b | 退格(BS:BackSpace),將目前位置移到前一列(換言之,就是刪除鍵) | 008 | | \f | 換頁(FF:Form Feed),將目前位置移到下頁開頭。 | 012 | | \n | 換行(LF:Line Feed),將目前位置移到下一行開頭。 | 010 | | \r | 回車(CR:Carriage Return),將目前位置移到本行開頭 | 013 | | \t | 水平製表(HT:Horizontal Tab)(跳到下一個 TAB 位置,也就是按一下 tab 縮排) | 009 | | \v | 垂直製表(VT:Vertical Tab) | 011 | | \\ | 代表一個反斜線字元''\' | 092 | | \' | 代表一個單引號字元 | 039 | | \" | 代表一個雙引號字元 | 034 | | \0 | 空字元(NULL) | 000 | | \ddd | 1 到 3 位八進位數所代表的任意字元 | 三位八進位 | | \xhh | 1 到 2 位十六進位所代表的任意字元 | 二位十六進位 | 這個表格呢,等我們真的有需要用到它的功能時,再來看就好了,硬要說一個常用的話,或許就是 \n 了吧。 然後讓我們來練習一下:) ```lua= print("str\ning") print("\"string\"") print("strin\0g") print("st\bring") print("s\ftring") print("strin\rg") ``` 輸出結果: ``` str ing "string" string sring s tring gtrin ``` 不曉得各位有沒有發現,Lua 的 print() 好像會自動幫我們換行欸?如果我不想換行怎麼辦?沒關係,以下這個函數就能夠實現不換行的機制:`io.write()` ### string 的方法(Method) --- 方法(Method)是什麼?這個問題,我們需要牽涉到程式設計當中,一個很重要的概念,稱之為類(Class),通常有 class 的語言,在程式設計上都被稱之為物件導向程式設計(Object-oriented programming)。 而方法其實是一種函數,屬於 class 的一部分,也就是說一個 class 當中可以有很多方法,這些方法都具有不同的功能,目前只要先理解這樣就好。 以下表格來源:[Lua 字符串 | 菜鸟教程](https://www.runoob.com/lua/lua-strings.html) | Number | 方法及用途 | | -------- | -------- | | 1 | **string.upper(argument)** : 字串全部轉為大寫字母。 | | 2 | **string.lower(argument)** : 字串全部轉為小寫字母。 | | 3 | **string.gsub(mainString,findString,replaceString,num)** : 在字串中替換。mainString 為要操作的字串,findString 為被替換的字元,replaceString 要替換的字元,num 替換次數(可以忽略,則全部替換)。 | | 4 | **string.find(str, substr, [init, [plain]])** : 在一個指定的目標字串 str 中搜尋指定的內容 substr,如果找到了一個符合的子字串,就會回傳這個子字串的起始索引和結束索引,不存在則回傳 nil。`init` 指定了搜尋的起始位置,預設為 1,可以是一個負數,表示從後往前數的字元數。`plain` 表示是否使用簡單模式,預設為 false、true,只做簡單的查找子字串的操作,false 表示使用使用正規模式比對。 | | 5 | **string.reverse(arg)** : 字串反轉 | | 6 | **string.format(...)** : 回傳一個類似 printf(C 語言 print 函數,表示格式化)的格式化字串 | | 7 | **string.char(arg) 和 string.byte(arg[,int])** : char 將整數數字轉成字元並連接,byte 轉換字元為整數值(可以指定某個字元,預設第一個字元)。 | | 8 | **string.len(arg)** : 計算字串長度。 | | 9 | **string.rep(string, n)** : 回傳字串 string 的 n 個拷貝 | | 10 | **..** : 連接兩個子字串 | | 11 | **string.gmatch(str, pattern)** : 回傳一個迭代器函數,每次呼叫這個函數,回傳一個在字串 str 找到的下一個符合 pattern 描述的子字串。如果參數 pattern 所描述的字串沒有找到,迭代函數回傳 nil。 | | 12 | **string.match(str, pattern, init)** : string.match() 只尋找來源字串 str 中的第一個配對。參數 init 為可選,指定搜尋過程的起點,預設為 1。在成功配對時,函數將回傳配對運算式中的所有捕獲結果;如果沒有設置捕獲標記,則回傳整個配對字串。當沒有成功的配對時,回傳 nil。 | :::spoiler 點開看以上方法的詳細範例 ```lua= -- 定義一個字串 local str = "Hello, Lua!" -- 1. 將字串轉為大寫 local upperStr = string.upper(str) print("Uppercase: " .. upperStr) -- 2. 將字串轉為小寫 local lowerStr = string.lower(str) print("Lowercase: " .. lowerStr) -- 3. 替換字串中的字元 local replacedStr = string.gsub(str, "Lua", "World") print("Replaced: " .. replacedStr) -- 4. 搜尋字串中的子字串 local startIdx, endIdx = string.find(str, "Lua") if startIdx then print("Found 'Lua' at: " .. startIdx .. " to " .. endIdx) else print("'Lua' not found") end -- 5. 反轉字串 local reversedStr = string.reverse(str) print("Reversed: " .. reversedStr) -- 6. 格式化字串 local formattedStr = string.format("Formatted: %s", str) print(formattedStr) -- 7. 將整數轉為字元,並將字元轉為整數 local charStr = string.char(76, 117, 97) -- 'Lua' print("Char: " .. charStr) local byteVal = string.byte(str, 1) print("Byte of first character: " .. byteVal) -- 8. 計算字串長度 local strLen = string.len(str) print("Length: " .. strLen) -- 9. 回傳字串的多個拷貝 local repeatedStr = string.rep(str, 2) print("Repeated: " .. repeatedStr) -- 10. 連接兩個子字串 local concatenatedStr = str .. " Let's learn Lua!" print("Concatenated: " .. concatenatedStr) -- 11. 使用迭代器函數匹配字串 for word in string.gmatch(str, "%a+") do print("Word: " .. word) end -- 12. 匹配字串中的第一個配對 local matchedStr = string.match(str, "L%a+") print("Matched: " .. matchedStr) ``` 輸出結果: ``` Uppercase: HELLO, LUA! Lowercase: hello, lua! Replaced: Hello, World! Found 'Lua' at: 8 to 10 Reversed: !auL ,olleH Formatted: Hello, Lua! Char: Lua Byte of first character: 72 Length: 11 Repeated: Hello, Lua!Hello, Lua! Concatenated: Hello, Lua! Let's learn Lua! Word: Hello Word: Lua Matched: Lua ``` ::: ### 字串格式化(String-Formating) --- 字串格式化是一個相當重要的語法之一,幾乎是每個程式語言都不可或缺的東西。實際應用有:進度條、醫院掛號顯示號碼、字串+顯示目前數字等等。 總之讓字串格式化之後,就能有許多操作、運算的空間,能讓原本不能動的字串,變成活生生的字串。 > Lua 提供了 `string.format()` 函數來產生具有特定格式的字串,函數的第一個參數是格式, 之後是對應格式中每個代號的各種資料。 以下是格式化字串的 format specifiers(格式化規範符號),來源自 [Lua 字符串 | 菜鸟教程](https://www.runoob.com/lua/lua-strings.html): * %c - 接受一個數字,並將其轉換為 ASCII 碼表中對應的字元(將ASCII碼轉成字元) * %d、%i - 接受一個數字並將其轉換為有號的整數格式 * %o - 接受一個數字並將其轉換為八進位數格式 * %u - 接受一個數字並將其轉換為無號整數格式 * %x - 接受一個數字並將其轉換為十六進位數格式,使用小寫字母 * %X - 接受一個數字並將其轉換為十六進位數格式,使用大寫字母 * %e - 接受一個數字並將其轉換為科學記號格式,使用小寫字母 e * %E - 接受一個數字並將其轉換為科學記號格式,使用大寫字母 E * %f - 接受一個數字並將其轉換為浮點數格式 * %g(%G) - 接受一個數字並將其轉換為 %e(%E,對應 %G)及 %f 中較短的一種格式 * %q - 接受一個字串並將其轉換為可安全被 Lua 編譯器讀入的格式 * %s - 接受一個字串並按照給定的參數格式化該字串 :::info 註:%u 被稱為是無號整數,是因為 C 語言有資料型態為 unsigned int、unsigned char 等等。unsigned(u) 就是沒有負數的意思,只有自然數。 ::: > 為進一步細化格式,可以在 % 號後新增參數,參數將以如下的順序讀入: 1. 符號:一個 + 號表示其後的數字 format specifiers 會讓正數顯示正號。預設只有負數顯示符號。 2. 占位修飾符號:一個 0,在後面指定了字串寬度時佔位用。不填時的預設占位修飾符號是空格。 3. 對齊標識:在指定了字串寬度時,預設為向右對齊,增加 - 號可以改為向左對齊。 4. 寬度數值:寬度數值用於指定輸出內容的最小寬度。如果實際內容的寬度小於指定的寬度數值,則會使用占位修飾符號(預設為空格)進行填充,以達到指定的寬度。(如:%05d、%5d) 5. 小數位數 / 字串裁切:在寬度數值後增加的小數部分 n,若後接 f(浮點數 format specifiers,如 %6.3f)則設定該浮點數的小數只保留 n 位,若後接 s(字串 format specifiers,如 %5.3s)則設定該字串只顯示前 n 位。 以下是個範例: ```lua= local number = 123.456 local integer = 42 local str = "Hello, Lua!" -- 1. 符號:顯示正號 print(string.format("%+d", integer)) -- +42 -- 2. 占位修飾符號:使用 0 進行填充 print(string.format("%05d", integer)) -- 00042 -- 3. 對齊標識:向左對齊 print(string.format("%-5d", integer)) -- 42 (後面有三個空格) -- 4. 寬度數值:指定最小寬度 print(string.format("%10s", str)) -- Hello, Lua! -- 5. 小數位數 / 字串裁切 -- 小數位數:保留 2 位小數 print(string.format("%.2f", number)) -- 123.46 -- 字串裁切:只顯示前 5 位 print(string.format("%.5s", str)) -- Hello -- 綜合範例:同時使用多個參數 print(string.format("%+010.2f", number)) -- +0000123.46 print(string.format("%-10.5s", str)) -- Hello (後面有五個空格) ``` 輸出結果: ``` +42 00042 42 Hello, Lua! 123.46 Hello +000123.46 Hello ``` 再來一個練習範例: ```lua= local number = 12345 local float_number = 123.456 local str = "Hello, Lua!" print(string.format("%%c: %c", number)) -- 將數字轉換為 ASCII 字元 print(string.format("%%d: %d", number)) -- 將數字轉換為有號整數 print(string.format("%%i: %i", number)) -- 將數字轉換為有號整數 print(string.format("%%o: %o", number)) -- 將數字轉換為八進位數 print(string.format("%%u: %u", number)) -- 將數字轉換為無號整數 print(string.format("%%x: %x", number)) -- 將數字轉換為十六進位數(小寫) print(string.format("%%X: %X", number)) -- 將數字轉換為十六進位數(大寫) print(string.format("%%e: %e", float_number)) -- 將數字轉換為科學記號(小寫 e) print(string.format("%%E: %E", float_number)) -- 將數字轉換為科學記號(大寫 E) print(string.format("%%f: %f", float_number)) -- 將數字轉換為浮點數 print(string.format("%%g: %g", float_number)) -- 將數字轉換為 %e 或 %f 中較短的一種格式 print(string.format("%%G: %G", float_number)) -- 將數字轉換為 %E 或 %f 中較短的一種格式 print(string.format("%%q: %q", str)) -- 將字串轉換為可被 Lua 編譯器讀入的格式 print(string.format("%%s: %s", str)) -- 將字串按照給定的參數格式化 -- 複合型格式化 print(string.format("數字 %d 的八進位表示為 %o", number, number)) print(string.format("數字 %d 的十六進位表示為 %x", number, number)) print(string.format("浮點數 %f 的科學記號表示為 %e", float_number, float_number)) ``` 輸出結果: ``` %c: 9 %d: 12345 %i: 12345 %o: 30071 %u: 12345 %x: 3039 %X: 3039 %e: 1.234560e+02 %E: 1.234560E+02 %f: 123.456000 %g: 123.456 %G: 123.456 %q: "Hello, Lua!" %s: Hello, Lua! 數字 12345 的八進位表示為 30071 數字 12345 的十六進位表示為 3039 浮點數 123.456000 的科學記號表示為 1.234560e+02 ``` ### 字串匹配模式 --- > Lua 中的匹配模式直接用常規的字串來描述。它用於模式匹配函數 `string.find`、 `string.gmatch`、`string.gsub`、`string.match`。 以下是一個範例,呈現 dd/mm/yyyy 格式的日期(來自:[Lua 字符串 | 菜鸟教程](https://www.runoob.com/lua/lua-strings.html)): ```lua= s = "Deadline is 30/05/1999, firm" date = "%d%d/%d%d/%d%d%d%d" print(string.sub(s, string.find(s, date))) --> 30/05/1999 ``` 輸出結果: ``` 30/05/1999 ``` 有關於 Lua 字元類別種類,可看以下條列: * .(點):與任何字元配對 * %a:與任何字母配對 * %c:與任何控制符配對(例如 \n) * %d:與任何數字配對 * %l:與任何小寫字母配對 * %p:與任何標點(punctuation)配對 * %s:與空白字元配對 * %u:與任何大寫字母配對 * %w:與任何字母 / 數字配對 * %x:與任何十六進制數配對 * %z:與任何代表 0 的字元配對 * %x(此處 x 是非字母非數字字元):與字元 x 配對。主要用來處理運算式中有功能的字元(^$()%.[]*+-?)的配對問題,例如 %% 與 % 配對。 * [數個字元類別]:與任何 [] 中包含的字元類別配對。例如 [%w_] 與任何字母 / 數字,或底線符號(_)配對。 * [^數個字元類別]:與任何"不"包含在 [] 中的字元類別配對。例如 [^%s] 與任何非空白字元配對。 > 當上述的字元類別以大寫書寫時,表示與非此字元類別的任何字元配對。例如,%S 表示與任何非空白字元配對。例如,'%A'非字母的字元: ```lua= > print(string.gsub("hello, up-down!", "%A", ".")) hello..up.down. 4 ``` 註:有一個 > 表示程式碼是在 Shell 底下執行。 接下來,同樣也是做練習啦,不練習不會進步~: ```lua= local test_string = "Hello, World! 1234\n" local function test_pattern(pattern, description) if string.match(test_string, pattern) then print(description .. ": 匹配成功") else print(description .. ": 匹配失敗") end end test_pattern(".", "與任何字元配對") test_pattern("%a", "與任何字母配對") test_pattern("%c", "與任何控制符配對") test_pattern("%d", "與任何數字配對") test_pattern("%l", "與任何小寫字母配對") test_pattern("%p", "與任何標點配對") test_pattern("%s", "與空白字元配對") test_pattern("%u", "與任何大寫字母配對") test_pattern("%w", "與任何字母 / 數字配對") test_pattern("%x", "與任何十六進制數配對") test_pattern("%z", "與任何代表 0 的字元配對") test_pattern("%%", "與字元 % 配對") test_pattern("[%w_]", "與任何字母 / 數字或底線符號配對") test_pattern("[^%s]", "與任何非空白字元配對") test_pattern("%a+", "與一個或多個字母配對") test_pattern("%d+", "與一個或多個數字配對") test_pattern("%s+", "與一個或多個空白字元配對") test_pattern("%u%l+", "與一個大寫字母後跟隨一個或多個小寫字母配對") print("測試字串: " .. test_string) ``` 輸出結果: ``` 與任何字元配對: 匹配成功 與任何字母配對: 匹配成功 與任何控制符配對: 匹配成功 與任何數字配對: 匹配成功 與任何小寫字母配對: 匹配成功 與任何標點配對: 匹配成功 與空白字元配對: 匹配成功 與任何大寫字母配對: 匹配成功 與任何字母 / 數字配對: 匹配成功 與任何十六進制數配對: 匹配成功 與任何代表 0 的字元配對: 匹配失敗 與字元 % 配對: 匹配失敗 與任何字母 / 數字或底線符號配對: 匹配成功 與任何非空白字元配對: 匹配成功 與一個或多個字母配對: 匹配成功 與一個或多個數字配對: 匹配成功 與一個或多個空白字元配對: 匹配成功 與一個大寫字母後跟隨一個或多個小寫字母配對: 匹配成功 測試字串: Hello, World! 1234 ``` > 在模式匹配中有一些特殊字元,他們有特殊的意義,Lua中的特殊字元如下: `( ) . % + - * ? [ ^ $` > '%' 用作特殊字元的跳脫字元,因此 '%.' 匹配點;'%%' 匹配字元 '%'。跳脫字元 '%' 不僅可以用來跳脫特殊字元,還可以用於所有的非字母的字元。 以下節錄自:[Lua 字符串 | 菜鸟教程](https://www.runoob.com/lua/lua-strings.html) **模式條目可以是:** * 單一字元類別符合該類別中任意單一字元; * 單一字元類別跟一個 '*',將匹配 0 或多個該類別的字元。這個條目總是匹配盡可能長的字串; * 單一字元類別跟一個 '+',將匹配 1 或更多該類別的字元。這個條目總是匹配盡可能長的字串; * 單一字元類別跟一個 '-',將匹配 0 或更多該類別的字元。和 '*' 不同,這個條目總是匹配盡可能短的字串; * 單一字元類別跟一個 '?',將匹配 0 或一個該類別的字元。只要有可能,它就會匹配一個; * %n,這裡的 n 可以從 1 到 9;這個條目匹配一個等於 n 號捕獲物(後面會描述)的子字串。 * %bxy,這裡的 x 和 y 是兩個明確的字元;這個條目匹配以 x 開始 y 結束,並且其中 x 和 y 保持平衡的字串。意思是,如果從左到右讀這個字串,對每次讀到一個 x 就 +1,讀到一個 y 就 -1,最終結束處的那個 y 是第一個記數到 0 的 y。舉個例子,條目 %b() 可以匹配到括號平衡的運算式。 * %f[set],邊境模式;這個條目會匹配到一個位於 set 內某個字元之前的一個空字串,且這個位置的前一個字元不屬於 set。集合 set 的意思如前面所述。配對出的那個空字串之開始和結束點的計算就看成該處有個字元 '\0' 一樣。 以下是一個範例: ```lua= -- 測試字串 1 local test_str = "abc123xyz" -- 單一字元類別匹配 local pattern1 = "[a-z]" -- 匹配任意小寫字母 print(test_str:match(pattern1)) -- a -- 單一字元類別跟 '*' local pattern2 = "[a-z]*" -- 匹配0或多個小寫字母 print(test_str:match(pattern2)) -- abc -- 單一字元類別跟 '+' local pattern3 = "[a-z]+" -- 匹配1或多個小寫字母 print(test_str:match(pattern3)) -- abc -- 單一字元類別跟 '-' local pattern4 = "[a-z]-" -- 匹配0或多個小寫字母(盡可能短) print(test_str:match(pattern4)) -- a -- 單一字元類別跟 '?' local pattern5 = "[a-z]?" -- 匹配0或1個小寫字母 print(test_str:match(pattern5)) -- a -- %n 捕獲物匹配 local pattern6 = "(%d)(%a)%1" -- 匹配數字後跟字母,再跟相同的數字 local match1, match2 = test_str:match(pattern6) print(match1, match2) -- nil nil (因為沒有符合的模式) -- %bxy 平衡匹配 local balanced_str = "a(b(c)d)e" local pattern7 = "%b()" -- 匹配平衡的括號 print(balanced_str:match(pattern7)) -- (b(c)d) -- %f[set] 邊境模式匹配 local pattern8 = "%f[%d]" -- 匹配位於數字前的空字串 local start_pos = test_str:find(pattern8) print(start_pos) -- 4 (因為 '1' 是第一個數字) -- 測試字串 2 local test_str2 = "hello123world" -- 單一字元類別匹配 local pattern9 = "[a-z]" -- 匹配任意小寫字母 print(test_str2:match(pattern9)) -- h -- 單一字元類別跟 '*' local pattern10 = "[a-z]*" -- 匹配0或多個小寫字母 print(test_str2:match(pattern10)) -- hello -- 單一字元類別跟 '+' local pattern11 = "[a-z]+" -- 匹配1或多個小寫字母 print(test_str2:match(pattern11)) -- hello -- 單一字元類別跟 '-' local pattern12 = "[a-z]-" -- 匹配0或多個小寫字母(盡可能短) print(test_str2:match(pattern12)) -- h -- 單一字元類別跟 '?' local pattern13 = "[a-z]?" -- 匹配0或1個小寫字母 print(test_str2:match(pattern13)) -- h -- %n 捕獲物匹配 local pattern14 = "(%d)(%a)%1" -- 匹配數字後跟字母,再跟相同的數字 local match3, match4 = test_str2:match(pattern14) print(match3, match4) -- nil nil (因為沒有符合的模式) -- %bxy 平衡匹配 local balanced_str2 = "a(b(c)d)e" local pattern15 = "%b()" -- 匹配平衡的括號 print(balanced_str2:match(pattern15)) -- (b(c)d) -- %f[set] 邊境模式匹配 local pattern16 = "%f[%d]" -- 匹配位於數字前的空字串 local start_pos2 = test_str2:find(pattern16) print(start_pos2) -- 6 (因為 '1' 是第一個數字) ``` :::warning 在 Lua 中,冒號 (:) 是一種語法糖(Syntactic sugar,暫且別管這啥)。 str:match("%d") 等價於 string.match(str, "%d")。冒號語法自動將 str 作為第一個參數傳遞給 string.match 函數。(總之有語法糖就能夠使程式碼變得更加簡潔有力) ::: :::info [a-z] 是一個字元類別(character class),表示匹配任意一個從 a 到 z 的小寫字母。這種寫法稱為範圍(range),用來指定一組連續的字元。 也有衍生以下這些寫法: * [a-z0-9]:匹配任意一個小寫字母或數字。 * [aeiou]:匹配任意一個母音字母(a、e、i、o、u)。 ::: :::info 在 %n 捕獲物匹配的地方,小括號 () 用於捕獲匹配到的內容。這裡的 (%d) 表示捕獲一個數字,並將其儲存為第一個捕獲組。 **(%a)**:這裡的 (%a) 表示捕獲一個字母,並將其儲存為第二個捕獲組。 **%1**:反向引用(backreference),表示匹配第一個捕獲組的內容。在這個模式中,%1 會匹配之前捕獲的數字。 所以 `(%d)(%a)%1` 是一個包含捕獲組(capture groups)和反向引用(backreference)的模式。 `(%d)(%a)%1` 可作以下三點總結: * 匹配一個數字,並將其捕獲為第一個捕獲組。 * 匹配一個字母,並將其捕獲為第二個捕獲組。 * 最後匹配與第一個捕獲組相同的數字。 而至於都沒匹配到,看一下字串:"hello123world" 第一個匹配到數字 1,但第二個不是字母。所以只可能是 3、w,但是 3、w、o 匹配第三個 o 並不是與 3 同樣的數字(因為反向引用 %1 必須與第一個捕獲組相同數字),所以 nil。 ::: :::info %bxy 平衡(平衡:指的是成對的符號,包括括號)匹配的部分: `local pattern15 = "%b()"`:x、y 都是括號,所以 `%b()` 用於匹配平衡的小括號對。 在字串 `"a(b(c)d)e"` 中,第一個平衡的括號對是 `"(b(c)d)"`,它從第二個字元開始,到第八個字元結束。需要注意的是,這個匹配包括了括號內的所有內容,包括巢狀的括號對 `"(c)"`。 ::: :::info %f[set] 邊境模式匹配(frontier pattern)的部分: %f[set] 會匹配一個位置,這個位置的前一個字元不在指定的集合 set 中,而位置本身的字元在集合中。集合 set 是由一組字元組成的,可以用一系列的字元或者字元類別(如 %d 表示數字,%s 表示空白字符等)來指定。 ```lua= -- 測試字串 2 local test_str2 = "hello123world" -- %f[set] 邊境模式匹配 local pattern16 = "%f[%d]" -- 匹配位於數字前的空字串 local start_pos2 = test_str2:find(pattern16) print(start_pos2) -- 6 (因為 '1' 是第一個數字) ``` 在這個範例當中,%f[%d] 用來匹配位於數字前的空字串位置。表示它會尋找一個位置,這個位置的前一個字元不是數字,而位置本身的字元是數字。對於識別字串中數字的開始位置非常有用。 如果 test_str2 是 "Hello 123",則 %f[%d] 會匹配到 " " 和 "1" 之間的位置,因為 " " 不是數字,而 "1" 是數字。由於 Lua 的字串索引從 1 開始,"1" 在字符串中的位置是 7,所以 start_pos2 的值會是 7,表示 %f[%d] 匹配到的位置。 講這麼多複雜的還有舉例,總之,%f[set] 的 set 主要是找第一個出現的字元。 ::: 總結 --- :::info **字串表示方式**: * 單引號:'String' * 雙引號:"String" * 多行字串:[[ 多行字串 ]] ::: :::info **取得字串長度**: * string.len(字串):取得字串長度 * `#`字串:取得字串長度 * utf8.len(字串):取得包含中文字的字串長度 ::: :::info **跳脫字元**: * 使用反斜線 \ 來表示特殊字元,如 \n 表示換行,\" 表示雙引號等。 ::: :::info **字串方法(Method)**: * string.upper(字串):轉為大寫 * string.lower(字串):轉為小寫 * string.gsub(字串, 查找字串, 替換字串, [次數]):替換字串 * string.find(字串, 子字串, [起始位置], [簡單模式]):查找子字串 * string.reverse(字串):反轉字串 * string.format(格式, ...):格式化字串 * string.char(數字):將數字轉為字元 * string.byte(字串, [位置]):將字元轉為數字 * string.len(字串):計算字串長度 * string.rep(字串, 次數):重複字串 * ..:連接兩個字串 * string.gmatch(字串, 模式):返回迭代器函數 * string.match(字串, 模式, [起始位置]):匹配字串 ::: :::info **字串格式化**: * 使用 string.format() 進行格式化,支持多種格式化規範符號,如 %d、%f、%s 等。 ::: :::info **字串匹配模式**: * 使用 string.find、string.gmatch、string.gsub、string.match 進行模式匹配。 * 支援多種字元類別,如 %a(字母)、%d(數字)、%s(空白字元)等。 * 支援捕獲組和反向引用,如 (%d)(%a)%1。 **常用模式條目**: * [a-z]:匹配任意小寫字母 * [a-z]*:匹配0或多個小寫字母 * [a-z]+:匹配1或多個小寫字母 * [a-z]-:匹配0或多個小寫字母(盡可能短) * [a-z]?:匹配0或1個小寫字母 * %b():匹配平衡的括號 * %f[set]:匹配位於 set 內某個字元之前的空字串 ::: 參考資料 --- [lua-users wiki: Frontier Pattern](http://lua-users.org/wiki/FrontierPattern) [Lua pattern matching - Series of same character - Stack Overflow](https://stackoverflow.com/questions/48541679/lua-pattern-matching-series-of-same-character) [【30天Lua重拾筆記08】基礎1: 類型 - 字串 | 又LAG隨性筆記](https://www.lagagain.com/post/30%E5%A4%A9lua%E9%87%8D%E6%8B%BE%E7%AD%86%E8%A8%9808%E5%9F%BA%E7%A4%8E1-%E9%A1%9E%E5%9E%8B-%E5%AD%97%E4%B8%B2/) [簡單記錄一下 lua 中 string.gmatch 的用法 | Medium](https://medium.com/learn-or-die/string-gmatch-in-lua-3f4162f0e850) [Lua 字符串 | 菜鸟教程](https://www.runoob.com/lua/lua-strings.html)