23 August 2015
版权声明:本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名elloop(包含链接)

lua function初学者参考

认识lua的function

function是lua基本类型之一, 看下面一小段代码如何来定义一个函数,并且如何来确定一个东西是一个函数:

-- define a function type variable f.
function f()
    print("hello")
end

-- output type of variable f.
print(type(f))          -- output: function

-- call function f.
f()                     -- output: hello

通过上面的代码可以看出:

  • 使用function关键字来定义一个函数

  • 使用type来返回一个变量的类型(string), “function”返回值即代表它是一个函数

  • 使用函数名加一对括号的形式来调用函数,这与其他语言是一样的

type是个什么东西?使用过c系语言的同学可能认为它是个操作符,像sizeof那样。其实,type是一个函数,可以使用print(type(type))检查,你会看到输出会是”function”.

上面代码中的函数f, 其实就是一个变量,类似c语言里的函数指针变量,除了上面的写法我们还可以使用显示的定义变量的形式来定义函数:

-- 第二种定义函数的语法形式
f = function() 
    print("hello")
end
print(type(f))  -- 输出: function
f()             -- 输出:hello

这两种定义的写法是等价的

table中的function

当函数变量是table中的一个字段的时候,我们使用下面的形式来定义函数:

local t = { x = 10 }
function t.f()
    print(t.x)
end

--[[ or
t.f = function() 
    print(t.x) 
end
--]]

t.f()   -- output: 10

面向对象风格的function

lua可以借助table实现面向对象编程,而面向对象的基本要素就是类,学过c++的同学都知道类是有成员变量和成员函数组成的。那么lua里面的成员函数是怎么实现的呢?

当然是使用function了,只不过这里又多了一种定义function的语法形式:

local t = { x = 10 }

function t:f()
    print(self.x)
end

t:f()

上面的代码使用了表名+冒号的形式定义了一个函数f,调用的时候也是使用冒号t:f()的形式. self是调用者传给f的第一个参数(隐含的), 就像c++里的this,它代表了调用此函数的table本身,比如在上面的代码中,使用t:f()的方式调用f, self就等于t,self.x就是t.x

不过,如果使用t.f()这种形式来调用f会是什么结果?答案是会报错。

t.f()

-- error:
stdin:1: attempt to index local 'self' (a nil value)
stack traceback:
    stdin:1: in function 'f'
    stdin:1: in main chunk
    [C]: ?

可以看到,lua解释器说self是nil。这是因为t.f()这种形式并没有传递任何参数到f,因此在f函数体内的self没有被赋值,即为nil。而t:f()这种形式其实等价于:t.f(t), 把自己传入了f函数,这样f就会把第一个参数绑定到self,这样就不会报错了。下面我们使用t.f(t)试一下:

t.f(t) -- output: 10

这回没有报错了。综上所述,我们得到以下结论:

  • 结论1:当使用function t:f(args) end这种形式定义函数f的时候,合法的调用形式有两种:1. t:f(args); 2. t.f(t, args)

如果table中的函数不是使用冒号的定义形式呢?即如果t.f是如下的定义形式,但是使用t:f()的方式调用呢?

local t = { x = 10 }
function t.f()
    print(t.x)
end

t:f() -- what will happen?

上面这段代码会正确的输出10,相信你已经想到了,见到t:f()翻译成t.f(t)就ok了,因此这个调用不会报错,只不过是多传了一个没用的参数到f而已

这是不是代表能使用t.f调用的地方都可以使用t:f代替呢?答案是不能完全代替,看下面的例子:

local t = { x = 10 }
function t.add(a)
    t.x = t.x + a
end

t.add(100)
print(t.x)  -- output: 110

t:add(100)    -- is this ok?
print(t.x)

你能想到上面t:f(100)之后的输出结果吗? 答案是:

stdin:1: attempt to perform arithmetic on local 'a' (a table value)
stack traceback:
    stdin:1: in function 'add'
    stdin:1: in main chunk
    [C]: ?

请忽略报错信息中的行号,因为我是使用终端交互方式测试的lua代码。

我们把t:add(100)展开一下,结果是t.add(t, 100), 这意味着add的形参a会被赋值为t,而不是100,结果add的函数体被替换为t.x = t.x + t, 并不是我们想要的t.x = t.x + 100. t.x和t相加就会得到上面的报错信息(通常情况下,一个number和一个table是不能相加的)

通过上面这个例子,我们知道t:f这种调用方式的使用范围是有一定的限制的,它强行把t绑定到f的第一个形参,因此一旦f是一个带参数的函数,使用t:f这种调用方式是肯定会出错的(除非f的第一个形参就是想要用t)

  • 结论2:当使用function t.f(args) end这种形式定义函数f的时候,应该使用t.f(args)这种调用形式.

只要记住t:f() == t.f(t)就不会出现上面提到的调用问题了。

t:f()这种调用方式是为了实现面向对象风格程序设计而提供的语法糖,本质上的函数调用方法只有一种:t.f(args)

要意识到一个事情,function t.f() end 这种形式的函数定义,其函数体内部是没有self这个变量的。而function t:f() end 这种形式,只要是合理的调用,其内部是有一个self变量的。

local t = { x = 10 }
function t.f()
    if self then
        print(self.x)
    else
        print("self is nil in t.f() ")
    end
end

function t:g()
    if self then
        print(self.x)
    else
        print("self is nil in t:g()")
    end
end

t.f(t)              -- output: self is nil in t.f()
t.g(t)              -- output: 10
t.g()               -- output: self is nil in t:g()

通过上面的例子可以看出,t:g()形式的函数定义,其实是有一个隐含的形参self的, 而t.f()形式的定义则没有这个形参. 下面的代码就是t:g()的等价定义形式.

function t.g(self)
    if self then
        print(self.x)
    else
        print("self is nil in t:g()")
    end
end

定义和调用形式总结

  • 定义中,t:f(args) 可以用 t.f(self, args) 来等价代替

  • 调用中,t:f(args) 可以用 t.f(t, args) 来等价代替


在这里也能看到这篇文章:github博客, CSDN博客, 欢迎访问



分享到