Lua笔记一

1.起点

Lua特色

Lua之于Perl、Tcl、Ruby、Forth、Python特有的特色:

  • 可扩展性:扩展性非常卓越
  • 简单:Lua本身简单,小巧
  • 高效率:目前平均效率最高的
  • 与平台无关:有ANSI C编译器即可编译

Chunks

Chunk是一系列语句,Lua执行的每一块语句,比如一个文件或者交互模式下的每一行都是一个Chunk。

每个语句结尾的分号(;)是可选的,但如果同一行有多个语句最好用;分开

a=1   b=a*2

一个Chunk可以是一个语句,也可以是一系列语句的组合,还可以是函数。

全局变量

全局变量不需要什么,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil

print(b)		-->nil
b=10
print(b)		-->10

如果删除一个全局变量.只需要将变量赋值为nil(当且仅当一个变量不等于nil时,这个变量存在)

b=nil
print(b)		-->nil

语法约定

标示符:字母(letter)或者下划线开头的字母、下划线、数字序列。最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。

注意:Lua示大小写敏感的

注释:单行注释:--

多行注释:--[[--]]

--[[
print(10)		-- no action(conment)
--]]

2.类型和值

Lua是动态类型语言,变量不需要类型定义。Lua中有8个基本的类型分为别为:nil、boolean、number、string、userdata、function、thread和table。函数type可以测试给定变量或者值。

print(type("Hello world")) --> string
print(type(10.4*3)) --> number(实数)
print(type(print)) --> function
print(type(type)) --> function
print(type(true)) --> boolean
print(type(nil)) --> nil
print(type(type(X))) --> string

变量没有预定义的类型,每一个变量都可能包含任一种类型的值。

Nil

Lua 中特殊的类型,他只有一个值:nil;一个全局变量没有被赋值以前默认值为 nil;给全局变量负 nil 可以删除该变量 。

Boolean

两个取值 false 和 true。但要注意 Lua 中所有的值都可以作为条件。在控制结构的条
件中除了 false 和 nil 为假,其他值都为真。所以 Lua 认为 0 和空串都是真

Numbers

表示实数,Lua 中没有整数。

Strings

指字符的序列。lua 是 8 位字节,所以字符串可以包含任何数值字符,包括嵌入的 0。

可以使用[[..]]表示字符串。这种形式的字符串可以包含多行也,可以嵌套且不会解释转义序列,如果第一个字符是换行符会被自动忽略掉。这种形式的字符串用来包含一段代码是非常方便的 。

..在 Lua 中是字符串连接符,当在一个数字后面写..时,必须加上空格以防止被解释错。

print(10 .. 20) --> 1020

Functions

函数是第一类值(和其他变量相同),意味着函数可以存储在变量中,可以作为函数的参数,也可以作为函数的返回值。

Userdata

userdata 可以将 C 数据存放在 Lua 变量中,userdata 在 Lua 中除了赋值和相等比较外没有预定义的操作。

3.表达式

算术运算符

二元运算符:+ - * / ^ (加减乘除幂)
一元运算符:- (负值)
这些运算符的操作数都是实数。

关系运算符

<     >     <=    >=    ===   ~=

这些操作符返回结果为false或者true;==和~=比较两个值,如果两个值类型不同,Lua认为两者不同;nil只和自己相等。Lua通过引用比较tables、userdata、functions。也就是说当且仅当两者表示同一个对象时相等。

a={};a.x=1;a.y=0
b={};b.x=1;b.y=0
c=a
a==c but a~=b

Lua比较数字按传统的数字大小进行,比较字符串按字母的顺序进行,但是字母依赖于本地环境。

当比较不同类型的值的时候要特别注意:

"0"==0     --false
2<15       --true
"2"<"15"   --false

为了避免不一致的结果,混合比较数字和字符串,Lua会报错,比如:2<"15"

逻辑运算符

and  or not			

逻辑运算符认为false和nil是假(false),其他为真,0也是true。

andor的运算结果不是true和false,而是和它的两个操作数相关。

a  and b		--如果a为false,则返回a,否则返回b
a  or  b		--如果a为true,则返回a,否则返回b	

一个实用的技巧:如果x为false或者nil则给x赋初始值v

x=x or v	

等价于

if not x then
	x=v
end	

and的优先级比or高。

