【Lua学习】字符串string和字符串标准库

字符串string

字符串常量

--短字符串使用单引号或双引号括起来,两者没区别。
shortStr1 = "Hello World!"
shortStr2 = '《你的名字》'

--若是需要打印出单引号或双引号,一般是需要用\进行转义。
shortStr3 = "\"Game Over!\""
print(shortStr3) --> "Game Over!"

--不过在双引号括起来的字符串中,单引号不需要转义;而在单引号括起来的字符串中,双引号不需要转义
shortStr4 = '"Game Over!"'
print(shortStr4) --> "Game Over!"

--长字符串可以使用[[]]括起来
longStr = [[长
字
符
串
]]

字符串操作符

--长度操作符#
str = 'How are you?'
print(str) --> 12

--连接操作符..
str1 = "Hello," .. "Mei." .. str
print(str1) --> Hello,Mei.How are you?

常见转义字符

转义字符 备注
\a 响铃(bell)
\b 退格(back space)
\f 换页(form feed)
\n 换行(newline)
\r 回车(carriage return)
\t 水平制表符(horizontal tab)
\v 垂直制表符(vertical tab)
\\ 反斜杠(backslash)
\" 双引号(double quote)
\' 单引号(single quote)
\u{h...h} 声明UTF-8字符,花括号中代表十六进制字符
\ddd 最多三个十进制数字组成的序列。\065 --> A
\xhh hh是十六进制数字组成的序列。\x41 --> A

强制类型转换

--数字使用连接操作符连接起来,会强转成字符串
print(34 .. 56) --> 3456

--可以表示为数字的字符串和数字运算,会当成浮点运算处理。
"6" + 3 -> 9

--字符串转数字,默认使用十进制转换
tonumber("10") -> 10

--tonumber也可以使用其它进制,第二个参数填写二进制到三十六进制的任何进制
tonumber("ffff", 16)  --> 65535
tonumber("101010", 2)  --> 42
tonumber("746", 8)  --> 486

--数字转字符串
tostring(10) -> "10"

字符串标准库

这个库提供了用于字符串操作的通用函数,比如查找和提取子字符串,以及模式匹配。在Lua中索引字符串时,第一个字符位于位置1(而不是像C语言中从0开始)。索引可以是负数,并解释为从字符串末尾向后索引。因此,最后一个字符位于位置-1,依此类推。

字符串库在其表string中提供了所有的函数。它还为字符串设置了一个元表,其中__index字段指向string表。因此,你可以以面向对象的方式使用字符串函数。例如,string.byte(s,i)可以写作s:byte(i)。

字符串库假设使用单字节字符编码。

string.byte (s [, i [, j]])

返回字符串 s 中s[i], s[i+1], ..., s[j]的内部数字编码。i 的默认值是 1;j 的默认值是 i。这些索引将按照 string.sub 函数的相同规则进行修正。

数字编码不一定在所有平台上都是可移植的。

s = 'abcde'
--不使用第二参数,默认转换第一个字符为数字编码
string.byte(s) --> 97
--使用第二参数,首字符的索引是1
string.byte(s, 1) --> 97
--使用第二参数,首字符的索引是1
string.byte(s, 3) --> 99
--取2至4号索引的字符的数字编码
string.byte(s, 2, 4) --> 98	99	100
--索引-1代表最后一个字符
string.byte(s, 1, -1) --> 97	98	99	100	101

string.char (···)

接收零个或多个整数。返回一个字符串,其长度等于参数的数量,其中每个字符的内部数字编码等于其对应的参数。

数字编码不一定在所有平台上都是可移植的。

print(string.char(97,98,99)) --> abc
string.len(string.char(97,98,99)) --> 3

string.dump (function [, strip])

返回一个包含给定函数的二进制表示(二进制块)的字符串,以便稍后对这个字符串进行加载时可以返回该函数的一个副本(但是具有新的上值)。如果 strip为true,二进制表示可能不会包含关于函数的所有调试信息,以节省空间。

