字符串和字符串标准库

1、简介编码方式

计算机的视角,世间万物不过是一串又一串的二进制字节流,以人类认识的字符为例,每一个字符串就必须有与之对应一个二进制码,这就叫做编码。然而在发明计算机时,发明者们只考虑到计算机处理的是小写字母和大写字母,以及一些特殊字符,这些加起来不超过128个,所以采用1字节(0-255)空间大小来编码绰绰有余,这就是经常听到的ASCII码;‘a’对应的ASCII码为97,‘b’对应的ASCII为65。

随着计算机的普及,计算机要处理不单单是英文字母,中国人也要使用计算机,对于那么多的中文汉字,ASCII码显然不够使用,于是后续就有了中国人为中文提出GBK,BG2312等编码,这些编码现在还有在使用。为了兼容任何国家的所有字符,国际上提出了Unicode编码:一个字符,不管是英文字母,还是中文汉字,还是其他,都统一用2字节来表示,2字节的大小已经能让计算机处理世界上的任何字符了。

但是对于一个只需要1字节空间大小就可以描述的英文字母来说,使用2字节(没用到的位填0)实在浪费,当时计算机硬件又比较珍贵,所以后续又提出基于Unicode的变换编码,UTF-8或UTF-16等,其中UTF-8是最为普及的。UTF-8是一种可变长的字符编码方式:对于英文字母还是采用1个字节表示,对于中文汉字则采用3字节。这里可能有人会问,Unicode编码表示中文才2字节,为什么UTF-8反而需要占据3字节?这是因为编码算法,UTF-8需要一些额外数据位记录以作区分,详细的UTF-8这里不做过多介绍,详细网上资料很多。

2、字符串常量和字符串标准库

我们知道c/c++中字符串有字符串常量和变量两种形式:

const char *str = "hello"; //字符串常量
char str2[] = "world";     //字符串变量

字符串变量str2是可通过数组下标或者指针的方式修改字符的,Lua语言则不同,它只有字符串常量,是不可变值。Lua中可以使用一对双引号或单引号来定义字符串常量:

str1 = “Lua: hello”
str2 = ‘Lua: world’

单引号和双引号是等价的,使用时注意配对即可。

因为Lua的字符串是不可变值,所以对字符串的任何修改、拼接操作都只能通过创建新字符串的方式实现。

Lua语言解析器本身处理字符串的能力十分有限:创建字符串,连接字符串和比较字符串、获取字符串长度:

(1) …用于实现字符串连接,如果操作数中存在数值类型,Lua会先将数值转为字符串;
(2) #操作符用于获取字符串长度,该操作符返回字符串占用的字节数。

Lua处理其他字符串的操作则依赖于Lua字符串标准库。字符串标准库默认处理的是占据1字节空间的字符,所以它仅适用于ASCII编码和部分UTF-8编码,不适用于Unicode编码;下面是标准库中常用的函数。

> str="abcd"
> print(string.len(str))  --获取字符串长度4
> string.rep("123", 4)    --创建由4个"123"组成的字符串123123123123
> string.reverse("abcd")  --字符串翻转dcba
> string.lower("AbCd")    --将字符串转为小写abcd
> string.upper("AbCd")    --将字符串转为大写ABCD

使用Lua字符串标准库的gsub()函数作字符串替换:

> str1 = "hello Shanghai"
> str2 = string.gsub(str1, "Shanghai", "Shenzhen")
> print(str1)
hello Shanghai
> print(str2)
hello Shenzhen

注意,调用字符串标准库的gsub()函数的调用需要指定作用域string。

string.sub(s, i, j)实现从字符串s中提取第i个到第j个字符(包括第i个和第j个,且字符串的第一个字符为索引值为1);该函数也支持负数索引,负数索引是从字符串的结尾开始计数,-1表示字符串的最后一个字符,-2表示倒数第二个字符,以此类推。如:

> string.sub(str, 2, -2)
hello lua
> string.sub(str, 1, 1)
[
> string.sub(str, -1, -1)
]
> print(str)
[hello lua]
>

string.char()和string.byte()函数实现字符和编码数值之间的转换:
string.char()接收零个或者多个整数作为参数,实现将每个整数转换成对应的字符,返回这些字符连接而成的字符串。

函数string.byte(s, i)返回字符串s中的第i个字符的编码数值,参数i是可选的,不指定i时返回字符串s中的第一个字符。该函数还支持string.byte(s, i, j)形式以返回索引i到j之间(包括i和j)的所有字符的数值表示:

> print(string.char(65))
A
> i = 65; print(string.char(i, i + 1, i+ 3))
ABD
> print(string.byte("abc"))
97
> print(string.byte("abc", 2))
98
> print(string.byte("abc", -1))
99
> print(string.byte("abcd", 2, 4))
98  99  100

string.byte(s, i, j)常见的用法为:

{ string.byte(str, 1, -1) }

这样将创建一个表,表中的成员对应字符串s每个字符对应的编码数值。由于Lua限制了栈的大小,所以该表的成员个数最多1百万个,对应的字符串大小不超过1MB。

