Lua程序设计学习笔记(一) Lua基本语法(1)

Lua基本语法(一)


1.  开始


(1)基本常识

  • lua中的标识符可以由任意字母、数字、下划线构成的字符串,但不能以数字开头。
  • 全局变量不需要申明,直接赋值即可创建,删除变量直接赋nil。未申明的变量不会报错,直接返回nil。
  • 行注释以“–”开头;块注释以“–[[”开始,“]]”结束,重启块注释加“-”即可。(住:块注释需独占一行)

(2)Lua解析器

  在代码文件中第一行输入:#!User/local/bin/lua,系统将直接用lua来解析这个脚本文件。
  Lua解析器的用法:lua [选项参数] [脚本[参数]],常用参数有

  • [-i] 用于调试和手工测试;
  • [-e] 直接输入代码,如:lua -e “print(“hello”)”;
  • [-l] 加载一个lua程序库;

  在脚本代码中,可以通过全局变量arg来检索脚本的启动参数。脚本名称索引为0(前一个索引为-1),第一个参数为1,依此类推…
  如:lua -e “sin=math.sin”script a b,则arg[0]=script,arg[1]=a,arg[-1]=“sin=math.sin”。

2.   类型与值


  Lua中有8中基本类型:nil(空)、boolean(布尔)、number(数字)、string(字符串)、userdata(自定义类型)、function(函数)、table(表)、thread(线程)。函数type(val)可返回val的类型名。

(1)nil(空)

  nil是一种类型,只有一个值nil,在lua中也叫无效值。将nil赋给一个变量,可删除变量。

(2)boolean(布尔)

  boolean类型只有2个值:false和true。在条件表达式中,只有值为false和nil为“假”,其它值均为“真”。

(3)number(数字)

  number用于表示实数,lua中没有整数类型。数字常量有2中表示法,分别为普通表示法和科学计数法,如:
4 0.4  4.57e-3   0.3e12   5e+20

(4)string(字符串)

  string通常用于表示“一个字符序列”,其表示方式有4中:

  • <1> 单引号表示’;
  • <2> 双引号表示“”;
  • <3> 用[[和]]包裹住字符串,可延申多行,可包含转移字符;
  • <4> 左边/右边2个方括号加任意数量的等号,如:[[…]],可防止字符串中出现代码注释;

  在lua中,“…”用于连接字符串,特殊地:纯数字之间用空格隔一下。tonumber用于将字符串转成数字,tostring则可将数字转成字符串。

(5)function(函数)

  在lua中,函数作为“第一类值”看待。这表示函数可以存储在变量中,可以通过参数传递给其它函数,还可以作为其它函数的返回值。lua既可以调用以自身lua语言编写的函数,又可以调用以C语言编写的函数。

(6)table(表)

  Table类型实现了“关联数组”,可以用特殊索引访问它。为整数索引时,可理解为数组;为字符串索引时,可理解为记录。基于table,还可实现一些较复杂的数据结构,如:队列、栈、集合、记录等。
  Lua仅持有一个对它们的引用,不会产生新的副本或创建table,可理解为两层含义:

  • table类型的变量都是引用,要保护变量,需要拷贝表;
  • 变量的赋值都是引用,可间接修改原值;

  删除一个表中元素,可直接赋值nil。Lua中将nil作为数组的结尾标志,#t表示数组长度,若要处理中间元素为nil的“空隙”数组,可使用table.maxn(t)获取最大索引。

3.  表达式


(1)算术表达式

  Lua中支持的算术操作符有:二元的+(加)、-(减)、*(乘)、/(除)、^(指数)、%(取模),一元的“-”(负号)。
特别地,x(1/2)表示平方根,x(1/3)表示立方根。
取模操作符的定义:a % b = a - math.floor( a / b ) * b。对于实数有这特殊用途,x % 1表示x的小数部分,x - x % 1表示x的整数部分,x - x % 0.01表示x精确到小数点后两位的结果。
angle % (2 * math.pi)表示将任意角度angle限制在[0.2Π]范围内。

(2)关系运算符

  关系运算符的结果只有true和false两种。
对于基本类型,只对其值作比较;而对于table、函数、userdata类型,lua只作引用比较,只有引用同一个对象时才判定为相等。

(3)逻辑操作符

  逻辑操作符有and、or、not。x=x or {} <> if not x then x={} end;C语言中的a?b:c <> (a and b)or c,其前提条件是:b不为假,否则无意义。
  示例1:比较大小

max=(x>y) and x or y;