C语言的三元运算符

a ? b : c	

在Lua中可以这样实现

(a and b) or c	

连接运算符

..  --两个点

字符串连接,如果操作数为数字,Lua将数字转成字符串。

优先级

从高到低的顺序:

^
not - (unary)
* /
+ -
..
< > <= >= ~= ==
and
or

除了^和..外所有的二元运算符都是左连接的。

a+i < b/2+1 <--> (a+i) < ((b/2)+1)
5+x^2*8 <--> 5+((x^2)*8)
a < y and y <= z <--> (a < y) and (y <= z)
-x^2 <--> -(x^2)
x^y^z <--> x^(y^z)

表的构造

构造器是创建和初始化表的表达式。表是Lua特有的功能强大的东西。最简单的构造函数是{},用于创建一个空表。可以直接初始化数组:

days = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}

Lua将"Sunday"初始化days[1] (第一个元素索引为1).....

构造函数可以使用任何表达式初始化:

tab = {sin(1), sin(2), sin(3), sin(4),
sin(5),sin(6), sin(7), sin(8)}

如果想初始化一个表作为record使用可以这样:

a = {x=0, y=0} <--> a = {}; a.x=0; a.y=0

不管用何种方式创建table,我们都可以向表中添加或者删除任何类型的域,构造函数仅仅影响表的初始化。

每次调用构造函数,Lua 都会创建一个新的 table,可以使用 table 构造一个 list

在同一个构造函数中可以混合列表分割和record风格进行初始化,如:

polyline = {color="blue", thickness=2, npoints=4,
				{x=0, y=0},
				{x=-10, y=0},
				{x=-10, y=1},
				{x=0, y=1}
}

这个例子也表明我们可以嵌套构造函数来表示复杂的数据结构

print(polyline[2].x)	-->-10	

上面两种构造函数的初始化方式还有限制,比如你不能使用符索引初始化以恶搞表中元素,字符串索引也不能被恰当的表示。下面介绍一种更一半的初始化方式,我们用[expression]显示的表示将被初始化的索引:

opnames = {["+"] = "add", ["-"] = "sub",
			["*"] = "mul", ["/"] = "div"}
i = 20; s = "-"
a = {[i+0] = s, [i+1] = s..s, [i+2] = s..s..s}
print(opnames[s]) 	--> sub
print(a[22]) 		--> ---

list风格初始化于record风格初始化是这种一般初始化的特例:

{x=0, y=0}			<-->		
							{["x"]=0, ["y"]=0}
{"red", "green", "blue"} <-->
				{[1]="red", [2]="green", [3]="blue"}	

如果真的想要数组下标从0开始:

days = {[0]="Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}

注意:不推荐数组下标从0开始,否则很多标准库不能使用。

在构造函数的最后" , "是可选的,可以方便以后的扩展。

a = {[1]="red", [2]="green", [3]="blue",}

在构造函数中域分隔逗号(",")可以使用分号(";")替代,通常我们使用分号用来分割不同类型的表元素。

{x=10, y=45; "one", "two", "three"}

4.基本语法

赋值语句

Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会一次赋给左边的变量。

a, b = 10, 2*x      <- ->     a=10; b=2*x

遇到赋值语句Lua会先计算右边的所有的值然后再执行赋值曹祖,所以我们可以这样进行交换变量的值:

x, y = y, x 			-- swap 'x' for 'y'
a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[i]'

当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略:

a. 变量个数>值的个数 按变量个数补足 nil
b. 变量个数<值的个数 多余的值会被忽略

局部变量域代码块(block)

使用loacl创建一个局部变量,与全局变量,局部变量旨在被声明的那个代码块内有效。

代码块:指一个控制结构内,一个函数体,或者一个chunk(变量被声明的那个文件或者文本串)

x = 10
local i = 1 				-- local to the chunk

while i<=x do
	local x = i*2 			-- local to the while body
	print(x) 				--> 2, 4, 6, 8, ...
	i = i + 1
end
if i > 20 then
	local x 				-- local to the "then" body
	x = 20
	print(x + 2)
else
	print(x) 				--> 10 (the global one)
end
	print(x) 				--> 10 (the global one)

因为第二句 local i=1是一个完整的 chunk,在交互模式下执行完这一句后,Lua 将开始一个新的 chunk,这样第二句的 i 已经超出了他的有效范围。可以将这段代码放在 do..end(相当于 c/c++的{})块中。
应该尽可能的使用局部变量,有两个好处:

  1. 避免命名冲突
  2. 访问局部变量的速度比全局变量更快

我们给block划定一个明确的界限:do..end内的部分。当你想更好的控制局部变量的作用范围的时候这是很有用的。

do
	local a2 = 2*a
	local d = sqrt(b^2 - 4*a*c)
	x1 = (-b + d)/a2
	x2 = (-b - d)/a2
end 				-- scope of 'a2' and 'd' ends here

print(x1, x2)

控制结构语句

控制结构的条件表达式结果可以时任何值,Lua认为flase和nil为假,其他值为真。

if语句,有三种形式:

if conditions then
	then-part
end;

if conditions then
	then-part
else
	else-part
end;

if conditions then
	then-part
elseif conditions then
	elseif-part
.. 				--->多个 elseif
else
	else-part
end;

while语句:

while condition do
	statements;
end;

repeat-until 语句:

repeat
	statements;
until conditions;

for语句有两大类:

第一,数据for循环:

for var=exp1,exp2,exp3 do
	loop-part
end

for将用exp3作为step从exp1(初始值)到exp2(终止值),执行loop-part。其中exp3可以省略.默认step=1

有几点需要注意:

  1. 三个表达式只会被计算一次,并且是在循环开始前。
  2. 控制变量var时局部变量自动被声明,并且只在循环内有效。
  3. 循环过程中不要改变控制变量的值,那样做的结果时不可预知的。如果要退出循环,使用break语句。

第二,范型 for 循环 :

一个遍历表key的例子:

-- print all keys of table 't'
for k in pairs(t) do print(k) end

范型 for和数值 for 有两点相同:

  1. 控制变量是局部变量
  2. 不要修改控制变量的值

break和return语句

break语句用来退出当前循环(for,repeat,while)。再循环外部不可使用。

return用来从函数返回结果,当一个函数自然结束结尾会有一个默认的return。

Lua语法要求break和return只能出现在block的结尾一句(也就是说:作为 chunk
的最后一句,或者在 end 之前,或者 else 前,或者 until 前 ),例如:

local i = 1
while a[i] do
	if a[i] == v then break end
	i = i + 1
end

有时候为了调试或者其他目的需要在block的中间使用retrurn或者break,可以显式的使用do..end来实现:

function foo ()
	return --<< SYNTAX ERROR
	-- 'return' is the last statement in the next block
	do return end -- OK
	... -- statements not reached
end

5.函数

函数有两个用途:

  1. 完成指定的任务,这种情况下函数作为调用语句使用
  2. 计算并放回值,这种情况下函数作为赋值语句的表达式使用。

语法:

function func_name(arguments-list)
	statements-list;
end

调用函数的时候,如果参数列表为空,必须使用()表明是函数调用。

上述规则有一个例外,当函数只有一个参数并且这个参数是字符串或者表构造的时候,()是可选的:

print "Hello World" 	<- -> 	print("Hello World")
dofile 'a.lua' 			<- -> 	dofile ('a.lua')
print [[a multi-line message]] 	<- -> 	print([[a multi-linemessage]])
f{x=10, y=20} 			<- ->   f({x=10, y=20})
type{} 					<- ->   type({})

Lua也提供了面向对象方式调用函数的语法,比如0:foo(x)与o.foo(o,x)是等价的。

Lua函数实参和形参的匹配与赋值语句类似,多余部分被忽略,缺少部分用nil补足。

返回多个结果值

Lua函数可以返回多个结果值.比如string.find,其返回匹配串"开始和结束的下标"(如果不存在匹配串返回nil)

s, e = string.find("hello Lua users", "Lua")
print(s, e) --> 7 9

Lua函数中,在return后列出要返回的值得列表即可返回多值。

Lua总是调整函数的放回值的个数去适用调用环境,当作为一个语句调用函数时,所有返回值被忽略。

假设有如下三个函数:

function foo0 () end 					 -- returns no results
function foo1 () return 'a' end 		  -- returns 1 result
function foo2 () return 'a','b' end 	   -- returns 2 results

第一,当作为表达式调用函数时,有以下几种情况:

  1. 当调用作为表达式最后一个参数或者仅有一个参数时,根据变量个数函数尽可能多地返回多个值,不足补nil,超出舍去。
  2. 其他情况下,函数调用仅返回第一个值(如果没有返回值为nil)
x,y = foo2() 				-- x='a', y='b'
x = foo2() 					-- x='a', 'b' is discarded
x,y,z = 10,foo2()  			 -- x=10, y='a', z='b'
x,y = foo0() 				-- x=nil, y=nil
x,y = foo1() 				-- x='a', y=nil
x,y,z = foo2()				-- x='a', y='b', z=nil
x,y = foo2(), 20 			-- x='a', y=20
x,y = foo0(), 20, 30 		-- x='nil', y=20, 30 is discarded

第二,函数调用作为函数参数被调用时,和多值赋值时相同。

第三,函数调用在表构造函数中初始化时,和多值赋值时相同。

另外,return f()这种类型的返回f()返回的所有值。

可以使用圆括号强制使调用返回一个值;一个 return 语句如果使用圆括号将返回值括起来也将导致返回一个值。

函数多值返回的特殊函数unpack,接受一个数组作为输入参数,返回数组的所有元素。unpack被用来实现范型调用机制,在c语言中可以使用函数指针调用可变的啊哈桑农户,可以声明参数可变的函数,但不能两者同时可变。在Lua中如果你想调用可变的参数的可变函数只需要这样:

f(unpack(a))

unpack返回a所有的元素作为f()的参数

预定义的unpack函数是用c语言实现的,我们也可以用Lua来完成:

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

可变参数

Lua函数可以接受可变数目的参数,和c语言类似在函数参数列表中使用三点(...)表示哈桑农户有可变的参数。Lua将函数的参数放在一个叫arg的表中,除了参数以外,arg表中还有一个域n表示参数的个数。

例如:我们可以重写print函数:

printResult = ""
function print(...)
	for i,v in ipairs(arg) do
	printResult = printResult .. tostring(v) .. "	"
	end
	printResult = printResult .. "
"
end

有时候我们可能需要几个固定参数加上可变参数

function g(a,b,...)	end

Lua会将前面的实参传给函数的固定参数,后面的实参放在arg表中。

命名参数

Lua的函数参数适合位置相关的,调用时实参会按顺序一次传给形参。有时候会名字指定参数是很有用。

Lua可以通过将所有的参数放在一个表中,把表作为函数的唯一参数。因为Lua语法支持函数调用时实参可以是表的构造。

6.再论函数

Lua中的函数时带有词法定界(lexical scoping)的第一类值(first-class values)

第一类值指:在Luaz中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。

词法定界指:被嵌套的函数可以访问他外部函数的中的变量。这一特性给Lua提供了强大的编程能力。

Lua中关于函数稍微难以理解的时函数也可以没有名字,匿名的。当我们提到函数名(比如print),实际上时说一个指向函数的变量,像持有其他类型值得变量一样:

a = {p = print}
a.p("Hello World") 			--> Hello World
print = math.sin 			-- `print' now refers to the sine function
a.p(print(1)) 				--> 0.841470
sin = a.p 					-- `sin' now refers to the print function
sin(10, 20) 				--> 10 20

既然函数是值,那么表达式也可以创建函数了,Lua中我们经常这样写:

function foo(x)	return 2*x end

这实际上是利用Lua提供得"语法上的甜头(语法糖)"(syntactic sugar)的结果,下面是原本的函数:

foo=function (x) return 2*x end

函数定义实际上是一个赋值语句,将类型为 function 的变量赋给一个变量。我们使用 function (x) ... end 来定义一个函数和使用{}创建一个表一样。

闭包

当一个函数内部嵌套另一个函数定义时,内部的函数体可以访问外部的函数的局部变量,这种特征我们称作词法定界。

下面看一个简单的例子,假定有一个学生姓名的列表和一个学生名和成绩对应的表;
现在想根据学生的成绩从高到低对学生进行排序,可以这样做 :

names = {"Peter", "Paul", "Mary"}
grades = {Mary = 10, Paul = 7, Peter = 8}
table.sort(names, function (n1, n2)
	return grades[n1] > grades[n2] -- compare the grades
end)

假定创建一个函数实现此功能:

function sortbygrade (names, grades)
	table.sort(names, function (n1, n2)
		return grades[n1] > grades[n2] -- compare the grades
	end)
end

例子中包含在sortbygrade函数内部的sort中的匿名函数可以sortbygrade的参数grades,在匿名函数内部grades不是全局变量也不是局部变量,我们称作外部的局部变量(external local variable)或者upvalue。

看下面代码:

function newCounter()
	local i=0
	return function()			-- anonymous function
		i =i+1
		return i
	end
end

c1=newCounter()
print(c1())		-->1
print(c1())		-->2

匿名函数使用upvalue i保存它的计数,当我们调用匿名函数的时候i已经超出了作用范围,因为创建i的函数newCounter已经返回了。然而Lua用闭包的思想正确处理了这种情况。简单的说闭包时一个函数加上它可以正确访问的upvaluse。如果我们再次调用newCounter,将创建一个新的局部变量i,因此我们得到了一个作用在新的变量i上的新闭包。

c2 = newCounter()
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2

c1、c2是建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个不同的闭包。

闭包在上下文环境中提供很有用的功能,如前面我们见到的可以作为高级函数(sort)的参数;作为函数嵌套的函数 。

闭包在完全不同的上下文中也是很有用途的。因为函数被存储在普通的变量内我们可以很方便的重定义或者预定义函数。通常当你需要原始函数有一个新的实现时可以重定义函数。

利用同样的特征我们可以创建一个安全的环境(也称作沙箱,和 java 里的沙箱一样)。

非全局函数

Lua中函数可以作为全局变量也可以作为局部变量。

当我们将函数保存在一个局部变量内时,我们得到了一个局部函数,也就是说局部函数像局部变量一样在一定范围内有效。这种定义在包中是非常有用的:因为Lua把chunk当作函数处理,在chunk内可以声明局部函数(仅仅在chunk内可见),词法定界保证了包内的其他函数可以调用此函数,下面是声明局部函数的两种方式:

  1. 方式一:

    local f=function (...)
    	...
    end
    local g=function (...)
    	...
    	f()  -- external local `f' is visible here
    	...
    end
    
  2. 方式二:

    local function f (...)
    	...
    end
    

    有一点需要注意的是在声明递归局部函数的方式:

    local fact = function (n)
    	if n == 0 then
    		return 1
    	else 
    		return n*fact(n-1)		--buggy
    	end
    end
    

    上面这种方式导致Lua编译时遇到fact(n-1)并不知道他是局部函数fact,Lua会去查找是否有这样的全局函数fack。为了解决这个问题我们必须在定义函数以前先声明:

    local fact
    

    这样在fact内部fact(n-1)调用是一个局部函数调用,运行时fact就可以获取正确的值了。但是Lua扩展了他的语法使得可以在直接递归函数定义时使用两种方式都可以。

    在定义非直接递归局部函数要先声明然后定义才可以:

    local f,g		-- `forward' declarations
    function g ()
    	... f() ...
    end
    function f ()
    	... g() ...
    end
    

正确的尾调用(Proper Tail Calls)

Lua中函数的另一个有趣的特征时可以正确的处理尾调用。

尾调用时一种类似在函数结尾的goto调用,但函数最后一个动作时调用另外一个函数时,我们称这种调用为尾调用。例如:

function f(x)
	return g(x)
end	

g的调用是尾调用。

例子中 f 调用 g 后不会再做任何事情,这种情况下当被调用函数 g 结束时程序不需要返回到调用者 f;所以尾调用之后程序不需要在栈中保留关于调用者的任何信息。一些编译器比如 Lua 解释器利用这种特性在处理尾调用时不使用额外的栈,我们称这种语言支持正确的尾调用

由于尾调用不需要使用栈空间,那么尾调用递归的层次是可以无限制的。例如下面调用不论n为何值都不会导致栈溢出。

function foo(n)
	if n > 0 then return foo(n-1)	end
end

需要注意的是:必须明确什么是尾调用。 一些调用者函数调用其他函数后也没有做其他的事情但不属于尾调用。

Lua 中类似 return g(...)这种格式的调用是尾调用。但是 g 和 g 的参数都可以是复杂表达式,因为 Lua 会在调用之前计算表达式的值。

原文地址:https://www.cnblogs.com/Ligo-Z/p/13537373.html