string.format()函数是字符串的格式化和将数值格式化为字符串的强大工具,其返回值是格式化后的字符串的副本。格式化字符串中的指示符(如%s、%c、%d)跟c语言中的print()函数规则类似。

> print(string.format("hello %s", "lua"))
hello lua
> string.format("x=%d, y=%x, z=%f", 12, 200, 16)
x=12, y=c8, z=16.000000
> string.format("x=%04d, y=%#x, z=%.2f", 12, 200, 16)
x=0012, y=0xc8, z=16.00

基于模式匹配的函数,如string.find()用在指定的字符串中进行搜索:

> string.find("hello lua", "lua")
7  9
> string.find("hello lua", "python")
nil

string.find()在指定字符串中到匹配的模式则返回该模式的开始和结束位置的索引,否则返回nil。

3、 utf-8字符串标准库

Lua字符串标准库只部分支持utf-8字符串:reverse()/upper()/lower()/byte()和char()针对的操作对象是1字节大小的字符,不可适用于utf-8编码的全部字符(比如一个中文字符的大小是3个字节);函数string.format()和string.rep()适用于utf-8字符串但是格式化选项不可以是“%c”,其索引并非以字符为单位而是以字节为单位。

Lua 5.3引入了用于操作utf-8编码的Unicode字符串的标准库,我们称之为utf-8字符串标准库。函数utf8.len()返回字符串中utf8字符(代码点。比如“编程”二字占据6个字节,其代码点为2,代码点计算的是真正的字符数而非字节数)的个数。

> string.len("编程")
6
> utf8.len("编程")
2

函数utf8.char()和utf.codepoint()等价于string.char()和string.byte():

> utf8.char(32534, 31243)
编程
> utf8.codepoint("编程")
32534
> utf8.codepoint("编程", 1)
32534

通过utf8.codepoint()打印“编程”中的“程”的utf编码:

> utf8.codepoint("编程", 2)
stdin:1: invalid UTF-8 code
stack traceback:
  [C]: in function 'utf8.codepoint'
  stdin:1: in main chunk
  [C]: in ?
> utf8.codepoint("编程", 4)
31243

从运行结果可看,不可通过索引2去获取“编程”中的“程”,而是要用4,这是因为utf8库中大多数函数使用字节作为索引,“编”字占据3字节,“程”的索引值是4。如果想要字符位置作为索引,可借助utf8.offset()将字符位置转为字节位置,也就是将字符索引转为字节索引:

> utf8.codepoint("编程", utf8.offset("编程", 2))
31243

utf8.codes()函数用于遍历utf8字符串中的每一个字符:

> for k, v in utf8.codes("池塘的水满了雨也停了") do
>> print(k, v)
>> end
1  27744
4  22616
7  30340
10  27700
13  28385
16  20102
19  38632
22  20063
25  20572
28  20102

Lua语言追求精简,所以没有再为Unicode编码提供更多机制。

4、Lua字符串其他特性

4.1、转义

Lua语言字符串支持c语言风格的转移字符,如:

\b: 退格
\n: 换行
\r: 回车
\t: 水平制表符
\v: 垂直制表符
\\: 反斜杠
\”: 双引号
\’: 单引号

另外,通过\ddd和\xhh声明字符,ddd是由最多3个十进制数字组成的序列,hh是由两个且必须是十六进制数字组成的序列,如:

> print("\065")
A
> print("\x41")
A
>

4.2、多行字符串

Lua的多行注释是用“–[[ 注释 --]]”的形式表示的,多行字符串的表示方式跟注释类似:

> html_str = [[
>> 
>> 
>> ...
>> 
>> ]]
>

4.3、强制类型转换

Lua语言在运行时会在数值和字符串之间自动转换,注意这种转换时不会出现在比较运算符的。

> a1 = "1"
> a2 = "3"
> print(a1 + a2)
4.0
> print(10 .. 24)
1024

注意,Lua中两个操作数都为整型值时进行算数运算才为整型,由于字符串不是整型值所以代码中a1加a2为浮点型;
函数tostring()和tonumber()可以显式地将数值转为字符串和将字符串转为数值。当操作数不能被正确转换时函数返回nil:

> print(tostring(100) == "100")
true
> tonumber("a")
nil
> tonumber("12")
12

默认情况下tonumber()转换后的数值为十进制,我们也可以指定为二进制到三十六进制之间的任意进制:

> tonumber("12", 16)
18
> tonumber("12", 2)
nil
> tonumber("10", 2)
2

比较操作符则不会进行类型转换,0和“0”不同,“2”<“16”为false(根据字母顺序比较),2<“16”比较时Lua语言会抛出异常:

> 0 == "0"
false
> 2 < 16
true
> "2" < "16"
false
> 2 < "16"
stdin:1: attempt to compare number with string
stack traceback:
  stdin:1: in main chunk
  [C]: in ?

字符串操作比较简单,但却是在使用实际开发的工作重心之一。

参考书籍:programming in Lua(fourth edition)

本文摘自公众号Linux开发者札记。如果觉得本文对你有点用处,欢迎关注。

你可能感兴趣的:(Lua编程)