Lua工具类:pack遇到nil截断,闭包绑定,深拷贝,字符串形式输出表中的内容

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

解决原生pack的nil截断问题

代码语言:javascript复制
local unpack = unpack or table.unpack

-- 解决原生pack的nil截断问题,SafePack与SafeUnpack要成对使用
function SafePack(...)
	local params = {...}
	params.n = select('#', ...) --返回可变参数的数量,赋值给n
	return params
end

-- 解决原生unpack的nil截断问题,SafePack与SafeUnpack要成对使用
function SafeUnpack(safe_pack_tb)
	return unpack(safe_pack_tb, 1, safe_pack_tb.n)
end

-- 对两个SafePack的表执行连接
function ConcatSafePack(safe_pack_l, safe_pack_r)
   local concat = {}
   for i = 1,safe_pack_l.n do
      concat[i] = safe_pack_l[i]
   end
   for i = 1,safe_pack_r.n do
      concat[safe_pack_l.n   i] = safe_pack_r[i]
   end
   concat.n = safe_pack_l.n   safe_pack_r.n
   return concat
end

实际操作

代码语言:javascript复制
local t = {nil,1,nil,3,nil,4,nil}
print(#t) -->0
print(select('#',nil,1,nil,3,nil,4,nil)) -->7
print(unpack(t)) -->空,因为第一个nil就一已经截断了

function c(a,b,c,d,e)
        print(a,b,c,d,e)

end

function a()
        local temp = {nil,1,nil,3,nil}
        c(unpack(temp))
end

a() -->nil	1	nil	nil	nil

function b()
	local temp =SafePack(nil,1,nil,3,nil)
	c(SafeUnpack(temp))
end

b() -->nil	1	nil	3	nil

#table的坑点 如果传递的数组中带有 nil 值空洞,# 操作符返回的数值并不能反映真实的大小。 简单说,Lua 里面 table 的长度的定义跟其他语言的不同。table 的长度,被定义成第一个值为 nil 的整数键(而不是像通常认为那样,等价于元素的数量)。 如果一个 array-like table 里面存在空洞,那么任意 nil 值前面的索引都有可能是 # 操作符返回的值。

闭包绑定

代码语言:javascript复制
-- 闭包绑定
function Bind(self, func, ...)
   assert(self == nil or type(self) == "table")
   assert(func ~= nil and type(func) == "function")
   local params = nil
   if self == nil then
      params = SafePack(...)
   else
      params = SafePack(self, ...)
   end
   return function(...)
      local args = ConcatSafePack(params, SafePack(...))
      func(SafeUnpack(args))
   end
end

-- 回调绑定
-- 重载形式:
-- 1、成员函数、私有函数绑定:BindCallback(obj, callback, ...)
-- 2、闭包绑定:BindCallback(callback, ...)
function BindCallback(...)
   local bindFunc = nil
   local params = SafePack(...)
   assert(params.n >= 1, "BindCallback : error params count!")
   if type(params[1]) == "table" and type(params[2]) == "function" then
      bindFunc = Bind(...)
   elseif type(params[1]) == "function" then
      bindFunc = Bind(nil, ...)
   else
      error("BindCallback : error params list!")
   end
   return bindFunc
end

单元测试 –下面是单元测试

代码语言:javascript复制
local function TestBind()
	local class = BaseClass("class")
	class.callback = function(self, a, b, c, d)
		self.var = self.var   1
		self.a = a
		self.b = b
		self.c = c
		self.d = d
	end

	local inst = class.New()
	inst.var = 111
	inst.a = nil
	inst.b = nil
	inst.c = nil
	inst.d = nil

	local bindFunc = Bind(inst, inst.callback, "aaa", 1234)
	assert(inst.var == 111)
	assert(inst.a == nil)
	assert(inst.b == nil)
	assert(inst.c == nil)
	assert(inst.d == nil)

	bindFunc()
	assert(inst.var == 112)
	assert(inst.a == "aaa")
	assert(inst.b == 1234)
	assert(inst.c == nil)
	assert(inst.d == nil)

	bindFunc("rrr")
	assert(inst.var == 113)
	assert(inst.a == "aaa")
	assert(inst.b == 1234)
	assert(inst.c == "rrr")
	assert(inst.d == nil)

	bindFunc("kkk", 999)
	assert(inst.var == 114)
	assert(inst.a == "aaa")
	assert(inst.b == 1234)
	assert(inst.c == "kkk")
	assert(inst.d == 999)

	local inst2 = class.New()
	inst2.var = 999
	inst2.a = nil
	inst2.b = nil
	inst2.c = nil
	inst2.d = nil

	local bindFunc2 = Bind(inst2, inst2.callback, "bbb", 4321)
	assert(inst2.var == 999)
	assert(inst2.a == nil)
	assert(inst2.b == nil)
	assert(inst2.c == nil)
	assert(inst2.d == nil)

	bindFunc2()
	assert(inst2.var == 1000)
	assert(inst2.a == "bbb")
	assert(inst2.b == 4321)
	assert(inst2.c == nil)
	assert(inst2.d == nil)

	bindFunc2("qqq")
	bindFunc2("vvv", 765)
	assert(inst.var == 114)
	assert(inst.a == "aaa")
	assert(inst.b == 1234)
	assert(inst.c == "kkk")
	assert(inst.d == 999)
	assert(inst2.var == 1002)
	assert(inst2.a == "bbb")
	assert(inst2.b == 4321)
	assert(inst2.c == "vvv")
	assert(inst2.d == 765)

	local bindFunc3 = Bind(inst, inst.callback)
	bindFunc3()
	assert(inst.var == 115)
	assert(inst.a == nil)
	assert(inst.b == nil)
	assert(inst.c == nil)
	assert(inst.d == nil)
	bindFunc3(4532, "4532")
	assert(inst.var == 116)
	assert(inst.a == 4532)
	assert(inst.b == "4532")
	assert(inst.c == nil)
	assert(inst.d == nil)

	local bindFunc4 = Bind(inst, inst.callback, nil, "ttt")
	bindFunc4()
	assert(inst.var == 117)
	assert(inst.a == nil)
	assert(inst.b == "ttt")
	assert(inst.c == nil)
	assert(inst.d == nil)
	bindFunc4(nil, "4532")
	assert(inst.var == 118)
	assert(inst.a == nil)
	assert(inst.b == "ttt")
	assert(inst.c == nil)
	assert(inst.d == "4532")
end

local function Run()
	TestBind()
	print("LuaUtilTest Pass!")
end

Run()

如何理解闭包绑定 1.local bindFunc = Bind(inst, inst.callback, “aaa”, 1234) Bind函数内部params = SafePack(self, …):把self,和Bind后面参数组合pack 2.Bind函数内部的return function(…):这里的…跟params = SafePack(self, …)中…不一样,这里是指bindFunc 传递过来的参数 3.整个逻辑:SafeUnpack:self(或者nil),Bind的参数列表,bindFunc的参数列表

深拷贝

代码语言:javascript复制
-- 深拷贝对象
function DeepCopy(object)
   local lookup_table = {}
   
   local function _copy(object)
      if type(object) ~= "table" then
         return object
      elseif lookup_table[object] then
         return lookup_table[object]
      end

      local new_table = {}
      lookup_table[object] = new_table
      for index, value in pairs(object) do
         new_table[_copy(index)] = _copy(value)
      end

      return new_table
      --return setmetatable(new_table, getmetatable(object))
   end

   return _copy(object)
end

单元测试

代码语言:javascript复制
tabA = { x= 1}
tabB = tabA

print(tabB.x) --1

tabA.x = 2

print(tabB.x) --2


tabC = DeepCopy(tabA)


print(tabC.x) --2

tabA.x = 3

print(tabC.x) --2

单元测试二

代码语言:javascript复制
tabA = { x= 1,inside = { y =1}}
tabB = tabA

print(tabB.inside.y) --1

tabA.inside.y = 2

print(tabB.inside.y) --2

tabC = DeepCopy(tabA)

print(tabC.inside.y)--2

tabA.inside.y = 3

print(tabC.inside.y) --2

代码解析

  1. tabB = tabA ,相当于是对象起别名,或者说赋值指针,tabA的任何改动也会实装在tabB上
  2. lookup_table相当于一个记忆表,里面的key为table的地址,这样可以保证每一个key都是唯一的,里面只包含,一个是要深拷贝的tabA,另外是tabA的里面的域为table 的
  3. _copy里面执行逻辑,如果复制的是值,直接返回,如果复制的是表,在记忆表里找,没找到接着创建一个记忆表key 为inside,然后执行复制值时,又创建了一个记忆表

可以做此测试加强代码回调调用理解 –深拷贝

代码语言:javascript复制
function DeepCopy(object)

	local lookup_table = {}
	local i = 0
	local function _copy(object)
		print("copy")
		print(object)

		if type(object) ~= "table" then
			return object
		elseif lookup_table[object] then
			return lookup_table[object]
		end

		local new_table = {}
		lookup_table[object] = new_table
		for index, value in pairs(object) do
			new_table[_copy(index)] = _copy(value)
		end

		i = i 1
		print("index:" .. i)
			for index, value in pairs(lookup_table) do
			print(value)
		end

		return new_table

		--return setmetatable(new_table, {__index = object})
	end



	return _copy(object)
end

--深拷贝测试

tabA = { 2,x= 1,inside = { y =2}}
tabB = tabA
print(tabA)
print(tabA.inside)


tabC = DeepCopy(tabA)



--输出
table: 00E87FA8 --a地址
table: 00E882C8 --a.inside地址
copy
table: 00E87FA8 --copy a   记忆表找不到a ,开始copy a 的index 和值
copy
1   -- copy 索引1   ,立马返回了1
copy  
2  --copy 索引1的值为2,由于不是表,立马返回 了2
copy
inside --copy 索引 为 inside ,这里一定是copy成功了,key和value是分开的,所有的key都是非表结构,然后变为  新表的一个key
copy
table: 00E882C8 --开始copy inside的value,是个表,相当于又创立个新表inside,又逐key赋值到新表
copy
y --inside key 为y  
copy
2  -inside  key为y  的值 为2
index:1   --新的inside创建好了,返回,因为记忆表已经创建好了 key为,tabA的table,key为inside的table
table: 00E880C0
table: 00E880E8
copy    --接着遍历,复制key  为x 
x
copy
1
index:2

pairs遍历顺序

代码语言:javascript复制
tabA = { 1,x= 2,inside = { y =3},{4,5}}
for index, value in pairs(tabA) do
			if (type(index) ~= "table") then
				print( index)
				print( ":")
				print( value)
			end
		end

print(tabA[2][2])

输出

代码语言:javascript复制
1
:
1
2
:
table: 00ED8B78
inside
:
table: 00ED8DD0
x
:
2
5

在使用pairs函数进行打印的时候,先打印表中的值,再按照键值对的键所对应的哈希值进行打印,后面的顺序是哈希顺序,并不是字母顺序

字符串形式输出表中的内容

代码语言:javascript复制
--tb:表
--dump_metatable:是否打印元表
--max_level:打印的层级,越大能打印更多嵌套表
local function dump(tb, dump_metatable, max_level)
	local lookup_table = {}
	local level = 0
	local rep = string.rep
	local dump_metatable = dump_metatable
	local max_level = max_level or 1

	local function _dump(tb, level)
		local str = "n" .. rep("t", level) .. "{n"
		for k,v in pairs(tb) do
			local k_is_str = type(k) == "string" and 1 or 0
			local v_is_str = type(v) == "string" and 1 or 0
			str = str..rep("t", level   1).."["..rep(""", k_is_str)..(tostring(k) or type(k))..rep(""", k_is_str).."]".." = "
			if type(v) == "table" then
				if not lookup_table[v] and ((not max_level) or level < max_level) then
					lookup_table[v] = true
					str = str.._dump(v, level   1, dump_metatable).."n"
				else
					str = str..(tostring(v) or type(v))..",n"
				end
			else
				str = str..rep(""", v_is_str)..(tostring(v) or type(v))..rep(""", v_is_str)..",n"
			end
		end
		if dump_metatable then
			local mt = getmetatable(tb)
			if mt ~= nil and type(mt) == "table" then
				str = str..rep("t", level   1).."["__metatable"]".." = "
				if not lookup_table[mt] and ((not max_level) or level < max_level) then
					lookup_table[mt] = true
					str = str.._dump(mt, level   1, dump_metatable).."n"
				else
					str = str..(tostring(v) or type(v))..",n"
				end
			end
		end
		str = str..rep("t", level) .. "},"
		return str
	end

	return _dump(tb, level)
end

tabA = {1,2,x = 3,4,{5,6}}
print(dump(tabA))

输出

代码语言:javascript复制
{
	[1] = 1,
	[2] = 2,
	[3] = 4,
	[4] = 
	{
		[1] = 5,
		[2] = 6,
	},
	["x"] = 3,
},

代码解析:

  1. _dump中pairs遍历表tb,如果是v的类型是table,如果没遍历过,且深度<最大深度,遍历v 的table
  2. 如果v的类型是普通域,直接字符串叠加
  3. 全部执行完子类的k-v,接着执行查找元表。按照1,2顺序再来一遍

0 人点赞