您的位置:首页 > 编程语言 > Lua

Lua从函数到类

2020-02-18 03:12 447 查看

第一部分 函数使用

函数调用与语法糖:

[code]local Monster = {
name = "Monster",
HP = 100,
pos = {x = 100, y = 200}
}

function Monster:TakeDamage(damage)
--等于Monster.TakeDamage = function(self, damage)
self.HP = self.HP - damage
end

print(Monster.HP)
Monster:TakeDamage(10)
--等于Monster.TakeDamage(Monster, 10)
print(Monster.HP)

其他对象调用函数:

[code]monster1 = {HP = 200}
print(monster1.HP)
Monster.TakeDamage(monster1, 10)
print(monster1.HP)

将TakeDamage函数写在内部:

[code]local Monster = {
name = "Monster",
HP = 100,
pos = {x = 100, y = 200},
TakeDamage = function(self, damage)
self.HP = self.HP - damage
end
}

print(Monster.HP)
Monster:TakeDamage(10)
--等于Monster.TakeDamage(Monster, 10)
print(Monster.HP)

monster1 = {HP = 200}
print(monster1.HP)
Monster.TakeDamage(monster1, 10)
print(monster1.HP)

已经有一点像面向对象的形式了。
但如果只是a对象=b对象,会发现,两者指向同样的地址,操作的是同样的内容:

[code]local monster1 = Monster
monster1.HP = 200
print(Monster.HP)   --200
print(monster1.HP)  --200
monster1:TakeDamage(10)
print(Monster.HP)   --190
print(monster1.HP)  --190

第二部分 metatable、__index与__newindex

于是需要引入云表meta table的概念。另一个例子起始如下:

[code]CMonster = {
name = "Monster",
HP = 100,
TakeDamage = function(self, damage)
self.HP = self.HP - damage
end
}

objMonster = {}
print(objMonster.HP)--nil
objMonster2 = { HP = 10 }
print(objMonster2.HP)--10

我们希望对象有自己的属性的时候,使用自己的,没有,则使用CMonster的。于是要设置云表:

[code]objMonster = {}
setmetatable(objMonster, {__index = CMonster})
print(objMonster.HP)--100

函数setmetatable()把后面设置为前面的云表,即后面的表定义了前面的表的一些行为,比如索引(即按照前面设定,想办法输出索引为"HP"的元素)。

[code]objMonster = {  }
setmetatable(objMonster, {
__index = CMonster
})
print(CMonster.HP)--100
print(objMonster.HP)--100
objMonster.HP = 123
print(CMonster.HP)--100
print(objMonster.HP)--123
objMonster.HP = 456
print(CMonster.HP)--100
print(objMonster.HP)--456

通过这样的方法,看上去是解决了第一部分例子中的问题。
但是,objMonster.HP = 123调用的是__newindex而不是__index。访问、查询用的是__index定义的方法或者表,改值、新增加值用的是__newindex。

[code]objMonster = {  }
setmetatable(objMonster, {
__index = CMonster,
__newindex = CMonster
})
print(CMonster.HP)--100
print(objMonster.HP)--100
objMonster.HP = 123
print(CMonster.HP)--123
print(objMonster.HP)--123
objMonster.HP = 456
print(CMonster.HP)--456
print(objMonster.HP)--456

这样就类似类的静态变量。

第三部分 类的构建与优化

根据上个例子进行修改:

[code]CMonster = {
name = "Monster",
HP = 100,
TakeDamage = function(self, damage)
self.HP = self.HP - damage
end,
ShowInfo = function(self)
print("name:", self.name, "HP:", self.HP)
end
}

function CMonster:new(name,hp, x, y)
local obj = {}
obj.name = name
obj.HP = hp
obj.x = x
obj.y = y
setmetatable(obj, {__index = CMonster})
return obj
end

local obj2 = CMonster:new("monster2", 20, 100, 200)
obj2:ShowInfo()--20
obj2:TakeDamage(20)
obj2:ShowInfo()--0
local obj3 = CMonster:new("monster3", 10, 200, 500)
obj3:ShowInfo()--10
obj3:TakeDamage(5)
obj3:ShowInfo()--5

这样已经很接近类的样子了。
这里有一个非常容易出现的错误:attempt to index a nil value (local 'self')
obj2.ShowInfo()改为obj2:ShowInfo()(上面已经修正)
再次改动,{__index = CMonster}等于额外构建了一张表,obj将这个额外构建的表作为云表。可以优化如下:

[code]CMonster = {
name = "Monster",
HP = 100,
TakeDamage = function(self, damage)
self.HP = self.HP - damage
end,
ShowInfo = function(self)
print("name:", self.name, "HP:", self.HP)
end
}
CMonster.__index = CMonster

function CMonster:new(name,hp, x, y)
local obj = {}
obj.name = name
obj.HP = hp
obj.x = x
obj.y = y
setmetatable(obj, CMonster)
return obj
end

上面已经是可以使用的版本了。
再次改写,靠近其他语言类的构建方法,可以利用__call函数在调用时执行。如下:

[code]CMonster = {
name = "Monster",
HP = 100,
TakeDamage = function(self, damage)
self.HP = self.HP - damage
end,
ShowInfo = function(self)
print("name:", self.name, "HP:", self.HP)
end
}
CMonster.__index = CMonster