(4)字符串连接

  Lua中连接2个字符串,用…(两点)表示。连接操作符只会创建一个新串,不会修改原值(字符串为常量)。

(5)Table构造式

  构造式是用于创建和初始化table的表达式。最简单的构造式是{},用于创建一个空表。
  两种常用的构造式:

  • 直接赋值。t={x=1,y=2};
  • 先初始为{},后赋值。t={},t.x=1,t.y=2;

  table作为数组时,下表默认从1开始,方便与#t表长对应,但也可以显示申明将第一个元素写成t[0]。table结尾的逗号是可选的,中间的分号可用来表示不同的成分(分类)。
  示例1:创建链表

List = nil
For line in io.lines() do
	List={next=List,str=line}
End

4.   语句


(1)赋值

  Lua不同于其它语言,允许“多重赋值”。在多重赋值中,lua先对等号右边的所有元素求值,然后才执行赋值,利用这项特性可快速交换元素,如:x,y=y,x,也常用于函数的返回值。
  赋值原则:若值的个数少于变量的个数,那么多余的变量湖北赋为nil;若值的个数更多的话,那么多余的值会被“静悄悄地”丢弃掉。

(2)局部变量和块

  全局变量一般直接使用,局部变量用local作限定,其作用域仅限于声明它们的那个块。将外部变量赋值给局部变量,可加速在作用域内对其的访问,如:local fn = fn。一般在需要的地方声明局部变量,这样可以缩短变量的作用域,且有助于提高代码的可读性。
  一个块是一个控制结构的执行体、或者是一个函数的执行体,再或者是一个程序块。显示声明一个块,只需将内容放在do … end之间。

(3)控制结构

if - then - else

  一个分支都是以end为结尾,不能出现多个end。lua中没有switch语句,若要实现复杂的嵌套,可使用if - then - elseif - then - else - end结构。

while和repeat

  while和repeat最大的区别在于,前者先判断条件,可能一次都不执行;后者先执行再判断条件,至少可执行一次,repeat循环需要以until结尾。与其它语言不同的是,在lua中,一个声明在循环体中的局部变量的作用域包括了条件测试。

数字型for

  数字型for基本语法:for var=exp1,exp2,exp3 do…end。Var从exp1变化到exp2,每次变化以exp3为步长递增,exp3是可选的,如果不指定默认为1。
  此外,控制变量会自动地声明为for的局部变量,如需跳出循环体,使用bread语句。

泛型for

  泛型for通过一个迭代器(iterator)函数来遍历所有值。数字型for和泛型for,有两个共同的特点:①循环变量是循环体的局部变量②决不应该对局部变量作任何赋值。
  lua标准库中常用的几种迭代器为:迭代文件中每行的(io.lines)、迭代数组元素的(ipairs)、迭代table元素的(pairs)、迭代字符串中单词的(gmatch)。具体语法如下:

  • for line in io.lines() do…end;
  • for i,v in ipairs(t) do…end;
  • for k,v in paris(t) do…end;
  • for v1,v2…vi in gmatch(s,pattern) do…end(gmatch可返回多个值,依托于()强制参数);

(4)模拟switch和continue

  Lua语言中是没有switch和continue语句的,但是我们可以模拟出其功能。
  <1> 模拟switch,用函数和table来实现

Local switch = {
	[case1]=function(arg)...end,
	[case2]=function(arg)...end,etc
}

Switch[iCase](arg)

  <2> 模拟contionue,用do…end包住整个for块

For k,v in pairs(t) do
	Do
		语句1
		Break;	--相当于continue的功能
		语句2
	End
End

5.  函数


  在lua中,函数是一种对语句和表达式进行抽象的主要机制。
  在面向对象式的函数调用中,提供:操作,可隐式传入self参数,表达式o.foo(o,x)可改写成o:foo(x)。
  在lua中,函数声明时不能指定默认参数,但可以在函数体内通过“n = n or 1”或“t = t or {}”方式实现。

(1)多重返回值

  Lua具有一项非常与众不同的特征,允许函数返回多个结果,只需在return关键字后列出所有的返回值即可。
  函数虽然能返回多个返回值,但某些情况下只能获取到部分值。将函数作为单独一条语句时,丢弃所有返回值;如果函数调用不是一系列表达式的最后一个元素,那么将只产生一个值。“一系列表达式”在Lua中表现为4种情况:多重赋值、函数调用时传入的实参列表、table的构造式和return语句。详细实例如下(假定函数f返回2个值):

  • x,y=f(),10;
  • print(f(),1);
  • t={f(),1};
  • return f(),1;

  如果将函数调用放在一对圆括号内,可强制其只返回一个结果,如(f())。
