【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)