欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Lua学习个人笔记

程序员文章站 2024-03-17 23:01:28
...

1.1程序块:Lua执行的每段代码,例如一个源代码文件或者交互模式中输入的一行代码,都称为一个程序块

1.2注释:- - or --[[ --]]

1.3全局变量:无需声明,只需将值赋予一个全局变量就可以创建了。无需删除,若要删除直接赋值nil。

1.4解释器程序
选项参数“-e”可以直接在命令行中输入代码。
选项参数“-l”用于加载库文件。
选项参数“-i”表示在运行完其他命令行参数后进入交互模式
只要定义了一个名为“_PROMPT”的全局变量。解释器就会用它的值作为交互模式的命令提示符。例如
CXQ-MacBook-Pro:LUA-learn chenxiaoqiang$ lua -i -e "_PROMPT = 'lua>'"Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
lua>//提示符

类型与值
Lua动态类型的语言。无类型定义的语法。
总共八种数据类型:nil表示“无效值”
boolean:true or false
number:表示的十实数

string字符串:Lua字符串是不可变的值。不能像C语言一样直接修改字符串的某个字符,而是应该根据修改要求来创建一个新的字符串。
a = “one string"
b= string.gsub(a, “one”, “anther")
“..”是Lua字符串连续操作符。数字后面记得先空格,不然会默认为小数点
例如:print(10 .. 20)
尽量不要使用这种强制转换。 而应该使用print(tostring(10)).

table:对象,永远是“匿名的”,一个持有table的变量与table自身之间没有固定的关联性。
lua>a = {}lua>k = "x"lua>a[k] = 100
lua>a[20] = "great"
lua>b=a
print(b[“x”]) —->100 等同于b.x

a = nil
b = nil
Lua中数组通常以1作为索引的起始值

Lua中长度操作符“#“用于返回一个数组或线性表的最后一个索引值(或为其大小)
— - 打印所有行

for i=1, #a do
print(a[i])
end
print(a[#a]) - -打印列表a的最后一个值
a[#a] = nil --删除最后一个值
a[#a+1] = v - -将v添加到列表末尾
user data(自定义类型),function,thread.
print(type(“hello world"))

表达式
table构造式
空构造式{}
days = {“Sunday”,”Monday”} days[1] = “Sunday”….
lua>polyline = {color = "blue", thickness = 2, npoints = 4,
{x=0,y=0},{x=-10,y=0}}
print(polyline[2].x) - - -10
构造式{x = 0, y = 0}相当于{[“x”]=0,[“y”]=0}
构造式{“r”,”g”,”b”}相当于{[1]= “r”,[2]=“g”,[3]=“b"}

语句
赋值:允许多重赋值,即一下子将多个值赋予多个变量
lua>a,b = 20,5*x
多重赋值:左边大于右边个数为nil, 反之则舍弃
局部变量:local语句来创建
局部变量只限于在块中使用。
一个块block:是一个控制结构的执行体,或者是一个函数的执行体再或者是一个程序块。
尽可能的使用局部变量是一种良好的编程风格

控制结构:
if then else end or elseif
while 条件 do
end
repeat-until 语句重复执行其循环体直到条件为真时结束。
在Lua中,一个声明在循环体中得局部变量的作用域包括了条件测试
数字型for : for var = exp1,exp2,exp3 do
end
var从exp1变化到exp2,每次步长为exp3。
如果不想有上限则使用math.huge
泛型for: 循环通过一个迭代器(iterator)函数来遍历所有值
打印a的所有值
for i,v in ipairs(a) do print(v) end
Lua的基础库提供了ipairs,这是一个用于遍历数组的迭代器函数。
i会被赋予一个索引值,v被赋予一个对应于索引值的数组元素值

函数
多重返回值:Lua允许函数返回多个结果
unpack函数。接受一个数组作为参数,并从下标1开始返回该数组的所有元素

function unpack(t,i)>> i = i or 1>> if t[i] then>> return t[i],unpack(t, i + 1)>> end

end

变长函数

function add(...)
local s = 0
for i,v in ipairs(...) do
s= s + v
end
return s
…表示函数可接受不同数量的实参

function fwrite(fmt, ...)>> return io.write(string.format(fmt, ...))>> end
fwrite("%d%d",4,5)
格式化文本string.format
一个函数在遍历其变长参数时只需使用表达式{…}。如果变长参数中包含一些故意传入的nil,那么此时就需要用函数select来访问变长参数了。调用select的时候必须传入一个固定参数selector和一系列变长参数。调用selector为数字N,那么select返回它的第n个可变实参;否则,selector只能为字符串”#“,这样select会返回变长参数的总数

具名实参

深入函数
在Lua中。函数是一种”“第一类值”,它们具有特定的词法域
第一类值:表示在Lua中函数与其他传统的类型的值(例如数字和字符串)具有相同的权利。函数可以存储到变量中无论是全局或者是局部。或table中,可以作为实参传递给其他函数,还可以作为其他函数的返回值
词法域:一个函数可以嵌套在另一个函数中,内部的函数可以访问外部函数中的变量。

函数与所有其他值一样都是匿名的。即它们都没有名称。当讨论一个函数名时(例如print),实际上是在讨论一个持有某函数的变量。这与其他变量持有各种值是一个道理。

a = {p = print}
a.p("hello")
hello
function foo (x) return 2x end
等同于 foo = function (x) 2
x end
一个函数定义实际就是一条语句。

闭合函数closure

function sortgrade(names,grades) table.sort(names, function(n1,n2)>> return grades[n1] > grades[n2]>> end)

end
grades称为“非局部的变量”。既不是全局也不是局部。但sort中得匿名函数却可以访问
function newCounter()>> local i = 0>> return function()>> i = i + 1>> return i>> end
end
c1 = newCounter()
c2 = newCounter()
一个closure就是一个函数加上该函数所需访问的所有“非局部的变量”。如果再次调用newCounter,那么它会创建一个新的局部变量i,从而得到一个新的closure.
closure对于回调函数也很有用。

非全局的函数

Lib = {}> Lib.foo = function (x,y) return x+y end
Lib.goo = function (x,y) return xy end
上下实现是相同的
function Lib.foo (x,y) return x+y end
function Lib.goo (x,y) return x
y end
定义递归的局部函数时,有特别需要注意的地方。先定义一个局部变量,然后再定义函数本身
local fact
fact = function(n) - - 若是local fact = function(n)会有错误,定义未完毕
if n == 0 then return 1
else return n*fact(n-1)
end
end
正确地尾调用
Lua支持“尾调用消除”:尾调用(tail call)就是一种类似于goto的函数调用。当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”。举例:
function f(x) return g(x) end
也就是说当f调用完g后就再无其他事情可做了。(尾调用不消耗栈空间)
不符合尾调用的一些情况
return g(x) + 1 必须做一次加法
return x or g(x)
return (g(x)) 必须调整为一个返回值
在Lua 中,只有return <func>(<args>)这样的调用形式才算是一条尾调用

尾调用的一大应用就是编写“状态机”。这种程序通常以一个函数来表示一个的状态,改变状态就是goto到另一个特定的函数。

迭代器与泛型for
迭代器与closure
迭代器:就是一种可以遍历一种集合中所有元素的机制。在Lua中通常将迭代器表示为函数,每调用一次函数,即返回集合中的“下一个”元素
一个closure结构通常涉及到两个函数:closure本身和一个用于创建该closure的工厂函数。
例子:

function values(t)>> local i = 0>> return function () i = i+1; return t[i] end>> end> t = {10,20,30}> iter = values(t)> while true do>> local element = iter()>> if element == nil then break end>> print(element)

end
泛型for
for element in values(t) do>> print(element)
end
i = i + 1>> return i>> end
>> end
> c1 = newCounter()
> c2 = newCounter()
一个closure就是一个函数加上该函数所需访问的所有“非局部的变量”。如果再次调用newCounter,那么它会创建一个新的局部变量i,从而得到一个新的closure.
closure对于回调函数也很有用。

非全局的函数
> Lib = {}> Lib.foo = function (x,y) return x+y end
> Lib.goo = function (x,y) return xy end
上下实现是相同的
> function Lib.foo (x,y) return x+y end
> function Lib.goo (x,y) return x
y end
定义递归的局部函数时,有特别需要注意的地方。先定义一个局部变量,然后再定义函数本身
local fact
fact = function(n) - - 若是local fact = function(n)会有错误,定义未完毕
if n == 0 then return 1
else return n*fact(n-1)
end
end
正确地尾调用
Lua支持“尾调用消除”:尾调用(tail call)就是一种类似于goto的函数调用。当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”。举例:
function f(x) return g(x) end
也就是说当f调用完g后就再无其他事情可做了。(尾调用不消耗栈空间)
不符合尾调用的一些情况
return g(x) + 1 必须做一次加法
return x or g(x)
return (g(x)) 必须调整为一个返回值
在Lua 中,只有return <func>(<args>)这样的调用形式才算是一条尾调用

尾调用的一大应用就是编写“状态机”。这种程序通常以一个函数来表示一个的状态,改变状态就是goto到另一个特定的函数。

迭代器与泛型for
迭代器与closure
迭代器:就是一种可以遍历一种集合中所有元素的机制。在Lua中通常将迭代器表示为函数,每调用一次函数,即返回集合中的“下一个”元素
一个closure结构通常涉及到两个函数:closure本身和一个用于创建该closure的工厂函数。
例子:
> function values(t)>> local i = 0>> return function () i = i+1; return t[i] end>> end> t = {10,20,30}> iter = values(t)> while true do>> local element = iter()>> if element == nil then break end>> print(element)
>> end
泛型for
> for element in values(t) do>> print(element)
>> end
泛型for的语法:for <var-list> in <exp-list> do
<body>
end
其中<var-list>是一个或多个变量名的列表,以逗号分隔;<exp-list>是一个或多个表达式的列表,同样以逗号分隔。通常表达式列表只有一个元素,即对迭代器工厂的调用。例如
for k,v in pairs(t) do print(k,v) end
变量列表的第一元素称为“控制变量”。在循环过程中该值绝不会是nil
for做的第一件事就是对in后面的表达式求值。这些表达式应该返回3个值供for保存:
迭代器函数、恒定状态和控制变量 的初值。这里类似于多重赋值,即只有最后一个表达式才会产生多个结果,并且只会保留前3个值,多余的值会被丢弃;而不足的话将以nil补足。

无状态的迭代器
就是一种自身不保存任何状态的迭代器。因此,我们可以在多个循环中使用同一个无状态的迭代器,避免创建新的closure开销。
在每次迭代中,for循环都会用恒定状态和控制变量来调用迭代器函数,一个无状态的迭代器可以根据这两个值来为下次迭代生成一个元素。例如ipairs。

具有复杂状态的迭代器

local iterator> function allwords()>> local state = {line = io.read(), pos = 1}>> return iterator, state>> end> function iterator(state)>> while state.line do>> local s,e = string.find(state.line, "%w+", state.pos)>> if s then>> state.pos = e + 1>> return string.sub(state.line, s, e)>> else>> state.line = io.read()>> state.pos = 1>> end>> end>> return nil

end

协同程序
概念:协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈、局部变量和指令指针,同时又与其他系统程序共享全局变量和其他大部分的东西。
一个具有多个协同程序的程序在任意时刻只能运行一个协同程序。并且正在运行的协同程序只会在其显式地要求挂起时,它的执行才会暂停。

1.基础知识:
协同程序的函数放置在一个名为“coroutine”的table中。
四个状态:挂起、运行、死亡、正常。
coroutine.create() coroutine.resume() coroutine.status() coroutine.yield()
协同程序的真正强大之处在于函数yield的使用上,该函数可以让一个运行中的协同程序挂起,而之后可以再恢复它的运行。
Lua>co = coroutine.create(function ()>> for i=1,10 do>> print("co",i)>> coroutine.yield()>> end

end)
在resume,并没有对应的yield在等待它,因此所有传递给resume的额外参数都将视为协同程序的主函数的参数。
Lua>co = coroutine.create(function (a,b,c) print("co",a,b,c) end)
Lua>return coroutine.resume(co,1,2,3)
在resume调用返回的内容中,第一个值为true则表示没错,而后面所有的值都是对yield传入的参数:
Lua>co = coroutine.create(function (a,b)>> coroutine.yield(a+b,a-b)>> end)
Lua>coroutine.resume(co,10,9)
与此对应的是,yield的返回的额外值就是对应resume传入的参数:
print("co",coroutine.yield())
end)
return iterator, state>> end> function iterator(state)>> while state.line do>> local s,e = string.find(state.line, "%w+", state.pos)>> if s then>> state.pos = e + 1>> return string.sub(state.line, s, e)>> else>> state.line = io.read()>> state.pos = 1>> end>> end>> return nil
>> end

协同程序
概念:协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈、局部变量和指令指针,同时又与其他系统程序共享全局变量和其他大部分的东西。
一个具有多个协同程序的程序在任意时刻只能运行一个协同程序。并且正在运行的协同程序只会在其显式地要求挂起时,它的执行才会暂停。

1.基础知识:
协同程序的函数放置在一个名为“coroutine”的table中。
四个状态:挂起、运行、死亡、正常。
coroutine.create() coroutine.resume() coroutine.status() coroutine.yield()
协同程序的真正强大之处在于函数yield的使用上,该函数可以让一个运行中的协同程序挂起,而之后可以再恢复它的运行。
Lua>co = coroutine.create(function ()>> for i=1,10 do>> print("co",i)>> coroutine.yield()>> end
>> end)
在resume,并没有对应的yield在等待它,因此所有传递给resume的额外参数都将视为协同程序的主函数的参数。
Lua>co = coroutine.create(function (a,b,c) print("co",a,b,c) end)
Lua>return coroutine.resume(co,1,2,3)
在resume调用返回的内容中,第一个值为true则表示没错,而后面所有的值都是对yield传入的参数:
Lua>co = coroutine.create(function (a,b)>> coroutine.yield(a+b,a-b)>> end)
Lua>coroutine.resume(co,10,9)
与此对应的是,yield的返回的额外值就是对应resume传入的参数:
>> print("co",coroutine.yield())
>> end)
Lua>coroutine.resume(co)trueLua>return coroutine.resume(co,1,2)
co 1 2
最后,当一个协同程序结束时,它的主函数所返回的值都将作为对应resume的返回值。
Lua>co = coroutine.create(function()>> return 6,7>> end)
Lua>print(coroutine.resume(co))
true 6 7

  1. 管道(pipe)与过滤器(filter)
    协同程序经典案例“生产者-消费者”
    Lua>function receive(prod)>> local status, value = coroutine.resume(prod)>> return value>> endLua>function send(x)>> coroutine.yield(x)>> endLua>function producer()>> return coroutine.create(function ()>> while true do>> local x = io.read()>> send(x)>> end>> end)>> endLua>function filter(prod)>> return coroutine.create(function()>> for line = 1, math.huge do>> local x = receive(prod)>> x = string.format(%5d %s",line,x)stdin:5: unexpected symbol near '%'
    Lua>function filter(prod)
    return coroutine.create(function()
    for line = 1, math.huge do
    local x = receive(prod)
    x = string.format("%5d %s",line,x)
  send(x)>>     end>>  end)>> endLua>function consumer (prod)>>    while true do>>      local x = receive(prod)>>      io.write(x, "\n")>>    end>> endLua>p = producer()Lua>f = filter(p)

Lua>consumer(f)

以协同程序实现迭代器
非抢占式的多线程

数据结构
1.数组
a = {} 新建
for i=1, 1000 do a[i] = 0
end

mt = {}

for i=1,N do mt[i] = {} --创建一个新行 for j=1,M do mt[i][j] = 0 end
Lua中table是一种对象,因此在创建矩阵时,必须显示地创建每一行。

元表(metatable)与元方法(meatmethod)

通常,Lua中得每个值都有一套预定义的操作集合。例如,数字相加,连接字符串。但是无法相加两个table,无法对函数比较,也无法调用一个字符串。
可以通过元表来修改一个值得行为,使其在面对一个非预定义的操作时执行一个指定的操作。
例如定义如何相加table的表达式。
Lua试图相加两个table时,它会先检查两者之一是否有元表,然后检查该元表中是否有一个叫_add的字段。如果Lua找到了该字段,就调用该字段对应的值。这个值也就是所谓的“元方法”,它应该是一个函数。
Lua中得每个值都有一个元表。Lua在创建新的table时不会创建元表:
t = {} print(getmetatable(t)) - - nil

使用setmetatable来设置或修改任何table的元表:

t = {}
t1 = {}
setmetatable(t, t1)
assert(getmetatable(t) == t1)

任何table都可以作为任何值的元表,而一组相关的table也可以共享一个通用的元表,此元表描述了它们的共同行为,一个table甚至可以作为它自己的元表,用于描述其特有的行为。
在Lua中只能设置table的元表。其他需要通过C代码来完成。

算术类的元方法
Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _,v in ipairs(l) do
set[v] = true
end
return set
end

function Set.union(a,b)
local res = Set.new{}
for k in pairs(a) do
res[k] = true
end
for k in pairs(b) do
res[k] = true
end
return res
end

function Set.intersection (a,b)
local res = Set.new{}
for k in pairs(a) do
res[k] = b[k]
end
return res
end

function Set.tostring(set)
local l = {}
for e in pairs(set) do
l[#l + 1] = e
end
return "{" .. table.concat(l, ", ") .. "}"
end
function Set.print(s)
print(Set.tostring(s))
end

s1 = Set.new{10,20,30,40}
s2 = Set.new{30,1}

mt.__add = Set.union
s3 = s1 + s2
Set.print(s3)
mt.__mul = Set.intersection
Set.print((s1+s2)*s1)
如果想要清楚得到错误信息,则必须在实际操作前显示地检查操作数的类型
关系类的元方法
table访问的元方法
有两种可以改变table的行为:查询table及修改table中不存在的字段
_ _index元方法:如果有此方法当访问查询不到的时候会调用此方法(继承)table的更新
元方法可以是函数也可以是另一个table
_ newindex:用于table的更新。功能相似
两者的组合可以实现Lua的一些强大的功能:只读的table,默认值的table和面向对象编程
1.默认值的table
function setDefault (t,d)
local mt = {
_index = function () return d end}
setmetatable(t,mt)
end

环境
Lua将其所有的全局变量保存在一个常规的table中,这个table称为“环境(environment)”
优点:其一,不需要再为全局变量创造一种新的数据结构,因此简化了Lua的内部实习。另外一个优点是,可以像其他table一样操作这个table。
为了便于实时这种操作,Lua将环境table自身保存在一个全局变量_G中。以下代码打印当前环境所有全局变量的名称:
for n in pairs(_G) do print(n) end
全局变量声明:

value = _G["varname"]> valuex> _G["test"] = value
test
非全局的环境
setfenv(1,{})将当前环境改为一个新的空table
1表示当前函数,2表示调用当前函数的函数
模块与包
1.require函数
即使知道某些用到的模块可能已经加载了,但只要用到require就是一个良好的编程习惯。
2.编写模块的基本方法
最简单的方法:创建一个table
3.使用环境
创建模块的基本方法的缺陷在于,它要求程序员投入一些额外的关注。当访问同一模块中的其他公共实体时,必须限定其名称。并且,只要一个函数的状态从私有变为公有(反之也可),就必须修改调用。
函数环境可以解决上述创建模块时遇到的问题

面向对象编程
Lua的table是一种对象,首先:table与对象一样可以拥有状态,其次,table也与对象一样拥有一个独立于其值得标识(一个self)。最后table与对象一样具有独立于创建者和创建地的生命周期。
self的使用

Account = {balance = 0}> function Account.withdraw(v)>> Account.balance = Account.balance - v>> end
Account.withdraw(100.0)
Account = nil
a.withdraw(0) --错误
通过一个额外的参数self or this修改。
Account = {balance = 0}> function Account.withdraw(self,v)>> self.balance = self.balance - v>> return self.balance>> end
a = Account
return a.withdraw(a,100)
在Lua中。只需要使用冒号,就可以隐藏self参数
function Account:withdraw(v)>> self.balance = self.balance - v

end
a = Account
Account = nil
return a:withdraw(100)
1.类:
在Lua中实现原型很简单,使用继承即可。更准确地说,如果有两个对象a和b,要让b作为a的一个原型,只需输入下面语句
setmetable(a,__index = b)
在此之后,a就会在b中查找所有它没有的操作,若将b称为是对象a的类,只不过是术语上的一个变化而已。
Account = {balance = 0}
function Account:new(o)
o = o or {} setmetatable(o,self) self.__index = self
return o
end
当调用Account:new的时候,self就等于Account
2.继承
由于类也是对象,它们也可以从其他类获得方法。这种行为就是一种继承,可以很容易地在Lua中。
Account = {balance = 0}
function Account:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end

function Account:withdraw(v)
if self.balance < v then print("insufficient funds") end
self.balance = self.balance - v
end
function Account:deposit(v)
self.balance = self.balance + v
end
--派生子类使得账户可以透支
SpecialAccount = Account:new()
s = SpecialAccount:new{limit = 1000.0}
function SpecialAccount:withdraw(v)
if v - self.balance >= self:getLimit() then
print("insufficient funds")
end
self.balance = self.balance - v
end

function SpecialAccount:getLimit()
return self.limit or 0
end

test = SpecialAccount:new()
test:withdraw(500)
print(test.balance)

3.多重继承

弱引用table
三种方式:弱引用table,弱引用key,弱引用两者
一个table的弱引用类型是通过其元表中得—mode字段来决定的。这个字段的值应为一个字符串,如果这个字符串包含字母’k’,那么这个table的key是弱引用;如果这个字符串包含字母’v’,那么这个table是value的弱引用,

a = {}b= {__mode = "k"}setmetatable(a,b) --'a'为key的弱引用key = {}a[key] = 1key = {}a[key] = 2collectgarbage()for k,v in pairs(a) do print(v)
end
备忘录
local results = {}
setmetateble(results,{__mode = "v"})function createRGB(r,g,b) local key = r .. "-" .. g "-" .. b local color = results(key) if color == nil then color = {red = r, green = g, blue = b} results[key] = color end return color
end