学习资料地址: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 | --[[ |
输入输出
1 | print("hello world!") |
数据类型与变量
变量
概述
Lua有三种变量类型:全局变量,局部变量和表中的域。默认情况下,变量都是全局变量,函数里面没有做特殊声明的变量也都是全局变量。全局变量不需要声明,直接赋值即创建了这个全局变量。如果访问了一个没有创建的变量,会返回一个nil。同样,如果想要删除一个变量,直接赋值nil即可。
1 | > print(b) |
如果想要一个局部变量,需要使用local显式声明:
1 | local b = 5 |
赋值语句
赋值时改变一个变量的值和改变表域的最基本方法。同大多数编程语言类似,Lua使用从右到左的赋值方式。除此之外,还能够实现多个变量同时赋值:
1 | a, b = 10, 2*x |
上面的语句在功能上,等同于:
1 | a = 10 |
在对多个变量进行赋值时,Lua采用的计算方式是先计算右侧的值,再进行赋值的操作。所以再Lua中,实现两个变量值的交换只需要一行代码:
1 | x, y = y, x |
除此之外,当变量的个数和数值的个数不一致时,Lua会遵循这个策略:
- 当 变量个数 > 值的个数 时,右侧没有指定值的变量赋值为nil
- 当 变量个数 < 值的个数 时,右侧多余的值会被忽略掉。
所以比较容易出现的错误是,在对多个变量进行相同赋值时:
1 | a, b, c = 0 |
多赋值语句,除了用在上面的值交换的过程中之外,还常用于记录函数的多个返回值。Lua中,函数可以有多个返回值。如果想要将函数f()的两个返回值存放到全局变量a和b中去,可以使用下面的语句:
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 | print(type("Hello world")) --> string |
nil(空)
nil类型表示没有任何有效值,只有一个值,nil。打印一个没有赋值的变量会输出nil。对于全局变量和table变量,nil可以起到删除的作用。全局变量或者 table表里的变量赋一个nil值,等同于将他们删除。
nil在比较时,需要使用双引号括起来。
1 | > type(X) |
boolean(布尔)
Lua的布尔值与其他语言有一个显著的区别,它将false和nil看作假,其他的值,包括0,都为真。长时间使用其他编程语言的人可能会很难反应过来。
除此之外,在进行逻辑判断时也和其他语言有很多不同。进行或、与运算时,如0 or false的值就为0,10 and 20返回值就为20。
number(数字)
Lua默认只有一种number类型,即双精度浮点型。
string(字符串)
字符串由一对单引号或者双引号括起来。
1 | string1 = "this is string1" |
也可以使用两个方括号[[]]来表示一块字符串。
1 | html = [[ |
运行时,Lua会自动在string和number之间进行格式转换。
1 | print("10"+ 1) --> 11 |
在一个数字后写..时应该加空格,防止被错误解释。
可以使用两点..来连接两个字符串:
1 | > print("a" .. 'b') |
可以使用#来计算字符串的长度:
1 | > len = "www.z16388.top" |
table(表)
表是一个关联数组,可以创建空表,也可以在创建的时候初始化:
1 | local tbl1 = {} |
数组的索引可以是数字或者字符串:
1 | a[key] = 22 |
需要注意的是,与其他语言不同,Lua默认初始索引是从1开始的!!!!
1 | local tbl = {"apple", "pear", "orange", "grape"} |
程序的运行结果是:
1 | Key 1 |
对table的索引除了使用国际惯例[]之外,还可以使用.进行操作。需要注意的是,使用点号来索引时,索引的关键字不能为数字。
1 | > site = {} |
最后,table不会固定长度,没有被赋值的部分,返回值都为nil。如果直接使用for循环进行遍历,循环会在遇到nil值时终止,可能无法实现对整个数组的遍历。(如果其中有空值的话)具体的解决方法在table部分给出。
function(函数)
在Lua中,函数被看作第一类值,函数可以存在变量里。
1 | function factorial1(n) |
可以以匿名函数的方式,通过参数传递。函数之所以有名字,就是为了方便进行调用。而只使用一次的函数,则可以不起名字,也就是匿名函数。
1 | function testFun(tab,fun) |
循环与流程控制
循环语句
while循环
条件为true时会重复执行循环体的语句。
1 | while(condition) |
for循环
for循环是可以制定循环体执行次数的循环语句。Lua中的for循环需要相当地注意,分为两大类:
数值for循环
1 | for var=exp1,exp2,exp3 do |
exp1为初值,exp2为终值,exp3为步长。若exp3不指定,值默认为1。需要注意的是,表达式中三个参数的值都会在循环开始前一次性求值,以后不再进行求值。
参数
泛型for循环
泛型for循环,通过一个迭代器来遍历所有的值,与C++类似。
1 | for i,v in ipairs(a) |
其中,i是索引,v是索引对应的元素的值,ipairs是Lua提供的一个迭代器函数,用来迭代数组。它的参数a就是数组名。下面是一个对数组进行泛型for循环输出的例子。
1 | days = {"Suanday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"} |
repeat … until循环
即do … while,重复执行循环体到循环条件为真时。
1 | repeat |
break语句
Lua支持break语句。
流程控制
if语句的格式:
1 | if(布尔表达式) |
if … else 语句格式:
1 | if(布尔表达式) |
需要注意的是,Lua中0也为真值,只有false和nil为假。
函数
定义
Lua中,函数的标准格式如下:
1 | optional_function_scope function function_name( argument1, argument2, argument3..., argumentn) |
optional_function_scope这个参数是可选的,表示函数是全局的还是局部的。默认为全局函数,设置为局部函数需要加关键字localfunction_name函数名argument函数的参数,用逗号隔开,也可以不带参数function_body函数体result_params_comma_separated返回值,可以有多个。
函数可以作为参数。
多返回值
Lua中的函数可以有多个返回值。
1 | return res1,res2; |
可变参数
Lua中的函数可以接受可变数目的参数,用三点…来代表多个参数。计算平均值的程序,可以这样写:
1 | function average(...) |
在新的版本里,#不能用于返回数组元素的个数了,而是需要使用:
1 | select("#",...) |
运算符
算数运算符
常用的算术运算符与其他语言类似。需要注意的是,Lua里没有整数,所以如果想要取整操作,必须要进行取整操作:
1 | x = math.ceil(x) |
需要注意的是,这里的取整仍然是向上取整。也就是说,无论小数部分有多小,取整的结果都是要比原数的整数部分大1。
除此之外,常用的操作应该还有数据精度的控制:
1 | string.format("%.2f",dt) |
关系运算符
| 符号 | 含义 |
|---|---|
| == | 等于 |
| ~= | 不等 |
| >= | 大于等于 |
| <= | 小于等于 |
| > | 大于 |
| < | 小于 |
逻辑运算符
| 符号 | 含义 |
|---|---|
| and | 与 |
| or | 或 |
| not | 非 |
其他运算符
| 符号 | 含义 |
|---|---|
| .. | 连结两个字符串 |
| # | 返回字符串或表的长度 |
#运算符,获取的表的长度实际上获取的是表最大的索引值。
1 | tab1 = {"1","2"} |
但是当下表的值,大小相差超过1的时候,就会停止计算。
1 | tab3={} |
运算符优先级
在原口诀的基础上,字符串连接运算符的优先级在算数运算符和关系运算符之间。
字符串
形式
共有三种类型
- 单引号括起来
- 双引号括起来
[[]]括起来
常用方法
| 方法 | 用途 |
|---|---|
| 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 | > string.reverse("Lua") |
字符串格式化,和C的输出类似
1 | > string.format("the value is:%d",4) |
char()函数,将整数按照ASCII码转换成对应的符号;byte(str,num)将第num个字幕转换为ASCII码,默认是第一个。
1 | > string.char(97,98,99,100) |
rep(string,n)将字符串复制n次。
1 | > string.rep("abcd",2) |
..可以用于连接字符串:
1 | > print("FF".."14") |
原课程笔记中提到了两个比较有用的程序。一个是将阿拉伯数字转换成大写的中文数字,另一个是移除一个字符串中的汉字字符和符号。
1 | local function NumToCN(num) |
1 | function StrSplit(inputstr, sep) |
数组
概念
数组就是同一类型数据,按照一定顺序排列的集合。数组可以是一维的,也可以是多维的。
Lua中的数组的索引键值可以使用整数表示,数组的大小是不固定的。
一维数组
一维数组是最简单的数组,逻辑结构是线性表。一维数组可以使用for循环输出数组中的元素:
1 | array = {"Hello", "World!"} |
Lua的索引默认从1开始,可以指定从0甚至从负数开始,索引指定的位置没有数值返回nil。
1 | array = {} |
多维数组
多维数组分为两种。数组中包含数组,或者是一维数组的索引键对应一个数组。
三行三列多维数组:
1 | -- 初始化数组 |
不同索引键的三行三列阵列多维数组:
1 | -- 初始化数组 |
迭代器
概念
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
泛型for迭代器
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:
1 | for k, v in pairs(t) do |
对于一个表来说,使用泛型for迭代器,用通俗直白的话来讲,k就是索引,v是索引对应的值,t则是这个表的表名。下面是一个实例:
1 | array = {"Lua", "Tutorial"} |
上面的代码输出结果为:
1 Lua
2 Tutorial
其他
常用的几种遍历方式
第一种,按照key的hash值排列的顺序遍历。
1 | for key, value in pairs(tbtest) do |
第二种,要求Key是连续的,从1开始的数字。ipairs只会从1开始按连续的key顺序遍历到key不连续为止。
1 | for key, value in ipairs(tbtest) do |
第三种,必须存在1这个key,而且是从1开始依次加一的顺序,找到一个不是1的就不再进行遍历。
1 | for i=1, #(tbtest) do |