lua语言:类型,基本语法,函数

2023-08-24 15:12:08 浏览数 (2)

类型和变量

Booleans

两个取值 false 和 true。但要注意 Lua 中所有的值都可以作为条件。在控制结构的条 件中除了 false 和 nil 为假,其他值都为真。所以 Lua 认为 0 和空串都是真。

Numbers

表示实数,Lua 中没有整数。一般有个错误的看法 CPU 运算浮点数比整数慢。事实 不是如此,用实数代替整数不会有什么误差(除非数字大于 100,000,000,000,000)。Lua 的 numbers 可以处理任何长整数不用担心误差。你也可以在编译 Lua 的时候使用长整型 或者单精度浮点型代替 numbers,在一些平台硬件不支持浮点数的情况下这个特性是非 常有用的,具体的情况请参考 Lua 发布版所附的详细说明。和其他语言类似,数字常量 的小数部分和指数部分都是可选的,数字常量的例子: 4 0.4 4.57e-3 0.3e12 5e 20

Strings

指字符的序列。lua 是 8 位字节,所以字符串可以包含任何数值字符,包括嵌入的 0。 这意味着你可以存储任意的二进制数据在一个字符串里。Lua 中字符串是不可以修改的, 你可以创建一个新的变量存放你要的字符串,如下:

代码语言:javascript复制
a = "one string" 
b = string.gsub(a, "one", "another") -- change string parts  
print(a) 
--> one string  
print(b) 
--> another string  

string 和其他对象一样,Lua 自动进行内存分配和释放,一个 string 可以只包含一个 字母也可以包含一本书,Lua 可以高效的处理长字符串,1M 的 string 在 Lua 中是很常见 的。可以使用单引号或者双引号表示字符串 a = “a line” b = ‘another line’ 运行时,Lua 会自动在 string 和 numbers 之间自动进行类型转换,当一个字符串使 用算术操作符时,string 就会被转成数字。

代码语言:javascript复制
print("10"   1) 
--> 11  
print("10   1") 
--> 10   1  
print("-5.3e - 10" * "2") 
--> -1.06e-09  
print("hello"   1) 
-- ERROR (cannot convert "hello")  

反过来,当 Lua 期望一个 string 而碰到数字时,会将数字转成 string。 print(10 … 20) --> 1020 …在 Lua 中是字符串连接符,当在一个数字后面写…时,必须加上空格以防止被解释 错 尽管字符串和数字可以自动转换,但两者是不同的,像 10 == "10"这样的比较永远 都是错的。如果需要显式将 string 转成数字可以使用函数 tonumber(),如果 string 不是正 确的数字该函数将返回 nil。

代码语言:javascript复制
line = io.read() 
-- read a line  
n = tonumber(line) 
-- try to convert it to a number  
if n == nil then 
 error(line .. " is not a valid number")  
else  
 print(n*2)  
end  

反之,可以调用 tostring()将数字转成字符串,这种转换一直有效:

代码语言:javascript复制
print(tostring(10) == "10") 
--> true  
print(10 .. "" == "10") 
--> true

想要输出正确数字后面要接空格,再接…

表达式

关系运算

< > <= >= == ~= 不等~=

逻辑运算符

and or not 逻辑运算符认为 false 和 nil 是假(false),其他为真,0 也是 true. and 和 or 的运算结果不是 true 和 false,而是和它的两个操作数相关。 a and b – 如果 a 为 false,则返回 a,否则返回 b a or b – 如果 a 为 true,则返回 a,否则返回 b –例如:

代码语言:javascript复制
print(4 and 5) 
--> 5  
print(nil and 13) 
--> nil  
print(false and 13) 
--> false  
print(4 or 5) 
--> 4  
print(false or 5) 
--> 5  

一个很实用的技巧:如果 x 为 false 或者 nil 则给 x 赋初始值 v x = x or v

C 语言中的三元运算符

a ? b : c 在 Lua 中可以这样实现: (a and b) or c

基本语法

赋值语句

遇到赋值语句 Lua 会先计算右边所有的值然后再执行赋值操作,所以我们可以这样 进行交换变量的值: x, y = y, x – swap ‘x’ for ‘y’ a[i], a[j] = a[j], a[i] – swap ‘a[i]’ for ‘a[i]’ 当变量个数和值的个数不一致时,Lua 会一直以变量个数为基础采取以下策略: a. 变量个数 > 值的个数 按变量个数补足 nil b. 变量个数 < 值的个数 多余的值会被忽略

控制结构语句

