(一)Lua开篇
本来是想研究下lua源码的,但是在看了云风的<<Lua 源码欣赏>>之后,觉得自己的level太低,跟不上楼主的节奏。
无奈又看了下<<The Implementation of Lua 5.0>>,从中吸取了部分营养。
也看了这个网站http://www.zhihu.com/question/20617406
在<<Lua 源码欣赏>>中提到LuaJIT的作者建议了一个lua源码的阅读顺序,首先应熟悉external C API。
https://www.reddit.com/comments/63hth/ask_reddit_which_oss_codebases_out_there_are_so/c02pxbp
lua的源码虽说只有2万多行,但是含量很高,所以没有一定的基础很难拿下,无奈打算从如下书籍开始。
以前学的时候囫囵吞枣,现在又重新的捡起来,这次英文版与以前的中文版一比较,天壤之别,应了那句话:一千个人有一千个哈姆雷特。
这里记录下这本书中的重要内容
chapter1 Getting Started
p5 => 标识符可以是字母,数字,下划线组成的任意字符,但不能以数字开头,建议最好不要以下划线跟大写字母,比如_VERSION,这样的标识符通常是保留的。这在C中很正常,之所以提到这个,是因为在table值的定义中“x"=10这样的key是string,如果是特殊的标识符,只能使用["+"]="add"这样的方式。
p6 => 提到一个很有用的块注释方式,正常的块注释是--[[和]], 但一般使用 --[[和--]],因为这样的注释在想取消注释的时候非常方便,只需在一开始的符号前加一个'-' ,如下
---[[
print(10) --> 10
--]]
后面还提到,如果要注释的内容中已经包括了这样的块注释后怎么办,可以使用--[==[和]==]这样的方式,只要中间的’=‘号个数相同即可,如
--[===[
a = 5
--[[
print(10) --> 10
--]]
--]===]
p8 => 提到在使用如下的命令行方式使用lua时,相应的命令行参数被装载在一个叫arg的环境变量中,如下
》lua -e "sin=math.sin" script a b
则
arg[-3] = "lua"
arg[-2] = "-e"
arg[-1] = "sin=math.sin"
arg[0] = "script"
arg[1] = "a"
arg[2] = "b"
这个特性可以用来打印出调用的文件名,比如
在一个脚本文件中hello.lua中输入print(arg[0]),当通过命令行调用lua "hello.lua”时会打印出文件名称,这在测试中打印出测试的文件名有一定的作用。
chapter 2 Types and Values
p12 => 提到可以使用#取得字符串的长度,后面也提到可以使用#取得连续table的长度,但#不能用于有nil hole的table中。
a = "hello"
print(#a) --> 5
print(a[#a]) -- prints the last value of sequence 'a'
a[#a] = nil -- removes this last value
a[#a + 1] = v -- appends 'v' to the end of the list
p13 => 长字符串可使用[[和]],以避免使用转义字符,如下
page = [[
<html>
<head>
<title>An HTML Page</title>
</head>
<body>
<a href="http://www.lua.org">Lua</a>
</body>
</html>
]]
print(page)
若字符串中已包含[[ ]],可使用[==[和]==],只要其中的’=‘个数相同即可。
如
page = [===[
[[
<html>
<head>
<title>An HTML Page</title>
</head>
<body>
<a href="http://www.lua.org">Lua</a>
</body>
</html>
]]
]===]
print(page)
chapter 3 Expressions
p23 => 条件运算符a?b:c,其实等价于a and b or c。如下
max = (x > y) and x or y
p24 => 字符串连接可使用 .. ,如下
print("Hello " .. "World") --> Hello World
p26 => table的创建
(1)days = {"Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday"}
等价于
days[1] = "Sunday", days[2] = "Monday",依此类推,下标从1开始。
(2)a = {x=10, y=20} 等价于a = {}; a.x=10; a.y=20也等价于a = {}; a["x"]=10; a["y"]=20
(3)直接使用a = {"x"=10, "y"=20}这样的方式时x,y必须是标识符,如果是特殊符号,需使用如下方式创建表
opnames = {["+"] = "add", ["-"] = "sub",
["*"] = "mul", ["/"] = "div"}
关于table创建p28 exercise 3.7可供练习
What will the following script print? Explain.
sunday = "monday"; monday = "sunday"
t = {sunday = "monday", [sunday] = monday}
print(t.sunday, t[sunday], t[t.sunday])
chapter 4 Statements
p29 => 支持多重赋值,可用于交换数值,做法是先计算右边的值,然后赋值给左边。
x, y = y, x -- swap 'x' for 'y'
a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]'
赋值参数多的会丢弃,不足的部分会用nil补齐。
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
p31 => 可以用do ... end封装一个chunk
do
local a2 = 2*a
local d = (b^2 - 4*a*c)^(1/2)
x1 = (-b + d)/a2
x2 = (-b - d)/a2
end -- scope of 'a2' and 'd' ends here
print(x1, x2)
p33 => repeat - until 即 do - while语句,且repeat中的local变量在until条件中也有效
-- print the first non-empty input line
repeat
line = io.read()
until line ~= ""
print(line)
p35 => numeric for 和 generic for
p36 => break, return and goto
chapter 5 Functions
p42 => 支持传递多个参数
function f (a, b) print(a, b) end
f(3) --> 3 nil
f(3, 4) --> 3 4
f(3, 4, 5) --> 3 4 (5 is discarded)
最有用的技巧是默认参数的设置,如下:
function incCount (n)
n = n or 1
count = count + n
end
p45 => 支持返回多个值
print(string.find("hello", "ll"))
print(table.unpack({"Sun", "Mon", "Tue", "Wed"}, 2, 3))
--> Mon Tue
p46 => 支持可变参数个数
function add (...)
local s = 0
for i, v in ipairs{...} do
s = s + v
end
return s
end
print(add(3, 4, 10, 25, 12)) --> 54
local a, b = ...
p48 => 支持命名参数,在win32编程中,因为API的参数个数较多,可运用这个技巧定义一个参数table,设定一些默认值,然后只设置需要的少数几个值即可。
rename{old="temp.lua", new="temp1.lua"}
chapter 6 More about Functions
p52 => function是first-class value(第一类值),与int等builtin类型属于一类,可进行赋值等
foo = function (x) return 2*x end
p53 => closures闭包
为了解决嵌套函数中的内部函数中引用的局部变量的作用域问题,lua定义了一个闭包结构,将使用到的local变量与函数一起进行了一个拷贝封装,closure中的局部变量因为历史原因称为upvalue。
function newCounter ()
local i = 0
return function () -- anonymous function
i = i + 1
return i
end
end
p59 => 尾调用(tail call)
提到了尾递归tail recursive,特意去查了下wiki和stack overflow,发现这里的尾调用与尾递归的含义不太一样,lua中的尾调用的意思是形如
function f (x) return g(x) end
这样的函数在最后一句是return g(x)则在调用完g(x)之后没有必要再返回f(x),这样就没有必要在f调用g的时候保存调用栈的信息。
所以类似下面的调用可以无限调用,而不会使调用栈溢出
function foo (n)
if n > 0 then return foo(n - 1) end
end
类似如下的函数则不是一个尾调用
return g(x) + 1 -- must do the addition
return x or g(x) -- must adjust to 1 result
return (g(x)) -- must adjust to 1 result
在lua中仅仅形如return func(args)的调用是一个尾调用,如下是一个尾调用
return x[i].foo(x[j] + a*b, i + j)
chapter 7 Iterators and the Generic for
p61 => 迭代器与闭包
这一节中描述了什么是迭代器,迭代器与闭包的关系,用闭包功能可以很容易的实现迭代器。
p63 => 泛型for的语义
在使用迭代器的时候,每次调用都需要创建一个闭包,大多数情况下,没什么问题,但在状态参数(upvalues)较多的情况下,创建闭包的代价是不能忍受的,这些情况下可使用泛型for本身来保存迭代的状态。
在循环过程中,泛型for在内部保存3个值:迭代函数,状态常量和控制变量。
范性for的文法如下:
for <var-list> in <exp-list> do
<body>
end
我们称变量列表中第一个变量为控制变量,其值为nil时循环结束。
下面我们看看范性for的执行过程:
首先,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数,状态常量和控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
第三,将迭代函数返回的值赋给变量列表。
第四,如果返回的第一个值为nil循环结束,否则执行循环体。
第五,回到第二步再次调用迭代函数。
更精确的来说:
for var_1, ..., var_n in explist do block end
等价于
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
如果我们的迭代函数是f,状态常量是s,控制变量的初始值是a0,那么控制变量将循环:a1=f(s,a0)、a2=f(s,a1)、⋯⋯,直到ai=nil。
p65 => 无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
p66 => 多状态的迭代器
很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到table内,将table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在table内,所以迭代函数通常不需要第二个参数。
我们应该尽可能的写无状态的迭代器,因为这样循环的时候由 for 来保存状态,不需要创建对象花费的代价小;如果不能用无状态的迭代器实现,应尽可能使用闭包;尽可能不要使用table这种方式,因为创建闭包的代价要比创建table小,另外Lua处理闭包要比处理table速度快些。后面我们还将看到另一种使用协同来创建迭代器的方式,这种方式功能更强但更复杂。
chapter 8 Compilation, Execution, and Errors
p71 => dofile和loadfile的区别
lua在运行代码之前会预编译成中间码。
loadfile编译代码成中间码并且返回编译后的chunk作为一个函数,而不执行代码;另外loadfile不会抛出错误信息而是返回错误代码。
而dofile相当于如下定义:
function dofile (filename)
local f = assert(loadfile(filename))
return f()
end
如果loadfile失败assert会抛出错误。
完成简单的功能dofile比较方便,他读入文件编译并且执行。然而loadfile更加灵活。在发生错误的情况下,loadfile返回nil和错误信息,这样我们就可以自定义错误处理。另外,如果我们运行一个文件多次的话,loadfile只需要编译一次,但可多次运行。dofile却每次都要编译。
p73 => loadstring总是在全局环境下编译它的chunk。如下
i = 32
local i = 0
f = loadstring("i = i + 1; print(i)")
g = function () i = i + 1; print(i) end
f() --> 33
g() --> 1
p74 => loadstring("a=1")等价于定义了一个支持可变参数的函数function (...) a = 1 end
p75 => loadfile只是编译一个chunk,并没有定义它,要想定义它,必须运行这个chunk,如下:
-- file 'foo.lua'
function foo (x)
print(x)
end
f = loadfile("foo.lua")
print(foo) --> nil --foo并没有定义
f() -- defines 'foo'
foo("ok") --> ok