具有上值的函数只保存它们上值的数量。当(重新)加载时,这些上值会接收新的实例。

-- 定义一个简单的函数
local function myFunction(a, b)
    print("The sum is:", a + b)
end

-- 使用 string.dump 来获取这个函数的二进制表示
local dumpedFunction = string.dump(myFunction)

-- 可以将这个二进制字符串保存到文件中,或者通过网络发送
-- 这里我们只是用 load 来重新加载这个函数
local loadedFunction = load(dumpedFunction)

-- 调用重新加载的函数
loadedFunction(3, 4)  --> The sum is: 7

备注:

  1. string.dump 只能用于 Lua 编写的函数,不能用于 C 语言编写的函数。
  2. 使用 string.dump 生成的二进制字符串在不同的 Lua 版本之间可能不兼容。
  3. 如果函数有上值(upvalues),它们在加载时会被初始化为新的实例,原始的上值不会被保留。
  4. 对于需要序列化和传输 Lua 函数的场景,string.dump 非常有用。

string.format (formatstring, ···)

与C函数sprintf具有相同的规则,但不支持C函数sprintf的F、n、*、h、L 和 l这几个修饰符。

--字符串格式化:d%,x%,f%,s%
string.format("%d", 40)  --> 40  【整数】
string.format("%5d", 40) -->      40  【整数】
string.format("%x", 40)  --> 28  【十六进制数】
string.format("%o", 40)  --> 50  【八进制数】
string.format("%f", 40)  --> 40.000000  【浮点数】
string.format("%s", 'How are you?')	--> How are you?  【字符串】

另外,Lua还有一个额外的说明符q,它的宽度和精度限制为两位数字。

q 可以格式化布尔值、nil、数字和字符串。

布尔值和 nil 就是输出true、false、nil。

string.format("%q,%q,%q",true, false, nil) --> true,false,nil

浮点数以十六进制写入,以保留全部精度。

> string.format("%q", 3.1415926) --> 0x1.921fb4d12d84ap+1

字符串在双引号之间写入,必要时使用转义序列,以确保可以由 Lua 解释器安全地读取回来。

string.format('%q', '一个带有"引号"和 \n 新行的字符串')
-->result
"一个带有\"引号\"和 \
 新行的字符串"

string.len (s)

返回一个字符串的长度。

s = 'hello world'
print(string.len(s)) --> 11

空字符串""的长度为0。嵌入式零(空字符)也会被计算在内,所以字符串"a\000bc\000″的长度为5。

s = 'a\000bc\000'
print(s:len())  --> 5
for i=1, s:len() do 
  print(s:byte(i)) 
end
--result
97  --> a
0   --> \000
98  --> b
99  --> c
0   --> \000

string.lower (s)

接收一个字符串并返回该字符串的一个副本,其中所有大写字母都变为小写。所有其他字符保持不变。大写字母的定义取决于当前的区域设置。

string.lower('HELLO WORLD!') --> hello world!

string.upper (s)

接收一个字符串并返回该字符串的一个副本,其中所有小写字母都变为大写。所有其他字符保持不变。小写字母的定义取决于当前的区域设置。

string.upper('hello world!') --> HELLO WORLD!

string.rep (s, n [, sep])

返回一个字符串,该字符串是由字符串 s 的 n 个副本通过字符串 sep 连接而成的。sep 的默认值是空字符串(即没有分隔符)。如果 n 不是正数,则返回空字符串。

(请注意,使用这个函数的单次调用非常容易耗尽机器的内存。)

s = "hello"
string.rep(s,5)     --> hellohellohellohellohello
string.rep(s,5,',') --> hello,hello,hello,hello,hello

string.reverse (s)

将字符串s反转。

s = 'hello world!'
string.reverse (s)
--> !dlrow olleh

string.sub (s, i [, j])

