基础语法


基本语法

注释

单行注释

两个减号是单行注释:

--

多行注释

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

标示符

Lua 标示符用于定义一个变量,函数获取其他用户定义的项。标示符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上 0 个或多个字母,下划线,数字(0 到 9)。

最好不要使用下划线加大写字母的标示符,因为 Lua 的保留字也是这样的。

Lua 不允许使用特殊字符如 @, $, 和 % 来定义标示符。 Lua 是一个区分大小写的编程语言。因此在 Lua 中 Test 与 test 是两个不同的标示符。以下列出了一些正确的标示符:

mohd         zara      abc     move_name    a_123
myname50     _temp     j       a23b9        retVal

关键词

以下列出了 Lua 的保留关键词。保留关键字不能作为常量或变量或其他用户自定义标示符:

一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于 Lua 内部全局变量。

全局变量

在默认情况下,变量总是认为是全局的。

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

如果你想删除一个全局变量,只需要将变量赋值为nil。

这样变量b就好像从没被使用过一样。换句话说, 当且仅当一个变量不等于nil时,这个变量即存在。


数据类型

Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。

Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。

  • nil : 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。

  • boolean : 包含两个值:false和true。

  • number : 表示双精度类型的实浮点数

  • string : 字符串由一对双引号或单引号来表示

  • function : 由 C 或 Lua 编写的函数

  • userdata : 表示任意存储在变量中的C数据结构

  • thread : 表示执行的独立线路,用于执行协同程序

  • table Lua : 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。

我们可以使用 type 函数测试给定变量或者值的类型:

nil(空)

nil 类型表示一种没有任何有效值,它只有一个值 -- nil,例如打印一个没有赋值的变量,便会输出一个 nil 值:

对于全局变量和 table,nil 还有一个"删除"作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉,执行下面代码就知:

nil 作比较时应该加上双引号 "

type(X)==nil 结果为 false 的原因是 type(X) 实质是返回的 "nil" 字符串,是一个 string 类型:

boolean(布尔)

boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 falsenil 看作是 false,其他的都为 true,数字 0 也是 true:

number(数字)

Lua 默认只有一种 number 类型 -- double(双精度)类型(默认类型可以修改 luaconf.h 里的定义),以下几种写法都被看作是 number 类型:

string(字符串)

字符串由一对双引号或单引号来表示。

也可以用 2 个方括号 [[]] 来表示"一块"字符串。

在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字:

以上代码中"error" + 1执行报错了,字符串连接使用的是 .. ,如:

使用 # 来计算字符串的长度,放在字符串前面,如下实例:

table(表)

在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是 {},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:

Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。

不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始。

table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 都是 nil。

function(函数)

在 Lua 中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里:

function 可以以匿名函数(anonymous function)的方式通过参数传递:

thread(线程)

在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。

线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。

userdata(自定义类型)

userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。


变量

变量在使用前,需要在代码中进行声明,即创建该变量。

编译程序执行代码之前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。

Lua 变量有三种类型:全局变量、局部变量、表中的域。

Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。

局部变量的作用域为从声明位置开始到所在语句块结束。

变量的默认值均为 nil。

赋值语句

赋值是改变一个变量的值和改变表域的最基本的方法。

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

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

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

上面最后一个例子是一个常见的错误情况,注意:如果要对多个变量赋值必须依次对每个变量赋值。

多值赋值经常用来交换变量,或将函数调用返回给变量:

f() 返回两个值,第一个赋给 a,第二个赋给 b

索引

对 table 的索引使用方括号 []。Lua 也提供了 . 操作。


流程控制

while

Lua 编程语言中 while 循环语句在判断条件为 true 时会重复执行循环体语句。

statements(循环体语句) 可以是一条或多条语句,condition(条件) 可以是任意表达式,在 condition(条件) 为 true 时执行循环体语句。

for

数值for循环

Lua 编程语言中 for 循环语句可以重复执行指定语句,重复次数可在 for 语句中控制。

var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。

for 的三个表达式在循环开始前一次性求值,以后不再进行求值。比如上面的 f(5) 只会在循环开始前执行一次,其结果用在后面的循环中。

泛型for循环

泛型 for 循环通过一个迭代器函数来遍历所有值,类似 java 中的 foreach 语句。

i 是数组索引值,v 是对应索引的数组元素值。ipairs 是 Lua 提供的一个迭代器函数,用来迭代数组。

repeat...unti

Lua 编程语言中 repeat...until 循环语句不同于 for 和 while循环,for 和 while 循环的条件语句在当前循环执行开始时判断,而 repeat...until 循环的条件语句在当前循环结束后判断。

循环嵌套

break 语句

Lua 编程语言 break 语句插入在循环体中,用于退出当前循环或语句,并开始脚本执行紧接着的语句。

如果你使用循环嵌套,break语句将停止最内层循环的执行,并开始执行的外层的循环语句。

goto 语句

Lua 语言中的 goto 语句允许将控制流程无条件地转到被标记的语句处。

if 语句

Lua if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。

以下实例用于判断变量 a 的值是否小于 20:

if...else 语句

Lua if 语句可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码块。

在布尔表达式为 true 时会 if 中的代码块会被执行,在布尔表达式为 false 时,else 的代码块会被执行。

Lua 认为 false 和 nil 为假,true 和非 nil 为真。要注意的是 Lua 中 0 为 true。

以下实例用于判断变量 a 的值

elseif

Lua if 语句可以与 elseif...else 语句搭配使用, 在 if 条件表达式为 false 时执行 elseif...else 语句代码块,用于检测多个条件语句。

以下实例对变量 a 的值进行判断


函数

在Lua中,函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。

Lua 提供了许多的内建函数,你可以很方便的在程序中调用它们,如 print() 函数可以将传入的参数打印在控制台上。