控制结构的条件表达式结果可以是任何值,Lua 认为 false 和 nil 为假,其他值为真。 if 语句,有三种形式: if conditions then then-part end; if conditions then then-part else else-part end; if conditions then then-part elseif conditions then elseif-part … —>多个 elseif else else-part end; while 语句: while condition do statements; end; repeat-until 语句 repeat statements; until conditions; for 语句有两大类: 第一,数值 for 循环: for var=exp1,exp2,exp3 do loop-part end for 将用 exp3 作为 step 从 exp1(初始值)到 exp2(终止值),执行 loop-part。其中 exp3 可以省略,默认 step=1 如果想颠倒来

代码语言:javascript复制
for i=10,1,-1 do 
 print(i)  
end 
-- print all values of array 'a'  
for i,v in ipairs(a) do print(v) end 

范型 for 遍历迭代子函数返回的每一个值。 再看一个遍历表 key 的例子: – print all keys of table ‘t’ for k in pairs(t) do print(k) end

函数

多返回值

函数多值返回的特殊函数 unpack,接受一个数组作为输入参数,返回数组的所有元 素。unpack 被用来实现范型调用机制,在 C 语言中可以使用函数指针调用可变的函数, 可以声明参数可变的函数,但不能两者同时可变。在 Lua 中如果你想调用可变参数的可 变函数只需要这样: f(unpack(a)) unpack 返回 a 所有的元素作为 f()的参数

代码语言:javascript复制
f = string.find  
a = {"hello", "ll"}  
print(f(unpack(a))) 
--> 3 4  

string.find 默认情况下返回两个值, 即查找到的子串的 起下标标和止下标 预定义的 unpack 函数是用 C 语言实现的,我们也可以用 Lua 来完成:

代码语言:javascript复制
function unpack(t, i)  
 i = i or 1  
if t[i] then 
 
return t[i], unpack(t, i   1)  
end  
end 

相当于把表的每一个item都作为参数依次传入

可变参数

Lua 函数可以接受可变数目的参数,和 C 语言类似在函数参数列表中使用三点(…) 表示函数有可变的参数。Lua 将函数的参数放在一个叫 arg 的表中,除了参数以外,arg 表中还有一个域 n 表示参数的个数。 例如,我们可以重写 print 函数:

代码语言:javascript复制
printResult = "" 
function print(...)  
for i,v in ipairs(arg) do 
 printResult = printResult .. tostring(v) .. "t" 
end  
 printResult = printResult .. "n" 
end 

如果是传入…,在函数内使用arg表

代码语言:javascript复制
function g (a, b, ...) end 

g(3) a=3, b=nil, arg={n=0}  
g(3, 4) a=3, b=4, arg={n=0}  
g(3, 4, 5, 8) a=3, b=4, arg={5, 8; n=2} 

