前言
最近看到一个基于ESP8266开发的固件NodeMCU,自己写的程序能直接运作于ESP8266,可以像Arduino那样实现实现GPIO、PWM、I2C、1-Wire、ADC等功能,比Arduino使用双串口配合AT指令来实现稳定且方便多了。虽说可以像Arduino一样操作硬件IO,但得使用Lua语言来开发,其语法、函数又与类C语言不一样,Lua再怎么简单没学也是不会用的,遂开始学Lua,在此记录学习时遇到的特殊语法和一些函数。
笔记
1.环境安装
sudo apt install libreadline-dev curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz tar zxf lua-5.3.4.tar.gz cd lua-5.3.4 make linux test sudo make install
2.特殊语法
2.1标示符
Lua 表示符用于定义一个变量,函数获取其他用户定义的项。标示符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上0个或多个字母,下划线(如果该变量没有什么意义可以只用_代替),数字(0到9)。最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。
2.2数据类型
1.boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是"假",其他的都为"真"(0 也为 true)。
2.Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始。
2.3变量
Lua 中的变量全是全局变量,那怕是语句块或是函数里的。使用 local 创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。代码块:指一个控制结构内,一个函数体,或者一个chunk(变量被声明的那个文件或者文本串)。注意,如果在交互模式下上面的例子可能不能输出期望的结果,因为第二句local i=1是一个完整的chunk,在交互模式下执行完这一句后,Lua将开始一个新的chunk,这样第二句的i已经超出了他的有效范围。可以将这段代码放在do...end(相当于c/c++的{})块中。变量的默认值均为 nil,变量可以使用 nil 删除。
2.4赋值
Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。当变量个数和值的个数不一致时,Lua 会一直以变量个数为基础,多余的值会被忽略。
-- swap 'x' for 'y' x, y = y, x
2.5逻辑运算符
a and b -- 如果a为false,则返回a,否则返回b
a or b -- 如果a为true,则返回a,否则返回b
-- 如果x为false或者nil则给x赋初始值v if not x then x = v end -- 等价于 x = x or v -- C语言中的三元运算符a ? b : c在Lua中可以这样实现: (a and b) or c
2.6函数
1.Lua 函数可以接受可变数目的参数,和C语言类似在函数参数列表中使用三点(...)表示函数有可变的参数。Lua 将函数的参数放在一个叫 arg 的表中,#arg 表示传入参数的个数。
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)) 结果: 总共传入 6 个数 平均值为 5.5
2.Lua 的函数参数是和位置相关的,调用时实参会按顺序依次传给形参。有时候用名字指定参数是很有用的,比如 rename 函数用来给一个文件重命名,有时候我们我们记不清命名前后两个参数的顺序了:rename(old="temp.lua", new="temp1.lua")
上面这段代码是无效的,Lua 可以通过将所有的参数放在一个表中,把表作为函数的唯一参数来实现上面这段伪代码的功能。因为Lua语法支持函数调用时实参可以是表的构造,即rename{old="temp.lua", new="temp1.lua"}
根据这个想法我们重定义了 rename:
function rename (arg) return os.rename(arg.old, arg.new) end
3.在 Lua 中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。既然函数是值,那么表达式也可以创建函数了,Lua中我们经常这样写:function foo (x) return 2*x end
这实际上是 Lua 语法的特例,原本的函数为:foo = function (x) return 2*x end
4.闭包:当一个函数内部嵌套另一个函数定义时,内部的函数体可以访问外部的函数的局部变量,这种特征我们称作词法定界。第一类值:lua当中函数是一个值,他可以存在变量中,可以作为函数参数,可以作为返回值。闭包即通过调用含有一个内部函数加上该外部函数持有外部的局部变量(upvalue)的外部函数产生的一个实例。
function newCounter() local i = 0 return function() -- 匿名函数 i = i + 1 return i end end c1 = newCounter() c2 = newCounter() -- c1、c2是建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个不同的闭包。 print(c1()) -- 第一次调用实例c1结果:1 print(c1()) -- 再次调用实例c1结果:2 重复调用时每一个调用都会记住上一次调用后的值,即变量i已经为1了。 print(c2()) -- 第一次调用实例c2结果:1 闭包不同所以upvalue不同 print(c2()) -- 再次调用实例c2结果:2
上述代码中的 newCounter 函数返回了一个函数,而这个返回的匿名函数就是闭包的组成部分中的函数,包含在匿名函数中的变量i可以访问 newCounter 的局部变量i,在匿名函数内部变量i既不是全局变量也不是局部变量,其作为闭包组成的另一部分--外部的局部变量(external local variable)或者 upvalue。实际上,闭包只是在形式和表现上像函数,但并不是真正的函数,我们都知道,函数就是一些可执行语句的组合体,这些代码语句在函数被定义后就确定了,并不会再执行时发生变化,所以函数只有一个实例。而闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例,就好比相同的类代码,可以创建不同的类实例一样。
function Fun1() local iVal = 10 -- upvalue function InnerFunc1() -- 内嵌函数 print(iVal) end function InnerFunc2() -- 内嵌函数 iVal = iVal + 10 end return InnerFunc1, InnerFunc2 end local a, b = Fun1() -- 将函数赋值给变量,此时变量a绑定了函数InnerFunc1, b绑定了函数InnerFunc2 a() -- 调用a结果:10 b() -- 调用b,在b函数中修改了upvalue iVal a() -- 再次调用a结果:20 打印修改后的upvalue
上述这段代码验证了在内嵌函数中 upvalue 是共享的。upvalue 这种变量主要应用在嵌套函数和匿名函数里,在 Lua 的函数中再定义函数即内嵌函数,内嵌函数可以访问外部函数已经创建的所有局部变量,而这些变量就被称为该内嵌函数的 upvalue,upvalue 指的是变量而不是值,这些变量可以在内部函数之间共享。
3.一些函数
3.1字符串操作
1.string.upper(argument)
字符串全部转为大写字母。string.lower(argument)
字符串全部转为小写字母。
2.string.char(arg)
char 将整型数字转成字符并连接,string.byte(arg[,int])
byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。
string.char(97, 98, 99, 100) 结果:abcd string.byte("ABCD", 4) 结果:68 string.byte("ABCD") 结果:65
3.string.gsub(mainString, findString, replaceString, num)
在字符串中替换,mainString为要替换的字符串,findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换)。
string.gsub("aaaa", "a", "z", 3) 结果:zzza 3
4.string.strfind (str, substr, [init, [end]])
在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回 nil。
string.find("Hello Lua user", "Lua", 1) 结果:7 9
5.string.reverse(arg)
字符串反转。
string.reverse("Lua") 结果:auL
6.string.format(...)
返回一个类似printf的格式化字符串。
string.format("the value is:%d", 4) 结果:the value is:4
7.string.len(arg)
计算字符串长度。
string.len("abc") 结果:3
8.string.rep(string, n))
返回字符串string的n个拷贝
string.rep("abcd", 2) 结果:abcdabcd
3.2表操作
1.table.concat (table [, sep [, start [, end]]])
concat是concatenate(连锁, 连接)的缩写。 table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。
fruits = {"banana", "orange", "apple"} -- 返回 table 连接后的字符串 print("连接后的字符串 ", table.concat(fruits)) -- 指定连接字符 print("连接后的字符串 ", table.concat(fruits, ", ")) -- 指定索引来连接 table print("连接后的字符串 ", table.concat(fruits, ", ", 2, 3)) 结果: 连接后的字符串 bananaorangeapple 连接后的字符串 banana, orange, apple 连接后的字符串 orange, apple
2.table.insert (table, [pos,] value)
在table的数组部分指定位置(pos)插入值为value的一个元素。 pos参数可选, 默认为数组部分末尾。table.remove(table [, pos])
返回table数组部分位于pos位置的元素,其后的元素会被前移。 pos参数可选,默认为table长度, 即从最后一个元素删起。
fruits = {"banana", "orange", "apple"} -- 在末尾插入 table.insert(fruits, "mango") print("索引为 4 的元素为 ", fruits[4]) -- 在索引为 2 的键处插入 table.insert(fruits, 2, "grapes") print("索引为 2 的元素为 ", fruits[2]) print("最后一个元素为 ", fruits[5]) table.remove(fruits) print("移除后最后一个元素为 ", fruits[5]) 结果: 索引为 4 的元素为 mango 索引为 2 的元素为 grapes 最后一个元素为 mango 移除后最后一个元素为 nil
3.table.sort (table [, comp])
对给定的table进行升序排序。
fruits = {"banana", "orange", "apple", "grapes"} print("排序前") for k, v in ipairs(fruits) do print(k, v) end table.sort(fruits) print("排序后") for k, v in ipairs(fruits) do print(k, v) end 结果: 排序前 1 banana 2 orange 3 apple 4 grapes 排序后 1 apple 2 banana 3 grapes 4 orange
3.3文件I/O
file = io.open(filename [, mode])
打开文件操作,io.close(file)
关闭打开的文件,io.read(file)
读取文件第一行内容,io.write("...")
文件最后一行添加内容,io.input(file)
设置默认输入文件,io.output(file)
设置默认输出文件,io.tmpfile()
返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除,io.type(file)
检测obj是否一个可用的文件句柄,io.flush()
向文件写入缓冲中的所有数据,io.lines(optional file name)
返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件。
mode 的值有:
r
以只读方式打开文件,该文件必须存在。
w
打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
a
以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
r+
以可读写方式打开文件,该文件必须存在。
w+
打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a+
与a类似,但此文件可读可写。
b
二进制模式,如果文件是二进制文件,可以加上b。
+
号表示对文件既可以读也可以写。
未完待续...