function CMonster:new(name,hp, x, y)
local obj = {}
obj.name = name
obj.HP = hp
obj.x = x
obj.y = y
setmetatable(obj, CMonster)
return obj
end

setmetatable(CMonster, {__call = function(self, name, hp, x, y)
return self:new(name,hp,x,y)
end
})

local obj2 = CMonster("monster2", 20, 100, 200)
obj2:ShowInfo()--20
obj2:TakeDamage(20)
obj2:ShowInfo()--0
local obj3 = CMonster("monster3", 10, 200, 500)
obj3:ShowInfo()--10
obj3:TakeDamage(5)
obj3:ShowInfo()--5

第四部分 继承

去掉测试部分代码,开头加上local并在最后加上return CMonster,形成文件。如下:

[code]local CMonster = {
name = "Monster",
HP = 100,
TakeDamage = function(self, damage)
self.HP = self.HP - damage
end,
ShowInfo = function(self)
print("name:", self.name, "HP:", self.HP)
end
}
CMonster.__index = CMonster

function CMonster:new(name,hp, x, y)
local obj = {}
obj.name = name
obj.HP = hp
obj.x = x
obj.y = y
setmetatable(obj, CMonster)
return obj
end

setmetatable(CMonster, {__call = function(self, name, hp, x, y)
return self:new(name,hp,x,y)
end
})

return CMonster

在新文件中,引用其他lua文件使用require。如上段代码文件名为2.lua,则使用方法如下:

[code]local CM = require("2")
local cm1 = CM:new("monster1",123,2,3)
cm1:ShowInfo()

可以使用print(package.path)打印引用路径。说白了,就是在这些目录下寻找2.lua文件。
另外注意,如果local CMonster不加上local,那么local cm1 = CMonster:new("monster1",123,2,3)也可行,其原因是2.lua文件中,其未被规定为局部变量。这容易出现问题,所以变量作用域一定要控制。

[code]local CM = require("2")
local cm1 = CM:new("monster1",123,2,3)
cm1:ShowInfo()

local MagicMonster = {
MP = 200,
Attack = function(self)
print("Attack..")
self.MP = self.MP - 10
end
}

setmetatable(MagicMonster, {
__index = CM,
__call = function(self,name,hp,mp,x,y)
local obj = CM(name,hp,x,y)
setmetatable(obj, {__index = MagicMonster})
return obj
end
})

local obj2 = MagicMonster("m1", 800, 22, 33)
obj2:ShowInfo()
obj2:TakeDamage(33)
obj2:ShowInfo()

继承类如上所示。外层setmetatable是MagicMonster的__index指向CM(即CMonster),同时调用时执行这个匿名函数。该匿名函数其实就是MagicMonster的构造函数,等于不再写MagicMonster:new()函数了。内层setmetatable是obj的__index指向MagicMonster。

[code]function MagicMonster:ShowInfo()
print("MagicMonster", self.name, self.HP)
end

 

优化补充:这种可以在构造时,提前传入一个表,构成某些属性。

[code]function ClassA:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end

第五部分 私有成员

通过封装的方式实现私有:

[code]function GetMonster()
local self = {HP = 123, Name = "monster", otherdata = {a=1,b=2}}
local function GetHP()
return self.HP
end
local function TakeDamage(_,count)
self.HP = self.HP - count
end
local function GetName()
return self.Name
end

return {GetHP = GetHP, TakeDamage = TakeDamage, GetName = GetName}
end

local monster = GetMonster()
print(monster:GetHP())--123
monster:TakeDamage(20)
print(monster:GetHP())--103
monster.HP = 10000    --等于在返回的表里增加了一个字段 但对原本的表没有影响
print(monster:GetHP())--103

这种方法对编程不友好。另一种方法是使用原表,把__index、__newindex指向不同的地方。

[code]local Monster = {}
Monster.HP = 100
Monster.x = 123
Monster.y = 456
Monster.Type = "Monster"

function Monster:GetHP()
return self.HP
end
function Monster:TakeDamage(count)
self.HP = self.HP - count
end
function Monster:SetHP(hp)
self.HP = hp
end

function Monster.new()
local obj = { HP = Monster.HP}
setmetatable(obj, Monster)
Monster.__index = {GetHP = Monster.GetHP,
TakeDamage = Monster.TakeDamage,
Type = Monster.Type}
Monster.__newindex = function(tab,key,value)
if key == "Type" then
print("Forbiden operate.")
return
end
rawset(tab,key,value)
end
return obj
end

return Monster
[code]local CMonster = require("4")
local obj = CMonster:new()

print(obj:GetHP())--100
obj:TakeDamage(33)
print(obj:GetHP())--67
--obj:SetHP(200)  --报错
print(obj.Type)
obj.Type = "Mon"  --显示Forbiden operate.
print(obj.Type)   --未修改
print(obj.z)      --nil
obj.z = 10
print(obj.z)      --10 增加字段不影响

print(obj:GetHP())、obj:TakeDamage(33)、print(obj.Type)对应__index的三条。
obj:SetHP(200)不在__index,所以报错。
1c6f4 obj.Type = "Mon"、obj.z = 10对应__newindex,前者报错直接返回了,后者执行rawset(tab,key,value)对表进行了修改。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
klm123698745 发布了10 篇原创文章 · 获赞 0 · 访问量 218 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: