Lua入门学习
了解到Lua
(一种轻量级、可嵌入式的脚本语言),其实是很早之前和一些做游戏的朋友聊天的时候知道的,他们在项目中广泛地使用到这个语言。于是自己上网也看了一些lua开发中的一些特定发现,Lua 被运用的领域远不止游戏。特别最近很火的Redis和OpenResty
(一个基于Nginx 与Lua 的高性能Web 平台)其内部集成了大量精良的Lua 库、第三方模块以及大多数的依赖项,开发人员使用Lua编写脚本就能非常轻松开发出高性能的服务了。
若要了解一下lua在行业更深的应用,可以阅读云风的相关文章:点击这里
下面着重的还是Lua入门级别的学习总结
特性
Lua是一个动态弱类型
语言,支持增量式垃圾收集策略。有内建的,与操作系统无关的协作式多线程(coroutine)支持。
- 可以很方便的和用C/C++互相调用
- 很简单,不涉及任何复杂的编程概念,对数据结构的描述能力强
- 轻量级,库体积很小,只有几百K。
- 学习很容易,开发效率高
- 性能好, 支持协程,闭包等简化实现的手段
安装
lua的安装非常简单,直接参考下面的命令
Linux环境
curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz
tar zxf lua-5.3.4.tar.gz
cd lua-5.3.4
make linux test
make install
Mac环境
curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz
tar zxf lua-5.3.4.tar.gz
cd lua-5.3.4
make macosx test
make install
安装完成后,我们在终端里面敲入lua,接口进入lua的命令交互模式
$ lua
Lua 5.3.4 Copyright (C) 1994-2017 Lua.org, PUC-Rio
> print("go")
go
内置类型
既然是动态类型的脚本语言,这说明同一个变量可以在不同时刻指向不同类型的数据
lua的类型主要包含 字符串、函数、布尔、数字、空(nil)、表
1、lua内置函数 type
能够返回一个值或一个变量所属的类型:
testtype.lua
#!/usr/local/bin/lua
print(type("golang"))
print(type(print))
print(type(true))
print(type(1.9))
print(type(nil))
执行输出
./testtype.lua
string
function
boolean
number
nil
2、字符串
字符串你可以用单引号,也可以用双引号,还支持C类型的转义,比如:
‘\a’ (响铃),
‘\b’ (退格),
‘\f’ (表单),
‘\n’ (换行),
‘\r’ (回车),
‘\t’ (横向制表),
‘\v’ (纵向制表),
‘\\’ (反斜杠),
‘\”‘ (双引号),
‘\” (单引号)
下面的四种方式定义了完全相同的字符串(其中的两个中括号可以用于定义有换行的字符串)
#!/usr/local/bin/lua
a = 'go\n1.9"'
print(a)
a = "alo\n1.9\""
print(a)
a = [[go
1.9"]]
print(a)
使用前缀运算符’#’(也称为长度运算符)可以得到字符串的长度。
#!/usr/local/bin/lua
a = "golang"
print(#a)
print(#"123\0xyz")
输出:
6
7
3、数值
Lua 默认只有一种 number 类型:double(双精度)
Lua没有整数类型
#!/usr/local/bin/lua
a = 10
b = 20
c = (a+b)/2
print(c)
print(type(c))
输出
15.0
number
4、空值
nil是单值类型,即空值,其主要属性就是区别于其他任何值。Lua用空值来表示无值,表示一个值不存在。我们已经知道,一个全局变量在首次赋值之前,其默认值为空值,所以你可以将一个全局变量赋值为nil来删除该变量。
5、布尔类型
布尔值有两个值:false和true,表示通常意义的布尔值。但是,布尔值不是唯一的条件值:在Lua中,任何值都代表了一个条件。条件判断测试(如控制结构中的条件)将布尔值false和空值(nil)计为false,其他任何值均计为true。
Lua在条件判断测试中将零(0)和空字符串(“”)计为true。
#!/usr/local/bin/lua
a = 0
if a then
print("yes")
end
b = ""
if b then
print("yes")
end
结果输出都是 yes
6、表
Lua表分为数组和散列表,其中数组部分不像其它语言那样从0开始作为第一个索引,而是从1开始。散列部分其实就是一个Key Value的数据结构,相当于Go里面的map或者python里面的dict。
使用Lua表,可以模拟出其它各种数据结构:数组、链表、树等。
book = {name="go in action", price=256, desc="programming language book"}
可以直接操作book表
book.name = "go web programming"
book.price = "100"
你也可以定义成不同的类型的数组,比如
数组是表的一种表现形式:
books = {10,20,30,40,50}
这样看上去就像数组了。但其实其等价于:
books = {[1]=10, [2]=20, [3]=30, [4]=40, [5]=50}
所以,你也可以定义成不同的类型的数组,比如:
books = {"string", 100, "golang", function() print("golang.org") end}
注:其中的函数可以这样调用:
arr[4]()
。
我们可以看到Lua的下标不是从0开始的,是从1开始的
。
for i=1, #books do
print(books[i])
end
控制流
while循环
sum = 0
num = 1
while num <= 100 do
sum = sum + num
num = num + 1
end
print("sum =",sum)
if-else分支
a = 50
if a ==1 then
print("start")
elseif a ~= 100 then
print("half")
else
print("finish")
end
另外,条件表达式中的与或非为分是:and, or, not关键字。
for 循环
从1加到100
sum = 0
for i = 1, 100 do
sum = sum + i
end
until循环
sum = 2
repeat
sum = sum ^ 2
print(sum)
until sum >1000
结构
先简单看一个自定义的Person结构
在 Lua 中,我们可以使用表和函数实现面向对象。将函数和相关的数据放置于同一个表中就形成了一个对象。
person.lua
#!/usr/local/bin/lua
local _Person = {}
local mt = { __index = _Person }
function _Person.func1 (self, v)
self.cardno = self.cardno + v
end
function _Person.func2 (self, v)
if self.cardno > v then
self.cardno = self.cardno - v
else
error("insufficient funds")
end
end
function _Person.new (self, cardno)
cardno = cardno or 0
return setmetatable({cardno = cardno}, mt)
end
return _Person
上面这段代码 “setmetatable({cardno = cardno}, mt)“,其中 mt 代表 { __index = _M },setmetatable 将 _M 作为新建表的原型(protype),所以在自己的表内找不到 ‘func1’、’func2’ 这些方法和变量的时候,便会到 __index 所指定的 _M 类型中去寻找
创建引用测试文件 demo.lua
#!/usr/local/bin/lua
local person = require("person")
local a = person:new()
a:func1(100)
local b = person:new()
b:func1(50)
print(a.cardno)
print(b.cardno)
输出
100
50
函数
跟go一样,函数在Lua中属于一等公民(程序将函数存在变量中,把函数作为参数传递给其他函数,返回函数作为返回结果)这些功能使语言有很大的灵活性;程序可以对函数进行重新定义,来增加新功能,或者在运行一段不信任的代码(比如从网上得到的代码)时简单地删除一个函数,从而创造安全的环境。
Lua可以调用在Lua里写的函数,以及在C语言里的函数。一般使用C语言函数,既可以实现更好的性能,还可以使用从Lua中难以直接方便地访问的功能,比如系统操作的硬件。所有的Lua标准库都是写在C语言里的,其中包括的函数用于字符串操作、表操作、I/O、访问操作系统设备、科学计算函数以及调试
定义
Lua 使用关键字 function 定义函数,语法如下
function function_name (arc)
-- body
end
arc 表示参数列表,函数的参数列表可以为空
上面的语法定义了一个全局函数,名为 function_name. 全局函数本质上就是函数类型的值赋给了一个全局变量,即上面的语法等价于
function_name = function (arc)
-- body
end
这种定义方式跟我们平常写javascript很像
由于全局变量一般会污染全局名字空间,同时也有性能损耗(即查询全局环境表的开销),因此我们应当尽量使用“局部函数”
:
lua中的全局变量使用需谨慎,可以使用 lua-releng-tool 脚本check非预期的global variable,尽量使用local 申明变量
local function function_name (arc)
-- body
end
由于函数定义本质上就是变量赋值,而变量的定义总是应放置在变量使用之前,所以函数的定义也需要放置在函数调用之前
local function max(a, b)
local temp = nil
if(a > b) then
temp = a
else
temp = b
end
return temp
end
local m = max(-12, 20)
print(m)
如果参数列表为空,必须使用 () 表明是函数调用。
local function func()
print("no parameter")
end
func()
由于函数定义等价于变量赋值,我们也可以把函数名替换为某个 Lua 表的某个字段,例如
function foo.bar(a, b, c)
-- body ...
end
此时我们是把一个函数类型的值赋给了 foo 表的 bar 字段。换言之,上面的定义等价于
foo.bar = function (a, b, c)
print(a, b, c)
end
对于此种形式的函数定义,不能再使用 local 修饰符了,因为不存在定义新的局部变量了。
传参
Lua 函数的参数大部分是按值传递的。值传递就是调用函数时,实参把它的值通过赋值运算传递给形参,然后形参的改变和实参就没有关系了。在这个过程中,实参是通过它在参数表中的位置与形参匹配起来的
local function swap(a, b)
local temp = a
a = b
b = temp
print(a, b)
end
local x = "hello"
local y = 20
print(x, y)
swap(x, y)
print(x, y)
调用swap函数后,x和y的值并没有交换,证明函数调用中x、y是按值传递
变长参数
Lua 还支持变长参数。若形参为 ...
, 表示该函数可以接收不同长度的参数。
访问参数的时候也要使用
...
local function func( ... )
local temp = {...}
local ans = table.concat(temp, " ")
print(ans)
end
func(1, 2)
func(1, 2, 3, 4)
输出:
1 2
1 2 3 4
返回值
和Go语言一样,可以一条语句上赋多个值,如:
function getBookInfo(id)
print(id)
return "go in action", 100, "SN123232323", "golang.org"
end
bookname, price, sn, website = getBookInfo()
值得一提的是,如果实参列表中某个函数会返回多个值,同时调用者又没有显式地使用括号运算符来筛选和过滤,则这样的表达式是不能被 LuaJIT 2 所 JIT 编译的,而只能被解释执行。
lua编程中,经常遇到函数的定义和调用,有时候用点号
调用,有时候用冒号
调用,这里简单的说明一下原理。如
点号调用:
-- 点号定义和点号调用:
girl = {money = 200}
function girl.goToMarket(girl ,someMoney)
girl.money = girl.money - someMoney
end
girl.goToMarket(girl ,100)
print(girl.money)
引用参数self:
-- 参数self指向调用者自身(类似于c++里的this 指向当前类)
girl = {money = 200}
function girl.goToMarket(self ,someMoney)
self.money = self.money - someMoney
end
girl.goToMarket(girl, 100)
print(girl.money)
冒号调用:
-- 冒号定义和冒号调用:
girl = {money = 200}
function girl:goToMarket(someMoney)
self.money = self.money - someMoney
end
girl:goToMarket(100)
print(girl.money)
冒号定义和冒号调用其实跟上面的效果一样,只是把第一个隐藏参数省略了,而该参数self指向调用者自身。
冒号只是起了省略第一个参数self的作用,该self指向调用者本身,并没有其他特殊的地方
一些坑
table.sort
table.sort是lua自带的排序函数,但传入的compare函数可能会出现某些问题。例如:
#!/usr/local/bin/lua
local demolist = { 9,8,3,7,0,4,5,1,5 }
table.sort(demolist,function(a,b) return a>=b end )
for i=1,#demolist do
print(demolist[i])
end
上面代码执行的时候报错了:
invalid order function for sorting
stack traceback:
[C]: in function 'table.sort'
./keng.lua:3: in main chunk
[C]: in ?
原因在于lua的compare函数中不能出现等于(=
),如果使用了=,当数组中出现两个权重一样的元素时,就会报错。具体原因你可以看table.sort的官方文档,有句话:
If comp is given, then it must be a function that receives two list elements and returns true when the first element must come before the second in the final order (so that, after the sort, i < j implies not comp(list[j],list[i]))
只能把代码改回以下的形式,结果执行就正常了
#!/usr/local/bin/lua
local demolist = { 9,8,3,7,0,4,5,1,5 }
table.sort(demolist,function(a,b) return a>b end )
for i=1,#demolist do
print(demolist[i])
end
顺序
lua的table结构背后有一套自己的结构,这样能够保证对数据做快速的查询操作。所以key被做了hash处理,所以最后变量table的时候, key不一定都是安装预期的插入顺序进行排列的。
错误
LUA代码执行如果出现错误会停止执行函数后面的代码,会导致需要严格控制的流程只执行了一部分,产生逻辑错误。