学习资料地址:http://www.runoob.com/lua/lua-tutorial.html

环境安装与使用

安装环境

  在Windows环境下,使用SciTE来运行Lua程序。

  项目GitHub地址:https://github.com/rjpcomputing/luaforwindows/releases

  上面的官方下载无法保证下载速度,我使用的是菜鸟教程分流的下载地址:http://static.runoob.com/download/LuaForWindows_v5.1.4-46.exe

开始编程

  和Python一样,Lua也有交互式的编程方式。在命令行中输入lua或者lua -i启用。

  同时也可以使用脚本式编程,就是将代码存放在一个lua文件中,命令行执行lua xxx.lua即可。

  需要注意的是,交互式编程状态下不能使用lua命令,会出现下面的错误,这一点我也是犯了好多次:

1
stdin:1: '=' expected near 'hello'

  教程中没有提到怎样退出lua环境 。os.exit()退出。

基础知识

注释

  单行注释:

1
--

  多行注释:

1
2
3
4
--[[
XXX
XXX
]]--

输入输出

1
print("hello world!")

数据类型与变量

变量

概述

  Lua有三种变量类型:全局变量,局部变量和表中的域。默认情况下,变量都是全局变量,函数里面没有做特殊声明的变量也都是全局变量。全局变量不需要声明,直接赋值即创建了这个全局变量。如果访问了一个没有创建的变量,会返回一个nil。同样,如果想要删除一个变量,直接赋值nil即可。

1
2
3
4
5
> print(b)
nil
> b=10
> print(b)
10

  如果想要一个局部变量,需要使用local显式声明:

1
local b = 5

赋值语句

  赋值时改变一个变量的值和改变表域的最基本方法。同大多数编程语言类似,Lua使用从右到左的赋值方式。除此之外,还能够实现多个变量同时赋值:

1
a, b = 10, 2*x

  上面的语句在功能上,等同于:

1
2
a = 10
b = 2*x

  在对多个变量进行赋值时,Lua采用的计算方式是先计算右侧的值,再进行赋值的操作。所以再Lua中,实现两个变量值的交换只需要一行代码:

1
x, y = y, x

  除此之外,当变量的个数和数值的个数不一致时,Lua会遵循这个策略:

  1. 当 变量个数 > 值的个数 时,右侧没有指定值的变量赋值为nil
  2. 当 变量个数 < 值的个数 时,右侧多余的值会被忽略掉。

  所以比较容易出现的错误是,在对多个变量进行相同赋值时:

1
2
3
4
a, b, c = 0
print(a,b,c) --> 0 nil nil
a, b, c = 0, 0, 0
print(a,b,c) --> 0 0 0

  多赋值语句,除了用在上面的值交换的过程中之外,还常用于记录函数的多个返回值。Lua中,函数可以有多个返回值。如果想要将函数f()的两个返回值存放到全局变量ab中去,可以使用下面的语句:

1
a, b = f()

  上面的语句,实现了将函数的第一个返回值给a,第二个给b。

  虽然Lua定义全局变量比较方便,但是应当尽可能地使用局部变量。因为局部变量的访问时间要比全局变量更快,同时也避免了变量命名的冲突。

数据类型

概述

  以下是常见的集中变量类型。

数据类型 描述
nil 无效值,没有赋值的变量都会返回nil
boolean 布尔
number 双精度浮点型
string 字符串,用单引号或双引号括起来
function 由C或Lua编写的函数
userdata *表示任意存储在变量中的C数据结构
thread *表示执行的独立线路,用于执行协同程序
table *Lua 中的表(table)其实是一个”关联数组”(associative arrays),数组的索引可以是数字或者是字符串。在 Lua 里,table 的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。

  可以使用type函数测试给定变量或常量的类型:

1
2
3
4
5
6
7
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(空)

  nil类型表示没有任何有效值,只有一个值,nil。打印一个没有赋值的变量会输出nil。对于全局变量和table变量,nil可以起到删除的作用。全局变量或者 table表里的变量赋一个nil值,等同于将他们删除。

  nil在比较时,需要使用双引号括起来。