Lua 函数主要有两种用途:

  1. 完成指定的任务,这种情况下函数作为调用语句使用;

  2. 计算并返回值,这种情况下函数作为赋值语句的表达式使用。

Lua 编程语言函数定义格式如下:

以下实例定义了函数 max(),参数为 num1, num2,用于比较两值的大小,并返回最大值:

Lua 中我们可以将函数作为参数传递给函数,如下实例:

多返回值

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

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

可变参数

Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 ... 表示函数有可变的参数。

我们可以将可变参数赋值给一个变量。

例如,我们计算几个数的平均值:

也可以通过 select("#",...) 来获取可变参数的数量:

有时候我们可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:

通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select('#', …) 或者 select(n, …)

  • select('#', …) 返回可变参数的长度。

  • select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。

调用 select 时,必须传入一个固定实参 selector(选择开关) 和一系列变长参数。如果 selector 为数字 n,那么 select 返回参数列表中从索引 n 开始到结束位置的所有参数列表,否则只能为字符串 #,这样 select 返回变长参数的总数。


运算符

运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。Lua 提供了以下几种运算符类型:

算术运算符

关系运算符

逻辑运算符

其他运算符

  • .. 连接两个字符串

  • # 一元运算符,返回字符串或表的长度。

运算符优先级


文件 I/O

Lua I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式。

  • 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。

  • 完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法

简单模式

简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适。

以下为 file.lua 文件代码,操作的文件为test.lua(如果没有你需要创建该文件),代码如下:

执行以上代码,你会发现,输出了 test.lua 文件的第一行信息,并在该文件最后一行添加了 lua 的注释

在以上实例中我们使用了 io."x" 方法,其中 io.read() 中我们没有带参数

  • "*n" 读取一个数字并返回它。例:file.read("*n")

  • "*a" 从当前位置读取整个文件。例:file.read("*a")

  • "*l" (默认) 读取下一行,在文件尾 (EOF) 处返回 nil。例:file.read("*l")

  • number 返回一个指定字符个数的字符串,或在 EOF 时返回 nil。例:file.read(5)

其他的 io 方法有:

  • io.tmpfile():返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除

  • io.type(file): 检测obj是否一个可用的文件句柄

  • io.flush(): 向文件写入缓冲中的所有数据

  • io.lines(optional file name): 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件

完全模式

通常我们需要在同一时间处理多个文件。我们需要使用 file:function_name 来代替 io.function_name 方法。以下实例演示了如何同时处理同一个文件:

执行以上代码,你会发现,输出了 test.lua 文件的第一行信息,并在该文件最后一行添加了 lua 的注释。

以下实例使用了 seek 方法,定位到文件倒数第 25 个位置并使用 read 方法的 *a 参数,即从当期位置(倒数第 25 个位置)读取整个文件。


错误处理

程序运行中错误处理是必要的,在我们进行文件操作,数据转移及 web service 调用过程中都会出现不可预期的错误。如果不注重错误信息的处理,就会造成信息泄露,程序无法运行等情况。

语法错误

语法错误通常是由于对程序的组件(如运算符、表达式)使用不当引起的。一个简单的实例如下:

以上代码执行结果为:

正如你所看到的,以上出现了语法错误,一个 "=" 号跟两个 "=" 号是有区别的。一个 "=" 是赋值表达式两个 "=" 是比较运算。

另外一个实例:

执行以上程序会出现如下错误:

语法错误比程序运行错误更简单,运行错误无法定位具体错误,而语法错误我们可以很快的解决,如以上实例我们只要在 for 语句下添加 do 即可:

运行错误

运行错误是程序可以正常执行,但是会输出报错信息。如下实例由于参数输入错误,程序执行时报错:

当我们编译运行以下代码时,编译是可以成功的,但在运行的时候会产生如下错误:

lua 里调用函数时,即使实参列表和形参列表不一致也能成功调用,多余的参数会被舍弃,缺少的参数会被补为 nil。

以上报错信息是由于参数 b 被补为 nil 后,nil 参与了 + 运算。

假如 add 函数内不是 "return a+b" 而是 "print(a,b)" 的话,结果会变成 "10 nil" 不会报错。

错误处理

我们可以使用两个函数:assert 和 error 来处理错误。实例如下:

执行以上程序会出现如下错误:

实例中 assert 首先检查第一个参数,若没问题,assert 不做任何事情;否则,assert 以第二个参数作为错误信息抛出。

error函数

功能:终止正在执行的函数,并返回 message 的内容作为错误信息 (error 函数永远都不会返回)

通常情况下,error 会附加一些错误位置的信息到 message 头部。

Level 参数指示获得错误的位置:

  • Level=1[默认]:为调用 error 位置 (文件 + 行号)

  • Level=2:指出哪个调用 error 的函数的函数

  • Level=0: 不添加错误位置信息

pcall 和 xpcall、debug

Lua 中处理错误,可以使用函数 pcall(protected call)来包装需要执行的代码。

pcall 接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值 true 或者或 false, errorinfo。

语法格式如下

pcall 以一种 "保护模式" 来调用第一个参数,因此 pcall 可以捕获函数执行中的任何错误。

通常在错误发生时,希望落得更多的调试信息,而不只是发生错误的位置。但 pcall 返回时,它已经销毁了调用桟的部分内容。

Lua 提供了 xpcall 函数,xpcall 接收第二个参数——一个错误处理函数,当错误发生时,Lua 会在调用桟展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用 debug 库来获取关于错误的额外信息了。

debug 库提供了两个通用的错误处理函数:

  • debug.debug:提供一个 Lua 提示符,让用户来检查错误的原因

  • debug.traceback:根据调用桟来构建一个扩展的错误消息