Lua

7.3k 词

前段时间在腾讯课堂上看到一个视频,是关于一个将C#项目转换成lua项目的一篇介绍。他们描述的方案是反编译C#输出的Dll文件,反编译得出的数据会作C#翻译lua的源数据。一时起了敬畏之心,借助这个机会恰好梳理一下自己的lua知识。

我是在c的基础之上进行lua学习的,期间发现有很多lua的特性是与C语言不相同的,个人认为如果不注意这些区别很容易在未来的工作中出现难以排查的bug,程序开发还是需要一定的严谨性。

缘起

正值待业充电期,在腾讯课堂上看到一篇高效热更新,Lua翻译工具详解,它采用的方案是反编译C#的dll文件,根据反编译的内容再将其翻译成Lua语言,继而实现项目的代码更新部分。为了能够熟知这种方案的可行性以及其效率和稳定性,决定先从lua这门语言着手研究。

关于lua的开发环境的配置

我使用的是LuaStudio,很便捷的一个IDE,安装完毕之后就可以直接使用。 lua官方网站也有免费提供在线解释器 当然如果想自己体验搭建解释器的话,可以参考从零开始的Lua宅[1]:编译Lua解释器这篇博文。

lua中声明的对象不具有C语言中引用类型的那种数据传递的特性。

table1 = {}
table2 = {}
table1[1] = "testTable1_key1"
table1[2] = "testTable1_key2"
table2[1] = "testTable2_key1"
table2[2] = "testTable2_key2"

table3 = {}
temp = table1
table3["temp"] = table1
print(table3["temp"][1])
temp = table2
print(table3["temp"][1])
temp = table3["temp"]
temp = table2
print(table3["temp"][1])

这是结果 即使temp对象被修改成table2,那table中的数值依旧是table1的数据。

当table中的索引不存在时,解释器并不会抛出异常。

lua中所有未声明和未赋值的对象的初始值,在被调用时都会被当作nil处理。

a = {}
for i = 1,1000 do 
a[i] = i * 2
end
a["x"] = 10
print(a["x"])
print(a["y"])

结果为:

【语法棒棒糖】实现table进行一个更简便的输入

刚开始接触到这类写法的时候,第一反应就是觉得好神奇。不管有没有声明与赋值都可以这样使用,当然结果也是在意料之中。

a = {}
a.y = 999
print(a.y)
print(a.x)

结果为:

关于获取一个数据对象的长度问题,有些需要注意的地方。

