Lua知识树整理————编程实操
编程实操
-
-
基础概念
- “第一类值” 意味着 Lua 语言中的函数与其他常见类型的值具有同等权限:一个程序可以将某个函数保存到变量中或表中,也可以将某个函数作为参数传递给其他函数,还可以将某个函数作为其他函数的返回值返回。
- ”词法定界“ 意味着 Lua 语言中的函数可以访问包含其自身的外部函数中的变量(也意味着 Lua 语言完全支持 Lambda 演算)。
-
函数是第一类值
-
如前所述,Lua 语言中的函数是第一类值。以下的示例演示了第一类值的含义
a = {p = print} a.p("Hello World") print = math.sin a.p(print(1)) math.sin = a.p math.sin(10, 20)
如果函数也是值的话,那么是否有创建函数的表达式呢?答案是肯定的。事实上,Lua 语言中常见的函数定义方式如下:
function foo(x) return 2*x end --就是所谓语法糖的例子,它只是下面这种写法的一种美化形式: foo = function (x) return 2*x end
在 Lua 语言中,所有的函数都是匿名的。像其他所有的值一样,函数并没有名字。当讨论函数名时,比如 print,实际上指的是保存该函数的变量。虽然我们通常会把函数赋值给全局变量,从而看似给函数起了一个名字。但在很多场景下仍然会保留函数的匿名性。比如table.sort可以传递匿名函数
-
-
非全局函数
-
由于函数是一种”第一类值“,因此一个显而易见的结果就是:函数不仅可以被存储在全局变量中,还可以被存储在表字段和局部变量中。
我们已经在前面的章节中见到过几个将函数存储在表字段中的示例,大部分 Lua 语言的库就采用了这种机制(例如io.read
和math.sin
)。如果要在 Lua 语言中创建这种函数,只需将到目前为止我们所学到的知识结合起来:Lib = {} Lib.foo = function (x, y) return x + y end Lib.goo = funtion(x, y) return x - y end Lib = { foo = function (x, y) return x + y end, goo = function (x, y) return x - y end, } --除此之外,Lua 语言还提供了另一种特殊的语法来定义这类函数 Lib = {} function Lib.foo(x, y) return x + y end function Lib.goo(x, y) return x - y end --在表字段中存储函数是 Lua 语言中实现面向对象编程的关键要素
-
-
词法定界
-
当编写一个被其他函数 B 包含的函数 A 时,被包含的函数 A 可以访问其他函数 B 的所有局部变量,我们将这种特性称为词法定界。虽然这种可见性规则听上去很明确,但实际上并非如此。
-
先看一个简单的例子。假设有一个表,其中包含了学生的姓名和对应的成绩,如果我们想基于分数对学生姓名排序,分数高者在前,那么可以使用如下的代码完成上述需求:
names = {"Peter", "Paul", "Mary"} grades = {Mary = 10, Pual = 7, Peter = 8} table.sort(names, 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
在后一个示例中,有趣的一点就在传给函数 sort 的匿名函数可以访问 grades,而 grades 时包含匿名函数的外层函数 sortbygrade 的形参。在该匿名函数中,grades 既不是全局变量也不是局部变量,而是我们所说的非局部变量。(由于历史原因,在 Lua 语言中非全局变量也被称为 Up-value)。
这一点之所以如此有趣是因为,函数作为第一类值,能够逃逸出它们变量的原始定界范围。考虑如下的代码:function newCounter() local count = 0 return function () count = count + 1 return count end end c1 = newCounter() print(c1()) --> 1 print(c1()) --> 2 c2 = newCounter() print(c2()) --> 1 print(c1()) --> 3 print(c2()) --> 2
在上述代码中,匿名函数访问了一个非局部变量 count 并将其当作计数器。然而,由于创建变量的函数已经返回,因此当我们调用匿名函数时,变量 count 似乎已经超出了作用范围。但其实不然,由于闭包概念的存在,Lua 语言能够正确地应对这种情况。简单地说,一个闭包就是一个函数外加能够使该函数正确访问非局部变量所需的其他机制。如果我们再次调用 newCounter,那么一个新的局部变量 count 和一个新的闭包会被创建出来,这个新的闭包针对的是这个新变量,因此,c1 和 c2 是不同的闭包。它们建立在相同的函数之上,但是格子拥有局部变量的独立实例。
-
从技术上讲,Lua 语言中只有闭包而没有函数。函数本身只是闭包的一种原型。不过尽管如此,只要不会引起混淆,我们就仍将使用术语”函数“来指代闭包。
-
闭包在许多场合中均是一种有价值的工具。正如我们之前已经见到过的,闭包在作为诸如 sort 这样的高阶函数的参数时就非常有用(类似Lambda )
-
闭包在另一种很不一样的场景下也非常有用。由于函数可以被保存在普通变量中,因此在 Lua 语言中可以轻松地重新定义函数,甚至是预定义函数。这种机制也正是 Lua 语言灵活地原因之一。
local oldSin = math.sin math.sin = function (x) return oldSin(x * (math.pi / 180)) end --假设要重新定义函数 sin 以使其参数以角度为单位而不是以弧度为单位。那么这个新函数就可以先对参数进行转换,然后再调用原来的 sin 函数进行真正的计算。
我们可以使用同样的技巧来创建安全的运行时环境,即所谓的沙盒。当执行一些诸如从远程服务器上下载到的未受信任代码时,安全的运行环境非常重要。例如,我们可以通过使用闭包重定义函数 io.open 来限制一个程序能够访问的文件:
do local oldOpen = io.open local access_OK = function(filename, mode) --check access end io.open = function(filename, mode) if(access_OK(filename, mode)) then return oldOpen(filename, mode) else return nil, "access denied" end end end --在经过重新定义后,一个程序就只能通过新的受限版本来调用原来未受限版本的 io.open 函数
-
-
-
-
模式匹配相关函数
-
函数 string.find
函数
string.find
具有两个可选参数。第三个参数是一个索引,用于说明从目标字符串的哪个位置开始搜索。第 4 个参数是一个布尔值,用于说明是否进行简单搜索。string.find("a [word]","[")-->malformed pattern (missing ']') string.find("a [word]", "[", 1, true) --由于 '[' 在模式中具有特殊含义,因此第 1 个函数调用会报错。在第 2 个函数调用中,函数只是把 '[' 当作简单字符串。请注意,如果没有第 3 个参数,是不能传入第 4 个参数的。
-
-
模式
字符 匹配 . 任意字符 %a 字母 %c 控制字符(所谓的控制字符指的是换行、退格、删除、转义、制表等等,空格并不包含在控制字符中) %d 数字 %g 除空格外的可打印字符 %l 小写字母 %p 标点符号 %s 空白字符 %u 大写字母 %w 字母和数字 %x 十六进制数字 -
模式的字母改成大写就表示类的补集,例如,'%A'表示任意非字母的字符。
-
在模式匹配中,还有一些被称为魔法字符的字符具有特殊含义。Lua 语言的模式所使用的魔法字符包括:
() . % + - * ? [] ^ $
-
在这些字符前面加百分号可以用来转移这些字符。我们不仅可以用百分号对魔法字符进行转义,还可以将其用于其他所有字母和数字外的字符。当不确定是否需要转义的时候,为了保险起见就可以使用转义符。
-
函数 string. gsub的第3个参数不仅可以是字符串,还可以是一个函数或表。当第3个参数是一个表时,函数string.gsub会吧第一个捕获到的内容当做建,然后将表中对应键的值作为替换的字符串。当第 3参数是一个函数时, 函数 string.gsub 会在每次找到匹配时调用调用该函数,参数是捕获到的内容而返回值则被作为替换字符串 如果函 的返回值为 nil 或表中不包含这个键或表中键的对应值 nil ,那么函数gsub不改变这个匹配
-
-
练习
--请编写一个函数 split,该函数接收两个参数,第 1 个参数是字符串, --第 2 个参数是分隔符模式,函数的返回值是分隔符分割后的原始字符串中每一部分的序列: --t = split("a whole new world", " ") --> t = {"a", "whole", "new", "world"} function split(input,delimiter) input = tostring(input) delimiter = tostring(delimiter) local pos,arr = 1 , {} for fStart, fEnd in function() return string.find(input,delimiter,pos,true) end do table.insert(arr,string.sub(input,pos,fStart)) pos = fEnd + 1 end table.insert(arr,string.sub(input,pos)) return arr end local t = split("a whole new world", " ") --> t = {"a", "whole", "new", "world"} print(table.unpack(t)) --请编写一个函数 transliterate,该函数接收两个参数,第 1 个参数是字符串,第 2 个参数是一个表。 --函数 transliterate 根据第二个参数中的表使用一个字符替换字符串中的字符。 --如果表中将 a 映射为 b,那么该函数则将所有 a 替换为 b。如果表中将 a 映射为 false,那么该函数则把结果中的所有 a 移除 function transliterate( str, tab ) for a, b in pairs(tab) do if b then str = string.gsub(str, tostring(a),tostring(b)) else str = string.gsub(str, tostring(a), "") end end return str end t = {a = "1", b = "2", c = "3", d = false} str = "aaabbbcccddd" print(transliterate(str,t)) --重写这个函数使得其时间复杂度为 O(n) function trim(s) s = string.gsub(s, "^%s*(.-)%s*$","%1") return s end str = "aaabbbcccddd"
-