《Lua程序设计第二版》
实验环境使用lua5.3
1.开始
- 函数调用需要加括号()
- -- 为行注释; --[[ xxxx ]] 为块注释。--[[ xxx –]]
- 代码块不需要花括号;代码块结束的时候,需要加end以区分结束
4 代码语句可以使用分号表示结束,也可以不用分号结束
5 输出:print (). 空: nil - dofile()函数,立即执行一个文件:dofile(“lua2.lua”)
- 以下划线开头,后面跟大写字母的标识符是lua的标识符保留用途,应该避免在自己的程序中这样使用。eg._VERSION
- lua保留字区分大小写的:
- and end elseif function in local nil repeat then until not or
- break do if else fasle true for return while
- 全局变量:
- 直接赋值给变量就可以了。变量不能是nil.所以如果全局变量后来被赋值为nil后,就不可用了。
- 在Unix下,脚本开始 #!/usr/local/bin/lua 或 #!/usr/bin/env lua表示将lua作为一种脚本解释器来使用。调用的时候,可以使用:lua [选项参数] [脚本 [参数]] 进行调用
lua参数
- -i 执行完之后,进入交互模型(进入lua命令行模式)
- -e 可以直接在命令行中输入代码
- -l 连接lib文件 eg. lua -l liba
- 启动参数: lua 脚本 argA argB argC lua解释在运行脚本前,会将所有的命令行参数创建一个名为“arg”的table,脚本名称在index=0出,脚本前的index为负数,脚本后的参数为正数
- 脚本使用“…”表示变长参数语法
2.类型与值
lua是动态语言类型。每个值都自己携带了它自身的类型信息.
- 8中基础类型名称:nil(空) boolean(布尔) number(数字) string(字符串) userdata(自定义类型) function(函数) thread(线程) table(表)
- type()函数可以返回值的类型名称
- boolean(布尔)
false和true
Lua中,false和nil为“假”,其他值都为“真”。(数字0和空字符串也视为“真”) - number(数字:实数)
Lua没有整数类型。
32位 - string(字符串)
“一个字符序列”
utf-8编码
字符串是不可变值
string函数:string.xxx()
可以使用[[ str… ]]括起来的方式界定一个字母字符串,可以多行,并且Lua不会解释其中的转义序列.
运行时数字与字符串尝试自动转换。
字符串连接操作符: ..
tonumber()函数将字符串转为数字
tostring()函数将数字转为字符串
io.read():读取一行
在字符串前放置操作符“#”可以获得该字符串的长度: a=”hello” print(#a) . # 称作“长度操作符” - table(表)
table实现了“关联数组”,可以通过整数索引,还可以使用字符串和其他非nil的值来索引。
table没有固定的大小,可以动态添加和删除元素。
Lua通过table来表示模块(module)、包(package)和对象(object)。
创建table使用{}
使用[]索引,[]中的内从为key的内容。可以是变量。将变量的内容作为key.
使用table.keyname进行索引,keyname是个字符串索引,相当于table[“keyname”]。keyname是个字符串,不是一个变量。eg. table1.key1
注意,当table定义的是键值对集合。如果[]用于进行key的索引;如果table定义的只是一个集合,那么,可以当做列表使用,此时[]用数字进行索引,并且习惯从index=1开始的。
table索引是,index从1开始。#table返回table最后一个元素的index,既返回了table的长度。 - function/userdata和thread简述
- function: “第一类值” 函数式编程
- userdata: 存储c语言创建的新类型数据。
- thread: 在第九章….
3.表达式
- 算数操作符:新
^ 指数操作符。 eg.x^0.5将计算x的平方根 - 关系操作符
注意,== 是相等测试,而~=是不等于测试,没有!= - 逻辑操作符
使用and or not,而不是& | ! - 字符串连接,使用”..”,两个点
如果将两个数字连接,结果会转换为字符串。(注意,第一个数字与连接符直接要有空格,否则编译不通过)eg 0 .. 1 -> “01” - #操作符,取长度
- table构造式(table constructor)
{}构造空table
初始化数组(此时key为从1开始的数字,元素为对应的value):array={“ele1”,”ele2”,”ele3”,”ele4”},则print(array[3]) –> ele3
初始化记录(键值对类型): xtab={x=10,y=20},等价于xtab={};xtab.x=10;xtab.y=20。
初始化记录和初始化数组可以混用。混用后,使用[index]的时候,得到的是数组内容,使用tab.key方式得到的是记录方式的内容。
当将元素设置为nil的时候,相当于是删除了这个元素
使用[]=value的方式进行元素的赋值。eg. xtab={[1]=”value1”,[2]=”value2”,[“x”]=3},xtab[1] –> “value1”. 其中,xtab[“x”]=3等同于xtab{x=3}的初始化方式。
使用index=1作为第一个元素.
4.语句
- 支持C语言或pascal语言中所支持的语句
- 支持多重赋值
赋值左右分别用逗号(,)隔开.
如果变量个数多于值的数量,没有值的变量被赋值为nil.
如果值的数量多于变量的数量,多的值不用. - 全局变量和局部变量
local var声明了局部变量,作用域为创建局部变量的block内。不使用local,则作为全局变量。
do-end 定义了一个block,类似于{}作为一个代码块 - 控制结构:
lua不支持switch语句
(block结束,加end)- 1.if-then-else-end/if-then-elseif-then-end
- 2.while-do-end
- 3.repeat-until(类似于c中的do-while)
- 4.for-end
- 1.数字型for(numeric for)
for var=form_exp1,to_exp2[,step_exp3] do
…execute…
end
var从form_exp1到to_exp2,每次变换的步长为step_exp3.
其中,step_exp3可选,默认为1. - 2.泛型for(generic for)遍历:
for k,v in ipairs(xtab) do
…execute body…
end
ipairs():返回数组迭代器,只返回数组类型的内容,不返回键值对类型的内容
或者:
for k,v in pairs(xtab) do
…execute body…
end
pairs():table迭代器,返回数组类型的内容和键值对类型的内容。
- 1.数字型for(numeric for)
- break和return:c中使用方法相同
5.函数(page35)
- 调用函数的时候,所有参数要放到圆括号中().但是,如果函数只有一个参数,并且次参数是一个字面字符串或者table构造式,那么,可以省略圆括号。
- 调用对象的函数时,冒号操作符(:)将隐含的将对象作为函数的第一个参数。即 o:foo(x) 等于 o.foo(o,x)这样的调用。
多重返回值:(自动调整返回值的数量)
- 1.函数可以返回多个返回值。在接收的时候,如果函数的调用作为参数,只有函数的调用结果放到参数列表的最后一个时,才会返回所有返回值,否则,只返回第一个。
- 2.另外,如果在函数的调用外面加圆括号会强制只让函数返回一个返回值。eg. function foo return “a”,”b” end. print( (foo()) ) —> “a”.
- 3.再另外,如果函数调用的返回值使用字符串连接,那么,只会连接第一个字符串。
- 4.利用函数的多重返回值特性,构造列表。注意,如果函数的调用不是最后一个参数,只会返回一个值。eg. t={123,foo()}
- 5.table.unpack()函数:接受一个数组作为参数,并从下标1开始返回该数组的所有元素:(lua5.3)
1
print(table.unpack({10,20,30})) --> 10 20 30
变长参数:(…)
- 使用…表示函数接收不同数量的实参
- 访问实参的时候,…作为一个表达式,返回所有的参数。因此可以用args={…}构造一个实参的列表,也可以直接接收赋值:local a,b,c=…
- 具名实参(named arguments)
- 使用匿名列表:
func({old="aaa",new="bbb"})
. 其中,因为参数是一个table,所以,可以省略圆括号,写为:func{old="aaa",new="bbb"}
- 使用匿名列表:
6.深入函数:(第一类值/closure(闭包))
- lua中的函数类型是第一类值。定义一个函数可以当做是定义了一个函数类型的变量来使用。
所以有两种定义函数的方式:
f=function(args) end
或 function f(args) end 定义非全局的函数:
由于函数也是一个变量,将函数定义到table的变量中,使得函数称为一个局部函数:1
2
3tabx={}
tabx.f1=function(args) <body> end
tabx.f2=function(args) <body> end等同定义:
1
2
3
4tabx={
f1=function(args) <body> end ,
f2=function(args) <body> end
}closure:
closure函数可以访问其外部嵌套环境中的局部变量.可以保持记录中间状态值.- 尾调用:
当函数中最后的返回是完完全全的调用另外一个函数时,lua内部会直接跳到另外一个函数的调用,而不会形压栈。
7.迭代器与泛型for
1.自定义迭代器(利用closure)
> 利用closure函数可以保存函数局部变量的特性,创建迭代函数需要closure本身和一个用于创建该closure的工厂函数。(工厂的临时状态有closure保存).
举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function ArrayIter(t)
local i=0
return function() i=i+1;return t[i] end
end
t={"a","b","c","d"}
for v in ArrayIter(t) do
print ("v:"..v)
end
print ("----或者如下调用:--------")
iter=ArrayIter(t)
while true do
local ele=iter()
if not ele then
break
end
print ("ele:" .. ele)
end
> 其中,ArrayIter函数每次都返回一个closure函数,而closure函数每次都会将ArrayIter的内部变量i值递增。直到t[i]为nil的时候,迭代停止。
2. 泛型for语义
泛音for的语法如下:
forin do
<body>
end
其中,是一个或多个变量名的列表,以逗号分隔; 是一个或多个表达式列表,以逗号分隔。
实际在for对in后面的表达式进行了求值,此表达式返回3个值供for保存:迭代器函数、恒定状态和控制变量的初值。以 for v in ArrayIter(t)
为例
首先,返回迭代器函数iter=ArrayIter(t),恒定状态是t,即不会对它进行改变,之后,调用iter并将返回值赋值给参数,即v=iter.
直到iter返回值为nil的时候,停止迭代。
3.无状态的迭代器
不保存任何状态的迭代器
举例说明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 local function n_iter(t,i)
i=i+1
local v=t[i]
if v then
return i,v
end
end
function n_pair(t)
return n_iter,t,0
end
for v,k in n_pair(t) do
print ("v:"..v..",k:"..k)
end
其中n_iter使用外部传进来的计数器i,而n_pair返回了迭代器n_iter、恒定状态t、和迭代器的初始值.
之后,for函数将迭代器的迭代结果返回给v,k参数列表。迭代的结果为nil的时候,for结束。
4.具有复杂状态的迭代器
如果要保存多个中间过程状态,可以使用列表保存将状态,然后返回列表。(ps.列表中的状态还可以修改哦)
8.编译、执行与错误
1.编译
函数介绍
- load:接收一个“读取函数”,并在内部调用它来获取程序块,直到返回为nil.
- loadfile:从文件加载代码块,但不运行,只进行编译,将编译结果返回。因为只有运行之后,文件中的代码块才生效,因此,如果f=loadfile(“foo.lua”),运行f()才真正执行了foo.lua.
- dofile:从文件加载代码块并运行。
- loadstring:从字符串中读取代码块。如果代码块中有错误,返回为nil。eg. f = loadstring(“i=i+1”) 等价于 f = function () i=i+i end. #Ps#.loadstring总是从全局环境中编译它的字符串。
2.lua调用c代码
- lua通过加载c的库文件,并将库文件中的所有方法注册到Lua中,就像在Lua代码中定义了其他函数一样。
- 关于动态链接的功能,在package.loadlib函数中。该函数接收两个字符串作为参数:动态库的完整路径和一个函数名。如果加载库或者查找初始化函数发生任何错误,将返回nil和一条错误消息。
- 通常使用require来加载C程序库。这个函数会搜索指定的库,然后用loadlib来加载库,并返回初始化函数。初始化函数应该讲库中提供的函数注册到Lua中。
3.错误
- Lua发生错误的时候,会结束当前程序块并返回应用程序。
- 可以使用error(“error msg”)函数,显式处理错误
- assert(expression,msg)函数,断言。如果expression不为true,将会引发一个错误并打印msg信息。
4.错误处理与异常
- 使用pcall函数(protected call)来包装需要执行的代码。如果代码没有错误,返回true和函数调用的返回值,否则返回false和错误消息.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15f=function()
i=i+1
end
if pcall(f) then
print("no error")
else
print ("f has error")
end
print("---------------------")
local status,err=pcall(f)
if not status then
print(err) -- 输出错误消息
end
因为没有提前定义i,所以是有错误的,执行结果为f has error
.
5.错误消息与追溯
- 当遇到一个内部错误时,Lua就会产生错误消息,错误消息会附加一些关于错误发生的位置的信息。
- 为了在发生错误的时候,pall函数返回错误消息的是已经销毁了栈的部分能容。为显示完整的函数调用栈,使用xpall(call_func,error_func)函数。
- xpall(call_func,error_func)的参数为调用函数和一个处理错误的函数。当发生错误的时候,Lua会展开当前调用错误的处理函数。
- 在错误处理函数中,使用debug库来获取关于错误的额外信息:
- debug.debug: 提供一个Lua提示符,让用户检查原因
- debug.traceback:根据调用栈来构建一个扩展的错误消息.
1
2
3
4
5
6f_error=function()
print("function error:")
print(debug.traceback())
end
local status,err=xpcall(f,f_error)
9.协同程序(coroutine)
1.协同程序基础
- 协同程序与线程差不多,拥有自己的栈、局部变量和指定指针,与其他协同程序共享全局变量和其他大部分东西。
- 主协同程序一次只能运行一个。
- 协同程序有4中不同的状态:挂起(suspended)、运行(running)、死亡(dead)、正常(normal).
- 初始创建一个协同程序之后,协同程序处于挂起状态。
当协同程序运行结束,状态为dead
函数:
- coroutine.create(func) 创建协同程序
- coroutine.resume(cor_name[,params]) 启动协同程序.如果正常运行,返回true和对参数的处理结果。否则返回false.
- coroutine.status(cor_name) 查看协同程序的当前状态
- coroutine.yield([params]) 运行至此并且挂起.(之后resume会接着运行后面的代码)
示例代码
resume-yield1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26function cor_func(a,b)
-- 第一次运行起始处
coroutine.yield(a*2,b*2) -- 返回
-- 第二次运行起始处
print("----nnn--")
coroutine.yield(a*3,b*3) -- 返回
-- 第三次运行起始处
print("----cor_func over--")
-- 返回
end
co=coroutine.create(cor_func)
print("init status"..coroutine.status(co))
print("1st res:")
print(coroutine.resume(co,1,2))
print("status:"..coroutine.status(co))
print("2ed res:")
print(coroutine.resume(co,1,2))
print("status:"..coroutine.status(co))
print("3rd res:")
print(coroutine.resume(co,1,3))
print("status:"..coroutine.status(co))
print("4th res:")
print(coroutine.resume(co,1,4))
print("status:"..coroutine.status(co))
2.管道(pipe)与过滤器(filter) page(76)???
3.非抢先式的多线程 ???
13.元表(metatable)与元方法(metamethod)
n.其他记录
- “:”操作符:隐式传入了自己
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23Account={balance=0}
function Account.widthdraw(v)
Account.balance=Account.balance-v
end
function Account:widthdraw2(v)
self.balance=self.balance-v
end
function Account.widthdraw3(self,v)
self.balance=self.balance-v
end
print(Account.balance)
Account.widthdraw(100)
print(Account.balance)
print(Account.balance)
Account:widthdraw2(100)
print(Account.balance)
print(Account.balance)
Account.widthdraw3(Account,100)
print(Account.balance)
其中,Account:widthdraw2等价于Account.widthdraw3.但是,注意如果函数里面用到了self,那么,调用的时候也要用冒号,如果没有用,也可以用点操作符。
综上,冒号操作符传入了self。
- 面向对象与继承
- 创建对象的原型定义Object={}
- 构造创建对象函数:
1
2
3
4
5
6
7 function Object:new()
o={}
setmetatable(o, self)
self.__index = self
-- .... 其他
return
end
setmetatable定义了metatable为Object
self.__index=self指定了从Object中寻找key
- 继承类似。
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 -- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (side)
o = {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("Shape面积为 ",self.area)
end
Square = Shape:new()
-- Derived class method new
function Square:new (side)
o = Shape:new(side)
setmetatable(o, self)
self.__index = self
self.area=side*side
return o
end
-- Square重写Shape的方法
function Square:printArea()
print("Square 面积为 ",self.area)
end
-- Square的新方法
function Square:printSquareArea()
print("printSquareArea 面积为 ",self.area)
end
s=Shape:new(20)
s:printArea()
squ=Square:new(20)
squ:printArea()
squ:printSquareArea(20)