lua元表元方法

Posted 一乐乐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lua元表元方法相关的知识,希望对你有一定的参考价值。


lua元表、元方法

lua官方参考手册:https://www.runoob.com/manual/lua53doc/manual.html#2.4



一、总结:

☺ 1、普通的表,找不到了,或者无法进行运算的时候,考虑设置到它身上的元表的元方法

2、元表的本质:其实元表本质上就是普通的表,它只是在功能上和别人不一样!实际上,它还是一张普通的表

3、元表的作用:定义原始值在特定操作下的行为

4、元方法:其实就是元表里的一些函数



二、元表、元方法

1、元表的使用,举例子:

  • 当tb是一张表的时候,直接对它进行加法运算,会保错!
tb = a = 1
print(tb + 1)
  • 结果,会报错:attempt to perform arithmetic on a table value (global \'tb\')
  • 解决:我们使用元表,定义一下,定义一个元方法 __add
tb = a = 1
-- 新建一个元表(元表其实就是普通的表,只是功能和普通表不一样)
mt = 
	-- 元表中的元方法__add
	__add = function(a, b)
		return a.a + b
	end

-- 给tb这张表绑定上元表,这样普通表的加法就被替换成元表的__add 方法
setmetatable(tb, mt)
print(tb + 1)
  • 结果:

    2


2、给普通的表设置上元表的函数 setmetatable(table, metatable)


3、元表决定了一个对象在数学运算、位运算、比较、连接、 取长度、调用、索引时的行为。

(1) 数学运算:加减乘除、取模、次方、取负、向下取整、按位与、按位或、按位异或、左移、右移

  • 接下来是元表可以控制的事件的详细列表。 每个操作都用对应的事件名来区分。 每个事件的键名用加有 \'__\' 前缀的字符串来表示; 例如 "add" 操作的键名为字符串 "_add"。

__add: + 操作。 如果任何不是数字的值做加法, Lua 就会尝试调用元方法。

比如下面的:tb 是表不是数字,不能直接做加法,lua就会去看看 tb 表是不是有设置上了元表mt,然后才会去看看元表mt中的元方法,发现是有加法__add 这个元方法的存在,然后就调用该加法元方法。

tb = a = 1
print(tb + 1)

4、重要且特殊的元方法 __index

(1) 作用:

table[key],当table不是表或是表table中不存在key这个键时,这个事件被触发。此时,会读出table相应的元方法。

(2) 举例子:

  • 当表tb中不存在索引是b的时候,tb[b] 最终会是nil
tb = a = "hello"
print(tb[b])
  • 解决:我们给tb设置上元表,然后元表定义一下,定义一个元方法 __index,这样当索引找不到的时候,就会去调用元方法
tb = a = "hello"
--定义一个元表
mt = 
	--定义一个元方法__index
	__index = function(table, key)
		return "hi,boy"	
	end


-- 给tb这张表绑定上元表
setmetatable(tb, mt)
print(tb[\'b\'])
  • 结果:hi,boy

(3) __index 细节:这个事件的元方法,其实可以是一个函数也可以是一张表!

  • 比如上面的
--定义一个元表
mt = 
	--定义一个元方法__index
	__index = 
        a = 10,
        b = 5,
    

  • 结果:print(tb[\'b\']) 返回的是 5
  • 如果 print(tb[\'b\']) 返回的是 hello,因为在tb表就可以找到,只有找不到,才会考虑设置在它身上的元表

5、元方法 __newindex

(1) 特点:赋值时触发

一旦有了"__newindex"元方法,Lua就不再做最初的赋值操作。(如果有必要,在元方法内部可以调用rawset方法来做赋值。)

  • 举例子:lua 原先的赋值方式如下:
tb = a = "hello"
tb[\'b\'] = 10
print(tb[\'b\'])
  • 结果是:10

  • 给tb表身上设置上元表,并且元表内有一个元方法 __newindex

tb = a = "hello"
--定义一个元表
mt = 
	--定义一个元方法__newindex
	__newindex = function(t, k, v) --因为————newindex 会覆盖给原来的表进行赋值的操作,即覆盖操作tb[\'b\'] = 10
	end

setmetatable(tb, mt)
tb[\'b\'] = 10
print(tb[\'b\'])
  • 结果是:nil【因为在表身上设置上了元表,而元表存在了__newindex 方法,原先的赋值方式,就不生效了】
  • 解决:在元方法内部可以调用rawset方法来做赋值
tb = a = "hello"
--定义一个元表
mt = 
	--定义一个元方法__newindex
	__newindex = function(t, k, v)
		rawset(t,k,v) --发现,tb[\'b\'] = 10 又可以正常赋值了
	end

setmetatable(tb, mt)
tb[\'b\'] = 10
print(tb[\'b\'])

(2) 为什么要使用rawset

  • 不使用rawset,可能会导致堆栈溢出


  • 使用rawset的原因:可以避免触发元方法__index



文章参考:

B站视频,作者-每日喝粥《【Lua】元表、元方法、面向对象》 https://www.bilibili.com/video/BV1f44y1a7Gk/




如果本文对你有帮助的话记得给一乐点个赞哦,感谢!

quick-cocos2d-xLua 面向对象(OOP)编程与元表元方法

版权声明:本文为博主原创文章,转载请注明出处。

 

  面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。 
早期的计算机编程是基于面向过程的方法,通过设计一个算法就可以解决当时的问题。随着计算机技术的不断提高,计算机被用于解决越来越复杂的问题。面向过程式的编程思想很难良好的解决这些复杂的问题,通过面向对象的方法,更利于用人理解的方式对复杂系统进行分析、设计与编程。同时,面向对象能有效提高编程的效率,通过封装技术,消息机制可以像搭积木的一样快速开发出一个全新的系统。 
  但是随着软件行业的飞速发展,渐渐的有人开始觉得面向对象编程让代码显的非常复杂,工程庞大。开始有一部分人回归到了面向过程编程,开始运用面向对象和面向过程结合的方式进行开发。 
  面向对象最主要的两个概念就是对象和类,能够充分理解这两个概念的人自然也很容易理解面向对象的编程思想。 
  所谓对象,就是用来描述客观存在的一个实体,它是构成系统的一个基本单位。一个对象应该由若干属性组成,这些属性用于描述这个实体的信息,比如某个人的名字。另外还应该包含一些方法,用于描述实体的动作,比如某个人吃饭的过程。 
  类是具有相同属性和方法的所有对象实体的抽象,类的内部为这些对象提供了统一的抽象描述。比如所有人都有名字,那么“人”这个类就应该包含一个属性叫做人名。所有的人都有这个属性,类的方法也是一样的道理。 
  Lua语言中没有类的定义,也不能定义类,但是我们可以使用table来模仿对象,如下:

 1     --  定义一个表来模拟对象,将id和name看成的它的属性
 2     local person = {id = 2156, name = "yzn",}
 3     --  给对象添加方法
 4     function person.sayHello(self) 
 5         print("你好,我的名字是" .. self.name)
 6     end
 7     - - 访问属性
 8     print(person.id)
 9     - -  调用方法 
10     person:sayHello()   

  接下来我们来访问person的属性和调用sayHello方法。发现无论是语法还是效果上面都和Java等其它语言操作对象的方式极其类似。但是值得注意的是我在调用方法的时候person后面用的是冒号(:)来调用sayHello方法,而不是使用一点。这其实是Lua的一个语法糖衣,使用冒号调用的时候Lua会自动将调用者当成函数的第一个参数的实参传递到函数内部去。所以我就没有传递参数给sayHello的self参数那边。 
  也是就是:

1 person:sayHello()

  与

1 person.sayHello(person)

  是一样的效果,冒号和点号只是用来决定是否需要手动传递第一个参数而已。 
  现在我们来看下类是如何模拟出来的:

 1     -- 定义一个Person表,当成类。添加一个属性id,和一个方法toString
 2     Person = {
 3         id          = "1", 
 4         toString    = function(p)
 5             print("id = ", p.id)
 6         end
 7     }
 8 
 9     -- 给Person再添加一个new函数,用于实例化对象。
10     function Person.new()
11         -- 定义一个表,当成被实例化出来的对象
12         obj = {}
13         -- 设置表的元表
14         setmetatable(obj, Person)
15         -- 设置__index方法为自身
16         Person.__index = Person
17         -- 返回创建的实例对象
18         return obj 
19     end
20 
21     -- 调用new函数实例化对象
22     local obj = Person.new() 
23     -- 访问属性
24     print("obj.id = ", obj.id)
25     -- 调用方法
26     obj:toString()

  其实Lua中就是通过设置元表的方式来实现继承关系的,这里所谓的类和对象,其实就是让一个obj表继承Person这个表(设置obj的元表为Person)。然后设置元方法__index为Person,这样我们访问obj的id属性时,它在自己的表中没有找到就会调用元方法__index去元表里面找,也就是去Person里面去找,然后返回了Person的id属性的值,方法调用也是一样的。如果没有设置__index,那么没有找到会直接返回nil。 
  元表和元方法,是Lua内置的一些操作。Lua的每个变量都具有一张元表,我们可以通过getmetatable(var)函数得到对象的元表。而对于setmetatable()方法,设置元表只能针对于table类型的变量。要修改设置其他值的元表,我们需要通过C的API来实现。 
  table默认是没有设置其元表的,我们可以通过以下代码进行验证:

1     local tab1 = {1, 2, 3}
2     print(getmetatable(tab1))  -- 输出nil    

  其实所谓的元表就是一个普通的表,我们现在来设置下tab1的元表:

1     local tab1  = {1, 2, 3}
2     -- 定义一个普通的空表作为元表
3     local mt    = {}
4     setmetatable(tab1, mt)
5     print(getmetatable(tab1))  -- 输出table  

  我们甚至可以给上面的mt添加一个项,然后获取出tab1的元表进行dump查看是否就是mt:

1     local tab1  = {1, 2, 3}
2     -- 定义一个普通的空表作为元表
3     local mt    = { id = 7, }
4     setmetatable(tab1, mt)
5     dump(getmetatable(tab1))  -- 输出表里面的所有内容

  我在quick-player模拟器上面运行,控制台窗口输入的内容如下图所示。 

技术分享

  我们可以清晰的看到确实是设置元表成功了。 
  接下来我们来重载Lua的运算符。Lua的运算符是通过元方法进行计算的,元方法分为算数元方法和关系元方法。Lua的元方法都是以双下划线开头的,比如上面提到的__index等。 
所谓的算数元方法就是在进行算数运算的时候会被调用的方法。比如进行加法的__add和减法的__sub。我们这里以加法运算符为例,在进行重载加法运算符(+)之前,对两个table进行相加肯定活报错。所以,我们现在先对加法进行重载,代码如下:

 1     local tab1  = {"a", "b", "c"}
 2     -- 定义一个普通的空表作为元表
 3     local mt    = {}
 4     -- 设置tab1的元表为mt
 5     setmetatable(tab1, mt)
 6     -- 重写元方法 
 7     mt.__add    = function(t1, t2)
 8         local t = {}
 9 
10         for i, v in ipairs(t1) do
11             table.insert(t, v)
12         end
13 
14         for i, v in ipairs(t2) do
15             table.insert(t, v)
16         end
17 
18         return t 
19     end
20 
21     -- 定义另一个table
22     local tab2  = {"d", "e",}
23 
24     -- 对tab进行加法运算,会触发__add元方法
25     local tab3  = tab1 + tab2 
26     -- 打印计算后的结果
27     dump(tab3)

  运行后看到打印的tab3的值如下所示: 

技术分享

  可见我们重载是成功了,其他的元方法重载和加法的基本一致。

 

以上是关于lua元表元方法的主要内容,如果未能解决你的问题,请参考以下文章

lua中的元表---metatable

lua中的元表---metatable

类与对象

quick-cocos2d-xLua 面向对象(OOP)编程与元表元方法

Lua元表和元方法DaemonCoder

Lua中的元表与元方法