chapter6 深入了解函数
Lua函数是具有特定词法域的第一类值,与其他传统类型的值(string and number)具有相同的权利。
它可以保存在变量和table中,也可以把它当参数传递,也可以作为返回值。
在Lua中有个容易混淆的概念,函数与所有其它值一样都是匿名的,即他们没有名称。
当讨论一个函数名(print),实际上是讨论一个持有某函数的变量。
这与其他变量持有各种值一个道理,可以用多种方式来操作这些变量:
a = {p = print}
a.p("Hello World") -->Hello World
print = math.sin -->"print" 现在引用了sin函数
a.p(print()) -->0.841470
sin = a.p -->"sin" 现在引用了print函数
sin(,) -->10 20
通常我们这样定义一个函数:
function foo (x) return *x end
这种写法仅仅是下面方式的一种简写:
foo = function(x) return *x end
实际上,函数定义就是创建了一个function类型的值,并把它赋值给一个变量。
可以将上面的function(x) body end表达式叫做函数构造式,就像{}是一个table构造式一样。
将这种函数构造式的结果叫作匿名函数。一些场合会用到匿名函数:
table.sort可以接收不同的排序方式,升序、降序、数字顺序、字符排序......
table.sort没有这些方式的函数实现,需要通过传入order function参数。
order function接收两个l列表内元素为参数,当第一个元素需要排在第二个元素之前,返回真并有序返回这两个参数。比如有一个table:
network = {
{name = "grauna", IP = "210.26.20.30"},
{name = "arraial", IP = "210.26.45.33"},
{name = "lua", IP = "210.45.34.56"},
{name = "derain", IP = "210.26.23.76"},
}
如果想以name字段按照反向的字符顺序进行排序,则:
table.sort(network,function (a,b) return (a.name > b.name) end)
--有点不好理解,a.name > b.name是ture 然后function是要排序还是不进行排序。
--但是从测试结果去了解,是要让table.name按照d,c,b,a进行排序
--索性就把(a.name > b.name )当成想要实现的结果,这样好理解一点
把这样的sort函数称作高阶函数,然后用匿名函数来创建高阶函数需要的实参。
另一个用高阶函数的例子,一个函数f在点x的导数(f(x+d)-f(x))/d ,其中d趋向无穷小:
function derivative (f,delta)
delta = delta or 1e-4
return function(x)
return (f(x +delta) -f(x))/delta
end
end
对于特定函数f调用derivative(f)将(近似地)返回其导数:
c = derivative(math.sin)
print(math.cos(5.2),c(5.2))
--> 0.46851667130038 0.46856084325086
print(math.cos(),c())
--> -0.83907152907645 -0.83904432662041
6.1 闭合函数
把一个函数嵌入到另一个函数内,并且它具有访问闭合函数里的局部变量。
有以下代码:
names = {"Peter", "Paul" , "Mary"}
grades = {Mary=,Paul=, Peter=}
table.sort(name,function(n1,n2) return grades[n1] > grades [n2] end
现在,用一个函数去实现这个功能:
function sortbygrade (names,grades)
table.sort(names,function (n1,n2) return grades[n1] > grades[n2] end
end
匿名函数function可以访问grades变量,grades变量是闭合函数sortbygrade的局部变量。
对于匿名函数,grades既不是全局变量,也不是局部变量,我们称它为upvalue——上值。
Why is this point so interesting? Because functions are first-class values,
and therefore they can escape the original scope of their variables.
这是原文,没有理解透彻。待以后再细细品尝。
要对upvalue的深入了解,参考云风博客里的一篇译文:the_implementation_of_lua_50.pdf
参考以下代码:的
function newCounter()
local i =
return function()
i = i +
return i
end
end c1 = newCounter()
print(c1()) -->1
print(c1()) -->2
在这个代码中,匿名函数引用了一个非局部变量i去计数。
当在调用匿名函数时,newCounter函数已经返回,i变量超出作用范围。
对此,Lua用了一个叫做closure的概念去处理这种情况。
closure是一个函数加上所有它需要访问的非局部变量。
当再重新调用newCounter,它会创建一个新的局部变量i,从而得到一个新的closure。
c2 = newCounter()
print(c2()) --> 1
print(c1()) --> 3
print(c1()) --> 2
此时,c1和c2是两个不同的closure函数,各自有独立的局部变量i。
从技术层面讲,Lua中只存在closure,不存在function。
function本身仅仅是closure的原形。
closure是一个很有用的工具,比如在高阶函数sort中作为参数传参。
对创建其他函数的函数也很有用(比如沙盒函数)。
这种机制使Lua程序可以融合那些在函数式编程世界中久经考验的编程技术。
closure对于回调函数也很有用。
一个典型的例子:当你调用传统的GUI 工具创建按钮时。每当用户按下按钮,就会触发一个
回调函数。你希望的是不同的按钮按下时,只做一点轻微不同的操作。
比如一个 十进制的计算器,需要10个相似的按钮,每个数字对应一个。
function digiButton (digit)
return Button { label = tostring(digit),
action = function()
add_to_display(digit)
end
}
end
该例子中,Button是一个创建按钮的函数,label是按钮标签,action是一个closur函数,每
次有按钮按下就会触发。即使在digitButton函数返回,digit变量超出范围。action任然可以
去访问这个digit变量。
函数可以保存在一个变量里,使得它在很多不同环境使用起来很方便。
像以下代码重定义sin函数:
oldSin = math.sin
math.sin = function (x)
return oldSin(x * math.pi/)
end
一个更测底的方法:
do
local oldsin = math.sin
local k = math.pi/
math.sin = function(x)
return oldSin(x * k)
end
end
把原始的sin保存为一个私有变量,只有通过新版本的sin才可以访问到它。
可以使用该技术创建一个安全的运行环境,即所谓的sandbox——”沙盒”。
当运行一些不受信任的代码时会用到该功能。比如限制一个程序访问文件:
do
local oldOpen = io.open
local access_OK = function (filename ,mode)
--check access authority
end
io.open = function(filename,mode)
if access_OK(filename,mode)
return oldOpen(filename,mode)
else
return nil,"access denied"
end
end
end
这样,只能通过新授权的版本才能访问原来的不受限的open函数。
对此Lua提供了一套元机制,可以根据特定的安全需要来创建一个安全的运行环境。
6.2 非全局函数
大多数的Lua库(io.read,math.sin)使用了这样的机制,把函数放在table字段里。
在Lua中创建这样的函数,只需要将函数语法与table语法结合起来即可。
Lib = {}
Lib.foo = function (x,y) return x+y end
Lib.goo = function (x,y) return x-y end
print(Lib.foo(,),Lib.goo(,)) -->5 -1
也可以这样写:
Lib = {
goo = function (x,y) return x+y end,
foo = 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
在程序块中:
local f = function()
<body>
end local g = function()
f() --f在这里可见
end
对于这种局部函数的定义,Lua支持的一种特殊的语法糖:
local function f()
<body>
end
当Lua在展开局部函数定义的语法糖时:并不是使用基本函数定义的语法。
local function foo() <body> end ---->展开后 local foo;foo = function() <body> end
在定义递归函数时,下面的代码是不能运行的:
local fact = function(n)
if n == then return
else return n*fact(n-)
end
end
原因是,当Lua编译fact (n-1)时,local fact 还没有定义。此时该表达式将会去调用全局的fact,而不是local fact。
解决办法:
local fact
fact = function(n)
if n == then return
else return n*fact(n-)
end
end ------------------或者---------------------- local function fact(n)
if n == then return
else return n*fact(n-)
end
end
当函数运行时,fact已经有了确切的值,所以不会产生错误。
当间接使用递归时,你必须明确地使用前向声明。
local f , g
function g()
f()
end function f()
g()
end
注意,别把第二个函数定义为local function f,否则 g 中调用的 f 处于未定义状态。
6.3 正确的尾调用
Lua函数的另一个特性,Lua支持尾调用消除。
当一个函数调用是另一个函数的最后一个动作,该调用就是一个尾调用。
有点类似于goto语句。
比如:
function f(x) return g(x) end
当g返回时,控制权直接返回到调用f的那个点上。
尾调用消除这个特点,使得在进行 “尾调用” 时不消耗任何栈空间。
所以一个程序可以有无数嵌套的“尾调用”
function foo(n)
if n> then
return foo(n-)
end
end
该函数支持任意的数字,都不会出现栈溢出。
想要用尾调用的好处,就必须要保证是尾调用。
以下代码都不是尾调用。
function f(x) g(x) end --在调用g后,f还要丢弃g的返回值
return g(x) + --g返回后,还要再加
return x or g(x) --g返回后,还要再或
return (g(x)) --g返回后,还要调整为一个返回值
在Lua中,只有“return func()” 才是尾调用,Lua会在调用前对func和它的参数求值。
比如下面的代码就是尾调用:
return x[i].foo(x[j] + a*b, i+j)
chapter6 深入了解函数的更多相关文章
- 【Unix网络编程】chapter6 IO复用:select和poll函数
chapter6 6.1 概述 I/O复用典型使用在下列网络应用场合. (1):当客户处理多个描述符时,必须使用IO复用 (2):一个客户同时处理多个套接字是可能的,不过不叫少见. (3):如果一个T ...
- Chapter6:函数
执行函数的第一步是(隐式地)定义并初始化它的形参.所以,函数最外层作用域中的局部变量也不能使用与函数形参一样的名字. 局部静态变量:在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被 ...
- Chapter6(函数) --C++Prime笔记
1.重载函数,也就是说一个名字可以对应几个不同的函数. 2.内置类型的未初始化局部变量将产生未定义的值. 3.局部静态对象在程序执行路径第一次进过对象定义语句时初始化,并且直到程序终止才被销毁. 内置 ...
- 《C++primer》v5 第6章 函数 读书笔记 习题答案
6.1 实参是在函数调用处填写的参数.形参是在函数体使用的参数. 实参是形参的初始值. 具体参见:http://blog.163.com/zhengguo_li/blog/static/7030148 ...
- ###《Effective STL》--Chapter6
点击查看Evernote原文. #@author: gr #@date: 2014-09-27 #@email: forgerui@gmail.com Chapter6 函数子.函数子类.函数及其他 ...
- C++ Primer 5th 第6章 函数
正如第一章所说:C++的函数是一个能够完成一个功能的模块或者说是一段命名了的代码块. 如下图所示,函数可以重载,是一段实现某些功能命名了的代码. 一个完整的函数的构成有四部分: 1.返回类型 2.函数 ...
- 【Linux_Unix系统编程】chapter6 进程
chapter6 进程 重点关注进程虚拟内存的布局及内容.6.1 进程和程序 进程(process)是一个可执行程序(program)的实例. 程序是包含了一系列信息的文件,这些信息描述了如何在运行时 ...
- Python 小而美的函数
python提供了一些有趣且实用的函数,如any all zip,这些函数能够大幅简化我们得代码,可以更优雅的处理可迭代的对象,同时使用的时候也得注意一些情况 any any(iterable) ...
- 探究javascript对象和数组的异同,及函数变量缓存技巧
javascript中最经典也最受非议的一句话就是:javascript中一切皆是对象.这篇重点要提到的,就是任何jser都不陌生的Object和Array. 有段时间曾经很诧异,到底两种数据类型用来 ...
随机推荐
- STM32驱动ht1621b显示LCD
这几天在写ht1621b显示LCD的程序,主芯片是Stm32f10的芯片.对于stm32和ht1621b的运用和操作本人是新手,属于赶鸭子上架,通过查看datasheet等资料和网上查看前人写的程序终 ...
- Attempt to write to field 'android.support.v4.app.FragmentManagerImpl android.support.v4.app.Fragment.mFragmentManager' on a null object reference
E/AndroidRuntime﹕ FATAL EXCEPTION: mainProcess: org.example.magnusluca.drawertestapp, PID: 3624java. ...
- PHP引用操作以及外部操作函数的局部静态变量的方法
通过引用方式在外部操作函数或成员方法内部的静态变量 下面举个简单的例子,说明三个关于引用方面的问题: 1. 参数引用后函数内进行类型转换同样是地址操作 2. 参数引用后再传递给其他函数时需要再次添加引 ...
- CSS 效果汇总
只要决心够, 就能征服痛苦. 把一些常用的 CSS 效果记录下来 1. 利用 z-index :hover 显示层 github 效果地址>> 此效果主要利用 a:hover 来改变 sp ...
- 查看Windows支持的内存大小
cmd命令: wmic memphysical get maxcapacity
- NSXMLParser自定义的一个xml解析工具
// // DenglXMLParser.h // #import <Foundation/Foundation.h> @interface DenglXMLParser : NSXMLP ...
- JavaScript DOM编程艺术-学习笔记(第三章、第四章)
第三章: 1.js的对象分为三种:①用户自定义对象 ② 内建对象(js提供的对象) ③宿主对象(js寄宿的环境-浏览器,提供的对象) 2.文档是由节点组成的集合,即dom树,html元素是根元素,是唯 ...
- Android Studio环境下代码混淆+签名打包
Android Studio环境下代码混淆+签名打包 作者 Mr_冯先生 关注 2016.08.21 01:10 字数 1040 阅读 734评论 5喜欢 34 注:本文使用的Android Stud ...
- ssh 依赖关系
安装ssh时: sudo apt-get install openssh-server 出现错误: 下列软件包有未满足的依赖关系: openssh-server : 依赖: openssh-clien ...
- android 一些数据转换方法
android 一些数据转换方法 package com.ai9475.util; import android.content.Context; import android.content.res ...