lua学习
lua简介
设计目的
- 为了嵌入到应用程序中,从而为应用程序提供灵活的扩展和定制功能
特性
-
可扩展
- 提供扩展接口和机制,通常扩展到c/C++中
-
轻量级
- 使用C语言编写,开源,编译后100k, 方便嵌入到别的程序中
-
自动内存管理
-
支持面向过程和函数式编程
应用场景
- 游戏开发
- 独立脚本
- web应用脚本
- 扩展和数据库插件:Mysql
环境安装
IDE:SciTe
- 需要安装vc++2005sp1 ,去官网下载
vsCode
- 调试环境复杂,git上好多做luadebug。稍后研究
基本语法
交互式编程
- 直接在lua中编写,回车就执行
脚本时编程
- 将lua程序代码保存为 .lua 格式 PS: hello.lua
- 使用lua执行脚本 lua hello.lua lua
windows F:\lua\hello.lua
注释
- 单行–
- 多行 --[[ 多行注释 --]]
标识符
- 不能用_ABC,下划线加大写字母是lua的保留字
全局变量
- 不需要声明
- 给一个变量赋值就创建了这个变量
- 访问没有初始化的变量不会出错,未初始化变量值为nil
- 删除全局变量,将变量赋值为nil
数据类型
动态类型语言,像python,不要定义类型,直接定义变量并赋值
基本类型
-
nil 表示无效值 相当于false
-
boolean
- false 和nil为假
- true为真
-
number相当于double
-
string
- 单引号或双引号都可以表示
- 使用两个方括号,表示一块字符串 [[ 字符串块,可以有回车 ]]
- 对数字字符串执行+ - ,编译器会隐式转换为number类
- 使用#来计算字符串的长度,放在字符串的 前面,例如: len = “hello” print(#len)
-
userdata
- 表示任意存储在变量中的C数据结构(通常是指针和struct)
-
table
-
是一个关联数组(键值对key–value),索引可以是数字、字符串或者表类型。
-
数组从1开始计数
-
table创建是通过”构造表达式“,{ }
- 采用引用计数模型,当所有引用的变量都为nil,系统垃圾回收
- 初始化表 myTable = { }
- 指定值 myTable[1] = “lua”
- 移除引用 myTable = nil
-
table操作
-
table.concat(table, [,seq [, start[,end]]]),将从start到end的所有元素列出(连接成新的字符串),使用分隔符seq隔开
- fruits = {“banana”, “orange”, “apple”}
- table.concat(fruits , " , ")
-
table.insert(table, [pos,] value);选择一个位置插入,默认为末尾
- table.insert(fruits, 2, “graps”)
-
table.remove(table [,pos])
- table.remove(fruits, 2)
-
table.sort( table[, comp])
- table.sort(fruits)
-
-
-
function
-
1 optional_function_scope function function_name(argument1, argument2, argument3…, argumentn)
- optional_function_scope设置函数为全局还是局部,设置为局部加local关键字
-
2 function_body
-
3 return result_params_comma_separated
- 函数返回值,lua可以有多个返回值,每个返回值以逗号隔开
-
4 end
- 以function开始,end结束
-
特色:
-
将函数作为参数传递给函数
- myprint = function(param) print(“this is print #”, param, “#”) end
- function add(num1, num2, funPrint) funprint(num1 + num2) end
- add(2, 3, myprint)
-
函数返回多个返回值
- 1 --[[例如:string.find 其返回匹配串“开始和结束的下标”(如果不存在匹配串返回nil)]]
- 2 s, e = string.find(“http://www.baidu.com”, “baidu”)
- 3 print(s, e)
- unpack 接受一个数组作为参数,并从下标1开始返回该数组的所有元素
-
不定形参
- 传入变长参数function add(…)
- 将可变形参赋值给一个变量 local arg = {…} --arg为一个表,局部变量
- 通过select("#", …) 可以获得可变参数的数量
- 需要固定形参加上可变参数时, 固定形参放在前面
- select(i, …) 用于访问i到最后的参数,一次读一个 arg = select(i, 。。。)
-
第一类值函数
-
函数可以存在变量
- add( add(5, 7), 8)
-
函数可以匿名传递 有点像lamban表达式
- add = function(a, b) return a + b end
- add(3, function(num1, num2) return num1> num2?num1; num2 )
-
-
-
实现switch
使用表,和匿名函数
local switch = {
[1] = function()
end,
[2] = function()
end,
}
local setUIFunc = switch[index]
if setUIFunc then
setUIFunc()
end
end
变量
所有变量默认值都为nil
分类
-
全局变量
- 在函数中不加local,也是全局变量
-
局部变量
- 必须在变量名前面加上local,即使在函数中也是
- 作用域为定义开始到语句块结束,和C语言中的局部变量一样
-
表中的域
赋值
-
主要用来交换变量,或者将函数调用那个返回给变量
-
lua可以对多个变量同时赋值
- a, b = 10, 5
-
遇到赋值语句会先计算右边所有值,在进行赋值操作
- 交换变量的值 x, y = y, x 交换x.y的值
-
赋值个数不一致时
- 多了忽略
- 少了为nil
流程控制
循环
-
while循环
- while(condition) do statements end
-
for循环
-
数值for循环
- for var = exp1, exp2, exp3 do <执行体> end (var 从1,变换到2,每次变化以3 为步长,3可以不写,默认为1
- for的三个表达式在开始循环前一次性求值,之后不再求值,像赋值操作,先计算右边的值,用临时变量存储,然后之后不再变化
-
泛型for循环
-
通过一个迭代器函数遍历所有值,类似java中的foreach语句
-
for i,v in ipairs(days) do print(v) end
- ipair是lua中一个迭代器函数,用来迭代数组,不能迭代k为string类型的表
-
-
-
repeat…until
- 在循环结束后判断条件,像do {} while
- repeat statement until(condition)
-
循环嵌套
-
break
判断
-
if 和elseif 后面要加then else 不需要
-
if语句
- if(condition) then 语句 end
-
if else
- if(condition) then 语句 else 语句 end
-
if elseif else
- if(condition) then 语句 elseif(condition) then 语句 else 语句 end
运算符
特殊
-
不等于 ~=
-
and or not
-
#一元运算符,返回字符串长度或者表的长度
- #“hello” 返回5
-
… 连接两个字符串
- a…b ,其中 a 为 "Hello " , b 为 “World”, 输出结果为 “Hello World”
-
:相当于在对象中添加函数,这个函数会传入一个self变量,通过self变量可以访问对象中的成员变量
- function MainScene:onCreate()
- function MainScene.onCreate(self)
- 上面两种方法等同
-
. 相对于:只能访问表中的元素
优先级
- ^
- not -(负号)
-
- /
-
-
-
< >= <= ~= =
- and
- or
字符串
字符串操作
-
string.upper(argument) 将字符串全部转化为大写字母
-
string.lower(argument)字符串全部转化为小写字母
-
string.gsub(mainString, findString, replaceString, num) 操作的字符,被替换的字符,要替换的字符,替换的次数(不写默认全换)
-
string.find(str, substr, [init, [end]]) 在str中搜索子串, 如果搜到返回具体位置,不存在返回nil
-
string.reverse(arg) 字符串反转
-
string.format(…) 类似sprintf格式化字符串
-
string.char(arg) 和string.byte(arg[,int]) char将数字转化为字符串格式并连接, byte将字符串转化为数字,默认第一个字
-
string.len(arg) 计算字符串长度
-
string.rep(string, n)返回字符串s串联n次的所组成的字符串,参数s表示基础字符串,参数n表示赋值的次数。
-
。。连接两个字符串
-
string.gmath(str, pattern) 查找子串,每一次调用都会返回下一个符合条件的子串,可以lua支持的符号类型
- .(点)匹配任意字符
- %a字母
- %c控制字符
- %d数字
- %l小写字母
- %p标点
- %s空白字符
- %u大写字母
- %w数字或者字母
- %x任何16进制数
- %z代表0的字符
- %x与摸个非数字非字母的字符x
-
string.match(str, pattern, init) 查找子串,只查找第一个子串,没有查到返回nil
-
string.sub(str, pos1, pos2) 截取字符串
-
tonumber(str) 将string转换成数字,当遇到"–" 无法识别的,返回nil
-
string.byte(s, i [,j]) 返回i到j的ASCII码
数组
一维数组
- 下标不写默认从1开始,下标也可以使用负数或者任意的key
- 遍历使用for循环 for i = 1, 3 do arrar[i] = i end
二维数组
- 是一个包含一维数组,键值为一维数组
- 初始化 array = {} for i = 1, 3 do array[i] = {} for j = 1, 3 do array[i][j] = i+ j end end
迭代器
泛型for
-
迭代函数保存的三个值,通过这三个量来实现迭代
- 迭代函数
- 状态常量
- 控制变量
-
for key, value in ipair(array) do print(key, value) end
-
执行过程
- 初始化三个值
- 状态常量,和控制变量作为参数调用迭代函数,类似 n , i i= 0; i< n; i++
- 将迭代函数返回值返回给变量列表 类似 key ,value = ipairs(arry)
- 然后执行函数体
- 返回的第一个值为nil 结束。类似 key = nil,说明数组空了
无状态的迭代器
-
ipairs()实现
- function iter (a, i)
-
i = i + 1
-
local v = a[i]
-
if v then
-
return i, v
-
end
- end
- function ipairs (a)
-
return iter, a, 0
- end
多状态的迭代器
- 需要保存多个状态信息,不只是状态变量和控制变量。状态变量只能从开始循环到结束,这个长度不能自己定义
- 多状态的迭代器,可以保存多个信息。可以通过其他状态信息控制迭代器遍历,,,具体方法使用闭包函数(将匿名函数直接在再定义的时候调用。比如函数的返回值)
模块与包
把公有代码 放在同一个文件中,,将变量函数封装到表中,相当于c++的类
- 函数可以在其他地方调用, local函数只可以在模块中调用
- 在文件名为module.lua中定义一个module的模块
- 创建一个table
- 写变量、常量、函数等
- return 这个table
require函数,加载模块,加载之后,后面就可以使用这个模块
- require("<模块名>")
- require"<模块名>"
- require(module) print(module.constant)
- 给模块起别名 local m = requir(“module”) print(m.constant)
模块加载机制
-
require函数原型
- 先判断模块是否已经加载,package.loaded表记录已经加载过模块的名字, 和模块
- 如果没有加载过,将模块设置为已加载,,已经加载过直接返回模块
- 初始化模块
-
require加载路径
- require搜索路径存在一个全局变量package.path中
- lua启动时,会用环境变量LUA_PATH的值来初始化package.path
- 如果没有找到该环境变量,使用编译时一个默认路径来初始化
-
package.path实例
- package.path = “D:/lua/test/hello.lua”…";…\?.lua
- 这个后面我不太清楚为什么要这样写
- package.cpath = ‘/usr/local/lib/lua/5.1/?.so;’ --搜索so模块
C包
-
lua在一个叫loadlib的函数内提供 了所有动态链接功能
- 函数有两个参数,一个是库的绝对路径,一个是初始化函数
- local path = “/usr/local/lua/lib/libluasocket.so” local f = loadlib(path, “luaopen_socket”)
- loadlib函数加载并连接到库,但是不打开(没有调用),返回一个初始化函数作为lua的函数,然后可以在lua中直接调用
-
实例
-
local path = “C:\windows\luasocket.dll”,这是 Window 平台下
-
linux 平台local path = “/usr/local/lua/lib/libluasocket.so”
-
local f = assert(loadlib(path, “luaopen_socket”))
- assert()用assert保卫代码后,遇到错误抛出异常
-
f() – 真正打开库
-
元表
使用场景:1.table元表实现lua中的面向对象编程 2.作为userdata的元表可以实现lua中对c中结构进行面向对象式的调用
用于对两个表操作,或者对usrdata操作,有点类似于C++运算符重载
元表调用过程
- 执行t1 + t2过程,会调用元表的_add方法
- 1先查看t1是否有元表,若有,查看t1的元表是否有_add方法,有则调用
- 2先查看t2是否有元表,若有,查看t2的元表是否有_add方法,有则调用
- 若都没有元表和_add方法,就报错
元表的元方法
- _add+ _sub- _mul* _div/ _mod% _unm-取反
- _concat … _eq == _lt < _le<= _call函数调用
- _tostring转化为字符串 _index调用一个索引 _newindex给索引赋值
_index
- 调用table一个不存在的索引时,会使用到_index元方法
- 将表和索引作为参数传入_index方法,return一个返回值
_newindex
- 当table中一个不存的索引赋值时,会调用_newindex元方法
- _newindex是一个函数时会将赋值语句中的表、索引、赋值当作参数去调用,不对表进行改变
- 当_newindex作为一个table。会将t中不存在的索引和值赋到_newindex所指向的表中
rawset
- 直接对表中不存在的索引赋值,不会用到_newindex
rawget
- 直接获取表中索引的实际值,不通过_newindex
协同程序
what? 协同程序coroutine 与线程比较相似
- 独立的堆栈、局部变量、独立的指针
- 共享全局变量和其他大部分的东西
协同程序与线程的区别
- 一个具有多线程的程序可以同时运行几个线程,而协同程序需要彼此协作才能运行
- 任一时刻只有一个协同程序在运行,协同程序只有在明确要求挂起时才会挂起
- 类似有锁的多线程
基本语法
-
coroutine.create()
- 创建coroutine,参数是一个函数,当和resume配合使用时,就要唤醒函数
-
coroutine.resume()
- 重启,和create配合使用
-
coroutine.yield()
- 挂起
-
coroutine.status()
-
查看状态
- dead
- suspended
- running
-
-
coroutine.wrap()
- 和create功能重复
-
corroutine.running()
- 返回正在跑的协同程序号
使用,函数传入参数等有点复杂,有时间再研究
文件I/O
简单模式
-
只能对一个文件进行文件相关操作
-
使用过程
- file = io.open(fileName [,mode]) --以指定mode方式打开文件,失败返回nil和错误信息
- io.close(file);
- io.input(file);
- io.output(file);
- io.write(“hello world”) --向文件写数据
- io.read() –
完全模式
-
file = io.open(fileName [, mode])
-
file:read()
-
file:close()
-
file:write(“hello world”)
-
file:seek(optional whence, optional offset)
- "set"从头开始
- “cur”从当前位置开始
- “end”从文件尾开始
- offset:默认为0
-
file:flush()想文件中写入缓存中的所有数据
-
io.lines(optional fileName)
错误处理
assert()
-
assert括号内包含的为true,不做任何事
-
括号内包含的为false,打印后面添加的错误信息,返回所在行
- assert(bool,“信息”)
error()
- 让程序停止,进行错误处理
- 返回error中的message作为错误信息,前面可能还会添加位置信息
pcall( )
-
效果: 程序错误也可以运行
-
接受一个函数类型的参数和要传递给这个函数的参数,并执行这个函数
-
异常返回false,否则返回true
-
使用例子
- if pcall(functionName, …) then
- –没有错误
- else
- –有错误
- end
xpcall( )
- 第二个参数是第一个函数发生错误时,调用的处理错误的函数
十进制十六进制转化
十六进制字符串==> 十进制数值
- tonumber(“0x1f”, 16)
tonumber(“1f”, 16)
result = 31
结果相同
十进制字符串==>十六进制字符串(有错误)
- string.fomat("%#02x",19); – ‘0x13’
带# — 0x - 不同进制的数值在内存里存储的二进制是相同的
- 数字之间的进制转换是没有意义的
十进制字符串==>数值
- tonumber(“23”) – 23
os库
os.date(format, temp)
-
将(时间戳)秒数(距离1970年)转换为可读的字符串形式,默认当前时间,
-
local t1 = os.date("%Y-%m-%d-%H-%M-%S")2019-12-10-11-28-25
-
format
- %a
星期简写,如Wed
%A
星期全称,如Wednesday
%b
月份简写,如Sep
%B
月份全称,如September
%c
日期和时间,如09/16/14 13:43:08
%d
一个月中的第几天,01-31
%H
24小时制中的小时数,00-23
%I
12小时制中的小时数,01-12
%j
一年中的第几天,001-366
%M
分钟数,00-59
%m
月份数,01-12
%p
上午(am)或下午(pm)
%S
秒数,00-59
%w
一星期中的第几天,0-6
%x
日期,如 09/16/14
%X
时间,如13:47:20
%y
两位数的年份,如14
%Y
完整的年份,如2014
%%
字符%
os.time()
- os.time()默认返回当前时间距离1970年一月一日零点的秒数
- os.time(tb) tb为一个表,其中year, month、day必须有,其他字段默认取12:00:00
判断表、字符串为空
a = {} if a == { } then
- 错误,在lua中,直接对表进行数字判断,其实是对table的内存地址
- 这个结果永远是true
- 正确:if next(tab) == nil then
str = “” if str ~= nil then
- 错误,在lua中,字符串不可以这样判断,这样结果永远为真
- 正确:#str ~= 0
- 正确:str:match("^%s") ~= nil
str = nil if str ~= nil then
- 这个是正确的
- str = nil 只能通过str == nil判断,不能使用# 、match判断,这样对nil操作会出错
控制浮点数精度
保留2位小数:string.format(“%.2f”, x)
会有误差
-
数字向下取整
- 使用%运算符,得到的结果是数字
x%1 表示x的小数部分,x-x%1 表示x的整数部分。
类似的,x-x%0.01 将x精确到小数点后2位。
- 使用%运算符,得到的结果是数字
-
数字四舍五入
-
在数字后面+0.05
- length = len + 0.0005 - (len + 0.0005) % 0.001, – 控制浮点数进度为3位小数,四舍五入
-
完整实现
-
四舍五入
- — nNum 源数字
-
n 小数位数
-
-四舍五入
function getPreciseDecimal(nNum, n)
if type(nNum) ~= “number” then
return nNum;
end
n = n or 0;
n = math.floor(n)
if n < 0 then
n = 0;
end
local nDecimal = 10 ^ n
local nTemp = math.floor(nNum * nDecimal + 0.5);
local nRet = nTemp / nDecimal;
return nRet;
end
位运算的实现
–bit 操作
bit={data32={}}
for i=1,32 do
bit.data32[i]=2^(32-i)
end
function bit:d2b(arg)
local tr={}
for i=1,32 do
if arg >= self.data32[i] then
tr[i]=1
arg=arg-self.data32[i]
else
tr[i]=0
end
end
return tr
end --bit:d2b
function bit:b2d(arg)
local nr=0
for i=1,32 do
if arg[i] ==1 then
nr=nr+2^(32-i)
end
end
return nr
end --bit:b2d
function bit:_or(a,b)
local op1=self:d2b(a)
local op2=self:d2b(b)
local r={}
for i=1,32 do
if op1[i]==1 or op2[i]==1 then
r[i]=1
else
r[i]=0
end
end
return self:b2d(r)
end
function bit:_xor(a,b)
local op1 = self:d2b(a)
local op2 = self:d2b(b)
local r={}
for i = 1,32 do
if op1[i] == 0 then
r[i] = op2[i]
else
if op2[i] == 0 then
r[i] = 1
else
r[i] = 0
end
end
end
return self:b2d(r)
end
function bit:_and(a,b)
local op1=self:d2b(a)
local op2=self:d2b(b)
local r={}
for i=1,32 do
if op1[i]==1 and op2[i]==1 then
r[i]=1
else
r[i]=0
end
end
return self:b2d(r)
end --bit:_and
function bit:_rshift(a,n)
local op1=self:d2b(a)
local r=self:d2b(0)
if n < 32 and n > 0 then
for i=1,n do
for i=31,1,-1 do
op1[i+1]=op1[i]
end
op1[1]=0
end
r=op1
end
return self:b2d(r)
end --bit:_rshift
function bit:_lshift(a,n)
local op1=self:d2b(a)
local r=self:d2b(0)
if n < 32 and n > 0 then
for i=1,n do
for i=1,31 do
op1[i]=op1[i+1]
end
op1[32]=0
end
r=op1
end
return self:b2d(r)
end --bit:_lshift
function bit:_not(a)
local op1=self:d2b(a)
local r={}
for i=1,32 do
if op1[i]==1 then
r[i]=0
else
r[i]=1
end
end
return self:b2d(r)
end --bit:_not
在表中直接添加key和value
使用tab.time = “haha” 也可以
tab[time] = “haha” 是错误的