返回从字符串 s 的索引 i 开始到索引 j 结束的子字符串;i 和 j 可以是负数。如果省略了 j,则假设它等于 -1(这与字符串长度相同)。特别是,调用 string.sub(s,1,j) 返回长度为 j 的 s 的前缀,而 string.sub(s, -i)(对于正数 i)返回长度为 i 的 s 的后缀。

如果负索引转换后 i 小于 1,则将其校正为 1。如果 j 大于字符串长度,则将其校正为该长度。经过这些校正后,如果 i 大于 j,则函数返回空字符串。

s = 'hello world!'
--返回从2号索引到7号索引的s的子串
string.sub(s, 2, 7) --> ello w
--返回从2号索引开始的s的子串
string.sub(s, 2) --> ello world!
--返回长度为5的s前缀
string.sub(s, 1, 5) --> hello
--返回长度为6的s后缀
string.sub(s, -6) --> world!
--返回空串
string.sub(s, 5, 1) --> 

模式匹配

模式匹配类似于缩小范围的正则表达式,规则如下:

字符类

字符类用于表示一组字符。在描述字符类时,允许以下组合:

|-------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| x | (其中 x 不是特殊字符 ^$()%.[]*±? 之一)代表字符 x 本身。 |
| . | 代表所有字符 |
| %a | 代表所有字母 |
| %c | 代表所有控制字符 |
| %d | 代表所有数字 |
| %g | 代表除了空白字符以外的所有可打印字符 |
| %l | 代表所有小写字母 |
| %p | 代表所有的标点符号 |
| %s | 代表所有空白字符 |
| %u | 代表所有大写字母 |
| %w | 代表所有字母和数字 |
| %x | (其中 x 是任意一个非字母数字字符)代表字符 x。这是转义特殊字符的标准方式。任何非字母数字字符(包括所有标点符号,即使是那些非特殊的)前面都可以加上 '%' 来在模式中表示它自己。您可以通过将闭合方括号放置在集合的第一个位置来在集合中包含它。您可以通过将连字符放置在集合的第一个或最后一个位置来在集合中包含它。(您也可以使用转义字符来处理这两种情况。) 范围和类别之间的交互作用没有定义。因此,像 [%a-z] 或 [a-%%] 这样的模式没有意义。 |
| set | 代表的是包含集合中所有字符的并集的类别。可以通过用'-'分隔范围内字符的结束字符来指定一个字符范围,结束字符需按升序排列。所有上面描述的类别 %x 也可以用作集合中的组成部分。集合中的所有其他字符都代表它们自身。例如,[%w_](或者 [_%w])代表所有字母数字字符加上下划线,[0-7]代表八进制数字,而[0-7%l%-]代表八进制数字加上小写字母加上'-'字符。 |
| ^set | 代表集合的补集 |

对于所有由单个字母表示的类别(%a, %c 等),相应的大写字母代表该类别的补集。例如,%S 代表所有非空白字符。

字母、空白和其他字符组的定义取决于当前的地区设置。特别是,类别 [a-z] 可能并不等同于 %l。

模式项

模式项可以有以下几种形式:

  • 单个字符类,它匹配该类中的任何一个字符;
  • 单个字符类后跟 '*',它匹配该类字符的零个或多个序列。这些重复项总是尽可能匹配最长的序列;
  • 单个字符类后跟 '+',它匹配该类字符的一个或多个序列。这些重复项总是尽可能匹配最长的序列;
  • 单个字符类后跟 '-',它也匹配该类字符的零个或多个序列。与 '*' 不同的是,这些重复项总是尽可能匹配最短的序列;
  • 单个字符类后跟 '?',它匹配该类字符的零次或一次出现。如果可能,它总是优先匹配一次出现;
  • %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'。

模式

模式是由一系列模式项组成的序列。以补字符'^'开头的模式表示从目标字符串的开头开始匹配。以美元符号''结尾的模式表示匹配到目标字符串的结尾。在其他位置,'\^'和''没有特殊含义,它们代表自身。

