//Lua正则表达式如何匹配中文///
function CheckChinese(s)
local ret = {};
local f = '[%z\1-\127\194-\244][\128-\191]*';
local line, lastLine, isBreak = '', false, false;
for v in s:gfind(f) do
table.insert(ret, {c=v,isChinese=(#v~=1)});
end
return ret;
end
for k, v in ipairs(CheckChinese('a中文b+')) do
print(k,v.c,#v.c,v.isChinese);
end
-------------------
1 a 1 false
2 中 3 true
3 文 3 true
4 b 1 false
5 + 1 false
匹配中英文混合输入的代码
function dlg:onBtnOKClick()
local input = self:getInputNewContent()
if input == "" then
flytip("输入不能为空")
return
end
if self.pattern then
if self.isContainChinese then
local charCount = 0
for i, v in ipairs(self:CheckChinese(input)) do
if v.isChinese then
charCount = charCount + 2
else
charCount = charCount + 1
if string.find(v.c, self.pattern) == nil then
flytip("抱歉,您输入的内容含有非法字符或屏蔽词,请重新输入。")
return
end
end
end
if self.characterLimit then
if charCount > self.characterLimit then
flytip("抱歉,您输入的字符长度有误,请重新输入。")
return
end
end
else
if string.find(input, self.pattern) == nil then
flytip("抱歉,您输入的内容含有非法字符或屏蔽词,请重新输入。")
return
end
end
end
self:Hide()
if self._onOK then
self._onOK()
end
end
//Lua正则表达式如何匹配中文///
//Lua的时间和时间戳相互转换///
1、时间戳转换成时间
local t = 1412753621000
function getTimeStamp(t)
return os.date("%Y%m%d%H",t/1000)
end
print(getTimeStamp(t))
2、得时间戳
os.time() -- 当前时间戳
os.time({day=17, month=5, year=2012, hour=0, minute=0, second=0}) -- 指定时间的时间戳
3、时间格式 yyyyMMddHHmmss
print(os.date("%Y%m%d%H%M%S", os.time()))
print(os.time()) --当前系统时间值 1456368102
print(os.date("%Y%m%d",os.time())) --当前系统时间的格式化字符串 20160225
print(os.date("*t"), os.time()) --当前系统时间表
table完整版本:
{year=2005, month=11, day=6, hour=22,min=18,sec=30,isdst=false}
分别是:年 月 日 小时 分钟 秒 是否夏令时
function string2time( timeString )
local Y = string.sub(timeString , 1, 4)
local M = string.sub(timeString , 5, 6)
local D = string.sub(timeString , 7, 8)
return os.time({year=Y, month=M, day=D, hour=0,min=0,sec=0})
end
local sTime = "20160202"
local timeVal = string2time(sTime)
print(sTime)
print(timeVal)
输出结果:
20160202
1454371200
思路:
字符串转换成table,table转换到时间值
Lua获取系统时间和时间格式化方法及格式化参数
一、系统当前时间对应的时间戳
复制代码代码如下:
local ntime = os.time
print(ntime)
二、格式化时间显示,参考下表
常用于设置header等
复制代码代码如下:
ngx.header["Last-Modified:"] = os.date("%a, %d %b %Y %X GMT")
ngx.header["Content-Type"] = "text/html; charset=utf-8"
ngx.header["Cache-Control"] = "public"
ngx.header["Expires"] = os.date("%a, %d %b %Y %X GMT", os.time() + cdnCacheTime)
%a |
abbreviated weekday name (e.g., Wed) |
%A |
full weekday name (e.g., Wednesday) |
%b |
abbreviated month name (e.g., Sep) |
%B |
full month name (e.g., September) |
%c |
date and time (e.g., 09/16/98 23:48:10) |
%d |
day of the month (16) [01-31] |
%H |
hour, using a 24-hour clock (23) [00-23] |
%I |
hour, using a 12-hour clock (11) [01-12] |
%M |
minute (48) [00-59] |
%m |
month (09) [01-12] |
%p |
either "am" or "pm" (pm) |
%S |
second (10) [00-61] |
%w |
weekday (3) [0-6 = Sunday-Saturday] |
%x |
date (e.g., 09/16/98) |
%X |
time (e.g., 23:48:10) |
%Y |
full year (1998) |
%y |
two-digit year (98) [00-99] |
%% |
the character '%'
|
// Lua的时间和时间戳相互转换///
//Lua的math函数库及一些自定义扩展///
math函数
name describe e.g result
abs 取绝对值 math.abs(-2015) 2015
ceil 向上取整 math.ceil(20.15) 21
floor 向下取整 math.floor(20.15) 20
max 取最大值 math.max(20, 15) 2 0
min 取最小值 math.min(20, 15) 15
pi 圆周率 math.pi 3.14…
pow 计算x的y次幂 math.pow (2, 15) 32768
sqrt 开平方 math.sqrt(1024) 32
fmod 取模 math.fmod(20, 15) 5
modf 取整数,小数部分 math.modf(20.15) 20 15
randomseed 设随机数种子 math.randomseed(os.time())
random 取随机数 math.random(2, 15) 2~15
rad 角度转弧度 math.rad(180) math.pi
deg 弧度转角度 math.deg(math.pi) 180
exp e的x次方 math.exp(4) e ^ 4
log x的自然对数 math.log(e ^ 4) 4
log10 10为底,x的对数 math.log10(1000) 3
frexp 格式化x * (2 ^ y) math.frexp(160) 0.625 8
ldexp 计算 x * (2 ^ y) math.ldexp(0.625,8) 160
sin 正弦 math.sin(math.rad(30)) 0.5
cos 余弦 math.cos(math.rad(60)) 0.5
tan 正切 math.tan(math.rad(45)) 1
asin 反正弦 math.deg(math.asin(0.5)) 30
acos 反余弦 math.deg(math.acos(0.5)) 60
atan 反正切 math.deg(math.atan(1)) 45
math.huge 最大数
math.randomseed(os.time())
i=math.random(1,6)
自定义扩展
-- 最小数值和最大数值指定返回值的范围。
-- @function [parent=#math] clamp
function math.clamp(v, minValue, maxValue)
if v < minValue then
return minValue
end
if( v > maxValue) then
return maxValue
end
return v
end
-- 根据系统时间初始化随机数种子,让后续的 math.random() 返回更随机的值
-- @function [parent=#math] newrandomseed
function math.newrandomseed()
local ok, socket = pcall(function()
return require("socket")
end)
math.randomseed(os.time())
math.random()
math.random()
math.random()
math.random()
end
-- 对数值进行四舍五入,如果不是数值则返回 0
-- @function [parent=#math] round
-- @param number value 输入值
-- @return number#number
function math.round(value)
value = tonumber(value) or 0
return math.floor(value + 0.5)
end
-- 角度转弧度
-- @function [parent=#math] angle2radian
function math.angle2radian(angle)
return angle*math.pi/180
end
-- 弧度转角度
-- @function [parent=#math] radian2angle
function math.radian2angle(radian)
return radian/math.pi*180
end
// Lua的math函数库及一些自定义扩展///
//Lua实现计算utf8字符串的长度///
function string.utf8len(input)
local len = string.len(input)
local left = len
local cnt = 0
local arr = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}
while left ~= 0 do
local tmp = string.byte(input, -left)
local i = #arr
while arr[i] do
if tmp >= arr[i] then
left = left - i
break
end
i = i - 1
end
cnt = cnt + 1
end
return cnt
end
//Lua实现计算utf8字符串的长度///
//获取table中最大值///
function table_maxn(t)
local mn=nil;
for k, v in pairs(t) do
if(mn==nil) then
mn=v
end
if mn < v then
mn = v
end
end
return mn
end
tbl = {[1] = 2, [2] = 6, [3] = 34, [26] =5}
print("tbl 最大值:", table_maxn(tbl))
print("tbl 长度 ", #tbl)
//获取table中最大值///
//Lua封装位运算///
1.移位运算基础
--与 同为1,则为1
--或 有一个为1,则为1
--非 true为 false,其余为true
--异或 相同为0,不同为1
--ZZMathBit = {}
function ZZMathBit.__andBit(left,right) --与
return (left == 1 and right == 1) and 1 or 0
end
function ZZMathBit.__orBit(left, right) --或
return (left == 1 or right == 1) and 1 or 0
end
function ZZMathBit.__xorBit(left, right) --异或
return (left + right) == 1 and 1 or 0
end
function ZZMathBit.__base(left, right, op) --对每一位进行op运算,然后将值返回
if left < right then
left, right = right, left
end
local res = 0
local shift = 1
while left ~= 0 do
local ra = left % 2 --取得每一位(最右边)
local rb = right % 2
res = shift * op(ra,rb) + res
shift = shift * 2
left = math.modf( left / 2) --右移
right = math.modf( right / 2)
end
return res
end
function ZZMathBit.andOp(left, right)
return ZZMathBit.__base(left, right, ZZMathBit.__andBit)
end
function ZZMathBit.xorOp(left, right)
return ZZMathBit.__base(left, right, ZZMathBit.__xorBit)
end
function ZZMathBit.orOp(left, right)
return ZZMathBit.__base(left, right, ZZMathBit.__orBit)
end
function ZZMathBit.notOp(left)
return left > 0 and -(left + 1) or -left - 1
end
function ZZMathBit.lShiftOp(left, num) --left左移num位
return left * (2 ^ num)
end
function ZZMathBit.rShiftOp(left,num) --right右移num位
return math.floor(left / (2 ^ num))
end
function ZZMathBit.test()
print( ZZMathBit.andOp(65,0x3f)) --65 1000001 63 111111
print(65 % 64)
print( ZZMathBit.orOp(5678,6789))
print( ZZMathBit.xorOp(13579,2468))
print( ZZMathBit.rShiftOp(16,3))
print( ZZMathBit.notOp(-4))
print(string.byte("abc",1))
end
cclog("aaaaaaa:")
ZZMathBit.test()
[LUA-print] aaaaaaa:
[LUA-print] 1
[LUA-print] 1
[LUA-print] 7855
[LUA-print] 15535
[LUA-print] 2
[LUA-print] 3
[LUA-print] 97
2.红点
function GlobalService:hasRedpointEmail( )
return ZZMathBit.andOp( ServerData.redPointStatus, dyt.RedPointStatus.Email ) ~= 0
end
-- 红点状态
dyt.RedPointStatus = {
Email = ZZMathBit.lShiftOp( 1, 0 ), --邮件
DailyTask = ZZMathBit.lShiftOp( 1, 1), -- 每日任务
CommonTask = ZZMathBit.lShiftOp( 1, 2 ), --任务
Draw = ZZMathBit.lShiftOp( 1, 3 ), --抽卡
Sign = ZZMathBit.lShiftOp( 1, 4 ), --签到
Activity = ZZMathBit.lShiftOp( 1, 5 ), --7日活动
Legion = ZZMathBit.lShiftOp( 1, 6 ), --军团
Escort = ZZMathBit.lShiftOp( 1, 7 ), --护航
Collect = ZZMathBit.lShiftOp( 1, 9 ), --委派
Union = ZZMathBit.lShiftOp( 1 , 10 ), --联动
SingleRecharge = ZZMathBit.lShiftOp( 1 , 11 ), --限时活动单笔充值
TotalRecharge = ZZMathBit.lShiftOp( 1 , 12 ), --限时活动累计充值
TotalCost = ZZMathBit.lShiftOp( 1 , 13 ), --限时活动累计消费
OilCost = ZZMathBit.lShiftOp( 1 , 14 ), --限时活动燃油消耗
TotalDraw = ZZMathBit.lShiftOp( 1 , 15 ), --限时活动金币抽卡
Qming = ZZMathBit.lShiftOp( 1 , 16 ), --限时活动清明好礼
BaoXiang = ZZMathBit.lShiftOp( 1 , 19 ), --限时活动金币抽卡宝箱
LevelFast = ZZMathBit.lShiftOp( 1 , 20 ), --快速补给活动
LevelFastRed = ZZMathBit.lShiftOp( 1 , 21 ), --快速补给活动红点
}
因此都是先左移然后表示一个唯一状态。
红点状态:利用与运算 同为1,则为1,用一个32位整数来表示活动。
//Lua封装位运算///
//Lua用Split函数分割字符串///
function Split(szFullString, szSeparator)
local nFindStartIndex = 1
local nSplitIndex = 1
local nSplitArray = {}
while true do
local nFindLastIndex = string.find(szFullString, szSeparator, nFindStartIndex)
if not nFindLastIndex then
nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, string.len(szFullString))
break
end
nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, nFindLastIndex - 1)
nFindStartIndex = nFindLastIndex + string.len(szSeparator)
nSplitIndex = nSplitIndex + 1
end
return nSplitArray
end
用法:
local list = Split("abc,123,345", ",")
然后list里面就是
abc
123
345
第二个参数可以是多个字符,但是不能是Lua正则表达式。例如. ,或者 %w 之类的。
//Lua用Split函数分割字符串///
//Lua中number转换各种进制以及string转number///
--region : NumConvert.lua
--Date : 2017-5-11
--Author : david
-- Bin 2
-- Oct 8
-- Dec 10
-- Hex 16
local _convertTable = {
[0] = "0",
[1] = "1",
[2] = "2",
[3] = "3",
[4] = "4",
[5] = "5",
[6] = "6",
[7] = "7",
[8] = "8",
[9] = "9",
[10] = "A",
[11] = "B",
[12] = "C",
[13] = "D",
[14] = "E",
[15] = "F",
[16] = "G",
}
local function GetNumFromChar(char)
for k, v in pairs(_convertTable) do
if v == char then
return k
end
end
return 0
end
local function Convert(dec, x)
local function fn(num, t)
if(num < x) then
table.insert(t, num)
else
fn( math.floor(num/x), t)
table.insert(t, num%x)
end
end
local x_t = {}
fn(dec, x_t, x)
return x_t
end
function ConvertDec2X(dec, x)
local x_t = Convert(dec, x)
local text = ""
for k, v in ipairs(x_t) do
text = text.._convertTable[v]
end
return text
end
function ConvertStr2Dec(text, x)
local x_t = {}
local len = string.len(text)
local index = len
while ( index > 0) do
local char = string.sub(text, index, index)
x_t[#x_t + 1] = GetNumFromChar(char)
index = index - 1
end
local num = 0
for k, v in ipairs(x_t) do
num = num + v * math.pow(x, k - 1)
end
return num
end
--endregion
/Lua中number转换各种进制以及string转number /
--[[
@desc: 计算字符串字符个数
author:{author}
time:2017-12-29 16:08:11
--@inputstr: 源字符串
return 字符个数
]]
function getStringCharCount(str)
local lenInByte = #str
local charCount = 0
local i = 1
while (i <= lenInByte)
do
local curByte = string.byte(str, i)
local byteCount = 1;
if curByte > 0 and curByte <= 127 then
byteCount = 1 --1字节字符
elseif curByte >= 192 and curByte < 223 then
byteCount = 2 --双字节字符
elseif curByte >= 224 and curByte < 239 then
byteCount = 3 --汉字
elseif curByte >= 240 and curByte <= 247 then
byteCount = 4 --4字节字符
end
local char = string.sub(str, i, i + byteCount - 1)
i = i + byteCount -- 重置下一字节的索引
charCount = charCount + 1 -- 字符的个数(长度)
end
return charCount
end
///
// LUA解析json /
需要修改的json数据gui-config.json
{
"configs": [{
"server": "JP3.ISS.TF",
"server_port": 443,
"password": "58603228",
"method": "aes-256-cfb",
"remarks": ""
},
{
"server": "US1.ISS.TF",
"server_port": 443,
"password": "37382928",
"method": "aes-256-cfb",
"remarks": ""
},
{
"server": "HK2.ISS.TF",
"server_port": 8989,
"password": "59434206",
"method": "aes-256-cfb",
"remarks": ""
}],
"strategy": null,
"index": 0,
"global": false,
"enabled": true,
"shareOverLan": false,
"isDefault": false,
"localPort": 1080,
"pacUrl": null,
"useOnlinePac": false,
"availabilityStatistics": false
}
LUA解析代码:
function FileRead()
local file = io.open("gui-config.json", "r");
local json = file:read("*a");
file:close();
return json;
end
function FileWrite()
local file = io.open("gui-config.json", "w");
file:close();
end
local cjson = require("cjson");
local file = FileRead();
local json = cjson.decode(file);
for i, w in ipairs(json.configs) do
print("server: " .. w.password)
print("server_port: " .. w.server_port)
print("password: " .. w.password)
print("method: " .. w.method .. '\n')
end
输出:
// LUA解析json /
Lua 闭包
相信,对于经常使用Javascript的前端开发者来说,闭包这个概念一定不会陌生,在Javascript开发中,一些高级的应用都需要闭包来实现。而对于传统的C++开发者或者C#开发者来说,闭包这个词或多或少都会有些玄之又玄的感觉。那么,在开讲之前,让我们先来了解几个Lua中基础知识和概念,这样有助于我们理解Lua闭包。
词法定界:当一个函数内嵌套另一个函数的时候,内函数可以访问外部函数的局部变量,这种特征叫做词法定界。如下面这段代码,func2作为func1的内嵌函数,可以自由地访问属于func1的局部变量i :
function func1()
local i = 100 --upvalue
local func2 = function()
print(i+1)
end
i = 101
return func2
end
local f = func1()
print(f()) --输出102
第一类值:在Lua中,函数是一个值,它可以存在于变量中、可以作为函数参数,也可以作为返回值return。还是以上面的代码举例,我们将一个内嵌在func1中的函数赋值给局部变量func2,并将func2这个变量在函数结尾return。
upvalue:内嵌函数可以访问外部函数已经创建的局部变量,而这些局部变量则称为该内嵌函数的外部局部变量(即upvalue)。在我们的第一个例子中,func1的局部变量i就是内嵌函数func2的upvalue。
好了有了以上的概念以后,我们也该引入Lua中闭包的概念了。闭包是由函数和与其相关的引用环境组合而成的实体,闭包=函数+引用环境。
在第一个例子中,func1函数返回了一个函数,而这个返回的函数就是闭包的组成部分中的函数;引用环境就是变量i所在的环境。实际上,闭包只是在形式和表现上像函数,但实际上不是函数。我们都知道,函数就是一些可执行语句的组合体,这些代码语句在函数被定义后就确定了,并不会再执行时发生变化,所以函数只有一个实例。而闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例,就好比相同的类代码,可以创建不同的类实例一样。
用一句比较通俗和不甚严谨的话来讲:子函数可以使用父函数中的局部变量,这种行为就叫做闭包。这种说法其实就说明了闭包的一种表象,让我们从外在形式上,能更好的理解什么是闭包。
对于学习C++或者是C#之类的语言入门的朋友,可能对闭包理解起来比较吃力(至少马三是这样,一会明白一会糊涂,看了很多文章、写了很多代码以后才理解,笨得要命~ o(≧口≦)o)。其实我们可以把Lua中的闭包和C++中的类做一下类比。闭包是数据和行为的结合体,这就好比C++中的类,有一些成员变量(Lua中的upvalue)+成员方法(Lua中的内嵌函数)。这样就使得闭包具有较好的抽象能力,在某些场合下,我们需要记住某次调用函数完成以后数据的状态,就好比C++中的static类型的变量,每次调用完成以后,static类型的变量并不会被清除。使用闭包就可以很好的完成该功能,比如利用Lua闭包特性实现一个简单地迭代器,在下面的小节中我们会介绍到。
1.闭包的数据隔离
function counter()
local i = 0
return function() --匿名函数,闭包
i = i + 1
return i
end
end
counter1 = counter()
counter2 = counter()
-- counter1,counter2 是建立在同一个函数,同一个局部变量的不同实例上面的两个不同的闭包
-- 闭包中的upvalue各自独立,调用一次counter()就会产生一个新的闭包
print(counter1()) -- 输出1
print(counter1()) -- 输出2
print(counter2()) -- 输出1
print(counter2()) -- 输出2
上面的代码中,注释已经解释地很详细了。尽管看起来counter1,counter2是由同一个函数和同一个局部变量创建的闭包。但是其实它们是不同实例上面的两个不同的闭包。闭包中的upvalue各自独立,调用一次counter()就会产生一个新的闭包。有点像工厂函数一样,每调用一次counter()都会new出来一个新的对象,不同的对象之间的数据,当然也就是隔离的了。
2.闭包的数据共享
function shareVar(n)
local function func1()
print(n)
end
local function func2()
n = n + 10
print(n)
end
return func1,func2
end
local f1,f2 = shareVar(1024) --创建闭包,f1,f2两个闭包共享同一份upvalue
f1() -- 输出1024
f2() -- 输出1034
f1() -- 输出1034
f2() -- 输出1044
乍一看起来,这个概念和第一个概念矛盾啊,其实他们之间并不矛盾。在Lua中,同一闭包创建的其他的闭包共享一份upvalue。闭包在创建之时其需要的变量就已经不在堆栈上,而是引用更外层外部函数的局部变量(即upvalue)。在上面的例子中,f1,f2共享同一份upvalue,这是因为f1、f2都是由同一个闭包shareVar(1024)创建的,所以他们引用的upvalue(变量n)实际也是同一个变量,而它们的upvalue引用都会指向同一个地方。说白了就是func1和func2的引用环境是一样,它们的上下文是一样的。再类比一下我们比较熟悉的C++,就好比C++类中有两个不同的成员函数,它们都可以对类中的同一个成员变量进行访问和修改。这第二点概念尤其要和第一点概念进行区分,它们很容易混淆。
3.利用闭包实现迭代器功能
--- 利用闭包实现iterator,iterator是一个工厂,每次调用都会产生一个新的闭包,该闭包内部包括了upvalue(t,i,n)
--- 因此每调用一次该函数都会产生闭包,那么该闭包就会根据记录上一次的状态,以及返回table中的下一个元素
function iterator(t)
local i = 0
local n = #t
return function()
i = i + 1
if i <= n then
return t[i]
end
end
end
testTable = {1,2,3,"a","b"}
-- while中使用迭代器
iter1 = iterator(testTable) --调用迭代器产生一个闭包
while true do
local element = iter1()
if nil == element then
break;
end
print(element)
end
-- for中使用迭代器
for element in iterator(testTable) do --- 这里的iterator()工厂函数只会被调用一次产生一个闭包函数,后面的每一次迭代都是用该闭包函数,而不是工厂函数
print(element)
end
利用闭包我们可以很方便地实现一个迭代器,例如上面代码中的iterator。iterator是一个工厂,每次调用都会产生一个新的闭包,该闭包内部包括了upvalue(t,i,n),因此每调用一次该函数都会产生闭包,那么该闭包就会根据记录上一次的状态,以及返回table中的下一个元素,从而实现了迭代器的功能。需要额外注意的是:迭代器只是一个生成器,他自己本身不带循环。我们还需要在循环里面去调用它才行。
在while循环的那段例子代码中,我们首先调用迭代器创建一个闭包,然后不断地调用它就可以获取到表中的下一个元素了,就好像是游标一样。而由于 for ... in ... do 的这种写法很具有迷惑性,所以在for循环中使用迭代器的话,我们需要注意:这里的iterator()工厂函数只会被调用一次产生一个闭包函数,后面的每一次迭代都是用该闭包函数,而不是工厂函数。相信许多朋友此时会和马三一样产生一个疑问,为什么在for循环中使用迭代器,iterator()工厂函数只会被调用一次呢?难道不是每次判断执行条件的时候都去执行一次iterator函数吗?其实这和Lua语言对for...in...do这种控制结构的内部实现方式有关。for in在自己内部保存三个值:迭代函数、状态常量、控制变量。for...in 这种写法其实是一种语法糖,在《Programming in Lua》中给出的等价代码是:
do
local _f,_s,_var = iter,tab,var
while true do
local _var,value = _f(_s, _var)
if not _var then break end
body
end
end
怎么样,for...in 的内部实现代码和我们在while中调用Iterator的方式是不是很类似?Iterator(table)函数返回一个匿名函数作为迭代器,该迭代函数会忽略掉传给它的参数table和nil,table和控制变量已被保存在迭代函数中,因此将上面的for循环展开后应该是这个样子:
iter = iterator(testTable)
element,value = iter(nil,nil)--忽略参数,value置为nil
if(element) then
repeat
print(element)
element,value = iter(nil,element)--忽略参数
until(not element)
end
回到顶部
我们在上面花了很大的篇幅来介绍Lua的闭包,其实在C#中也是有闭包概念的。由于我们已经有了之前的Lua闭包基础,所以再理解C#中的闭包概念也就不那么困难了。照例在开讲之前我们还是先介绍一些C#中的基础知识与概念,一边有助于我们的理解。
变量作用域:在C#里面,变量作用域有三种,一种是属于类的,我们常称之为field(字段/属性);第二种则属于函数的,我们通常称之为局部变量;还有一种,其实也是属于函数的,不过它的作用范围更小,它只属于函数局部的代码片段,这种同样称之为局部变量。这三种变量的生命周期基本都可以用一句话来说明,每个变量都属于它所寄存的对象,即变量随着其寄存对象生而生和消亡。
对应三种作用域我们可以这样说,类里面的变量是随着类的实例化而生,同时伴随着类对象的资源回收而消亡(当然这里不包括非实例化的static和const对象)。而函数(或代码片段)的变量也随着函数(或代码片段)调用开始而生,伴随函数(或代码片段)调用结束而自动由GC释放,它内部变量生命周期满足先进后出的特性。
那么,有没有例外的情况呢?答案当然是有的,它就是我们的今天的主角:C#闭包。
委托:委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。(关于委托的讲解,网上已经有很多文章了,这里不再赘述,笼统一点你可以把委托简单地理解为函数指针)
闭包其实就是使用的变量已经脱离其作用域,却由于和作用域存在上下文关系,从而可以在当前环境中继续使用其上文环境中所定义的一种函数对象。(本质上和Lua闭包的概念没有什么不同,只是换种说法罢了)
首先让我们来看下面这一段C#代码:
public class TCloser
{
public Func<int> T1()
{
var n = 999;
return () =>
{
Console.WriteLine(n);
return n;
};
}
}
class Program
{
static void Main()
{
var a = new TCloser();
var b = a.T1();
Console.WriteLine(b());
}
}
从上面的代码我们不难看到,变量n实际上是属于函数T1的局部变量,它本来的生命周期应该是伴随着函数T1的调用结束而被释放掉的,但这里我们却在返回的委托b中仍然能调用它,这里正是C#闭包的特性。在T1调用返回的匿名委托的代码片段中我们用到了n,而在编译器看来,这些都是合法的,因为返回的委托b和函数T1存在上下文关系,也就是说匿名委托b是允许使用它所在的函数或者类里面的局部变量的,于是编译器通过一系列操作使b中调用的函数T1的局部变量自动闭合,从而使该局部变量满足新的作用范围。
所以对于C#中的闭包,你就可以像之前介绍的Lua闭包那样理解它。由于返回的匿名函数对象是在函数T1中生成的,因此相当于它是属于T1的一个属性。如果你把T1的对象级别往上提升一个层次就很好理解了,这里就相当于T1是一个类,而返回的匿名对象则是T1的一个属性,对属性而言,它可以调用它所寄存的对象T1的任何其他属性或者方法,包括T1寄存的对象TCloser内部的其他属性。如果这个匿名函数会被返回给其他对象调用,那么编译器会自动将匿名函数所用到的方法T1中的局部变量的生命周转期自动提升,并与匿名函数的生命周期相同,这样就称之为闭合。
///
从Lua查找表元素的过程看元表、元方法/
在Lua table中我们可以访问对应的key来得到value值,但是却无法对两个table进行操作。因此Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。通俗来说,元表就像是一个“操作指南”,里面包含了一系列操作的解决方案,例如__index方法就是定义了这个表在索引失败的情况下该怎么办,__add方法就是告诉table在相加的时候应该怎么做。这里面的__index,__add就是元方法,下面我们详细解读一下元方法。
回到顶部
通过上面的知识,我们知道了通过使用元表可以定义Lua如何计算两个table的相加操作。当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"__add"的字段,若找到,则调用对应的值。"__add"等即时字段,其对应的值(往往是一个函数或是table)就是"元方法"。很多人对Lua中的元表和元方法都会有一个这样的误解:“如果A的元表是B,那么如果访问了一个A中不存在的成员,就会访问查找B中有没有这个成员”。如果说这样去理解的话,就大错特错了,实际上即使将A的元表设置为B,而且B中也确实有这个成员,返回结果仍然会是nil,原因就是B的__index元方法没有赋值。别忘了我们之前说过的:“元表是一个操作指南”,定义了元表,只是有了操作指南,但不应该在操作指南里面去查找元素,而__index方法则是“操作指南”的“索引失败时该怎么办”。下面我们通过几段实际的代码来看一下Lua的表元素的查找过程以便更深入地体会上述这些概念。
下面是一些Lua表中可以重新定义的元方法:
__add(a, b) --加法
__sub(a, b) --减法
__mul(a, b) --乘法
__div(a, b) --除法
__mod(a, b) --取模
__pow(a, b) --乘幂
__unm(a) --相反数
__concat(a, b) --连接
__len(a) --长度
__eq(a, b) --相等
__lt(a, b) --小于
__le(a, b) --小于等于
__index(a, b) --索引查询
__newindex(a, b, c) --索引更新(PS:不懂的话,后面会有讲)
__call(a, ...) --执行方法调用
__tostring(a) --字符串输出
__metatable --保护元表
回到顶部
众所周知,Lua的表本质其实是个类似Dictionary的东西,其元素是很多的Key-Value键值对。如果尝试访问了一个表中并不存在的元素时,就会触发Lua的一套查找机制,Lua也是凭借这个机制来模拟了类似“类”的行为。下面是一段简单地访问表中元素的代码:
myTable = {
prop1 = 'Property',
}
print (myTable.prop1)
print (myTable.prop2) --打印不存在的成员prop2
稍微有些Lua语法的同学,一看就可以看出,上面的输出结果为:Property nil 。输出为nil的原因很简单,myTable中并没有prop2这个成员,这符合我们平时操作Dictionary的习惯。但对于Lua的表,如果myTable有元表和元方法,情况就不同了。下面我们再看一下设置了元表和元方法的代码:
father = {
prop1=1
}
son = {
prop2=1
}
setmetatable(son, father) --把son的metatable设置为father
print (son.prop1)
执行输出的结果仍然为:nil,这正印证了上面所说的,只设置元表是不管用的。再来看看同时设置元表和对应的元方法的代码:
father = {
prop1=1
}
father.__index = father -- 把father的__index方法指向它本身
son = {
prop2=1
}
setmetatable(son, father) --把son的metatable设置为father
print (son.prop1)
执行输出的结果为:1。
结合上述的几个小例子,我们再来解释一下__index元方法的含义:在上面的例子中,当访问son.prop1时,son中是没有prop1这个成员的。接着Lua解释器发现son设置了元表:father,(需要注意的是:此时Lua并不是直接在fahter中找到名为prop1的成员,而是先调用father的__index方法),如果__index方法为nil,则直接返回nil。如果__index指向了一张表(上面的例子中father的__index指向了自己本身),那么就会到__index方法所指向的这个表中去查找名为prop1的成员。最终,我们在father表中找到了prop1成员。这里的__index方法除了可以是一个表,也可以是一个函数,如果是函数的话,__index方法被调用时会返回该函数的返回值。
Lua查找一个表元素的规则可以归纳为如下几个步骤:
从Lua查找表元素的过程看元表、元方法/
Lua中rawset和rawget的作用浅析/
rawget是为了绕过__index而出现的,直接点,就是让__index方法的重写无效。(我这里用到"重写"二字,可能不太对,希望能得到纠正)
Window = {}
Window.prototype = {x = 0 ,y = 0 ,width = 100 ,height = 100,}
Window.mt = {}
function Window.new(o)
setmetatable(o ,Window.mt)
return o
end
Window.mt.__index = function (t ,key)
return 1000
end
Window.mt.__newindex = function (table ,key ,value)
if key == "wangbin" then
rawset(table ,"wangbin" ,"yes,i am")
end
end
w = Window.new{x = 10 ,y = 20}
print(rawget(w ,w.wangbin))
打印结果是:nil。这里的元表中__index函数就不再起作用了。
但是rawset呢,起什么作用呢?我们再来运行一段代码。
Window = {}
Window.prototype = {x = 0 ,y = 0 ,width = 100 ,height = 100,}
Window.mt = {}
function Window.new(o)
setmetatable(o ,Window.mt)
return o
end
Window.mt.__index = function (t ,key)
return 1000
end
Window.mt.__newindex = function (table ,key ,value)
table.key = "yes,i am"
end
w = Window.new{x = 10 ,y = 20}
w.wangbin = "55"
然后我们的程序就stack overflow了。可见,程序陷入了死循环。因为w.wangbin这个元素本来就不存在表中,然后这里不断执行进入__newindex,陷入了死循环。
Lua中rawset和rawget的作用浅析/
Lua中的异常处理pcall、xpcall、debug使用///
如果需要在Lua中处理错误,必须使用函数pcall(protected call)来包装需要执行的代码。
pcall接收一个函数和要传递个后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo
if pcall(function_name, ….) then
-- no error
else
-- some error
end
简单示例
> =pcall(function(i) print(i) end, 33)
33
true
> =pcall(function(i) print(i) error('error..') end, 33)
33
false stdin:1: error..
这里注意对返回值的逻辑判断
> function f() return false,2 end
> if f() then print '1' else print '0' end
0
pcall以一种"保护模式"来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。
通常在错误发生时,希望落得更多的调试信息,而不只是发生错误的位置。但pcall返回时,它已经销毁了调用桟的部分内容。Lua提供了xpcall函数,xpcall接收第二个参数——一个错误处理函数,当错误发生时,Lua会在调用桟展看(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。
debug库提供了两个通用的错误处理函数:
debug.debug:提供一个Lua提示符,让用户来价差错误的原因
debug.traceback:根据调用桟来构建一个扩展的错误消息
>=xpcall(function(i) print(i) error('error..') end, function() print(debug.traceback()) end, 33)
33
stack traceback:
stdin:1: in function
[C]: in function 'error'
stdin:1: in function
[C]: in function 'xpcall'
stdin:1: in main chunk
[C]: in ?
false nil
Lua中的异常处理pcall、xpcall、debug使用///
Lua 打印Table表 //
local print = print
local tconcat = table.concat
local tinsert = table.insert
local srep = string.rep
local type = type
local pairs = pairs
local tostring = tostring
local next = next
function print_r(root)
local cache = { [root] = "." }
local function _dump(t,space,name)
local temp = {}
for k,v in pairs(t) do
local key = tostring(k)
if cache[v] then
tinsert(temp,"+" .. key .. " {" .. cache[v].."}")
elseif type(v) == "table" then
local new_key = name .. "." .. key
cache[v] = new_key
tinsert(temp,"+" .. key .. _dump(v,space .. (next(t,k) and "|" or " " ).. srep(" ",#key),new_key))
else
tinsert(temp,"+" .. key .. " [" .. tostring(v).."]")
end
end
return tconcat(temp,"\n"..space)
end
print(_dump(root, "",""))
end
例子:
a = {}
a.a = {
hello = {
alpha = 1 ,
beta = 2,
},
world = {
foo = "ooxx",
bar = "haha",
root = a,
},
}
a.b = {
test = a.a
}
a.c = a.a.hello
print_r(a)
function BetterPrintTable(lua_table)
local cache = { [lua_table] = "." }
local function _dump(t, space, name)
local temp = {}
tinsert(temp, "{")
for k,v in pairs(t) do
local key = tostring(k)
if type(k) == "string" then
key = "\"" .. tostring(k) .. "\""
end
if cache[v] then
tinsert(temp, " [" .. key .. "] = {" .. cache[v] .. "}")
elseif type(v) == "table" then
local new_key = name .. "." .. key
cache[v] = new_key
tinsert(temp, "\t [" .. key .. "] = " .. _dump(v, space .. (next(t,k) and " " or " " ) .. srep(" ", #key), new_key))
else
local tempV = tostring(v)
if type(v) == "string" then
tempV = "\"" .. tostring(v) .. "\""
end
tinsert(temp, "\t [" .. key .. "] = " .. tempV)
end
end
tinsert(temp, "}")
return tconcat(temp, "\n" .. space)
end
print("-------------------- Better Print Table ------------------------")
print(_dump(lua_table, "", ""))
end
按照lua的table格式进行缩进打印lua的table,目前还不支持key为table
function print_lua_table(lua_table, indent)
indent = indent or 0
for k, v in pairs(lua_table) do
if type(k) == "string" then
k = string.format("%q",k)
end
local szSuffix = ""
if type(v) == "table" then
szSuffix = "{"
end
local szPrefix = string.rep(" ", indent)
formatting = szPrefix.."["..k.."]".." = "..szSuffix
if type(v) == "table" then
print(formatting)
print_lua_table(v, indent + 1)
print(szPrefix.."},")
else
local szValue = ""
if type(v) == "string" then
szValue = string.format("%q", v)
else
szValue = tostring(v)
end
print(formatting..szValue..",")
end
end
end
//
local raw_print = print
local function printTable(t)
if type(t) == "table" then
for _,v in pairs(t) do
if type(v) == "table" then
printTable(v)
else
raw_print(v)
end
end
end
end
local function printExt(...)
local args = { ... }
for _,v in pairs(args) do
if type(v) == "table" then
printTable(v)
else
raw_print(v)
end
end
end
rawset(_G,"print",printExt)
print(1,2,3,{123,456,{"abc"},nil})
Lua 打印Table表 //