Lua 是一门小型的脚本语言。我学习他的最初目的是因为他简单的设计理念,简单的语法和实现,学习成本很低(但是要学好还是难的),以及由于其实现简单,被广泛运用在嵌入式领域中,我觉的很有意思。后面在慢慢学习的过程中,我发现Lua也是学习编译原理、虚拟机和脚本引擎的一个很好的开始。恰巧我对这些都很感兴趣。所以暑假我想好好研究一下这个语言,看看能不能把他融入进我现在技术路线里面。
我参考的书是《Lua程序设计(第四版)》和菜鸟教程,之后可能会参考一些新的吧但是不重要,我使用的版本是Lua5.3。今天的学习目标主要是掌握Lua的基本语法和数据结构,了解一下这个语言的框架,那么现在就开始吧。学习顺序就按书上来了。
语言基础
Lua语言入门
简单的规范
关于变量的命名要求和风格和C差不多,不能以数字开头,通常为任意字母、数字、下划线组成。其中_ + 大写字母的用法一般有特殊用途,_ + 小写字母的用法为哑变量(就是0/1的变量)
保留字很少,这里记录一下就当是熟悉了:
and break do else elseif
end false goto for function
if in local nil not
or repeat return then true
until while
除了repeat和until其他的还挺熟悉的
然后注释有两种类型:
-- 这是单行注释
print(0)
--[[
这是多行注释
这是多行注释
--]]
语句间的分隔比较自由,我的习惯还是以;分割
全局变量
Lua语言中,变量默认为全局变量,使用未初始化的全局变量会得到nil,不会得到错误:
> a
nil
> a = 1
> a
1
同样的,将nil赋值给变量,就等同于回收这个变量:
> a = nil
> a
nil
类型和值
Lua是动态类型语言,每个值自带类型信息,Lua中以下八种基本类型:
nil(空)boolean(布尔)number(数值)string(字符串)userdata(用户数据)thread(线程)function(函数)table(表)
我们可以通过type()来查看值的对应的类型:
> type(nil)
nil
> type(type)
function
> type(type(type))
string
> type(3.14)
number
> type({})
table
> type(io.stdin)
userdata
这里的userdata和C相关,关于他的解释,我现在有点看不懂,所以就不做解释了。
而nil则是一个特殊的类型,他的作用就是和其他所有值进行区分。它代表无效值。
Boolean是布尔类型,除了false和nil以外的所有值均为真,这里的逻辑运算符运算结果为:
- and:若第一个数为假,则返回第一个数
- or:若第一个数为真,则返回第一个数
由于关键词和逻辑运算符比较少,所以这一部分的设计会有点复杂,后面应该用多了就还好了。
参数传递
编译器在运行代码之前会创建一个叫arg的表,用来存储命令行参数:
- 参数0:保存内容为脚本名
- 参数1:保存内容为第一个参数
- 在脚本名之前的选项为负数
lua -e "sin = math.sin" script a b
arg[-3] = "lua"
arg[-2] = "-e"
arg[-1] = "sin = math.sin"
arg[0] = "script"
arg[1] = "a"
arg[2] = "b"
Lua中也有可变长参数,不过等后面再说。
数值
在Lua5.3开始,Lua的数值格式只有两种选择:
- 被称为
integer的64位整型- 被称为
float的双精度浮点数(不是C中的单精度)
数值常量
在Lua中我们除了正常书写一个整数或者小数,我们还可以通过科学计数法来表示一个数值:
> 0xff -- 支持十六进制
255
> 3.4
3.4
> 3e2
300.0
> 3e-2
0.03
> 5E20
5e+20
其中,我们把具有十进制小数和指数的数值当作浮点数值,否则则视为整数。我们可以通过math.type()来分辨number是整数还是浮点数:
> math.type(1)
integer
> math.type(1.0)
float
> math.type(1e+10)
float
由于integer和float都是number类型,所以他们可以相互转换。具有相同算术值的整型值和浮点值在Lua中是相等的:
> 1 == 1.0
true
> -2 == -2.000
true
> 1e2 == 100
true
算数运算
和其他语言差不多,主要遵循以下规则:
- 如果两个操作数都是整型,那结果一定是整型
- 反之,结果是浮点数。当一个操作数是浮点数时,Lua会把另一个操作数也转换成浮点数
- 由两个整数相除的结果不要一定是整数,所以除法的结果均为浮点数。除非使用整数除法
//
这里不做过说明。
关系运算
Lua提供了一下几种关系运算:
< > <= >= == ~=
他们的运算结果都是Boolean。
其中==用来做相等性测试,~=是不等性测试。操作数可以是任意两个值,但是如果值的类型不同,则无法比较,是不等的。
数学库
Lua中有标准库math,提供标准的数学函数。这里就记录下书上的几个例子:
随机数发生器
math.random()用于生成伪随机数,有三种调用方式:
math.random()-> 返回一个的数 math.random(n)-> 返回一个的数 math.random(l,y)-> 返回一个的数
我们还可以用math.randomseed()设置随机数的种子,常见用法:
math.randomseed(os.time())
取整函数
共提供了三种取整函数:
floor:负无穷取整ceil:向正无穷取整modf:向0取整,返回整数部分和小数部分
下面演示一下基本用法了:
> math.floor(3.3)
3
> math.floor(-3.3)
-4
> math.ceil(3.3)
4
> math.ceil(-3.3)
-3
> math.modf(3.3)
3 0.3
> math.modf(-3.3)
-3 -0.3
表示范围
数值在范围和精度上是用限制的。
标准Lua使用64bit来存储整数,其最大值为math.maxinteger和math.minteger来访问整型的最大和最小值:
> math.maxinteger
9223372036854775807 -- 0x7fffffffffffffff
> math.mininteger
-9223372036854775808 -- 0x8000000000000000
大于这个值的数就会溢出然后回环。
浮点数的范围大概是
有个小技巧是:可以通过对整型加上0.0从而将整型转换成浮点数
剩下的就是一些,边界情况,这里就不做讨论
运算符优先级
这个和C是相同的,不做说明
字符串
在Lua中字符串既可以是单个字符也可以是一段长文本,并没有直接的限制。同时单字符由8位存储,对于特殊的编码方式,如UTF-8会在后面提到。
需要注意的是Lua的字符串是不可变的值。我们不能像C一样对字符串中的单个词进行修改,我们需要创建一个新的字符串以达到改变字符串内容的效果。(Lua中的字符串是自动内存管理的对象,所以不用手动回收)
> a = "one string"
> b = string.gsub(a, "one", "another")
> a
one string
> b
another string
我们可以使用#作为长度操作符,获取字符串的长度:
> #b
14
> #a
10
> #"hello world"
11
我们还可以通过连接字符串..来进行字符串的拼接(拼接时会把数转换为字符串形式再拼接):
> a = "hello"
> a .. "world"
helloworld
> a .. " world"
hello world
> a
hello
字符串常量
像a = "hello"和b = 'world'这几种形式的声明就是字符串常量,所以这里不用做过多说明,其中字符串常量的转义字符和C是差不多的所以也不必多说。
需要提一嘴的是,在Lua中可以通过转义序列\u{h..h}的方式来声明UTF-8字符常量:
> "\u{3b1} \u{3b2} \u{3b3}"
α β γ
长字符串/多行字符串
Lua中的特殊语法——可以使用一对双方括号来声明长字符串/多行字符串,下面就是一个很好的例子:
page = [[
<html>
<head>
<title> Hello </title>
</head>
<body>
<a href="http://www.lua.org"> Lua </a>
</body>
</html>
]]
print(page)
得到:
> lua hello.lua
<html>
<head>
<title> Hello </title>
</head>
<body>
<a href="http://www.lua.org"> Lua </a>
</body>
</html>
这里可以注意到[[后的第一个换行符是不生效的,但是在]]前的依然生效,需要注意一下。
强制类型转换
Lua在运行时提供了数值和字符串的自动转换。针对字符串的所有算数操作都会转换成数值进行,以及在参数应该是数值时进行。相反的,当解释器发现在需要字符串的地方出现了数值,也会将数值转换成字符串。
如果需要显示的将一个字符串转成数值,我们可以使用这个函数tonumber():
> tonumber("-3")
-3
> tonumber("3123")
3123
> tonumber("3123.0")
3123.0
> tonumber("asd")
nil
> tonumber("10e4")
100000.0
我们还可以给tonumber()提供第二个参数来指明进制转换:
> tonumber("11111111",2)
255
> tonumber("ff",16)
255
> tonumber("123",4)
27
> tonumber("123",3)
nil --传入的进制参数无法正确转换,所以为nil
相反的,调用函数tostring()也可以将数值转换成字符串:
> tostring(10) == "10"
true
字符串标准库
Lua处理字符串的完整能力,主要来自于标准字符串库,这里就按照书上的顺序一点点复现一遍
string.len(s)-> 获取s的字符串长度,等价于#sstring.lower()/string.upper-> 将所有字符小写/大写string.rep(s,n)-> 将字符串s重复n次string.reverse(s)-> 将字符串反转
e.g.
> a = "Hello World"
> string.len(a) == #a
true
> string.upper(a)
HELLO WORLD
> string.lower(a)
hello world
> string.rep(a,3)
Hello WorldHello WorldHello World
> string.reverse(a)
dlroW olleH
string.sub(s,i,j)-> 从字符串s中提取第i到j个字符
e.g.
> s = "[hello world]"
> string.sub(s,2,-2)
hello world
> string.sub(s,1,1)
[
> string.sub(s,-1,-1)
]
string.char(...)-> 接收一个或多个整数作为参数,然后将每个整数转换成对应的字符string.byte(s,i,j)-> 根据参数的数量决定用,总之是将字符转换成整数
e.g.
> string.char(97)
a
> i = 98
> string.char(i,i+1,i+2)
bcd
> string.byte("abc")
97
> string.byte("abc",2)
98
> string.byte("abc",-1)
99
> string.byte("abcdef",1,-1)
97 98 99 100 101 102
> string.byte("abcdef",1,3)
97 98 99
string.format(...)-> 和C差不多,用于格式化字符串参数
e.g.
> string.format("x = %d",10)
x = 10
> string.format("x = 0x%x",255)
x = 0xff
> string.format("x = %f",10)
x = 10.000000
> string.format("PI = %.2f",math.pi)
PI = 3.14
> tag,title = "h1","a little"
> string.format("<%s>%s</%s>",tag,title,tag)
<h1>a little</h1>
string.gsub(s,a1,a2)-> 将字符串s中的a1替换成a2
e.g.
> string.gsub(s, "l", ".")
He..o Wor.d 3
> string.gsub(s, "ll", ".")
He.o World 1
> string.gsub(s, "a", ".")
Hello World 0
UTF-8编码
我觉的这一部分比较复杂,所以就直接跳过了