快速掌握Lua 5.3 —— 函数

Q:Lua中如何定义以及调用函数?

A:

function foo(arg1, arg2, ...)
  dosomething
  return ret1, ret2, ... or nothing
end

-- add all elements of array 'a'.
function add (a)
  local sum = 0
  for i,v in ipairs(a) do
    sum = sum + v
  end
  return sum
end

-- call it.
a = {1, 2, 3, 4}
foo(a)

Q:如何以面向对象的方式调用函数?

A:o:foo(x),其等同于o.foo(o, x)

Q:Lua编写的函数如何返回多个返回值?

A:在return的后面写多少个值,就可以让函数返回多少个值。

function maximum (a)
  local mi = 1          -- maximum index
  local m = a[mi]       -- maximum value
  for i,val in ipairs(a) do
    if val > m then
      mi = i
      m = val
    end
  end
  return m, mi
end

Q:Lua如何控制函数的返回值?

A:
1、当你以一条语句的形式调用函数时,lua忽略函数所有的返回值(因为也没有变量接收)。

foo()

2、当函数作为一系列表达式中最后一个表达式被调用时,lua保留函数所有的返回值。这一系列表达式包括:一个表达式对多个变量赋值,函数的参数传递,表的构造以及作为另一个函数的返回值。

function foo() return 'a','b' end    -- returns no results

-- 一个表达式对多个变量赋值
x, y = foo()    --> x = 'a', y = 'b'
x, y, z = foo()    --> x = 'a', y = 'b', z = nil
x, y, z = 10, foo()    --> x = 10, y = 'a', z = 'b'
x, y, z = foo(), 10    --> x = 'a', y = 10, z = nil -- foo()不是表达式的最后,所以只有第一个返回值被保存。

-- 函数的参数传递
print(foo())    --> a    b
print(1, foo())    --> 1    a    b
print(foo(), 1)    --> a    1
-- 注意!这里有个特殊情况:
print(foo() .. "x")    --> ax
print("x" .. foo())    --> xa -- 注意这里也只保留了第一个返回值。
-- 推测:".."右边只接收一个参数,foo()所提供的多余的参数被丢弃了。

-- 表的构造
a = {1, foo()}    --> a[1] = 1, a[2] = 'a', a[3] = 'b'
a = {foo(), 1}    --> a[1] = 'a', a[2] = 1

-- 作为另一个函数的返回值
function foo1()
  return 5, foo()
end
function foo2()
  return foo(), 5
end
print(foo1())    --> 5    a    b
print(foo2())    --> a    5

3、其他情况下,lua只会保留函数的第一个返回值(在上面已经看到了一些情况)。

Q:如何强制限定函数只返回第一个返回值?

A:使用”()”将函数调用括起来,

function foo() return 'a','b' end

print((foo()))    --> a

Q:如何定义以及使用可变参数函数?

A:使用...

-- 用lua自己实现C语言中的printf()。
function printf(fmt, ...)
  --[[ select (index, ···)
       如果index是一个数字,那么返回可变参数中第index个参数之后的所有参数值。
       负的index代表从可变参数中最后一个参数开始计算index(-1是最后一个参数)。
       否则,如果index只能是"#",select()会返回可变参数的总个数。]]
  for i = 1, select('#', ...) do    -- select()返回可变参数的总个数。
    -- 这里select()返回了第i个参数之后所有的参数值,但多余的值被抛弃了。
    arg[i] = select(i, ...)
  end

  -- string.format()格式化字符串;io.write()向标准输出写。
  return io.write(string.format(fmt, table.unpack(arg)))
end

Q:Lua中的函数是一种”first-class values”,什么是”first-class values”?

A:与传统的变量(比如数字和字符串)拥有相同的权限。可以被存储在变量中,可以被存储在表中,可以作为参数传递,可以作为函数的返回值(C语言中的”函数指针”就有这些特性)。在Lua中函数被看作一种值,Lua中所说的函数名实际上是存储函数的变量的名字。

