Lua

Lua学习笔记:高级概念

Posted by 蔡华的博客 on August 7, 2017

高级概念

  • 这部分中会记录一些lua语言中的高级概念和技术。

模块

概念

  • 从我的理解看,模块更像是一个类。但是因为lua中没有类的概念,模块的实现是依赖于table。
  • 一些公共或者私有的变量+函数组成了模块的主体,最后一个return module完成了基本的构造。其实这个return就是返回了这个table。
  • 具体实现看教程

使用

-- 创建一个叫Class的模块
Class = {}

Class.Name = "name"

Class.Grades =
{
  "Grade1",
  "Grade2",
  "Grade3"
}

temp = "a"
local temp2 = "b"
Class.temp3 = "c"

function Class.FindGrade(key)
   return Class.Grades[key]
end

return Class
-- temp = "local temp1"

-- 导入模块
require("Class")

-- temp = "local temp1"

print(temp)
print(Class.temp2)
print(Class.temp3)

-- 调用模块函数
print(Class.FindGrade(1))

-- 结果
a
nil
c
Grade1-abc
  • 分析一下模块的变量,从例子中可以总结,模块中的以模块名+.形成的变量(Class.temp3)是全局变量,而且即使在模块内部调用用也要用模块名+.的形式使用。

  • 没有加模块名的变量其实也是全局变量,外部代码也可以直接访问。注意代码中在require的前后分别定义了一个和模块中名字相同的变量,这里有一个很有意思的事情,就是如果是之前定义的,那么在print时是模块中的值,如果在之后定义那么就是本地定义的值。这说明require这种过程实际上等于在本地定义了一些变量。所以最终输出的值可以按照后定义的输出。

  • 显示标记为local的变量是本地变量,在模块内部使用是没有问题的,但是外部代码无法访问。

加载机制

  • 基本上遵循C的加载机制,也就是先找同一目录下的文件,然后会找全局变量中path里面定义的文件。
  • 通常我们在编写代码的时候肯定是会有物理的文件夹结构的,此时如果我们不去改package.path的值,那么可以在引用时加速文件夹名字,比如我把class和student放到了Module文件夹下,那么代码中写require("Module/Class")即可正常调用。

协程

  • 首先是真的多线程,并非unity的那种主线程内部的线程。
require("MyCoroutine")
cor1 = MyCoroutine.CreatCor()
print(cor1)
for i = 1,  10 do
	MyCoroutine.RunCor(cor1)
end


cor2 = MyCoroutine.CreatCor()
print(cor2)
for i = 1,  10 do
	MyCoroutine.RunCor(cor2)
end

-- 结果
thread: 0089D628
1
2
3
running
thread: 0089D628
4
5
6
7
8
9
10
thread: 00897D60
1
2
3
running
thread: 00897D60
4
5
6
7
8
9
10

  • 用法比较繁琐,但是可以明确的控制协程的执行步骤。有点类似python。
  • 建议直接看教程。

元表(MetaTable)

  • metatable更像是lua为table提供的依赖倒置的接口,以__index这个函数为例,本身如果lua提供了固定的函数,那么我们在取值时候只能遵从lua的规定。但是现在提供了一个__index,你可以自定义这个__index的行为,而lua在运行期间遇到了需要用到__index的时候就会按照你所规定的准则行事了。这个思维是很好的。

  • 下面是__index的用法,metatable。

Lua查找一个表元素时的规则,其实就是如下3个步骤:

1.在表中查找,如果找到,返回该元素,找不到则继续

2.判断该表是否有元表,如果没有元表,返回nil,有元表则继续。

3.判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值。

代码示例:

mt = {}

mt.__index =
function (t,key)
	if key == "key3" then
		return 5
	elseif key == "key4"  then
		return 6
	else
		return 0
	end
	
end

mt.__newindex =
function (t,key,value)
	t.key = value
end

mt.__tostring = function (t)
	local tempTable = {}
	local index = 0
	for i,v in pairs(t) do
		index = index + 1
		tempTable[index] = v
		print(i.."/"..v)
	end
	return '{' .. table.concat(tempTable, ', ') .. '}'
end

-- 一旦定义了__metatable,就代表getmetatable只能得到这个函数的值,
-- 而不能再次赋值,否则会报错“cannot change a protected metatable”
mt.__metatable = "不能看啊不能看"


local t = {1, 2, 3, key1 = 3,key2 = 4}
setmetatable(t, mt)
print(t)

print(getmetatable(t))

print(t["key4"])


mt2 = {}

-- 下面的代码会报错
--setmetatable(t,mt2)


  • 这个东西有个好处,如果我写的key不存在,那么我是可以自定义它的行为的。我可以返回一个数值,也可以执行一段代码。但是一般来说还是提前预置好数据比较好。

  • PS:元表本身是一个table,如果按照table就是类这个概念来看的话,元表对应了类类型,而实际的使用中它也是起了这样的作用。元表中以__开头的方法们算是它保留的方法,我们可以给一个元表定义这些方法实际是一个什么样的行为。比如__tostring方法,我们可以在一个元表中重新定义它的实现,有点像C#中override ToString方法。

面向对象

  • lua的oop是基于table的,table中可以设置全局或局部的变量、方法等。
  • 继承通过子类调用父类的构造函数(其实lua没有构造函数一说,只是我们人为的在代码中创造出来了一个创建对象的函数)来创建,然后在扩展,从而实现了继承。
  • 从本质上说,lua的继承和C的继承是一样的,依赖的是table嵌套table,而C中是结构体嵌套结构体。
  • 具体看下链接里教程的例子吧。

一些体会

  • 其实只要学过一个脚本语言,lua就不算陌生,还算是上手比较快。
  • 所有语言的核心在api的使用,所以熟悉标准库是关键。
  • lua与C/C++的交互也是大头,不过目前暂时不用就先不学了,等后续学了再填坑。
  • 多写,多写,多写。
  • 一个学习lua编程的方法是看WOW的插件源码。

调试

  • 因为用的是LuaStudio,在调试的过程中也遇到了很多的问题。首先正常的自己写的代码都可以require成功,os\io\math这样的库也都没有问题,但是用到socket库的时候怎么都无法require。最后发现在LuaStudio中必须这样设置才行

image

参考