lua面向对象学习

元表和元方法

在Lua中,每个值都有一个元表,table和userdata类型的每个变量都可以有各自独立的元表,
其他类型的值则共享其类型所属的单一元表。

基本的metatable

  • 创建新的table时不会创建元表
  • getmetatable(table) 获取table或者userdata类型变量的元表
  • setmetatable(t,ot) 设置table或者userdata类型变量的元表

    1
    

    2

    3

    4

    5

    6

    local t = {1,1}

print getmetatable(t)     -- nil

local t1 = {}

setmetatable(t,t1)

assert(getmetatable(t) == t1)  

—|—

  • Lua代码中,只能设置table的元表,若要设置其他类型的值的元表,必须通过C代码来完成。

  • 标准的字符串程序库为所有的字符串都设置了一个元表,其他类型在默认情况下没有元表。
  • table中可以重新定义的元方法:

    1
    

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    __add(a,b) --加法

__sub(a,b) --减法

__mul(a,b) --乘法

__div(a,b) --除法

__mod(a,b) --取模

__pow(a,b) --乘幂

__unm(a)   --相反数

__concat(a,b) -- 连接

__len(a)   --长度

__eq(a,b)  --相等

__lt(a,b)  --小于

__le(a,b)  --小于等于

__index(a,b) --索引查询

__newindex(a,b,c) --索引更新

__call(a,...) --执行方法调用

__tostring(a) --字符串输出

__metatable --保护元表  

—|—

  • 当操作符的两个操作数都有元表时的函数获取规则:

    1. 对于二元操作符,如果第一个操作数有元表,并且元表中有所需要的字段定义,那么lua就以这个字段为元方法。
    2. 对于二元操作符,如果第一个操作数有元表,但是元表中没有所需要的字段定义,那么lua就去查找第二个操作数的元表。
    3. 如果两个操作数都没有元表,或者都没有对应的元方法定义,lua就会引发一个错误。
  • 保护元表:

    1
    

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    function Set.new(l)

    local set = {}

    setmetatable(set,mt)

    for _, v in pairs(l) do set[v] = true end

    mt.__metatable = "You cannot get the metatable"

    return set

end

local tb = Set.new({1,2})

print(tb)                -- 输出:{1, 2}

print(getmetatable(tb))  -- 输出__metatable的内容:You cannot get the metatable

setmetatable(tb,{})      -- 输出错误信息:lua: test.lua:56: cannot change a protected metatable  

—|—

  • 当访问一个table中不存在的字段时,lua的处理规则:

    1. 当table有这个字段时,直接返回对应的值;
    2. 当table没有这个字段时,解释器会去查找一个叫__index的元方法,并调用对应的元方法,返回结果
    3. 如果没有这个元方法,返回nil
      __index使用的例子:

           1
      

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

            -- 假设要创建一些描述窗口,每个table中都必须描述一些窗口参数,例如颜色,位置和大小等
    
    Windows = {} -- 创建一个命名空间
    
    -- 创建默认值表
    
    Window.default = {x = 0, y = 0, width = 100, height = 100, color = {r = 255, g = 255, b = 255}}
    
    Window.mt = {} --创建元表
    
    -- 声明构造函数
    
    function Windows.new(0)
    
        setmetatable(o, Window.mt)
    
    end
    
    -- 定义__lindex元方法
    
    Window.mt.__index = function (table, key)
    
        return Windows.default[key]
    
    end
    
    local win = Window.new({x = 10, y = 10})
    
    print(win.x)     -- >10 访问自身已经拥有的值
    
    print(win.width) -- >100 访问default表中的值
    
    print(win.color.r) -- >255 访问default表中的值
    
    -- __index元方法不一定必须是个函数,它还可以是一个table。  

—|—
* newindex元方法,与 index类似, newindex用于更新table中的数据, index用于查询table中的数据。当对table中不存在的索引赋值时,执行步骤如下:

  1. Lua解释器先判断这个table是否有元表,如果没有元表,就直接添加这个索引,然后对应赋值
  2. 如果有了元表,就查找元表中是否有__newindex元方法,如果有这个方法,lua解释器就执行这个元方法,而不是赋值
  3. 如果这个__newindex对应的不是一个函数,而是一个table时,lua解释器就在这个table中执行赋值,而不是在原来的table中   示例代码:

        
                    1
        
        2
        
        3
        
        4
        
        5
        
        6
        
        7
        
        8
        
        9
        
        10
                    local tb1 = {}
        
        local tb2 = {}
        
        tb1.__newindex = tb2
        
        tb2.__newindex = tb1
        
        setmetatable(tb1,tb2)
        
        setmetatable(tb2,tb1)
        
        tb1.x = 10  

—|—

上面的代码存在循环引用的问题,lua解释器会抛出如下错误:

            1
            loop in settable  

—|—

- rawget(tb,i); 直接访问tb属性,不使元表,即忽略__index的定义
- rawset(tb,k,v); 直接设置tb属性,不使用元表,即忽略__newindex的定义

代码示例

记录table的访问



1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25
local t = {} --原来的table

-- 保持对原table的一个引用

local _t = t

-- 创建代理

t = {}

-- 创建元表

local mt = {

__index = function(t, k)

    print("access to element " .. tostring(k))

    return _t[k]

end,

__newindex = function(t, k, v)

    print("update of element " .. tostring(k))

    _t[k] = v

end

}

setmetatable(t, mt)

t.x = 10  -- update of element x

print(t.x) -- access to element x  

—|—
多个table访问日志,使用代理模式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28
-- 创建唯一索引

local index = {}

-- 创建元表

local mt = {

    __index = function(t, k)

        print("access to element " .. tostring(k))

        return t[index][k]

    end,

    

    __newindex = function(t, k, v)

        print("update of element " .. tostring(k))

        t[index][k] = v

    end

}

function track(t)

    local proxy = {}

    proxy[index] = t

    setmetatable(proxy, mt)

    return proxy

end

local t = {}

local proxy = track(t)

proxy.x = 10

print(proxy.x)  

—|—
只读的table(代理模式)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18
function readOnly(t)

    local proxy = {}

    

    -- 创建元表

    local mt = {

    __index = t,

    __newindex = function(t, k, v)

        error("Attempt to update a read-only table", 2)

    end

    }

    

    setmetatable(proxy, mt)

    return proxy

end

local tb = readOnly{1, 2, 3, 4, 5}

print(tb[1])

tb[1] = 20  -- Attempt to update a read-only table  

—|—
`

面向对象

lua中,table就是对象,对象就是表,lua中的类本质上也是对象(表),不过是用来做模版的对象。
lua中的类和javascript中的非常类似,也是采用原型(prototype)的概念。lua通过元表实现原型。
像下面这样:

1
setmetatable(a, {__index = b})  

—|—

lua中方法的调用:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16
local Account = {balance = 0}

function Account.withDraw(self, v)  -- 需要明确定义传入self参数

    self.balance = self.balance - v

end

local a = Account

a.withDraw(a,10) -- 调用函数,需要明确指定self参数

-- 隐藏self参数的语法糖

function Account:withDraw(v) -- 注意,是":"号,且省略了self参数,但是函数仍然可以使用self

    self.balance = self.balance - v   

end

a = Account

a:withDraw(100) -- 注意这里的调用使用":"并且也不需要传入a本身

print(a.balance)  

—|—

参考文章

糖果

糖果
LUA教程

Lapis框架的常用处理方法

Lapis框架的常用处理方法 Continue reading

MoonScript实现选择排序

Published on February 26, 2017

MoonScript与Redis客户端

Published on January 19, 2017