Q:Lua中的函数具有”lexical scoping”特性,什么是”lexical scoping”?

A:函数可以访问包裹它的函数的值,

function foo()
   a=1
   foo1()    -- foo1()中可以访问a。
end

Q:什么是”Proper Tail Calls”?

A:”Proper Tail Calls”是一种特性,实现方式类似于C语言中”goto”调用。当一个函数的最后一个动作是调用另一个函数时,被调用的函数就具有”Proper Tail Calls”特性。

--[[ g()就是f()的"Proper Tail Calls"。
     当在f()中调用完g()后,没有必要再返回到f()中,因为f()没有任何事要做了。
     函数调用就是入栈出栈的过程,支持这种特性可以在递归函数的调用中节省大量的栈空间。]]
function f(x)
  return g(x)
end

Q:”Proper Tail Calls”的实际应用?

A:一个解谜小游戏,

function room1 ()
  local move = io.read()
  if move == "south" then return room3()
  elseif move == "east" then return room2()
  else print("invalid move")
       return room1()   -- stay in the same room
  end
end

function room2 ()
  local move = io.read()
  if move == "south" then return room4()
  elseif move == "west" then return room1()
  else print("invalid move")
       return room2()
  end
end

function room3 ()
  local move = io.read()
  if move == "north" then return room1()
  elseif move == "east" then return room4()
  else print("invalid move")
       return room3()
  end
end

function room4 ()
  print("congratulations!")
end

-- 可以从room1开始游戏。
room1()

south
west
invalid move
east
congratulations!

Q:什么是”Closures”?

A:一个匿名函数,他能够访问包含他的”chunk”中的局部变量。”Closures”用到了”lexical scoping”以及”Proper Tail Calls”特性。

function newCounter ()
  local i = 0
  -- 只有返回匿名函数才会有这种特性,如果返回一个已定义的函数,不会有这种特性。
  return function ()
    i = i + 1
    return i
  end
end

c1 = newCounter()           -- c1 is a "closure".
print(c1())  --> 1
print(c1())  --> 2

c2 = newCounter()           -- c2 is another "closure".
print(c2())  --> 1
print(c1())  --> 3
print(c2())  --> 2

Q:什么是”factory”?

A:创建”Closures”的函数。上面的newCounter()就是一个”factory”。

Q:什么是”sandbox”?

A:利用”Closures”所创建的安全的lua运行环境。从Internet上获取的lua程序在读懂它的源码前,我们不清楚它的安全性,不清楚它是否在其内部有一些危险的实现(比如读取隐私文件,删除系统文件等)。”Closures”的优势就在于它可以允许我们访问包含匿名函数的”chunk”中的局部变量,所以我们可以利用这一特性对一些敏感的函数做一些限制,从而创建一个安全的”sandbox”。

do
  local oldOpen = io.open
  io.open = function (filename, mode)
    --[[ access_OK()中可以列出允许访问的文件名,以及这些文件所允许的访问方式。
         只有access_OK()返回"true"财允许调用原先的io.open()打开文件。]]
    if access_OK(filename, mode) then
      -- 可以访问包含匿名函数的"do-end"中的局部变量oldOpen。
      return oldOpen(filename, mode)
    else
      return nil, "access denied"
    end
  end
end
-- [[这样做的强大之处在于,将原先的io.open()封装在内部,
     外部程序调用的是我们创建的"sandbox"中安全的io.open(),
     而外部程序想要访问原先的io.open(),也只能通过调用我们规定的io.open(),别无它法。]]

Q:如何将函数存储在table中?

A:

-- 使用table构造的形式。
Lib = {
  foo = function (x,y) return x + y end,
  goo = function (x,y) return x - y end
}

-- 使用赋值table中元素的形式。
Lib = {}
Lib.foo = function (x,y) return x + y end
Lib.goo = function (x,y) return x - y end

-- 使用创建函数的方式。
Lib = {}
function Lib.foo (x,y)
  return x + y
end
function Lib.goo (x,y)
  return x - y
end

Q:如何创建本地函数?