捕获

模式可以包含用小括号括起来的子模式;它们描述了捕获。当匹配成功时,匹配捕获的主题字符串的子字符串将被存储(捕获)以备后用。捕获根据其左括号编号。例如,在模式"(a*(.)%w(%s* ))"中,匹配"a*(.)%w(%s* )"的字符串部分被存储为第一个捕获,因此其编号为1;匹配"."的字符被编号为2的捕获,匹配"%s*"的部分编号为3。

作为一个特例,捕获()捕获当前的字符串位置(一个数字)。例如,如果我们对字符串"flaaap"应用模式"()aa()",将会有两个捕获:3和5。

多重匹配

函数 string.gsub 和迭代器 string.gmatch 在主题字符串中匹配给定模式的多个出现。对于这些函数,只有当新匹配的结束位置至少比前一个匹配的结束位置多一个字节时,才认为是有效的匹配。换句话说,模式匹配器永远不会在另一个匹配之后立即接受空字符串作为匹配。例如,考虑以下代码的结果:

> string.gsub("abc", "()a*()", print);
--> 1 2
--> 3 3
--> 4 4

第二个和第三个结果来自 Lua 在 'b' 之后和 'c' 之后匹配一个空字符串。Lua 不会在 'a' 之后匹配空字符串,因为它会结束在前一个匹配的同一位置。

模式匹配相关函数

模式匹配相关的函数为string.find、string.match、string.gsub、string.gmatch。

string.find (s, pattern [, init [, plain]])

在字符串 s 中查找第一个匹配pattern的项。如果找到匹配项,则 find 函数返回 s 中这次匹配开始和结束的索引;否则,它返回失败。第三个可选的数字参数 init 指定了搜索开始的位置;其默认值为 1,也可以是负数。第四个可选参数 plain 如果为真,则会关闭模式匹配功能,因此函数执行的是一个简单的"查找子字符串"操作,模式中的字符不会被特殊对待。

如果模式中有捕获组,那么在成功匹配后,捕获的值也会在两个索引之后返回。

s = 'hello world'
print(string.find(s, 'wor'))  --> 7 9
print(string.find(s, 'wor', 2, true))  --> 7 9

--[[模式匹配--]]
--以补字符'^'开头的模式表示从目标字符串的开头开始匹配。以美元符号'$'结尾的模式表示匹配到目标字符串的结尾。
--^%d代表从字符串的开头匹配一个数字
string.find("lazyelf89","^%d") --> nil
string.find("89lazyelf", "^%d") --> 1 1

--[+-]?代表0或1个+号或-号,%d+代表1个或多个数字,^$是字符串整个范围,
--也即是说以下模式会过滤掉前后缀没有多余字符的整数
string.find("-89", "^[+-]?%d+$")  --> 1	3
string.match (s, pattern [, init])

在字符串s中查找模式的第一次匹配 。如果找到匹配,则match返回模式中的捕获;否则返回失败。如果模式没有指定捕获,则返回整个匹配。第三个可选的数字参数init用于指定搜索的起始位置;其默认值为1,也可以是负数。

--默认从字符串"hello world!"的第1个字符开始进行匹配
string.match("hello world!", "hello")
hello
--从字符串"hello world!"的第2个字符开始进行匹配
string.match("hello world!", "hello", 2)
nil

--[模式匹配--]
--%d的使用:匹配数字
s = "武器:黄金之刃 创建时间:2024/10/10 创建者:经云"
matchDate = "%d%d%d%d/%d%d/%d%d"
string.match(s, matchDate) --> 2024/10/10  --每一个%d都代表一位数字
matchDate = "%d%d"
string.match(s, matchDate) --> 20          --string.match查找的是第一次匹配,因此输出20
matchDate = "%d%d"
string.match(s, matchDate, 40) --> 24      --从40号位置开始搜索,略过了前面的20
matchDate = "%d+/%d+/%d+"  
string.match(s, matchDate) --> 2024/10/10  --%d+的意思是一个或多个数字组成的序列,2024,10,3,都算
s = "武器:黄金之刃 创建时间:2024//1 创建者:经云"
matchDate = "%d+/%d+/%d+"
string.match(s, matchDate) --> nil  --因为+代表最少为一个,因此结果输出nil
matchDate = "%d*/%d*/%d*"
string.match(s, matchDate) --> 2024//1 --因为*代表最少为零个,因此结果正常输出
string.gsub (s, pattern, repl [, n])

返回一个字符串s的副本,其中所有(或前n个,如果给定)出现的模式pattern都被替换为repl指定的替换字符串,repl可以是字符串、表或函数gsub还返回其第二个值,即发生的匹配总数。gsub的名字来源于全局替换(Global SUBstitution)。

如果repl是一个字符串 ,那么它的值将用于替换。字符%用作转义字符:repl中形如%d的任何序列,其中d在1到9之间,代表第d个捕获的子字符串的值;序列%0代表整个匹配;序列%%代表单个%

如果repl是一个 ,那么每次匹配时都会查询这个表,使用第一个捕获作为键。

如果repl是一个函数 ,那么每次发生匹配时都会调用这个函数,所有捕获的子字符串按顺序作为参数传递。

无论如何,如果模式没有指定捕获,那么它的行为就像整个模式都在一个捕获中。

如果表查询或函数调用返回的值是一个字符串或数字,那么它将用作替换字符串;否则,如果是falsenil,则不会进行替换(即原始匹配将保留在字符串中)。

--(%w+)代表一次捕获,内容是一个或多个(+)字母或数字(%w),%1代表第1个捕获的子字符串的值
--按照模式(%w+)进行匹配,先匹配出hello,然后按照%1%1的方式替换,也就是hellohello
--再次按照模式(%w+)进行匹配,匹配出world,然后按照%1%1的方式替换,也就是worldworld
--返回的第二个值是2,代表匹配的次数
string.gsub("hello world", "(%w+)", "%1%1") --> "hellohello worldworld 2"
--这次用第4个参数1代表强制匹配1次,结果只匹配hello,后面就不进行匹配了
string.gsub("hello world", "(%w+)", "%1%1", 1) --> "hellohello world 1"
--这次匹配使用(%w+)%s*(%w+),两个括号也就是说会有两个匹配,第一回匹配,1号hello,2号就是world
--第二回匹配,1号是from,2号是Lua,替换方式为%2 %1,结果就是将两个词语交换位置。
string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") --> "world hello Lua from 2"
--这次用%0%0作为替换,%0代表整个匹配,模式参数%w+也就不需要括号
string.gsub("hello world", "%w+", "%0%0") --> "hellohello worldworld 2"

--[[
    string.gsub("hello world", "(%w+)(%w+)", "%2%1")
   
    这次结果会是什么呢?
   
    思考
    。
    。
    。
    。
    。
    。
    
    答案居然是 ohell dworl	2,出乎我的意料。
    
    不过,经过一个中午的考虑,我明白了。
    
    上面的这个例子:
    string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
    与这个只差了中间的%s*,这代表0个或多个(*)空白字符(%s)
    从结果来看,(%w+)(%w+)第1次会匹配hell和o,然后根据%2%1,结合成为ohell,第2次结合为dworl
    以第1次匹配来说,(%w+)(%w+)会匹配一个单词的两个部分,前一个部分会尽可能的多,因为有+,所以是hell,
    但因为有后一个(%w+)的存在,必须留下一个o,所以%1代表hell,%2代表o,最后就是这个结果。

    那么这个string.gsub("hello world", "(%w%w)(%w+)", "%2%1")的结果,不容置疑,就是
    llohe rldwo	2

--]]
 
 
--这是将函数os.getenv作为第3个参数,将匹配的结果作为参数传入进去,返回结果
--%$代表$本身,也就是会分别匹配$HOME,$USER,将两者作为参数传给os.getenv
--返回结果和系统环境相关,我在华为云上返回结果分别是/root和root
x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
--> x="home = /root, user = root"
 
--(.-)这是匹配0个或多个任意字符(最小匹配),匹配结果为return 4+5,然后将它传给匿名函数,返回结果为9
x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s)
   return load(s)()
end)
--> x="4+5 = 9"
 
--最后使用表t作为匹配第3个参数,会将匹配的结果作为key去表t中查询,最后输出结果
local t = {name="lua", version="5.4"}
x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t) --> x="lua-5.4.tar.gz"
string.gmatch (s, pattern [, init])

返回一个迭代器函数,每次调用 时,它都会从字符串 s 中返回 模式的下一个捕获。如果模式没有指定捕获,那么每次调用都会产生整个匹配。第三个可选的数值参数 init 指定了搜索的起始位置;其默认值为 1,也可以是负数。

--依次输出s中的单词
s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do
 print(w)
end
--输出
hello
world
from
Lua

--从给定的字符串中找到所有的键值对并存储到表t中
t = {}
s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
   t[k] = v
end

字符串格式

以下的string.pack、string.unpack和string.packsize都需要用到特定字符串格式,因此这里先说说这个字符串格式fmt。

格式字符串可以包含以下几种格式说明符:

  • <>:表示字节序,< 表示小端字节序(little-endian),> 表示大端字节序(big-endian)
  • b/B: 有符号/无符号 8 位整数
  • h/H: 有符号/无符号 16 位整数
  • i/I: 有符号/无符号 32 位整数
  • l/L: 有符号/无符号 32 位整数(也可能是 64 位,取决于平台)
  • j/J: 有符号/无符号 64 位整数
  • t/T: 有符号/无符号 128 位整数(如果平台支持)
  • f: 32 位浮点数
  • d: 64 位浮点数
  • n: size_t 类型(无符号整数,大小足够存储指针)
  • c: 固定长度的字符串
  • z: 以 null 结尾的字符串
  • s: 长度前导的字符串

string.pack (fmt, v1, v2, ···)

返回一个二进制字符串,该字符串包含按格式字符串fmt序列化(打包)的值v1v2等。

string.unpack (fmt, s [, pos])

返回根据格式字符串 fmt在字符串 s 中打包的值(参见 string.pack)。可选参数 pos 标记在 s 中开始读取的位置(默认为 1)。读取值之后,此函数还会返回 s 中第一个未读字节的索引。

string.packsize (fmt)

返回使用给定的格式字符串通过 string.pack 打包后字符串的长度。格式字符串不能包含变长选项 's' 或 'z'。

--fmt = <I4d
--<代表使用小端字节序
--I代表使用无符号整数
--4代表整数的大小4字节
--d代表使用64位浮点数(8字节)
packed = string.pack("<I4d", 1024, 3.14159)
print(packed) --> 乱码
int_val, float_val = string.unpack("<I4d", packed)
print(int_val, float_val) --> 1024	3.14159
string.packsize("<I4d") --> 12
相关推荐
As977_1 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
ajsbxi4 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
Rattenking5 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
dsywws27 分钟前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
道法自然040228 分钟前
Ethernet 系列(8)-- 基础学习::ARP
网络·学习·智能路由器
爱吃生蚝的于勒34 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
cuisidong19972 小时前
5G学习笔记三之物理层、数据链路层、RRC层协议
笔记·学习·5g
南宫理的日知录2 小时前
99、Python并发编程:多线程的问题、临界资源以及同步机制
开发语言·python·学习·编程学习
数据与后端架构提升之路3 小时前
从神经元到神经网络:深度学习的进化之旅
人工智能·神经网络·学习
一行13 小时前
电脑蓝屏debug学习
学习·电脑