Lua 知识点整理
经过几个月的学习和使用,对 Lua 有了更深的理解,以下整理参考自 Programming in Lua 这本书,挑出来一些常用的知识点以及重写了部分示例代码,最后有一些不错的文章推荐。
第一篇 语法
第三章 表达式
3.6 table 的构造
-- list类型table
table = {1, 2, 3, 4, 5}
-- table中含有table
table = {
{x=0, y=0},
{x=-10, y=0},
{x=-10, y=1},
{x=0, y=1}
}
--字典类型table
table = {color="blue", thickness=2, npoints=4}
--混合类型
table = {color="blue", thickness=2, npoints=4,
{x=0, y=0},
'second',
[1] = 'first', -- 无法覆盖原来的值,所以无卵用
[7] = 'seventh',
12581,
{x=-10, y=0},
{x=-10, y=1},
{x=0, y=1}
}
for k, v in pairs(table) do -- ipairs遍历不出字典型和已经0索引和非连续索引
print(k, v)
end
-- 运行结果
1 table: 0x2386ab0
2 second
3 12581
4 table: 0x2386b40
5 table: 0x2386bd0
6 table: 0x2386c60
thickness 2
color blue
npoints 4
7 seventh
第四章 基本语法
4.1 赋值语句
a, b, c = 0, 1
print(a,b,c) --> 0 1 nil
a, b = a+1, b+1, b+2 -- value of b+2 is ignored
print(a,b) --> 1 2
a, b, c = 0
print(a,b,c) --> 0 nil nil
a, b = f() --f()返回两个值,第一个赋给 a,第二个赋给 b
4.3 控制结构语句
-- if
if conditions then
then-part
elseif conditions then
elseif-part
else
else-part
end
-- while
while condition do
statements;
end
-- repeat-until
repeat
statements
until conditions
-- 数值for 默认step=1
for var = startIndex,endIndex,step do
loop-part
end
-- 泛型for
for i,v in ipairs(a) do print(v) end
for k in pairs(t) do print(k) end
4.4 break 和 return 语句
- Lua 语法要求 break 和 return 只能出现在 block 的结尾一句
- 也就是说:作为 chunk 的最后一句,或者在 end 之前,或者 else 前,或者 until 前
local i = 1
while a[i] do
if a[i] == v then
break
end
i = i + 1
end
- 有时候为了调试或者其他目的需要在 block 的中间使用 return 或者 break,可以显式 的使用 do…end 来实现
function foo ()
return --<< SYNTAX ERROR
-- 'return' is the last statement in the next block
do return end -- OK
... -- statements not reached
end
第五章 函数
5.1 多返回值
- 当作为表达式调用函数时,有以下几种情况:
- 当调用作为表达式最后一个参数或者仅有一个参数时,根据变量个数函数尽可能;
- 多地返回多个值,不足补 nil,超出舍去。
- 其他情况下,函数调用仅返回第一个值(如果没有返回值为 nil);
- 另外,return f()这种形式,则返回“f()的返回值”;
- 可以使用圆括号强制使调用返回一个值。
function foo0 () end -- returns no results
function foo1 () return 'a' end -- returns 1 result
function foo2 () return 'a','b' end -- returns 2 results
function foo (i)
if i == 0 then return foo0()
elseif i == 1 then return foo1()
elseif i == 2 then return foo2()
end
end
print(foo(2)) --> a b
print(foo(0)) -- (no results)
print(foo(3)) -- (no results)
print((foo0())) --> nil
print((foo1())) --> a
- 函数多值返回的特殊函数 unpack,接受一个数组作为输入参数,返回数组的所有元素
5.2 可变参数
- Lua 函数可以接受可变数目的参数,在函数参数列表中使用三点(…)表示函数有可变的参数。Lua 将函数的参数放在一个叫 arg 的表中,除了参数以外,arg 表中还有一个域 n 表示参数的个数。
-- ... 代表可变参数
function g (a, b, ...) end
CALL PARAMETERS
g(3) a=3, b=nil, arg={n=0}
g(3, 4) a=3, b=4, arg={n=0}
g(3, 4, 5, 8) a=3, b=4, arg={5, 8; n=2}
-- 可以利用可变参数声明一个 select 函数
function select (n, ...)
return arg[n]
end
print(string.find("hello hello", " hel")) --> 6 9
print(select(1, string.find("hello hello", " hel"))) --> 6
print(select(2, string.find("hello hello", " hel"))) --> 9
5.3 命名参数
- Lua 的函数参数是和位置相关的,调用时实参会按顺序依次传给形参。Lua 可以通过将所有的参数放在一个表中,把表作为函数的唯一参数来实现参数命名。
w = Window {
x=0, y=0, width=300, height=200,
title = "Lua", background="blue",
border = true
}
-- 把表作为函数的唯一参数来实现参数命名
function Window (options)
-- check mandatory 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
-- everything else is optional
_Window(options.title,
options.x or 0, -- default value
options.y or 0, -- default value
options.width, options.height,
options.background or "white", -- default
options.border -- default is false (nil)
)
end
第六章 再论函数
- Lua 中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。
- 第一类值指:在 Lua 中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。
- 词法定界指:嵌套的函数可以访问他外部函数中的变量。这一特性给 Lua 提供了强大的编程能力。
- Lua中的函数可以是匿名的,当提到函数名(比如 print),实际上是说一个指向函数的变量,像持有其他类型值的变量一样。
p = print;
print = math.sin;
p(print(1)) -- print -> math.sin; p -> print ====> print(math.sin(1))
-- 详细解释
a = {p = print}
a.p("Hello World") --> Hello World = print("Hello World")
print = math.sin -- `print' now refers to the sine function
a.p(print(1)) --> 0.841470
sin = a.p -- `sin' now refers to the print function
sin(10, 20) --> 10 20
- 既然函数是值,那么表达式也可以创建函数了。
- 函数定义实际上是一个赋值语句,将类型为 function 的变量赋给一个变量。我们使用 function (x) … end 来定义一个函数和使用{}创建一个表一样。
function foo (x) return 2*x end -- 表达式创建函数
foo = function (x) return 2*x end -- 原本的函数
- 以其他函数作为参数的函数在 Lua 中被称作高级函数,高级函数与普通函数没有区别,它们只是把“作为参数的函数”当作第一类值(first-class value)处理而已。例如table 标准库提供一个排序函数,接受一个表作为输入参数并且排序表中的元素,和接受一个排序函数作为参数,这个排序函数需要接受两个排序元素作为输入参数,并且返回两者的大小关系,table的排序函数将根据输入的排序函数对整个表格进行排序。
names = {"Peter", "Paul", "Mary"}
grades = {Mary = 10, Paul = 7, Peter = 8}
-- 按照成绩对names进行排名
table.sort(names, function (n1, n2)
return grades[n1] > grades[n2] -- compare the grades
end)
for k,v in pairs(names) do print(k,v) end
6.1 闭包
- 闭包实现的基础是函数是第一类值和词法定界
- 简单的说,闭包是一个函数以及它的 upvalues
- 技术上来讲,闭包指值而不是指函数,函数仅仅是闭包的一个原型声明;尽管如此,在不会导致混淆的情况下我们继续使用术语函数代指闭包
function newCounter()
local i = 0
return function() -- 匿名函数
i = i + 1 -- i 即不是全局变量也不是局部变量,称为upvalue
-- 当我们调用匿名函数的时候, i 已经超出了作用范围,因为创建 i 的函数 newCounter 已经返回了
-- 再次调用 newCounter时,将创建一个新的局部变量 i,因此我们得到了一个作用在新的变量 i 上的新闭包
return i
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
-- c1、c2 虽建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个闭包是独立的
c2 = newCounter()
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2
- 闭包也可用在回调函数中,比如在 GUI 环境中你需要创建一系列 button,用户按下 button 时回调函数被调用。
- 例如,一个十进制计算器需要 10 个相似的按钮,每个按钮对应一个数字
function digitButton (digit)
return Button{
label = digit,
action = function () -- 按钮被按下时调用的回调函数,实际上是一个闭包,因为它访问 upvalue digit
add_to_display(digit) -- upvalue
end
} -- digitButton 完成任务返回后,局部变量 digit 超出范围,回调函数仍然可以被调用并且可以访问局部变量 digit
end
button_one = digitButton(1)
print(button_one.action) -- 显示 add_to_display(1)
button_two = digitButton(2)
print(button_two.action) -- 显示 add_to_display(2)
- 闭包在完全不同的上下文中也是很有用途的。因为函数被存储在普通的变量内我们可以很方便的重定义或者预定义函数。
- 通常当你需要原始函数有一个新的实现时可以重定义函数。例如你可以重定义 sin 使其接受一个度数而不是弧度作为参数:
- 利用同样的特征我们可以创建一个安全的环境(沙箱),使用闭包重定义 io 库的 open 函数来限制程序打开的文件
-- 重定义 sin 使其接受一个度数而不是弧度作为参数
print(math.sin(math.pi/180*90)) --> 1
-- 把原始版本放在一个局部变量内,访问 sin 的唯一方式是通过新版本的函数
oldSin = math.sin
math.sin = function (x)
return oldSin(x*math.pi/180)
end
-- 更清晰的版本
do
local oldSin = math.sin
local k = math.pi/180
math.sin = function (x)
return oldSin(x*k)
end
end
print(math.sin(90)) --> 1
6.2 非全局函数
- 函数作为局部变量函数(作为 table 的域)
- 当我们将函数保存在一个局部变量内时,我们得到一个局部函数,也就是说局部函数像局部变量一样在一定范围内有效
-- 1. 表和函数放在一起
Lib = {}
Lib.foo = function (x,y) return x + y end
Lib.goo = function (x,y) return x - y end
-- 2. 使用表构造函数
Lib = {
foo = function (x,y) return x + y end,
goo = function (x,y) return x - y end
}
-- 3. Lua 提供另一种语法方式
Lib = {}
function Lib.foo (x,y)
return x + y
end
function Lib.goo (x,y)
return x - y
end
- 声明局部函数的方式
local function f () --f 必须写在 g 的前面
print('f')
end
local function g ()
print('g')
f() -- external local `f' is visible here
end
g() --> g f
- 定义局部函数时要先声明然后定义才可以
local fact -- 不先声明会出错
-- local fact = function(n)
fact = function (n)
if n == 0 then
return 1
else
return n*fact(n-1) -- (buggy) attempt to call a nil value (global 'fact')
end
end
print(fact(5))
-- 定义局部函数时要先声明然后定义才可以
local f, g -- `forward' declarations
cnt = 0
function g ()
print(cnt)
cnt = cnt + 1
if cnt == 10 then return end
f()
end
function f ()
print(cnt)
cnt = cnt + 1
if cnt == 10 then return end
g()
end
g()
6.3 正确的尾调用
- 尾调用是一种类似在函数结尾的 goto 调用,当函数最后一个动作是调用另外一个函数时,我们称这种调用尾调用。例如:
- g 的调用是尾调用例子中 f 调用 g 后不会再做任何事情,这种情况下当被调用函数 g 结束时程序不需要返回到调用者 f;所以尾调用之后程序不需要在栈中保留关于调用者的任何信息。一些编译器比如 Lua 解释器利用这种特性在处理尾调用时不使用额外的栈。
function f(x)
return g(x) -- g 的调用是尾调用
end
- 由于尾调用不需要使用栈空间,那么尾调用递归的层次可以无限制的
function foo (n)
if n > 0 then return foo(n - 1) end
end
- 如果没有正确的尾调用,每次移动都要创建一个栈,多次移动后可能导致栈溢出。但正确的尾调用可以无限制的尾调用,因为每次尾调用只是一个 goto 到另外一个函数并不是传统的函数调用。
第七章 迭代器与泛型for
7.1 迭代器与闭包
- 代器需要保留上一次成功调用的状态和下一次成功调用的状态,也就是他知道来自于哪里和将要前往哪里。闭包提供的机制可以很容易实现这个任务。
- 闭包是一个内部函数,它可以访问一个或者多个外部函数的外部局部变量。每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)。当然如果要创建一个闭包必须要创建其外部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)。
function list_iter (t)
local i = 0
local n = table.getn(t)
return function ()
i = i + 1
if i <= n then
return t[i]
end
end
end
- 这个例子中 list_iter 是一个工厂,每次调用他都会创建一个新的闭包(迭代器本身)。闭包保存内部局部变量(t,i,n),因此每次调用他返回 list 中的下一个元素值,当 list 中没有值时,返回 nil。
- 我们可以在 while 语句中使用这个迭代器
t = {10, 20, 30}
iter = list_iter(t) -- creates the iterator
while true do
local element = iter() -- calls the iterator
if element == nil then
break
end
print(element)
end
- 范性 for 为迭代循环处理所有的薄记(bookkeeping):首先调用迭代工厂;内部保留迭代函数,因此我们不需要 iter 变量;然后在每一个新的迭代处调用迭代器函数;当迭代器返回 nil 时循环结束(后面我们将看到范性 for 能胜任更多的任务)
--范性 for 语句
t = {10, 20, 30}
for element in list_iter(t) do
print(element)
end
7.2 泛型for的语句
- 避免上一节迭代器每次调用都需要创建一个闭包的弊端
- 在循环过程中范性 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量
-- 范性 for 的文法
-- <var-list>是以一个或多个逗号分隔的变量名列表
-- <exp-list>是以一个或多个逗号分隔的表达式列表,通常情况下 exp-list 只有一个值
for <var-list> in <exp-list> do
<body>
end
-- 迭代工厂的调用
-- k, v 为变量列表
-- pair(t)为表达式列表
for k, v in pairs(t) do
print(k, v)
end
-- 在很多情况下变量列表也只有一个变量
-- 变量列表中第一个变量为控制变量,其值为 nil 时循环结束
for line in io.lines() do
io.write(line, '\n')
end
- 泛型for的执行过程
- 初始化,计算 in 后面表达式的值,表达式应该返回范性 for 需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用 nil 补足,多出部分会被忽略;
- 将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说,、状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数);
- 将迭代函数返回的值赋给变量列表;
- 如果返回的第一个值为 nil 循环结束,否则执行循环体;
- 回到第二步再次调用迭代函数
for var_1, ..., var_n in explist do block end
等价于
-- 如果我们的迭代函数是 f,状态常量是 s,控制变量 var 的初始值是 a0
-- 那么控制变量将循环:a1=f(s,a0)、a2=f(s,a1)、……,直到 ai=nil
do
local _f, _s, _var = explist
while true do
local var_1, ... , var_n = _f(_s, _var)
_var = var_1
if _var == nil then
break
end
block
end
end
7.3 无状态的迭代器
- 无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。 每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
- 这种无状态迭代器的典型的简单的例子是 ipairs,它遍历数组的每一个元素:
- 当 Lua 调用 ipairs(a)开始循环时,他获取三个值:迭代函数 iter、状态常量 a、控制变量初始值 0;
- 然后 Lua 调用 iter(a,0)返回 1,a[1](除非 a[1]=nil);
- 第二次迭代调用 iter(a,1) 返回 2,a[2] …… 直到第一个非 nil 元素
a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end
-- 迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标控制变量)
-- ipairs 和迭代函数都很简单,在 Lua 中可以这样实现
function iter (a, i)
i = i + 1
local v = a[i]
if v then
return i, v
end
end
function ipairs (a)
return iter, a, 0
end
-- 解释
- Lua 库中实现的 pairs 是一个用 next 实现的原始方法
- 使用 pairs 的时候,exp-list 返回结果会被调整为三个,所以 Lua 获取 next、t、nil
-- pairs 的实现方式
function pairs (t)
return next, t, nil
end
-- 直接使用next调用pairs
t = {1, 2, 3}
for k, v in next, t do
print(k,v)
end
7.4 多状态的迭代器
- 很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包;
- 还有一种方法就是将所有的状态信息封装到 table 内,将 table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函
数通常不需要第二个参数; - 我们应该尽可能的写无状态的迭代器,因为这样循环的时候由 for 来保存状态,不需要创建对象花费的代价小;如果不能用无状态的迭代器实现,应尽可能使用闭包;尽可能不要使用 table 这种方式,因为创建闭包的代价要比创建 table 小,另外 Lua 处理闭包要比处理 table 速度快些。
7.5 真正的迭代器
- 迭代器的名字有一些误导,因为它并没有迭代,完成迭代功能的是 for 语句,也许更好的叫法应该是生成器(generator);
- 有一种方式创建一个在内部完成迭代的迭代器。这样当我们使用迭代器的时候就不需要使用循环了;我们仅仅使用每一次迭代需要处理的任务作为参数调用迭代器即可,具体地说,迭代器接受一个函数作为参数,并且这个函数在迭代器内部被调用。
- 更一般的做法是我们使用匿名函数作为作为参数,下面的例子打印出单词’hello’出现的次数:
local count = 0
allwords(function (w)
if w == "hello" then count = count + 1 end
end)
print(count)
-- 用 for 结构完成同样的任务
local count = 0
for w in allwords() do
if w == "hello" then count = count + 1 end
end
print(count)
第九章 协同程序
- 协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈,自己的局部变量,有自己的指令指针,与其它协同程序共享全局变量等很多信息。
- 线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程;而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只在必要时才会被挂起。
9.1 协同的基础
- Lua 的所有协同函数存放于 coroutine table 中:
-- create 函数用于创建新的协同程序
-- 参数为一个函数,即协同程序将要运行的代码
-- 返回值为 thread 类型,表示创建成功
co = coroutine.create(function ()
print("hi")
end)
print(type(co)) --> thread
print(co) --> thread: 0x55b548f79dd8
-- 协同有三个状态:挂起态(suspended)、运行态(running)、停止态(dead)
-- 当我们创建协同程序成功时,其为挂起态,即此时协同程序并未运行
-- 我们可用 status 函数检查协同的状态:
print(coroutine.status(co)) --> suspended
-- 函数 coroutine.resume 使协同程序由挂起状态变为运行态
coroutine.resume(co) --> hi
-- 协同程序打印出"hi"后,任务完成,便进入终止态
print(coroutine.status(co)) --> dead
- yield 函数可以将正在运行的代码挂起
co = coroutine.create(function ()
for i=1,5 do
print("co", i)
coroutine.yield() -- yield 函数,它可以将正在运行的代码挂起
end
end)
-- 执行这个协同程序,程序将在第一个 yield 处被挂起
coroutine.resume(co) --> co 1
print(coroutine.status(co)) --> suspended
-- 从协同的观点看:使用函数 yield 可以使程序挂起
-- 当我们激活被挂起的程序时,将从函数 yield 的位置继续执行程序,直到再次遇到 yield 或程序结束
coroutine.resume(co) --> co 2
coroutine.resume(co) --> co 3
coroutine.resume(co) --> co 4
coroutine.resume(co) --> co 5
coroutine.resume(co) -- prints nothing
print(coroutine.status(co)) --> dead
-- 上面最后一次调用时,协同体已结束,因此协同程序处于终止态
-- 如果我们仍然希望激活它,resume 将返回 false 和错误信息
-- 注意:resume 运行在保护模式下,因此,如果协同程序内部存在错误,Lua 并不会抛出错误,而是将错误返回给 resume 函数
print(coroutine.resume(co)) --> false cannot resume dead coroutine
- Lua 中协同的强大能力,还在于通过 resume-yield 来交换数据
-- 第一个例子中只有 resume,没有 yield,resume 把参数传递给协同的主程序
co = coroutine.create(function (a,b,c)
print("co", a,b,c)
end)
coroutine.resume(co, 1, 2, 3) --> co 1 2 3
-- 第二个例子,数据由 yield 传给 resume,true 表明调用成功,true 之后的部分,即是 yield 的参数
co = coroutine.create(function (a,b)
coroutine.yield(a + b, a - b)
end)
print(coroutine.resume(co, 20, 10)) --> true 30 10
-- 相应地,resume 的参数,会被传递给 yield
co = coroutine.create (function ()
print("co", coroutine.yield())
end)
coroutine.resume(co) --> true 30 10
coroutine.resume(co, 4, 5) --> co 4 5
-- 最后一个例子,协同代码结束时的返回值,也会传给 resume
co = coroutine.create(function ()
return 6, 7
end)
print(coroutine.resume(co)) --> true 6 7
- Lua的协同称为不对称协同,指“挂起一个正在执行的协同函数”,与“使一个被挂起的协同再次执行的函数”是不同的,有些语言提供对称协同,即使用同一个函数负责“执行与挂起间的状态切换”。
第二篇 tables 与 objects
第11章 数据结构
- table 是Lua 中唯一的数据结构,其他语言所提供的数据结构,如:arrays、records、lists、queues、sets 等,Lua 都是通过 table 来实现,并且在 lua 中 table 很好的实现了这些数据结构。
11.1 数组
- 在 lua 中通过整数下标访问 table 中元素,即是数组。并且数组大小不固定,可动态增长。
- 数组下标可以根据需要,从任意值开始。 然而习惯上,Lua 的下标从 1 开始。Lua 的标准库遵循此惯例,因此你的数组下标必须也是从 1 开始,才可以使用标准库的函数。
a = {}
-- 数组下标可以从任意值开始
for i=-5, 5 do
a[i] = 0
end
-- 用构造器在创建数组的同时初始化数组
squares = {1, 4, 9, 16, 25, 36, 49, 64, 81}
11.2 矩阵和多维数组
- Lua 中有两种表示矩阵的方法:
- 一是“数组的数组”。也就是说,table 的每个元素是另一个 table;
- 二是将行和列组合起来。如果索引下标都是整数,通过第一个索引乘于一个常量(列)再加上第二个索引;
- 例如,可以使用下面代码创建一个 n 行 m 列的矩阵:
-- 方法一
mt = {} -- create the matrix
for i=1,N do
-- 由于Lua 中table 是对象,所以每一行我们必须显式地创建一个table
mt[i] = {} -- create a new row
for j=1,M do
mt[i][j] = 0
end
end
-- 方法二
mt = {} -- create the matrix
for i=1,N do
for j=1,M do
mt[i*M + j] = 0
end
end
- Lua 中用 table 实现的数据本身天生的就具有稀疏的特性,只需要存储那些非 nil 的元素。例如用方法一
for j=1,M do
生成三角矩阵,仅使用一半的内存空间。
11.3 链表
- Lua 中用 tables 很容易实现链表,每一个节点是一个 table,指针是这个表的一个域field),并且指向另一个节点(table)。例如,要实现一个只有两个域:值和指针的基本链表,代码如下:
list = nil -- 根节点
list = {next = list, value = v} -- 在链表开头插入一个值为 v 的节点
-- 要遍历这个链表只需要
local l = list
while l do
print(l.value)
l = l.next
end
11.4 队列和双向队列
- 虽然可以使用 Lua 的 table 库提供的 insert 和 remove 操作来实现队列,但这种方式实现的队列针对大数据量时效率太低,有效的方式是使用两个索引下标,一个表示第一个元素,另一个表示最后一个元素:
-- 使用两个索引下标,一个表示第一个元素,另一个表示最后一个元素
List = {}
function List.new ()
return {first = 0, last = -1}
end
-- 在常量时间内,完成在队列的两端进行插入和删除操作
function List.pushleft (list, value)
local first = list.first - 1
list.first = first
list[first] = value
end
function List.pushright (list, value)
local last = list.last + 1
list.last = last
list[last] = value
end
function List.popleft (list)
local first = list.first
if first > list.last then error("list is empty") end
local value = list[first]
list[first] = nil -- to allow garbage collection
list.first = first + 1
return value
end
function List.popright (list)
local last = list.last
if list.first > last then error("list is empty") end
local value = list[last]
list[last] = nil -- to allow garbage collection
list.last = last - 1
return value
end
list = List.new()
List.pushright(list,1)
List.pushright(list,2)
List.pushright(list,3)
print(List.popleft(list))
print(List.popleft(list))
print(List.popleft(list))
第十六章 面向对象程序设计
16.0 冒号调用和点调用
冒号的效果相当于在函数定义和函数调用的时候,增加一个额外的隐藏参数。这种方式只是提供了一种方便的语法,实际上并没有什么新的内容。我们可以使用 dot 语法定义函数而用冒号语法调用函数,反之亦然,只要我们正确的处理好额外的参数:
-- 冒号调用和dot调用的区别
Account = { balance=0 }
function Account:printBalance()
print(self.balance)
end
-- 使用dot定义函数而用冒号语法调用函数
function Account.deposit (self, v)
self.balance = self.balance + v
self:printBalance()
end
Account:deposit(200.00)
account = Account
account.deposit(account, account.balance)
-- 使用冒号语法定义函数而用dot语法调用函数
function Account:deposit (v)
self.balance = self.balance + v
self.printBalance(self)
end
Account.deposit(Account, 200.00)
account = Account
account:deposit(account.balance)
16.1 类
- Lua 中对象没有类。相反,每个对象都有一个
prototype
(原型),当调用不属于对象的某些操作时,会最先会到prototype
中查找这些操作。 - 让对象 b 作为对象 a 的元表,对象 a 调用任何不存在的成员都会到对象 b 中查找,因此可以将 b 看作类,a 看作对象。
Account = { balance=0 }
function Account:deposit (v)
self.balance = self.balance + v
self:printBalance()
end
function Account:printBalance()
print(self.balance)
end
-- 实例化一个对象,可以将Account看作一个类,o看作一个对象
-- 对象o调用任何不存在的成员都会到Account中查找
function Account:new (o)
o = o or {} -- create object if user does not provide one
setmetatable(o, self)
self.__index = self
return o
end
a = Account:new({balance = 120})
for k,v in pairs(a) do
print(k,v)
end
-- a 中没有 deposit,回到 metatable 的__index 对应的表中查找
-- 由于a 的metatable 是Account,Account.__index 也是Account
-- a:deposit(100.00) --> getmetatable(a).__index.deposit(a, 100.00) --> Account.deposit(a, 100.00)
a:deposit(100.00) --> 220
b = Account:new()
--> b 中没有balance,找的是元表Account中的balance
for k,v in pairs(b) do print(k,v) end
-- b:deposit(100.00) --> b.balance = b.balance + 100
b:deposit(100.00) --> 100
-- 下一次问这个balance的时候,不会在涉及到 index 元方法,因为 b 已经存在 balance 域
for k,v in pairs(b) do print(k,v) end --> b 中有balance,值为默认值 0 + 100
16.2 继承
Account = {balance = 0}
-- Account:new (o) 和 Account:deposit (v) 同上
function Account:withdraw (v)
if v > self.balance then
error"insufficient funds"
end
self.balance = self.balance - v
end
-- 从基类派生出一个子类 SpecialAccount,这个子类允许客户取款超过它的存款余额限制
SpecialAccount = Account:new()
-- SpecialAccount 重定义从父类中继承来的方法
function pSpecialAccount:withdraw (v)
if v - self.balance >= self:getLimit() then
error"insufficient funds"
end
self.balance = self.balance - v
end
function SpecialAccount:getLimit ()
return self.limit or 0
end
-- SpecialAccount 从 Account 继承了 new 方法,当 new 执行的时候,self 参数指向 SpecialAccount
-- 所以,s 的 metatable 是 SpecialAccount,__index 也是 SpecialAccount
-- 这样,s 继承了 SpecialAccount,SpecialAccount 继承了 Account
s = SpecialAccount:new{limit=1000.00}
s:deposit(100.00) -- 调用的是 Account 中的 deposit
s:withdraw(200.00) -- 调用的是 SpecialAccount 中重定义的 withdraw
print(s.balance) --> 100 - 200 (limit = 10000) = - 100
-- 如果仅仅一个对象需要特殊的行为,你可以直接在对象中实现,而不需要创建一个新类去指定一个新的行为
-- 如果账号 s 表示一些特殊的客户:取款限制是他的存款的 10%,
function s:getLimit ()
return self.balance * 0.10
end
-- 调用 s:withdraw(200.00)将运行 SpecialAccount 的 withdraw 方法
-- 但是当方法调用 self:getLimit 时,s 的 getLimit被触发
s:withdraw(200.00)
16.3 多重继承
-- look up for `k' in list of tables 'plist'
local function search (k, plist)
for i = 1, table.getn(plist) do
local v = plist[i][k] -- try 'i'-th superclass
if v then
return v
end
end
end
function createClass (...)
local c = {} -- new class
local parents = { ... } -- list of parents
-- class searches for absent methods in its list of parents
setmetatable(c, { __index = function(t, k)
return search(k, parents)
end })
-- prepare 'c' to be the metatable of its instances
c.__index = c
-- define a new constructor for this new class
function c:new (o)
o = o or {}
setmetatable(o, c)
return o
end
return c -- return new class
end
Named = {}
function Named:getname ()
return self.name
end
function Named:setname (n)
self.name = n
end
NamedAccount = createClass(Account, Named)
account = NamedAccount:new{name = "Paul"}
print(account:getname()) --> Paul
其它文章
给大佬点赞