字符串 (String) Lua 字符串 (String) 详解与实践指南 引言 1. 字符串基础 1.1 字符串的定义 在 Lua 中,字符串是不可变的字节序列。这意味着一旦字符串被创建,就无法直接修改其内容。任何看似修改字符串的操作实际上都会创建一个新的字符串。 Lua 字符串可以使用以下三种方式定义: 单引号 ( ) 或双引号 ( ): 这是最常用的字符串定义方式,适用于大多数情况。 单引号和双引号在 Lua 中基本等价,唯一的区别在于转义字符的处理。 长括号 ( ): 用于定义多行字符串,可以包含换行符和引号,无需进行额外的转义。 长括号还可以嵌套,例如 , 等,通过等号的数量来区分嵌套的层次。这在处理包含 或 的字符串时非常有用。
引言
1. 字符串基础
1.1 字符串的定义
在 Lua 中,字符串是不可变的字节序列。这意味着一旦字符串被创建,就无法直接修改其内容。任何看似修改字符串的操作实际上都会创建一个新的字符串。
Lua 字符串可以使用以下三种方式定义:
单引号 (') 或双引号 ("): 这是最常用的字符串定义方式,适用于大多数情况。
str1 = 'Hello, Lua!' str2 = "World of Strings"
单引号和双引号在 Lua 中基本等价,唯一的区别在于转义字符的处理。
长括号 ([[ ]]): 用于定义多行字符串,可以包含换行符和引号,无需进行额外的转义。
str3 = [[ This is a multi-line string in Lua. ]]
长括号还可以嵌套,例如 [=[ ... ]=], [==[ ... ]==] 等,通过等号的数量来区分嵌套的层次。这在处理包含 [[ 或 ]] 的字符串时非常有用。
转义序列: Lua 支持常见的转义序列,用于表示特殊字符。
| 转义序列 | 含义 |
|---|---|
\a |
响铃 (bell) |
\b |
退格 (backspace) |
\f |
换页 (form feed) |
\n |
换行 (newline) |
\r |
回车 (carriage return) |
\t |
水平制表符 (horizontal tab) |
\v |
垂直制表符 (vertical tab) |
\\ |
反斜杠 (backslash) |
\" |
双引号 (double quote) |
\' |
单引号 (single quote) |
\[ |
左方括号 (left square bracket) |
\] |
右方括号 (right square bracket) |
例如:
str4 = "This string contains a newline: \n and a tab: \t" str5 = 'This string contains a single quote: \'' str6 = "This string contains a double quote: \""
1.2 字符串的类型
可以使用 type() 函数来检查变量的类型,字符串的类型为 "string"。
print(type(str1)) --> string print(type(str2)) --> string print(type(str3)) --> string
2. 字符串操作
Lua 提供了丰富的内置函数和操作符来处理字符串。
2.1 字符串连接
连接操作符 ..: 用于连接两个或多个字符串。
str7 = "Hello" .. ", " .. "Lua" print(str7) --> Hello, Lua str8 = "Number: " .. 10 -- Lua 会自动将数字转换为字符串 print(str8) --> Number: 10
.. 操作符会将非字符串类型的值自动转换为字符串。
string.format() 函数: 更灵活的字符串格式化方法,类似于 C 语言的 sprintf 函数。
str9 = string.format("Name: %s, Age: %d", "Alice", 30) print(str9) --> Name: Alice, Age: 30 str10 = string.format("Pi: %.2f", math.pi) -- 格式化浮点数,保留两位小数 print(str10) --> Pi: 3.14
string.format() 使用格式化字符串,以 % 开头,后跟格式化指示符 (如 %s, %d, %f 等)。常用的格式化指示符包括:
| 指示符 | 含义 |
|---|---|
%s |
字符串 |
%d |
有符号十进制整数 |
%i |
有符号十进制整数 |
%u |
无符号十进制整数 |
%o |
无符号八进制整数 |
%x |
无符号十六进制整数 (小写字母) |
%X |
无符号十六进制整数 (大写字母) |
%f |
浮点数 (默认精度) |
%e |
科学计数法 (小写 'e') |
%E |
科学计数法 (大写 'E') |
%g |
使用 %e 或 %f 中较短的形式 |
%G |
使用 %E 或 %f 中较短的形式 |
%c |
ASCII 码对应的字符 |
%% |
输出百分号 % |
还可以在格式化指示符中添加修饰符,例如:
宽度: %10s 表示字符串宽度为 10,不足则用空格填充。
精度: %.2f 表示浮点数保留两位小数。
标志:
-: 左对齐 (默认右对齐)。 例如 %-10s 左对齐字符串。
+: 在正数前显示 + 号。 例如 %+d。
0: 用 0 填充空白。 例如 %05d,数字不足 5 位用 0 填充。
2.2 字符串长度
# 操作符: 获取字符串的长度 (字节数)。
str11 = "Lua String" print(#str11) --> 10 str12 = "中文字符串" -- UTF-8 编码,每个汉字通常占 3 个字节 print(#str12) --> 12 (4个汉字 * 3字节/汉字)
# 操作符返回的是字符串的字节长度,对于 UTF-8 编码的字符串,一个 Unicode 字符可能占用多个字节。
string.len() 函数: 与 # 操作符功能相同,获取字符串的字节长度。
print(string.len(str11)) --> 10 print(string.len(str12)) --> 12
2.3 字符串索引和子串
Lua 字符串是基于 1 的索引,即第一个字符的索引为 1,第二个字符的索引为 2,以此类推。
string.sub() 函数: 提取字符串的子串。
str13 = "Hello, Lua World" sub_str1 = string.sub(str13, 1, 5) -- 从索引 1 到 5 的子串 (包含索引 1 和 5) print(sub_str1) --> Hello sub_str2 = string.sub(str13, 8) -- 从索引 8 到字符串末尾的子串 print(sub_str2) --> Lua World sub_str3 = string.sub(str13, -5) -- 从倒数第 5 个字符到末尾的子串 print(sub_str3) --> World sub_str4 = string.sub(str13, 1, -1) -- 整个字符串 (相当于复制) print(sub_str4) --> Hello, Lua World
string.sub(s, i, j) 函数返回字符串 s 从索引 i 到 j 的子串。
i 和 j 可以是正数或负数。
正数索引从字符串开头计数,负数索引从字符串末尾倒数。
如果省略 j,则默认提取到字符串末尾。
如果 i 大于 j,则返回空字符串。
2.4 字符串查找
string.find() 函数: 在字符串中查找指定的模式 (pattern),并返回匹配的起始和结束索引。如果没有找到匹配项,则返回 nil。
str14 = "Lua is a powerful scripting language." start_index, end_index = string.find(str14, "Lua") if start_index then print(string.format("Found 'Lua' at index %d to %d", start_index, end_index)) --> Found 'Lua' at index 1 to 3 else print("'Lua' not found") end start_index2, end_index2 = string.find(str14, "Python") if start_index2 then print("'Python' found") else print("'Python' not found") --> 'Python' not found end start_index3, end_index3 = string.find(str14, "language", 10) -- 从索引 10 开始查找 if start_index3 then print(string.format("Found 'language' starting from index 10 at %d to %d", start_index3, end_index3)) --> Found 'language' starting from index 10 at 28 to 35 end
string.find(s, pattern, init, plain) 函数:
s: 要搜索的字符串。
pattern: 要查找的模式字符串。Lua 的模式匹配功能非常强大,稍后会详细介绍。
init (可选): 搜索的起始索引,默认为 1。
plain (可选): 布尔值,如果为 true,则禁用模式匹配,将 pattern 视为普通字符串进行精确查找。默认为 false (启用模式匹配)。
string.match() 函数: 在字符串中查找指定的模式 (pattern),并返回第一个匹配到的子串。如果没有找到匹配项,则返回 nil。
str15 = "Name: John, Age: 25, City: New York" name = string.match(str15, "Name: (%w+)") -- 捕获名字 (一个或多个字母数字字符) print(name) --> John age = string.match(str15, "Age: (%d+)") -- 捕获年龄 (一个或多个数字) print(age) --> 25 city = string.match(str15, "City: (.+)") -- 捕获城市 (任意字符,直到行尾) print(city) --> New York
string.match(s, pattern, init) 函数:
s: 要搜索的字符串。
pattern: 要查找的模式字符串,可以使用捕获组 () 来提取匹配的部分。
init (可选): 搜索的起始索引,默认为 1。
string.gmatch() 函数: 返回一个迭代器,用于遍历字符串中所有匹配指定模式的子串。
str16 = "apple, banana, orange, grape" for fruit in string.gmatch(str16, "%w+") do -- 匹配一个或多个字母数字字符 print(fruit) end --> apple --> banana --> orange --> grape str17 = "set name='John', age=30, city='London'" for key, value in string.gmatch(str17, "(%w+)=['\"]?([^'\"]*)['\"]?") do -- 捕获键值对 print(string.format("Key: %s, Value: %s", key, value)) end --> Key: name, Value: John --> Key: age, Value: 30 --> Key: city, Value: London
string.gmatch(s, pattern) 函数返回一个迭代器函数。每次调用迭代器函数,都会返回下一个匹配的子串。可以使用 for ... in 循环来遍历所有匹配项。
2.5 字符串替换
string.gsub() 函数: 全局替换字符串中所有匹配指定模式的子串。
str18 = "replace all spaces with underscores" new_str1 = string.gsub(str18, " ", "_") -- 将所有空格替换为下划线 print(new_str1) --> replace_all_spaces_with_underscores str19 = "count vowels in this string" count, num_replacements = string.gsub(str19, "[aeiouAEIOU]", "") -- 替换所有元音字母为空字符串,并返回替换次数 print(count) --> cnt vwl in ths strng print(num_replacements) --> 8 str20 = "swap words: first second" swapped_str = string.gsub(str20, "(%w+) (%w+)", "%2 %1") -- 交换两个单词的位置,使用捕获组的反向引用 print(swapped_str) --> swap words: second first
string.gsub(s, pattern, replacement, n) 函数:
s: 要进行替换操作的字符串。
pattern: 要查找的模式字符串。
replacement: 用于替换匹配项的字符串。可以是字符串或函数。
n (可选): 最大替换次数。如果指定,则只替换前 n 个匹配项。
函数返回两个值:
替换后的新字符串。
替换的次数。
replacement 可以是:
字符串: 直接替换匹配项。可以使用 %n (n 为 1-9) 来引用模式中的捕获组。%0 代表整个匹配项。
函数: 每次匹配到模式时,都会调用该函数,并将捕获组作为参数传递给函数。函数的返回值将作为替换字符串。
2.6 字符串分割
虽然 Lua 标准库中没有直接提供字符串分割函数,但可以使用 string.gmatch() 结合模式匹配来实现字符串分割功能。
function split(str, delimiter) local result = {} for segment in string.gmatch(str, "([^" .. delimiter .. "]*)") do -- 匹配非分隔符的字符序列 table.insert(result, segment) end return result end str21 = "apple,banana,orange,grape" fruits = split(str21, ",") for i, fruit in ipairs(fruits) do print(string.format("Fruit %d: %s", i, fruit)) end --> Fruit 1: apple --> Fruit 2: banana --> Fruit 3: orange --> Fruit 4: grape str22 = " leading and trailing spaces " segments = split(str22, " ") -- 使用空格分割,但会包含空字符串 for i, segment in ipairs(segments) do print(string.format("Segment %d: '%s'", i, segment)) end --> Segment 1: '' --> Segment 2: '' --> Segment 3: Segment 4: leading --> Segment 5: and --> Segment 6: trailing --> Segment 7: spaces --> Segment 8: '' --> Segment 9: '' -- 改进的 split 函数,去除空字符串 function split_improved(str, delimiter) local result = {} for segment in string.gmatch(str, "([^" .. delimiter .. "]*)") do if segment ~= "" then -- 忽略空字符串 table.insert(result, segment) end end return result end segments2 = split_improved(str22, " ") for i, segment in ipairs(segments2) do print(string.format("Segment %d: '%s'", i, segment)) end --> Segment 1: 'leading' --> Segment 2: 'and' --> Segment 3: 'trailing' --> Segment 4: 'spaces'
2.7 字符串大小写转换
string.lower() 函数: 将字符串转换为小写。
str23 = "MixedCase String" lower_str = string.lower(str23) print(lower_str) --> mixedcase string
string.upper() 函数: 将字符串转换为大写。
upper_str = string.upper(str23) print(upper_str) --> MIXEDCASE STRING
2.8 字符串反转
function reverse_string(str) local reversed_str = "" for i = #str, 1, -1 do -- 从字符串末尾向前遍历 reversed_str = reversed_str .. string.sub(str, i, i) end return reversed_str end str24 = "hello" reversed = reverse_string(str24) print(reversed) --> olleh
2.9 其他字符串函数
Lua 的 string 库还提供了其他一些有用的函数,例如:
string.char(...): 将 ASCII 码转换为字符。
char_str = string.char(65, 66, 67) -- ASCII 码 65, 66, 67 分别对应 'A', 'B', 'C' print(char_str) --> ABC
string.byte(s, i, j): 返回字符串 s 中索引 i 到 j 的字符的 ASCII 码。
byte_values = string.byte("ABC", 1, 3) for i, byte_value in ipairs{byte_values} do print(string.format("Byte %d: %d", i, byte_value)) end --> Byte 1: 65 --> Byte 2: 66 --> Byte 3: 67
string.rep(s, n): 重复字符串 s n 次。
repeated_str = string.rep("repeat ", 3) print(repeated_str) --> repeat repeat repeat
string.reverse(s): 反转字符串 s (与上面自定义的 reverse_string 函数功能相同,但效率更高,是 C 实现的)。
reversed_str2 = string.reverse("hello") print(reversed_str2) --> olleh
string.dump(function): 返回一个包含 Lua 函数二进制表示的字符串。可以将这个字符串保存到文件或通过网络传输,然后在其他地方使用 loadstring() 或 load() 函数加载并执行。
3. Lua 模式匹配
Lua 的模式匹配系统是一种轻量级的正则表达式实现,功能强大且易于学习。它在 string.find(), string.match(), string.gsub(), string.gmatch() 等函数中被广泛使用。
3.1 模式字符
| 字符或字符类 | 含义 |
|---|---|
. |
匹配任何字符。 |
%a |
匹配任何字母。 |
%c |
匹配任何控制字符。 |
%d |
匹配任何数字。 |
%g |
匹配除空格外的任何可打印字符。 |
%l |
匹配任何小写字母。 |
%p |
匹配任何标点符号。 |
%s |
匹配任何空白字符 (空格、制表符、换行符等)。 |
%u |
匹配任何大写字母。 |
%w |
匹配任何字母数字字符 (字母或数字)。 |
%x |
匹配任何十六进制数字。 |
%z |
匹配以 \0 字符表示的字符。 |
%x (x 为非字母数字字符) |
代表字符 x 本身。用于转义特殊字符 (例如 `%., %[, %%, 等)。 |
[set] |
匹配集合 set 中的任何字符。可以使用 - 表示范围,例如 [0-9] 表示数字,[a-z] 表示小写字母。 |
[^set] |
匹配不在集合 set 中的任何字符。 |
3.2 模式修饰符
| 修饰符 | 含义 |
|---|---|
* |
匹配 0 个或多个前面的模式项。尽可能多地匹配。 (贪婪匹配) |
+ |
匹配 1 个或多个前面的模式项。尽可能多地匹配。 (贪婪匹配) |
- |
匹配 0 个或多个前面的模式项。尽可能少地匹配。 (非贪婪匹配) |
? |
匹配 0 个或 1 个前面的模式项。 |
3.3 锚点
| 锚点 | 含义 |
|---|---|
^ |
匹配字符串的开头。 |
$ |
匹配字符串的结尾。 |
3.4 捕获组
使用括号 () 可以创建捕获组,用于提取匹配到的子串。捕获组在 string.match() 和 string.gsub() 函数中非常有用。
3.5 模式匹配示例
str25 = "The price is $19.99 and quantity is 10." -- 查找价格 price = string.match(str25, "%$%d+%.%d+") -- 匹配 '$' 符号,后跟一个或多个数字,一个点号,再后跟一个或多个数字 print(price) --> $19.99 -- 查找所有数字 for num in string.gmatch(str25, "%d+") do -- 匹配一个或多个数字 print(num) end --> 19 --> 99 --> 10 -- 查找单词 for word in string.gmatch(str25, "%w+") do -- 匹配一个或多个字母数字字符 print(word) end --> The --> price --> is --> 19 --> 99 --> and --> quantity --> is --> 10 -- 替换所有数字为 '*' replaced_str2 = string.gsub(str25, "%d+", "*") print(replaced_str2) --> The price is $*.** and quantity is **. -- 使用捕获组交换两个单词 str26 = "word1 word2" swapped_str2 = string.gsub(str26, "(%w+) (%w+)", "%2 %1") -- 捕获两个单词,并使用反向引用交换位置 print(swapped_str2) --> word2 word1 -- 使用非贪婪匹配 str27 = "<a>tag1</a> <a>tag2</a>" tags = {} for tag in string.gmatch(str27, "<a>(.+)</a>") do -- 贪婪匹配会匹配到 "tag1</a> <a>tag2" table.insert(tags, tag) end print(table.concat(tags, ", ")) --> tag1</a> <a>tag2 (错误的结果) tags_non_greedy = {} for tag in string.gmatch(str27, "<a>(.-)</a>") do -- 非贪婪匹配,使用 '-' 修饰符 table.insert(tags_non_greedy, tag) end print(table.concat(tags_non_greedy, ", ")) --> tag1, tag2 (正确的结果)
4. 字符串编码和 Unicode
Lua 字符串本质上是字节序列,它本身并不关心字符编码。这意味着 Lua 可以处理任何编码的字符串,包括 ASCII, UTF-8, Latin-1 等。