1
2
3
4
5
6
7
> type(X)
nil
> type(X)==nil
false
> type(X)=="nil"
true
>

boolean(布尔)

  Lua的布尔值与其他语言有一个显著的区别,它将false和nil看作假,其他的值,包括0,都为真。长时间使用其他编程语言的人可能会很难反应过来。

  除此之外,在进行逻辑判断时也和其他语言有很多不同。进行或、与运算时,如0 or false的值就为0,10 and 20返回值就为20。

number(数字)

  Lua默认只有一种number类型,即双精度浮点型。

string(字符串)

  字符串由一对单引号或者双引号括起来。

1
string1 = "this is string1"

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

1
2
3
4
5
6
7
8
html = [[
<html>
<head></head>
<body>
<a href="http://www.z16388.com/">崎径 其镜</a>
</body>
</html>
]]

  运行时,Lua会自动在string和number之间进行格式转换。

1
2
3
4
print("10"+ 1) --> 11
print("10 + 1") --> 10 + 1
print("hello"+ 1) -- 报错 (无法转换 "hello")
print(10 .. 20) --> 1020

  在一个数字后写..时应该加空格,防止被错误解释

  可以使用两点..来连接两个字符串:

1
2
3
4
> print("a" .. 'b')
ab
> print(157 .. 428)
157428

  可以使用#来计算字符串的长度:

1
2
3
4
5
> len = "www.z16388.top"
> print(#len)
14
> print(#"www.z16388.top")
14

table(表)

  表是一个关联数组,可以创建空表,也可以在创建的时候初始化:

1
2
local tbl1 = {}
local tbl2 = {"apple", "pear", "orange", "grape"}

  数组的索引可以是数字或者字符串:

1
2
a[key] = 22
a[2] = 11

  需要注意的是,与其他语言不同,Lua默认初始索引是从1开始的!!!!

1
2
3
4
local tbl = {"apple", "pear", "orange", "grape"}
for key, val in pairs(tbl) do
print("Key", key)
end

  程序的运行结果是:

1
2
3
4
Key 1
Key 2
Key 3
Key 4

  对table的索引除了使用国际惯例[]之外,还可以使用.进行操作。需要注意的是,使用点号来索引时,索引的关键字不能为数字

1
2
3
4
5
6
> site = {}
> site["key"] = "www.z16388.top"
> print(site["key"])
www.z16388.top
> print(site.key)
www.z16388.top

  最后,table不会固定长度,没有被赋值的部分,返回值都为nil。如果直接使用for循环进行遍历,循环会在遇到nil值时终止,可能无法实现对整个数组的遍历。(如果其中有空值的话)具体的解决方法在table部分给出。

function(函数)

  在Lua中,函数被看作第一类值,函数可以存在变量里。

1
2
3
4
5
6
7
8
9
10
function factorial1(n)
if n == 0 then
return 1
else
return n * factorial1(n - 1)
end
end
print(factorial1(5))
factorial2 = factorial1
print(factorial2(5))

  可以以匿名函数的方式,通过参数传递。函数之所以有名字,就是为了方便进行调用。而只使用一次的函数,则可以不起名字,也就是匿名函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function testFun(tab,fun)
for k ,v in pairs(tab) do
print(fun(k,v));
end
end
tab={key1="val1",key2="val2"}
testFun(tab,
function(key,val)--匿名函数
return key.."="..val
end
);

循环与流程控制

循环语句

while循环

  条件为true时会重复执行循环体的语句。

1
2
3
4
while(condition)
do
statements
end

for循环

  for循环是可以制定循环体执行次数的循环语句。Lua中的for循环需要相当地注意,分为两大类:

数值for循环

1
2
3
for var=exp1,exp2,exp3 do
<执行体>
end

  exp1为初值,exp2为终值,exp3为步长。若exp3不指定,值默认为1。需要注意的是,表达式中三个参数的值都会在循环开始前一次性求值,以后不再进行求值

  参数

泛型for循环

  泛型for循环,通过一个迭代器来遍历所有的值,与C++类似。

1
2
3
for i,v in ipairs(a)
do print(v)
end

  其中,i是索引,v是索引对应的元素的值,ipairs是Lua提供的一个迭代器函数,用来迭代数组。它的参数a就是数组名。下面是一个对数组进行泛型for循环输出的例子。

1
2
3
4
days = {"Suanday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
for i,v in ipairs(days) do
print(v)
end

repeat … until循环

  即do … while,重复执行循环体到循环条件为真时。

1
2
3
repeat
statements
until( condition )

break语句

  Lua支持break语句。

流程控制

  if语句的格式:

1
2
3
4
if(布尔表达式)
then
--[ 在布尔表达式为 true 时执行的语句 --]
end

  if … else 语句格式:

1
2
3
4
5
6
if(布尔表达式)
then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end

  需要注意的是,Lua中0也为真值,只有false和nil为假。

函数

定义

  Lua中,函数的标准格式如下:

1
2
3
4
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
  1. optional_function_scope这个参数是可选的,表示函数是全局的还是局部的。默认为全局函数,设置为局部函数需要加关键字local
  2. function_name函数名
  3. argument函数的参数,用逗号隔开,也可以不带参数
  4. function_body函数体
  5. result_params_comma_separated返回值,可以有多个。

  函数可以作为参数

多返回值

  Lua中的函数可以有多个返回值。

1
return res1,res2;

可变参数

  Lua中的函数可以接受可变数目的参数,用三点来代表多个参数。计算平均值的程序,可以这样写:

1
2
3
4
5
6
7
8
9
10
11
function average(...)
result = 0
local arg={...}
for i,v in ipairs(arg) do
result = result + v
end
print("总共传入 " .. #arg .. " 个数")
return result/#arg
end
print("平均值为",average(10,5,3,4,5,6))

  在新的版本里,#不能用于返回数组元素的个数了,而是需要使用:

1
select("#",...)

运算符

算数运算符

  常用的算术运算符与其他语言类似。需要注意的是,Lua里没有整数,所以如果想要取整操作,必须要进行取整操作:

1
x = math.ceil(x)

  需要注意的是,这里的取整仍然是向上取整。也就是说,无论小数部分有多小,取整的结果都是要比原数的整数部分大1。

  除此之外,常用的操作应该还有数据精度的控制:

1
string.format("%.2f",dt)

关系运算符

符号 含义
== 等于
~= 不等
>= 大于等于
<= 小于等于
> 大于
< 小于

逻辑运算符

符号 含义
and
or
not

其他运算符

符号 含义
.. 连结两个字符串
# 返回字符串或表的长度

  #运算符,获取的表的长度实际上获取的是表最大的索引值。

1
2
3
4
5
6
7
8
9
10
11
12
13
tab1 = {"1","2"}
print("tab1长度"..#tab1)
tab2 = {key1="1","2"}
print("tab2长度"..#tab2)
tab3 = {}
tab3[1]="1"
tab3[2]="2"
tab3[4]="4"
print("tab3长度"..#tab3)
tab1长度2
tab2长度1
tab3长度4

  但是当下表的值,大小相差超过1的时候,就会停止计算

1
2
3
4
5
6
7
tab3={}
tab3[1]="1"
tab3[2]="2"
tab3[5]="5"
print("tab3的长度",#tab3)
tab3的长度 2

运算符优先级

  在原口诀的基础上,字符串连接运算符的优先级在算数运算符和关系运算符之间。

字符串

形式

  共有三种类型

  1. 单引号括起来
  2. 双引号括起来
  3. [[]]括起来

常用方法

方法 用途
uper(str) 全部转为大写字母
lower(str) 全部转为小写字母
len(str) 计算长度
gsub(str,str1,str2,num) 将str中的str1,替换为str2,替换num个,num可以省略
find(str,str1,[init, [end]]) 在str中搜索str1,返回其位置。如果不存在返回nil。第三个参数为索引

  字符串翻转

1
2
> string.reverse("Lua")
auL

  字符串格式化,和C的输出类似

1
2
> string.format("the value is:%d",4)
the value is:4

  char()函数,将整数按照ASCII码转换成对应的符号;byte(str,num)将第num个字幕转换为ASCII码,默认是第一个。

1
2
3
4
5
6
> string.char(97,98,99,100)
abcd
> string.byte("ABCD",4)
68
> string.byte("ABCD")
65

  rep(string,n)将字符串复制n次。

1
2
> string.rep("abcd",2)
abcdabcd

  ..可以用于连接字符串:

1
2
> print("FF".."14")
FF14

  原课程笔记中提到了两个比较有用的程序。一个是将阿拉伯数字转换成大写的中文数字,另一个是移除一个字符串中的汉字字符和符号。

1
2
3
4
5
6
7
8
9
10
local function NumToCN(num)
local size = #tostring(num)
local CN = ""
local StrCN = {"一","二","三","四","五","六","七","八","九"}
for i = 1 , size do
CN = CN .. StrCN[tonumber(string.sub(tostring(num), i , i))]
end
return CN
end
print(NumToCN(56665))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function StrSplit(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={}
local i=1
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
t[i] = str
i = i + 1
end
return t
end
local a = "23245023496830,汉字。。。。"
local b = ":"
b = StrSplit(a,",")
print(b[1])

数组

概念

  数组就是同一类型数据,按照一定顺序排列的集合。数组可以是一维的,也可以是多维的。

  Lua中的数组的索引键值可以使用整数表示,数组的大小是不固定的。

一维数组

  一维数组是最简单的数组,逻辑结构是线性表。一维数组可以使用for循环输出数组中的元素:

1
2
3
4
5
array = {"Hello", "World!"}
for i= 0, 2 do
print(array[i])
end

  Lua的索引默认从1开始,可以指定从0甚至从负数开始,索引指定的位置没有数值返回nil。

1
2
3
4
5
6
7
8
9
array = {}
for i= -2, 2 do
array[i] = i *2
end
for i = -2,2 do
print(array[i])
end

多维数组

  多维数组分为两种。数组中包含数组,或者是一维数组的索引键对应一个数组。

  三行三列多维数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 初始化数组
array = {}
for i=1,3 do
array[i] = {}
for j=1,3 do
array[i][j] = i*j
end
end
-- 访问数组
for i=1,3 do
for j=1,3 do
print(array[i][j])
end
end

  不同索引键的三行三列阵列多维数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 初始化数组
array = {}
maxRows = 3
maxColumns = 3
for row=1,maxRows do
for col=1,maxColumns do
array[row*maxColumns +col] = row*col
end
end
-- 访问数组
for row=1,maxRows do
for col=1,maxColumns do
print(array[row*maxColumns +col])
end
end

迭代器

概念

  迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。

泛型for迭代器

  泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:

1
2
3
for k, v in pairs(t) do
print(k, v)
end

  对于一个表来说,使用泛型for迭代器,用通俗直白的话来讲,k就是索引,v是索引对应的值,t则是这个表的表名。下面是一个实例:

1
2
3
4
5
array = {"Lua", "Tutorial"}
for key,value in ipairs(array)
do
print(key, value)
end

  上面的代码输出结果为:

1 Lua
2 Tutorial

  

其他

常用的几种遍历方式

  第一种,按照key的hash值排列的顺序遍历。

1
2
3
for key, value in pairs(tbtest) do
XXX
end

  第二种,要求Key是连续的,从1开始的数字。ipairs只会从1开始按连续的key顺序遍历到key不连续为止。

1
2
3
for key, value in ipairs(tbtest) do
XXX
end

  第三种,必须存在1这个key,而且是从1开始依次加一的顺序,找到一个不是1的就不再进行遍历。

1
2
3
for i=1, #(tbtest) do
XXX
end