关于多重返回值,有一个特殊的函数——unpack,它接收一个数组作为参数。其功能是展开数组中的所有元素(下标从1开始),unpack的一项重要用途体现在“泛型调用”,通用语法:f(unpack(a)),f为任意函数,只要数组a取对应的参数值即可。
比如:

f = string.find;a={“hello”,“ll”}

  示例1:用lua实现unpack函数

Function unpack(t,i)
	i = i or 1
	if t[i] then
		Return t[i],unpack(t,i+1)
	End
end

(2)变长参数

  在参数列表中,用3个点…表示变长参数,可当作table来解析(或先保存)。具有变长的实参,函数调用实参对形参作初始化时,跟“多重赋值”类似。
  对于变长参数,如需获取指定位置的元素,可使用select函数。此函数会处理值为nil的参数,原型为select(n,…),当n为数字时,返回第n个可变实参;当为字符串“#”,则返回变长参数的长度。

(3)具名形参

  Lua中的参数传递机制是具有“位置性”的,也就是在调用一个函数时,实参是通过参数表中的位置取匹配形参的。
具名参数可防止参数书写顺序问题。当一个函数拥有大量的参数时,很容易把参数位置搞错,将其参数列表打包table作为唯一参数传递是很有用的。
  实例1:文件改名

Function rename(arg)
	Return os.rename(arg.old,arg.new)
End

Local arg={old=1.lua”,new=1.lua”}
Rename(arg)

  在设计一些接口的时候,可能有很多参数,但只有部分参数特别重要,此时可对原有函数进行封装,内部调用时指定部分默认值,关键参数作校验。这样,用户调用时只需关注部分参数,构造一个table即可,这也是一个很多的设计思路。

6.   深入函数


  Lua中有一个容易混淆的概念,当讨论一个函数名时,实际上是在讨论一个持有某函数的变量,因为函数是匿名的。
函数在Lua中属于“第一类值”,不仅可以存储在全局变量中,还可以存储在局部变量甚至table的字段中。所以,可以将函数应用于回调、参数、table存储等方面。

(1)closure(闭合函数)

  Lua词法域:一个函数可以嵌套在另一个函数中,内部函数可以访问外部函数中的变量。
按变量的作用域来分,除全局变量和局部变量外,还有一种“非局部变量(外部变量)”,也就是closure中的局部变量。一个closure就是一个函数加上该函数所需访问的所有“非局部的变量”。
  示例1:简单计数

Function newCount()
	Local i=0
	Return function()
		i=i+1
		return i
	End
End

c1=newCount()
Print(c1) --结果1
Print(c1) --结果2
c2=newCount()
Print(c2) --新的closure,结果1
Print(c1) --持续累加,结果3

  示例2:函数重定义

Do
	Local sin=math.sin
	Math.sin=function(x)
		Reutrn sin(x*math.pi/180)
	End
End

  示例2中的奇妙之处在于,将老版本的sin保存在一个私有变量中,并使用do…end程序块限制sin的作用域。此后,只有新版本的sin才能访问到它,外部调用math.sin只能访问新版本的函数。
  使用这种技术,可以创建一个安全的运行环境,即所谓的“沙盒(sandbox)”。

(2)非全局函数

  函数不仅可以作为全局变量,还可以存储在table的字段和局部变量中。典型的应用:面向对象和递归,如下:
  示例1:面向对象

Lib={}
Lib.foo=function(x,y) return x+y end
Lib.goo=function(x,y) return x-y end

  示例2:递归求阶层

Local function fact(n)
If n == 0 then return 1
Else return n*fact(n-1) end
End

  对于一些间接的递归调用,需要提前将函数申明为局部变量,如:

Local f,g
Function g() ... f() ... end
Function f() ... g() ... end

(3)尾调用消除

  Lua中函数中还有一个特性:尾调用消除,类似于goto语句,直接展开代码,不耗费任何栈空间。当一个函数调用另一个函数的最后一个动作时,该调用是一条“尾调用”,准确地说是“return ()”(也适用于复杂的表达式)。
  由于“尾调用”不会耗费栈空间,所以一个程序可以拥有无数嵌套的“尾调用”,且不会造成栈溢出。
  示例1:迷宫游戏

Function room1()
	Local move = io.read()
	If move == “south” then return room2()
	Elseif move == “north” then return room3()
	Else
			print(“invaild romm”)
		Return room1()
	End
End
-- room2和room3实现类似
Function room4()
	Print(“congratulations”)
end
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值