【Lua 筆記】字串(String) - part 8
【Lua 筆記】字串(String) - part 8
由於有款遊戲叫做 CSO(Counter-Strike Online),內建模式創世者模式(Studio)新增使用 Lua 及其遊戲的 API,所以突發奇想製作這個筆記。
這個筆記會在一開始先著重純粹的程式設計自學,在最後的章節才會與 CSO 遊戲 API 進行應用。
※本文篇幅較長,十分複雜,請小心閱讀,請務必收藏!!※
字串(String)
字串(String)是由許多字元(Character)所組成的。
字元(Character):中文字、英文字母(a-z)、底線等等任何符號皆是一種字元。
字串可以用以下三種方式表示:
- 成對單引號括起來(’String’)
- 成對雙引號括起來(”String”)
- [[ ]] 裡面放字串 -> 多行字串
第三種方式舉例:
1 | a = [[ |
輸出結果:
1 | Hi, this is a string |
取得字串長度的另一種方式
string.len(裡面放字串),實際上這個方式與用 # 這個運算子的方式並沒有什麼區別,但 string.len() 的可讀性會比用 # 更高。
以下是用兩種方式的比較:
1 | a = [[ |
1 | a = [[ |
也可以用 utf8.len 函数:
1 | local myString = "Hello, 世界!" |
註:utf8 是一種編碼,主要針對 Unicode 進行編碼,詳細具體內容筆者推薦看這篇文章:UTF-8
可以看出當我們字串是中文字時,使用 string.len()(註:使用 # 的效果同屬於 string.len())便會使字串長度輸出不準確,這是因為中文字編碼的關係。
string.len() 跟 # 回傳的會是 ASCII 編碼字串的長度,而 utf8.len() 包含中文字與英文。
所以歸結出一個結論:
- 一般英文字母(ASCII)用 string.len() 或 #
- 中文字與英文字母混合用 utf8.len()
跳脫字元(Escape Character)
跳脫字元(Escape Character),又稱轉義字元(陸譯)、逃逸字元、逸出字元。是一種能夠針對字串進行特殊操作的字元,例如原本不能在字串裡面顯示單雙引號的字元,所以透過反斜線(\)結合單雙引號形成一個跳脫字元,就能夠變成一種能夠出現在字串內的字元。
話說這麼多,跳脫字元其實本身還是一個字元(廢話XD),總之他就是一種特殊的字元,能讓字串的操作變得更加多元、方便。
所以要使用跳脫字元之前,必須要有一個反斜線存在(\)。
以下有個表格是關於跳脫字元的使用(表格來源:Lua 字符串 | 菜鸟教程):
| 跳脫字元 | 意義 | 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 了吧。
然後讓我們來練習一下:)
1 | print("str\ning") |
輸出結果:
1 | str |
不曉得各位有沒有發現,Lua 的 print() 好像會自動幫我們換行欸?如果我不想換行怎麼辦?沒關係,以下這個函數就能夠實現不換行的機制:io.write()
string 的方法(Method)
方法(Method)是什麼?這個問題,我們需要牽涉到程式設計當中,一個很重要的概念,稱之為類(Class),通常有 class 的語言,在程式設計上都被稱之為物件導向程式設計(Object-oriented programming)。
而方法其實是一種函數,屬於 class 的一部分,也就是說一個 class 當中可以有很多方法,這些方法都具有不同的功能,目前只要先理解這樣就好。
以下表格來源:Lua 字符串 | 菜鸟教程
| 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 點開看以上方法的詳細範例
1 | -- 定義一個字串 |
輸出結果:
1 | Uppercase: HELLO, LUA! |
:::
字串格式化(String-Formating)
字串格式化是一個相當重要的語法之一,幾乎是每個程式語言都不可或缺的東西。實際應用有:進度條、醫院掛號顯示號碼、字串+顯示目前數字等等。
總之讓字串格式化之後,就能有許多操作、運算的空間,能讓原本不能動的字串,變成活生生的字串。
Lua 提供了
string.format()函數來產生具有特定格式的字串,函數的第一個參數是格式, 之後是對應格式中每個代號的各種資料。
以下是格式化字串的 format specifiers(格式化規範符號),來源自 Lua 字符串 | 菜鸟教程:
- %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) 就是沒有負數的意思,只有自然數。
:::
為進一步細化格式,可以在 % 號後新增參數,參數將以如下的順序讀入:
- 符號:一個 + 號表示其後的數字 format specifiers 會讓正數顯示正號。預設只有負數顯示符號。
- 占位修飾符號:一個 0,在後面指定了字串寬度時佔位用。不填時的預設占位修飾符號是空格。
- 對齊標識:在指定了字串寬度時,預設為向右對齊,增加 - 號可以改為向左對齊。
- 寬度數值:寬度數值用於指定輸出內容的最小寬度。如果實際內容的寬度小於指定的寬度數值,則會使用占位修飾符號(預設為空格)進行填充,以達到指定的寬度。(如:%05d、%5d)
- 小數位數 / 字串裁切:在寬度數值後增加的小數部分 n,若後接 f(浮點數 format specifiers,如 %6.3f)則設定該浮點數的小數只保留 n 位,若後接 s(字串 format specifiers,如 %5.3s)則設定該字串只顯示前 n 位。
以下是個範例:
1 | local number = 123.456 |
輸出結果:
1 | +42 |
再來一個練習範例:
1 | local number = 12345 |
輸出結果:
1 | %c: 9 |
字串匹配模式
Lua 中的匹配模式直接用常規的字串來描述。它用於模式匹配函數
string.find、string.gmatch、string.gsub、string.match。
以下是一個範例,呈現 dd/mm/yyyy 格式的日期(來自:Lua 字符串 | 菜鸟教程):
1 | s = "Deadline is 30/05/1999, firm" |
輸出結果:
1 | 30/05/1999 |
有關於 Lua 字元類別種類,可看以下條列:
- .(點):與任何字元配對
- %a:與任何字母配對
- %c:與任何控制符配對(例如 \n)
- %d:與任何數字配對
- %l:與任何小寫字母配對
- %p:與任何標點(punctuation)配對
- %s:與空白字元配對
- %u:與任何大寫字母配對
- %w:與任何字母 / 數字配對
- %x:與任何十六進制數配對
- %z:與任何代表 0 的字元配對
- %x(此處 x 是非字母非數字字元):與字元 x 配對。主要用來處理運算式中有功能的字元(^$()%.[]*+-?)的配對問題,例如 %% 與 % 配對。
- [數個字元類別]:與任何 [] 中包含的字元類別配對。例如 [%w] 與任何字母 / 數字,或底線符號()配對。
- 數個字元類別:與任何”不”包含在 [] 中的字元類別配對。例如 %s 與任何非空白字元配對。
當上述的字元類別以大寫書寫時,表示與非此字元類別的任何字元配對。例如,%S 表示與任何非空白字元配對。例如,’%A’非字母的字元:
1 | > print(string.gsub("hello, up-down!", "%A", ".")) |
註:有一個 > 表示程式碼是在 Shell 底下執行。
接下來,同樣也是做練習啦,不練習不會進步~:
1 | local test_string = "Hello, World! 1234\n" |
輸出結果:
1 | 與任何字元配對: 匹配成功 |
在模式匹配中有一些特殊字元,他們有特殊的意義,Lua中的特殊字元如下:
( ) . % + - * ? [ ^ $
‘%’ 用作特殊字元的跳脫字元,因此 ‘%.’ 匹配點;’%%’ 匹配字元 ‘%’。跳脫字元 ‘%’ 不僅可以用來跳脫特殊字元,還可以用於所有的非字母的字元。
以下節錄自:Lua 字符串 | 菜鸟教程
模式條目可以是:
- 單一字元類別符合該類別中任意單一字元;
- 單一字元類別跟一個 ‘*’,將匹配 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’ 一樣。
以下是一個範例:
1 | -- 測試字串 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 表示空白字符等)來指定。
1 | -- 測試字串 2 |
在這個範例當中,%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
Lua pattern matching - Series of same character - Stack Overflow
【30天Lua重拾筆記08】基礎1: 類型 - 字串 | 又LAG隨性筆記