testTable = {}
for i = 1,1000 do 
testTable[i] = i * 2
end
testValue = "this is Test"
print("table length = " .. #testTable)
print("testValue length = " .. #testValue)
testValue = {} -- 重置一下
testValue[1000] = "Test 1000"
print("testValue 1000 = " .. #testValue)

结果为: 最后一条的输出结果有些让人意外,原因是所有未初始化的元索引结果都是nil,lua将nil作为界定数组结尾的标志,当一个数组中间存在nil时,长度操作符会认为这些nil元素就是结尾标记。 另外在lua中table[0]和table[“0”]是两种索引,并不指向一个数据。 如果对这两点不加以注意,那很可能会给未来的程序开发带来一些难以解决的bug。 一般情况下的table表中一般不会存在那种中间nil。如果真的需要处理这种情况函数table.maxn(目标对象),会返回table的最大正索引。

关于lua的数学运算符

+(加法) -(减法) *(乘法) /(除法) ^(指数) % (求模) 取模的公式实现 a % b == a - floor(a/b) * b

x = 9.123456
print(x - x % 0.01) -- 保留小数点后两位的写法
print(x % 1) -- 小数部分部分
print(x - x % 1) --整数部分

结果为

关于Table索引的一些注意事项

  • table中 默认起始索引是1 (不同于C的0),按照从左到右的方法按照顺序依次索引,期间会跳过已经声明的显示索引,即a[1000] = 1
  • 当table中存在显示索引与隐式索引存在相同值时,lua会优先隐式索引
  • 当table中存在两个相同值的显式索引时,会优先索引大的那个方向的值
opnames = 
{ 
	["+"] = "add",
	["-"] = "sub",
	["*"] = "mul",
	["/"] = "div",
}
--table 测试1
i = 2;s = "-"
a = {[i+0]=s,[i+1]= s..s,[i+2] = s..s..s,"Sunday","Monday","Tuesday","WednesDay","ThursDay","Friday","Saturday"}
print("测试1" .. opnames[s])
print("测试1" .. a[2])
print("测试1" .. a[1])
print("测试1" .. a[2])
a = {[i+1]= s..s,[i+2] = s..s..s,"Sunday","Monday",[i+0]=s,"Tuesday","WednesDay","ThursDay","Friday","Saturday"}
--table 测试2
print("测试2" .. opnames[s])
print("测试2" .. a[2])--当table中存在显示索引与隐式索引存在相同值时,lua会优先隐式索引
print("测试2" .. a[3])
--table 测试3
s = "-"
a = {[22]=s,[22] = s..s,"Sunday","Monday"}
print("测试3" .. a[22])--当table中存在两个相同值的显式索引时,会优先索引大的那个方向的值

结果为

Lua中有趣的赋值-多重赋值

多重赋值——用作数据的两两交换 左侧变量的数量,与右侧数值的变量要相等。不匹配的数据会被设置成nil或者被丢弃 看到这一特性时,第一时间想到的时c语言中的冒泡排序核心语法可以从三行缩略成一行 list[a],list[b] = list[b],list[a]

x = "test string"
y = 50
x,y = y,x
print("output: X = " .. x .. " Y = " .. y)
a,b,c = 0
print("output: a = " .. a .. " b = " .. tostring(b) .. " c = " .. tostring(c))

结果为

有关于程序块与local的注意事项

如果这些语句没有被包裹在程序块(do end)中,一般情况下会很好的运行下去。但是在交互模式中可能就会出现一些问题了。 在交互模式下,lua的每一行一句都可能被视为一个代码块,比如local x = 10是一个代码块,local i = 1是另外的一个代码块。稍微熟悉lua的人都知道,不同的块内是无法访问块内的loacl字段的。

local x = 10
local i = 1

while i <= x do
local x = i* 2
print("x = " … x)
i = i+1
end

if i > 20 then
local x
x = 20
print(x +2 )
else
print(x)
end

print(x)
print(i)

lua中的for

for语句有两种形式:数字型for(Numeric for)和泛型for(generic for) 在C语言中,有一种写法是遍历删除列表的形式

		List<int> testList = new List<int>(){1,2,3,4,5};
		for(int i = 0;i < testList.Count;i++)
		{
			testList.Remove(i);
			i--;
		}

有一点需要提醒的时,lua一般不建议在for语法体里去修改var的值

--Numeric for
	--for	var = exp1,exp2,exp3 do
	--<执行体>
	--endexp1和exp2表示var从exp1变化至exp2,exp3表示不长,省略时默认为1
	for var = 10,1,-1 do
	print(var)
	end
--Generic for
	--他的作用相当于C语言中的foreach
	day = { "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday" }
	revDays = {}
	for k,v in ipairs(day) do
		revDays[k] = v
		print("K == " .. k .. " V == " .. v)
	end

【语法棒棒糖】一个函数若只有一个参数,并且该参数是一个string或者table构造式,那么圆括号便是可有可无的。

	function test(value)
	print(value)
	end
	print("Hello World")
	print "Hello World"
	print [[Hello World]]
	test("Hello World","Hey World")
	--测试2
	function testToString(value)
	print("toString = " .. tostring(value))
	end
	do
	local testInt = 1314520
	testToString(testInt)
	--testToString testInt -- 这里会出现编译错误
	end

结果为

关于函数传参的问题

多重返回值,在这个特性的测试上。我犯了一个错误,自以为返回的是多个返回值返回的是一个table,实际上不是,确实是返回了多个参数的数值,在返回赋值这一操作中恰好是应用了前面的赋值中的一个特性 ”a ,b = c,d“

function maximum(a)
	local mi = 1
	local m = a[mi]
		for i,v in ipairs(a) do
			if #a < 10 then
			return 999
			elseif v > m then
			mi = i;m = v
			end
		end
return m,mi
end

function foo() end
function foo1() return "foo1" end
function foo2() return "foo2_1","foo2_2" end

testTable = {0,1,2,3,4,5,6,7,8}
result1,result2 = maximum(testTable)–测试单个返回值
print("测试1 = " … tostring(result1)) print("测试1 = " … tostring(result2))
testTable[#testTable + 1] = 9
testTable[#testTable + 1] = 10
result1,result2 = maximum(testTable)–测试两个个返回值
print("测试2 = " … tostring(result1)) print("测试2 = " … tostring(result2))
–测试3
–如果函数调用”foo2()“不是一系列表达式的最后一个元素,那么将只产生一个值
x,y = foo2() print("测试3:x = " … tostring(x) …" y = " …tostring(y))
x = foo2() print("测试3:x = " … tostring(x))
x,y,z = 10,foo2();print("测试3:x = " … tostring(x) …" y = " …tostring(y)…" z = " …tostring(z))
x,y = foo2(),20; print("测试3:x = " … tostring(x) …" y = " …tostring(y))
print(foo2(),2)–函数穿参数的测试
print(2,foo2())–函数穿参数的测试

有一点需要注意的是,如果函数调用”foo2()“不是一系列表达式的最后一个元素,那么将只产生一个值。

最后来了解一下关于递归函数的一点注意事项

先来了解闭合函数closure个人觉它得类悉于C语言中的匿名函数 (a,b) =>{方法体内容},在lua所有的函数都可视为是某种意义上闭合函数。

names = { "Peter","Paul","Mary"}
grades = { Mary = 10, Paul = 7,Peter = 8}
function SortByGrades(names,grades)
	local closure = function(n1,n2) return grades[n1] > grades[n2]end
	table.sort(names,closure)

接下来说一下注意事项,我们先看代码。

local fact = function (n)
	if n == 0 then return 1
	else return n * fact(n - 1)--这种情况下会出错
	end
end

运行中第三行会出现fact为nil的错误,不细细研究的话还真不清楚是什么情况。 它的原因是当lua编译到函数体中调用fact(n-1)的地方时,由于局部的fact尚未定义完毕,因此这句表达式实际上是指向了了一个全局的fact,那此时的fact没有显示声明即 fact = nil,而非此函数本身。 为了解决这个问题,可以先定义一个局部变量,然后再定义函数本身。

local fact
fact = function (n)
	if n == 0 then return 1
	else return n * fact(n - 1)
	end
end
--当然也可以通过这种方式来定义
local function fact(n)
	if n == 0 then return 1
	else return n * fact(n - 1)
	end
end

另外一点关于间接递归需要了解的注意事项

local f,g 	--前向声明
function g()
	f()
end
--local function f()
function f()	
	g()			
end

这种情况下不能在使用 local function这种定义,如果这样的话lua会创建一个全新的局部变量f,而将原来声明的f(函数g中所引用的那个)至于未定义的状态

            <hr style="visibility: hidden;"/>

            


            
            
            
            <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css"/>
            <script src="https://unpkg.com/gitalk@latest/dist/gitalk.min.js"></script>
            <div id="gitalk-container"></div>
            
            
            <script src="/js/md5.min.js"></script>
            <script type="text/javascript">
                var gitalk = new Gitalk({
                clientID: '87bb43eae82eeff997e1',
                clientSecret: '0827a8562d4b23c7094ec1ccac67be270ca354df',
                repo: 'RoneBlog.github.io',
                owner: 'RoneBlog',
                admin: ['RoneBlog'],
                distractionFreeMode: true,
                id: md5(location.pathname),
                });
                gitalk.render('gitalk-container');
            </script>