A:使用local关键字。

local function f(arg1, arg2, ...)
  dosomething

  return ret1, ret2, ... or nothing
end

Q:如何定义一个本地的递归函数?

A:

-- 直接递归函数。
-- 方式1
local fact
fact = function (n)
  if n == 0 then return 1
  else return n*fact(n-1)
  end
end

-- 方式2
local function fact (n)
  if n == 0 then return 1
  else return n*fact(n-1)
  end
end

-- 间接递归函数。
local f, g    -- 定义间接递归函数时,必须先声明本地变量。

function g ()
  ...  f() ...
end

function f ()
  ...  g() ...
end

附加:

1、lua中的函数如果只有一个参数,并且这个参数是字符串常量或者是一个表的构造,则函数调用时括号可以不写:

print "Hello World"     <-->     print("Hello World")
dofile 'a.lua'          <-->     dofile ('a.lua')
print [[a multi-line    <-->     print([[a multi-line
 message]]                        message]])
f{x=10, y=20}           <-->     f({x=10, y=20})
type{}                  <-->     type({})

建议写上比较好,又规范,又不容易混乱。
2、调用函数时,如果实参的数量多于形参的数量,则多余的实参会被忽略;而如果实参的数量少于形参的数量,剩余的形参值为nil。

function foo(a, b)
  print(a, b)
end

foo(5, 6, 7)    --> 5   6
foo(5)    --> 5 nil

3、注意,当函数return一个表达式时,并不需要像C语言那样为了规范而加上一个括号,

function foo() return 'a','b' end
-- 这样些只能得到foo()的第一个返回值,这也许是你想要的,也许不是。
function foo1() return (foo()) end

4、通过table实现可选参数函数。

--[[ 对于参数很多的函数,有时很难记住参数的名字和参数的顺序以及哪些参数是可选的。
     通过table让你在调用这类函数时可以随意指定参数的顺序,并且可以只传递需要设定的参数。]]
function create_window(options)
  -- 只有必须的参数做检查,非必须的参数下面会给出默认值。
  if type(options.title) ~= "string" then
    error("no title")
  elseif type(options.width) ~= "number" then
    error("no width")
  elseif type(options.height) ~= "number" then
    error("no height")
  end

  _create_window(options.title,
          options.x or 0,    -- default value is 0
          options.y or 0,    -- default value is 0
          options.width, options.height,
          options.background or "white",   -- default is "white"
          options.border      -- default is false (nil)
         )
end

5、既然function被看作一种可以被变量存储的值,那么也就可以按照变量赋值的操作来创建以及使用函数:

foo = function (x) return 2*x end
print(foo(5))    --> 10

6、一些被误认为是”Proper Tail Calls”的情况:

-- 在调用g()之后,还需要丢弃g()的返回才能return。
function f()
  g()
  return
end

-- 在调用g()之后,还需要做加法。
function f()
  return g() + 1
end

-- 在调用g()之后,还需要做"or"操作。
function f()
  return x or g()
end

-- 在调用g()之后,还需要截取g()的第一个返回值。
function f()
  return (g())
end

7、”Closures”的一个应用,将math.sin()转换为接收角度值,

do
  local oldSin = math.sin    -- 原先的math.sin()接收弧度值。
  local k = math.pi/180    -- 弧度转角度系数。
  math.sin = function (x)    -- 新的math.sin()接收角度值。
    return oldSin(x*k)    -- 弧度转为角度调用原先的math.sin()。
  end
end

8、函数存储在表中方便管理。比如可以将函数以及其所需数据都存储在一个table中,他们形成一个整体来管理。
9、以下定义本地的递归函数的方式是错误的,

local fact = function (n)
  if n == 0 then return 1
  else return n*fact(n-1)   -- buggy
  end
end

当Lua编译”n*fact(n-1)”时,”local fact”还并不存在。因此这样写的结果是使用了全局的”fact”,而非那个”local fact”。为了解决这个问题,需要”Q & A”中提到的正确的方式那样,先定义”local fact”。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值