天天看点

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

类型和变量

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 中字符串是不可以修改的,

你可以创建一个新的变量存放你要的字符串,如下:

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 就会被转成数字。

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。

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()将数字转成字符串,这种转换一直有效:

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

–例如:

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

如果想颠倒来

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()的参数

f = string.find  
a = {"hello", "ll"}  
print(f(unpack(a))) 
--> 3 4      

string.find 默认情况下返回两个值, 即查找到的子串的 起下标标和止下标

预定义的 unpack 函数是用 C 语言实现的,我们也可以用 Lua 来完成:

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 函数:

printResult = "" 
function print(...)  
for i,v in ipairs(arg) do 
 printResult = printResult .. tostring(v) .. "\t" 
end  
 printResult = printResult .. "\n" 
end      

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

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++的函数对象),排序函数接受两个排序元素作为输入参数,并且返回两者的大小关系,

例如:

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

table.sort(tmpQueue, function(a, b)
        if (a == nil or b == nil) then
            return (a.endTime < b.endTime) --此处千万不能用小于等于,不然顺序错乱
    end)      
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。

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 使其接受一个度数而不是弧度作为参数:

do  
local oldSin = math.sin  
local k = math.pi/180  
 math.sin = function (x)  
 
return oldSin(x*k)  
end  
end      

利用同样的特征我们可以创建一个安全的环境(也称作沙箱,和 java 里的沙箱一样),

当我们运行一段不信任的代码(比如我们运行网络服务器上获取的代码)时安全的环境

是需要的,比如我们可以使用闭包重定义 io 库的 open 函数来限制程序打开的文件。

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。为了解决这个问题我们必须在定义函数以前先声明:

local fact  
fact = function (n)  
if n == 0 then  
 return 1  
else  
 return n*fact(n-1)  
end  
end      

迭代器与泛型for

迭代器与闭包

迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素

举一个简单的例子,我们为一个 list 写一个简单的迭代器,与 ipairs()不同的是我们

实现的这个迭代器返回元素的值而不是索引下标:

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。

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:\windows\luasocket.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 , 无错误信息

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------

协同程序

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)