三十分钟学会 lua
lua是的小巧的脚本语言,它的设计目的是为了能够嵌入到应用程序中,从而为应用程序提供灵活的扩展和定制功能。
先执行百度安装好 lua,本文是采用 vscode 运行的,windows 用户可以使用lua 自带的编辑器。
注释
-- 这是单行注释
--[[
这里是多行注释
注释
--]]
数据类型
ua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
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 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
运算符
操作符 | 描述 | 实例 |
---|---|---|
+ | 加法 | A + B 输出结果 30 |
- | 减法 | A - B 输出结果 -10 |
* | 乘法 | A * B 输出结果 200 |
/ | 除法 | B / A w输出结果 2 |
% | 取余 | B % A 输出结果 0 |
^ | 乘幂 | A^2 输出结果 100 |
- | 负号 | -A 输出结果 -10 |
语法
基本语法
bool = nil -- 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
id = 1 -- 声明整数
amount = 1.2 -- 声明整数(lua 整数和小数都叫做整数)
name = 'jacky' -- 声明字符串,单引号双引号一个意思,都是字符串
nickName = "哈基石" -- 声明字符串
local age = 18 -- 声明局部变量,带有 local 的是局部定义,不带有的是全局定义
print(id..name) -- 连接两个变量或字符类似 php 中的 . 和 javascript 中的 +
Lua if else
主要是先熟悉一下 Lua 的语法结构,你不用记后面会大部分用到,有一点印象即可。
bool = true -- 定义全局变量
if bool == true then
print(true) -- 输出 true
end
if 0 then
print(true) -- 输出 true
end
if nil then
print(true) -- 不输出 重点记忆
end
if bool == true then
print(true) -- 输出 true
else
print(false)
end
local num = 3
if num == 1 then
print(1)
elseif num == 2 then
print(2)
else
print(3) -- 输出3
end
table
其实 table 应该放后面学的,但总觉得放后面会导致知识很零散,没有办法高效学习。学会了 table,本文基本上60%就已经学完了。
相对于其他语言而言,lua 不需要学习那么多结构像数组,结构体,map,切片之类的。都说 php 的数组一招鲜走遍天下,啥都能干。
但是 lua更恐怖,有着更强大的功能呢,包括数组,类,匿名...
数组
lua 的数字键值是从1开始的,这一点需要特别注意。
一维数组
数字作为 key 的数组
--创建一个空的 table
tab = {}
-- 直接初始化 table
tab = {"java", "golang", "lua", "python"}
-- 数字下标访问
print(tab[1]) -- 输出 java
print(tab[2]) -- 输出 golangtabl
tab[1] = "C#" -- 赋值
tab[7] = "C++" -- 赋值
print(tab[1]) -- 输出 C#
print(tab[7]) -- 输出 C++
tab[1] = nil -- 删除数组中的元素, key 保留值为 nil
print(tab[1]) -- 输出 nil
print(tab[1000]) -- 输出 nil 不会报错哦
字符串作为 key 的数组
tab = {beijing="北京", shanghai="上海", chengdu="成都"}
tab["hangzhou"] = "杭州"
print(tab.beijing, tab.hangzhou, tab["shanghai"]) -- 输出 北京 杭州 上海
总结: 如果是数字作为索引,那么需要用[]
来访问,如果字符串作为索引,那么可以使用[], 和 xx.xxx
两种方式都可以访问和修改
table的增加
-- 新增元素到 tab 的末尾
tab = {"beijing", "shanghai"}
table.insert(tab, "hangzhou") -- 将广州写入tab的末尾,下标就是3
print(tab[3]) -- hangzhou
-- 新增到指定的位置
table.insert(tab, 1, "neimeng") -- 插入到1的位置,其原来位置后面的元素向后偏移
print(tab[1], tab[2]) -- 输出 neimeng beijing
table 的删除
tab = {"java", "C++", "golang", "sql"}
res = table.remove(tab, 2) -- 删除第二个元素,C++,其他元素会进行补位(向前偏移)
print(res, tab[1], tab[2]) -- 输出C++ java golang
多维数组
多维 table 与一位多了一层,使用情况完全一样。
tab2 = {{}} -- 定义个空的二维 tab
tab2 = {{"北京", "上海"}, {"java", "go"}}
print(tab2[1][1], tab2[2][2]) -- 输出 北京 go
元表 matatable
元表( metatable
)是一个表,它是使用键集和相关元方法来修改附加到的表的行为。这些元方法是强大的Lua功能。
-
__index
这是 metatable 最常用的键。当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable,是否存在__index 方法。如果存在就会调用它,不存在就会返回 nil
-
__newindex
如果对 table 里面的数据进行新增,例如table["xxx"] = 300
那么就会调用 metatable__newindex
方法 -
__call
如果对 table 直接调用,那么会调用 metatable 中的__call
这个方法, 例如调用tab()
。 -
__tostring
如果堆 table 直接输出,那么会调用 metatable 中的__tostring
这个方法,例如print(tab)
-- __index 索引
tab = {10, 20, 30}
tab2 = {} -- __newindex 赋值方式1
metatab = {
-- __index 如果tab中不存在key,则调用__index。
__index = function(t, k)
print(string.format( "调用了 tab 不存在的索引,key=%s", k))
end,
-- __newindex = tab2, -- 赋值方式1,直接赋值給 tab2 例如调用 xxx["user"]=1 ,那么tab2["user"]的值为1
__newindex = function(t, k, v) -- 赋值方式2方法自定义调用
rawset(t, k, v) -- 赋值給 t
print(string.format( "新增了tab不存在的索引,k=%s, v=%s", k, v))
end,
__call = function(t, arg1, arg2)
print(string.format("调用了__call, arg1=%s, arg2=%s,返回了true", arg1, arg2))
end,
__tostring = function(t)
print("调用了直接输出 __tostring") -- 直接输出 tab 会调用这个方法
return "call __tostring"
end,
__le = function(t, newtab) --两个表调用了 <= 比较运算符
print("调用了两个表==运算符")
return true
end,
__add = function(t, newtab) -- 根据自己的场景来处理,这里处理的是两个表合并
print("调用了__add")
for k, v in pairs(newtab) do -- 还没有学习 for,先不要关心
table.insert(t, v)
end
return t
end,
}
-- 对指定 table 设置元表(metatable)
restab = setmetatable(tab, metatab)
restab[20] = 2000 -- 调用 __index 方法
restab(32, 45) -- 调用 __call 方法
print(restab) -- 调用 __tostring
newtab = {80, 90}
res = restab + newtab -- 调用两个表想加
if restab <= n then -- 调用了 __le
-- do something
end
操作运算符
模式 | 描述 |
---|---|
__add | 对应的运算符 '+'. |
__sub | 对应的运算符 '-'. |
__mul | 对应的运算符 '*'. |
__div | 对应的运算符 '/'. |
__mod | 对应的运算符 '%'. |
__unm | 对应的运算符 '-'. |
__concat | 对应的运算符 '..'. |
__eq | 对应的运算符 '=='. |
__lt | 对应的运算符 '<'. |
__le | 对应的运算符 '<='. |
好吧,原表终于学完了,如果你在学习中也进行实际操作,那么一定超过30分钟了。不过坚持一下,剩下的部分很快了。有了元表的基础就可以学习面向对象了
Lua 循环
Lua 的循环有3中分别是 for, while, repeat unitl。其中 for 包括基本 for 与泛型 for,相当于其他语言的 for in 或者 foreach。
while 循环
-- 最简单的循环
a = 5
while a >= 0 do
print(a) -- 输出 5 4 3 2 1 0
a = a -1
end
for 循环
数值循环
for 循环分为两种,数值型和泛型,数值型比其他语言更简介,也有点怪怪的,熟悉就好。
数值型的 for 循环长这样
for var=exp1,exp2,exp3 do
<执行体>
end
exp1代表初始化的数值,exp2代表结束条件,注意这里没有比较运算符,exp3代表步长,也可以省略。
for i = 1, 5, 1 do
print(i) -- 输出 1 2 3 4 5
end
-- 步长是2的情况
for i = 1, 5, 2 do
print(i) -- 输出 1 3 5
end
步长也就是 exp3可以省略,默认步长是1
for i = 1, 5 do
print(i) -- 输出 1 2 3 4 5
end
这里需要注意一点,与其他语言不一样 for的三个表达式在循环开始前一次性求值,以后不再进行求值。比如上面的f(x)只会在循环开始前执行一次,其结果用在后面的循环中。
泛型循环
泛型循环有两个迭代函数 pairs 与 ipairs,他们的区别是 ipairs遇到nil会停止,pairs会输出nil值然后继续下去,文字看不动没关系,看代码。
table = {"1", "2", "3", nil, "5"}
for k, v in pairs(table) do
print("k="..k..",val="..v)
end
这个栗子会输出
k=1,val=1
k=2,val=2
k=3,val=3
k=5,val=5
-- ipairs 如果文字没有理解啥意思,可以对比一下上一个栗子与这个栗子的结果
table = {"1", "2", "3", nil, "5"}
for k, v in ipairs(table) do
print("k="..k..",val="..v)
end
这个栗子会输出
k=1,val=1
k=2,val=2
k=3,val=3
repeat until
-- repeat
a = 1
repeat -- 开始重复
print(a)
a = a +1
until(a>3) -- 条件 直到条件判断语句(condition)为 true 才会停止执行。
这个栗子会输出
1
2
3
迭代器
趁热打铁,迭代器与 for 循环一起结合着理解会很好理解。其实上个例子中 pairs 与 ipairs 就是两个泛型迭代器,lua 内部都给我们实现好了。
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
在 Lua 中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
泛型迭代器
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
-- 迭代函数 state 只有在初始化的时候赋值
function square(max, add)
if add >= max then -- 当累加器到达最大返回 nil 外部会跳出循环
return nil
else
add = add+1 -- lua 没有 ++
return add, add*add -- 返回两个参数与 go 相似,返回当前的值和乘
end
end
for i, j in square , 5, 0 do -- 调用自定义迭代器
print(i, j)
end
这个栗子输出
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
多状态迭代器
很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到 table 内,将 table 作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函数通常不需要第二个参数
-- 多状态迭代器
tab = {"google", "baidu"}
function square (con)
local index = 0
local count = #con -- #代表计算长度
return function() -- 这是一个匿名函数会直接调用
index = index + 1
if index <= count then -- index 小于总长度
return con[index] -- 返回这个 val
end
end
end
for val in square(tab) do
print(val) -- 输出 google baidu
end
函数
支持返回多个返回值
function op(key, val)
return key, val -- 支持两个参数返回
end
arg1, arg2 = op("user", 100)
参数支持传递函数
-- 参数函数传递的方式
function sum(a, b, func)
return func(a, b)
end
function func(a, b)
return a + b
end
val = sum(10, 20, func)
print(val) -- 输出30
匿名函数的方式传递
function max(a, b, func)
max = func(a, b)
print(max) -- 输出 39
end
getmax = function(a, b)
if a > b then
return a
else
return b
end
end
max(10, 39, getmax)
可变参数
function args(...)
local arg = {...} -- 局部定义否则会有重复赋值
print("总共传入 " .. select("#",...) .. " 个数")
print(arg[1], arg[2], arg[3])
end
args(10, 20, 30)
这个栗子会输出
总共传入 3 个数
10 20 30
面向对象
基础定义
.
的定义定义方式,不能直接使用 self
-- table 引用类型
phone = {
name = "马化腾",
memory = "128GB"
}
phone.call = function(self)
str = string.format( "%s 正在打电话", self.name)
print(str)
end
phone.call(phone) -- 输出 马化腾 正在打电话
:
的定义方式
phone = {
name = "马化腾",
pname = "李彦宏"
}
function phone:call()
str = string.format( "%s 正在打电话", self.name)
print(str)
end
function phone:say(name)
self.pname = name
str = string.format( "%s 说%s你赶紧充钱", self.name, self.pname)
print(str)
end
phone:call()
phone:say("马云")
输出
马化腾 正在打电话
马化腾 说马云你赶紧充钱
完整的面向对象实例
-- table 引用类型
phone = {
name = "马化腾",
}
function phone:call()
str = string.format("%s 正在打电话", self.name)
print(str)
end
function phone:new(sel)
sel = sel or {}
-- 由于 table 是引用类型,这里需要重新赋值
setmetatable(sel, {__index = self}) -- 将 self 赋给 sel
return sel
end
mahuat = phone:new()
mayun = phone:new()
mahuat.name = "马化腾"
mayun.name = "马云"
mahuat:call() -- 注意 :call()的形式,否则无法获得到 self
mayun:call()
输出
马化腾 正在打电话
马云 正在打电话
Lua 继承
看代码,认真看,没那么繁琐
Phone = {
name = "",
}
function Phone:call() -- 定义基本方法
str = string.format("%s 正在打电话", self.name)
print(str)
end
function Phone:new(sel)
sel = sel or {}
setmetatable(sel, {__index = self}) -- 将 self 赋给 sel
return sel
end
-- Mahuat继承了 Phone 方法
Mahuat = Phone:new()
function Mahuat:say()
self.name = "马化腾"
self:call() -- 调用父类的方法
str = string.format("%s 说赶紧充钱!", self.name)
print(str)
end
function Mahuat:new(sel)
sel = sel or Phone:new()
setmetatable(sel, self)
self.__index = self
return sel
end
Mahuat:say() -- 调用自己的方法
-- Mayun继承了 Phone 方法
Mayun = Phone:new()
function Mayun:say()
self.name = "马云"
self:call() -- 调用父类的方法
str = string.format("%s 说快还花呗!", self.name)
print(str)
end
function Mayun:new(sel)
sel = sel or Phone:new()
setmetatable(sel, self)
self.__index = self
return sel
end
Mayun:say() -- 调用自己的方法
-- 派生重写
liyanhong = Phone:new()
function liyanhong:call()
print("我们不生产假药!我们只是假药的搬运工~ ")
end
-- 重写了 call 方法
function liyanhong:say()
self.name = "李彦宏"
self:call() -- 调用父类的方法
str = string.format("%说欧耶!", self.name)
print(str)
end
function liyanhong:new(sel)
sel = sel or Phone:new()
setmetatable(sel, self)
self.__index = self
return sel
end
liyanhong:say() -- 调用自己的方法
这个例子输出
马化腾 正在打电话
马化腾 说赶紧充钱!
马云 正在打电话
马云 说快还花呗!
我们不生产假药!我们只是假药的搬运工~
李彦宏 说欧耶!
恭喜您读到这里,实际上还没有完!抱歉了,
Lua 的最基础部分就学完了,还需要一个篇写其他的操作,例如文本操作,函数库调用。