域 n 表示参数的个数 举个具体的例子,如果我们只想要 string.find 返回的第二个值。一个典型的方法是 使用哑元(dummy variable,下划线): local _, x = string.find(s, p) – now use `x’

再论函数

a = {p = print} a.p(“Hello World”) –> Hello World print = math.sin – print' now refers to the sine function a.p(print(1)) --> 0.841470 sin = a.p --sin’ now refers to the print function sin(10, 20) –> 10 20

table.sort

内部是快速排序法实现 table 标准库提供一个排序函数,接受一个表作为输入参数并且排序表中的元素。这 个函数必须能够对不同类型的值(字符串或者数值)按升序或者降序进行排序。Lua 不 是尽可能多地提供参数来满足这些情况的需要,而是接受一个排序函数作为参数(类似 C 的函数对象),排序函数接受两个排序元素作为输入参数,并且返回两者的大小关系, 例如:

代码语言:javascript复制
network = {
 {name = "grauna", IP = "210.26.30.34"},
 {name = "arraial", IP = "210.26.30.23"},
 {name = "lua", IP = "210.26.23.12"},
 {name = "derain", IP = "210.26.23.20"},
}

for i,v in ipairs(network) do print(v.name) end

table.sort(network, function (a,b)
return (a.name > b.name)
end)

for i,v in ipairs(network) do print(v.name) end

输出 grauna arraial lua derain

lua grauna derain arraial

  1. table中不能有nil table.sort是排序函数,它要求要排序的目标table的必须是从1到n连续的,即中间不能有nil。
  2. 重写的比较函数,两个值相等时不能return true 此外,当比较函数没有写的时候,table.sort默认按照lua里面的排序规则升序排序; 当额外写了比较函数时,相当于用你额外写的比较函数重载了lua中自带的“<”操作符。 这就有一个特别要注意的问题,当两个数相等的时候,比较函数一定要返回false! 如果两个值相等都, 排序函数返回true时则会报错 invalid order function for sorting
代码语言:javascript复制
table.sort(tmpQueue, function(a, b)
        if (a == nil or b == nil) then
            return (a.endTime < b.endTime) --此处千万不能用小于等于,不然顺序错乱
    end)
代码语言:javascript复制
network = {
 {name = "grauna", IP = "210.26.30.34"},
 {name = "arraial", IP = "210.26.30.23"},
 {name = "lua", IP = "210.26.23.12"},
 {name = "grauna", IP = "210.26.23.20"},
}

for i,v in ipairs(network) do print(v.name) end

table.sort(network, function (a,b)
return (a.name >= b.name)
end)

for i,v in ipairs(network) do print(v.name) end

输出 grauna arraial lua grauna

lua grauna arraial grauna

闭包

函数内部有函数 在匿名函数内部 grades 不是全局变量也不是局部变量,我们称作外部的局部变 量(external local variable)或者 upvalue。

代码语言:javascript复制
function newCounter()
local i = 0
return function() -- anonymous function
 i = i   1
 return i
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2


c2 = newCounter()
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2

匿名函数使用 upvalue i 保存他的计数,当我们调用匿名函数的时候 i 已经超出了作 用范围,因为创建 i 的函数 newCounter 已经返回了。然而 Lua 用闭包的思想正确处理了 这种情况。简单的说,闭包是一个函数以及它的 upvalues。如果我们再次调用 newCounter, 将创建一个新的局部变量 i,因此我们得到了一个作用在新的变量 i 上的新闭包。 闭包在完全不同的上下文中也是很有用途的。因为函数被存储在普通的变量内我们 可以很方便的重定义或者预定义函数。通常当你需要原始函数有一个新的实现时可以重 定义函数。例如你可以重定义 sin 使其接受一个度数而不是弧度作为参数:

代码语言:javascript复制
do  
local oldSin = math.sin  
local k = math.pi/180  
 math.sin = function (x)  
 
return oldSin(x*k)  
end  
end 

利用同样的特征我们可以创建一个安全的环境(也称作沙箱,和 java 里的沙箱一样), 当我们运行一段不信任的代码(比如我们运行网络服务器上获取的代码)时安全的环境 是需要的,比如我们可以使用闭包重定义 io 库的 open 函数来限制程序打开的文件。

代码语言:javascript复制
do  
local oldOpen = io.open  
 io.open = function (filename, mode)  
 
if access_OK(filename, mode) then 
 
return oldOpen(filename, mode)  
 
else  
 
return nil, "access denied" 
 
end  
end  
end 

非全局函数

递归函数先声明 上面这种方式导致 Lua 编译时遇到 fact(n-1)并不知道他是局部函数 fact,Lua 会去查 找是否有这样的全局函数 fact。为了解决这个问题我们必须在定义函数以前先声明:

代码语言:javascript复制
local fact  
fact = function (n)  
if n == 0 then  
 return 1  
else  
 return n*fact(n-1)  
end  
end 

迭代器与泛型for

迭代器与闭包

迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素 举一个简单的例子,我们为一个 list 写一个简单的迭代器,与 ipairs()不同的是我们 实现的这个迭代器返回元素的值而不是索引下标:

代码语言:javascript复制
function list_iter (t)  
local i = 0  
local n = table.getn(t)  
return function ()  
 i = i   1  
 
if i <= n then return t[i] end 
end  
end 

t = {10, 20, 30}  
for element in list_iter(t) do 
 print(element)  
end 

可以自己写些遍历的条件,例如取table里满足条件的item

范性for

ipairs与pairs区别

ipairs 仅仅遍历值,按照索引升序遍历,索引中断停止遍历。即不能返回 nil,只能返回数字 0,如果遇到 nil 则退出。它只能遍历到集合中出现的第一个不是整数的 key。必须是连续的,从1开始,只要中间为nil,即断开 pairs 能遍历集合的所有元素。即 pairs 可以遍历集合中所有的 key,并且除了迭代器本身以及遍历表本身还可以返回 nil。

代码语言:javascript复制
local tab= {  [1] = "a",  [3] = "b",  [4] = "c"  }  for i,v in pairs(tab) do        -- 输出 "a" ,"b", "c"  ,     
print( tab[i] )  end   for i,v in ipairs(tab) do    -- 输出 "a" ,k=2时断开      print( tab[i] )  end

编译,运行,错误信息

assert断言

local f = assert(loadstring("return " … l))

require 函数

Lua 提供高级的 require 函数来加载运行库。粗略的说 require 和 dofile 完成同样的功 能但有两点不同:

  1. require 会搜索目录加载文件
  2. require 会判断是否文件已经加载避免重复加载同一文件。由于上述特征,require 在 Lua 中是加载库的更好的函数。 require 的另一个功能是避免重复加载同一个文件两次。Lua 保留一张所有已经加载 的文件的列表(使用 table 保存)。如果一个加载的文件在表中存在 require 简单的返回; 表中保留加载的文件的虚名,而不是实文件名。所以如果你使用不同的虚文件名 require 同一个文件两次,将会加载两次该文件。比如 require "foo"和 require “foo.lua”,路径为 "?;?.lua"将会加载 foo.lua 两次。

C Package

local path = “/usr/local/lua/lib/libluasocket.so” – or path = “C:windowsluasocket.dll” local f = assert(loadlib(path, “luaopen_socket”)) f() – actually open the library

异常与错误处理

lua实现try catch

当我们的Lua程序遇到有需要保护的代码或者方法时(即使程序异常,也只是抛出异常信息,而不是让程序崩溃),Lua为我们提供了两种解决的办法,这两种方法可以让我们捕获异常,因此封装自己的tryCatch函数。 1.pcall调用 2.xpcall调用 相同点: 当程序正常时,返回true,被执行函数的返回值 不同点: 1.参数不同 pcall(fun) ,参数只有一个被调用函数 xpcall(fun,errHandleFun),参数是被调用函数,错误函数处理 2.执行结果 pcall:返回错误信息时,已经释放了保存错误发生情况的栈信息。 xpcall:会在栈信息释放之前调用错误处理程序(可以使用debug库收集错误信息) 3.返回结果 pcall 返回 nil , 错误信息 xpcall返回nil , 无错误信息

代码语言:javascript复制
local fun=function ( b)
  local a=1;
  print(a b);
  return a b;
end

tryCatch=function(fun)
  local ret,errMessage=pcall(fun);
  print("ret:" .. (ret and "true" or "false" )  .. " nerrMessage:" .. (errMessage or "null"));
end

xTryCatchGetErrorInfo=function()
  print(debug.traceback());
end
xTryCatch=function(fun)
  local ret,errMessage=xpcall(fun,xTryCatchGetErrorInfo);
  print("ret:" .. (ret and "true" or "false" )  .. " nerrMessage:" .. (errMessage or "null"));
end

print("n------A------n")
tryCatch(fun("1"));

print("n------B------n")

xTryCatch(fun("1"));
print("n------C------n")

输出 ------A------

2 ret:false errMessage:attempt to call a number value

------B------

2 stack traceback: PInLua.lua:13: in function PInLua.lua:12 [C]: in function ‘xpcall’ PInLua.lua:16: in function ‘xTryCatch’ PInLua.lua:25: in main chunk [C]: ? ret:false errMessage:null

------C------

协同程序

Lua中的协程和unity协程的区别,最大的就是其不是抢占式的执行,也就是说不会被主动执行类似MoveNext这样的操作,而是需要我们去主动激发执行,就像上一个例子一样,自己去tick这样的操作。 Lua中协程关键的三个API: coroutine.create()/wrap: 构建一个协程, wrap构建结果为函数,create为thread类型对象 coroutine.resume(): 执行一次类似MoveNext的操作 coroutine.yield(): 将协程挂起 比较简易,可以写也给例子测试一下:

代码语言:javascript复制
local func = function(a, b)
    for i= 1, 3 do
        print(i, a, b)
    end
end

local func1 = function(a, b)
    for i = 1, 3 do
        print(i, a, b)
        coroutine.yield()
    end
end


co =  coroutine.create(func)
coroutine.resume(co, 1, 2)
--此时会输出 1 ,1, 2/ 2,1,2/ 3, 1,2

co1 = coroutine.create(func1)
coroutine.resume(co1, 1, 2)
--此时会输出 1, 1,2 然后挂起
coroutine.resume(co1, 3, 4)
--此时将上次挂起的协程恢复执行一次,输出: 2, 1, 2 所以新传入的参数3,4是无效的
coroutine.resume(co1, 3, 4)
coroutine.resume(co1, 3, 4)
coroutine.resume(co1, 3, 4)
coroutine.resume(co1, 3, 4)

输出 1 1 2 2 1 2 3 1 2 1 1 2 2 1 2 3 1 2

0 人点赞