shownb / shownb.github.com

shownb.github.io
shownb.github.io
5 stars 1 forks source link

lua学习的读书笔记 #5

Open shownb opened 7 years ago

shownb commented 7 years ago

tips lua的table就是{},可以用这个一个来定义类似python的list dict set三种类型。

代码风格,代码规范

语法约定

1,大小写敏感 2,多行注释 --[[ --]] 3, ..在 Lua 中是字符串连接符,当在一个数字后面写..时,必须加上空格以防止被解释错。

变量

table table类型实现了关联数组,关联数组是一种具有特殊索引方式的数组;不仅可以通过整数来索引它,还可以使用字符串或其它类型的值(除了nil)来索引它。此外,table没有固定的大小,可以动态得添加任意数量的元素到一个table中。 在Lua中,table既不是“值”,也不是“变量”,而是对象。可以将table想象成一种动态分配的对象,程序中仅仅有一个队它们的引用(指针)。table的创建是通过“构造表达式”完成的,最简单的构造表达式就是{}。 table永远是匿名的,一个引用table的变量与table自身之间没有固定的关联性

长度操作符“#”用于返回一个数组或线性表的最后一个索引值。

{x=0, y=0} --> {["x"]=0, ["y"]=0}
{"red", "green", "blue"} --> {[1]="red", [2]="green", [3]="blue"

function 在Lua中,函数被当做值来对待,这表示函数可以存储在变量中,可以通过参数传递给其它函数,还可以作为其它函数的返回值

常用函数

tonumber() tostring() ipairs() 迭代数组 pairs() 迭代数组和dict string.find() string.sub() string.gsub() string.match() string.gmatch()

表达式

基本语法

if 语句,三种形式

if conditions then
   then-part
end;
if conditions then
   then-part
else
   else-part
end;
if conditions then
   then-part
elseif conditions then
   elseif-part
elseif else
   else-part
end;

while 语句

while condition do
   statements;
end

for 语句两大类

break和return语句 break 语句用来退出当前循环(for,repeat,while)。在循环外部不可以使用。 Lua 语法要求 break 和 return 只能出现在 block 的结尾一句

函数

可变参数

Lua 函数可以接受可变数目的参数,和 C 语言类似在函数参数列表中使用三点(...) 表示函数有可变的参数。Lua 将函数的参数放在一个叫 arg 的表中,除了参数以外,arg 表中还有一个域 n 表示参数的个数。 虚变量(下划线)_

命名参数

Lua 可以通过将所有的参数放在一个表中,把表作为函数 的唯一参数

function rename (arg)
   return os.rename(arg.old, arg.new)
end

再论函数

元表

每一个tabel都可以附加元表 setmetatable(t,mt), 元表是带有keys和values的表,它可以改变被附加表的行为。setmetatable 返回第一个参数。 元表可以包含任何东西,但是元表通常以""(两个下划线)开头的keys(当然string类型)来调用,例如index和__newindex。 和keys对应的values可以是表或者函数.

任何table都可以作为任何值得元表,而一组相关的table有可以共享一个通用的元表,此元表描述了它们共同的行为。一个table甚至可以作为它自己的元表,用于描述其特有的行为。总之,任何搭配形式都是合法的。

shownb commented 7 years ago

有一个自认为无亲无故的小朋友t,突然有一天发现mt是他的祖父。有个祖父有什么好处呢?其实有些关于自己身世的问题就会有一些答案了。 例如 get 我的父亲名字;get 我的出生日期,mt都会返回一些答案,当然有些答案,mt也不知道,只能返回nil这个答案了。

t还可以从mt身上get一些特殊的东西。例如一个精致的木雕作品,因为mt有一个特殊的技能,可以雕刻木头。但t无需关心自己会不会,只要将自己想要的东西告诉mt,mt就会去做,然后返回t想要的就行了。但如果t想get一些mt也不会的东西,只能返回nil了。

shownb commented 7 years ago

元表

http://www.cnblogs.com/xdao/archive/2013/04/02/lua-metatable.html

本文简译自一篇老外的博客,写得不错可惜我翻译的太烂,简译如下。

(key--value常见翻译为“键值对”,我翻译为索引、值)

在这篇教程里我会介绍Lua中一个重要的概念: metatable(元表),掌握元表可以让你更有效的

使用Lua。 每一个tabel都可以附加元表, 元表是带有索引集合的表,它可以改变被附加表的行为。

看下例:

t = {} -- 普通表 mt = {} -- 元表,现在暂时什么也没有 setmetatable(t, mt) -- 把mt设为t的元表 getmetatable(t) -- 这回返回mt

如你所见 getmetatable 和setmetatable 是主要的函数。 当然我们可以把上面的三行代码合为:

t = setmetatable({}, {})

setmetatable 返回第一个参数, 因此我们可以使用这个简短的表达式。现在,我们在元表里放些什

么呢? 元表可以包含任何东西,但是元表通常以"__"(两个下划线)开头的索引(当然string类型)

来调用,例如index和newindex。 和索引对应的值可以是表或者函数,例如:

t = setmetatable({}, {
  __index = function(t, key)
    if key == "foo" then
      return 0
    else
      return table[key]
    end
  end
})

我们给__index索引分配了一个函数, 让我们来看看这个索引是干啥的。

__index

元表里最常用的索引可能是__index,它可以包含表或函数。

当你通过索引来访问表, 不管它是什么(例如t[4], t.foo, 和t["foo"]), 以及并没有分配索引的值时,

Lua 会先在查找已有的索引,接着查找表的metatable里(如果它有)查找__index 索引。 如果

index 包含了表, Lua会在index包含的表里查找索引。 这听起来很迷糊,让我们看一个例子。

other = { foo = 3 } 
t = setmetatable({}, { __index = other }) 
t.foo -- 3 ,现在__index包含的表{foo=3}查找 
t.bar -- nil ,没找到 

如果__index 包含一个函数,当被它调用时,会把被访问的表和索引作为参数传入。从上面的例子来看,

我们可以使用带有条件语句的索引,以及任意的Lua语句。因此在这种情况下,如果索引和字符串"foo"

相等,我们可以返回0,否则,我们可以查询表中被使用的索引;当"foo"被使用时,让t作为table的

别名并返回0。(这句不是太懂,原文为:Therefore,in that example, if the key was equal to

the string "foo" we would return 0, otherwise we look up the table table with the key that

was used; this makes t an alias of table that returns 0 when the key "foo" is used.)

你可能会疑问,怎么把表作为是第一个传给__index 函数的参数。当你在多个表里使用相同的元表时,

这会很方便,并支持代码复用和节省电脑资源。我们会在最下面的Vector 类里看到解释。

--注:下面是我的一个例子

other = function(t,k) if k=="foo" then return 0 end end 
t = setmetatable({}, { __index = other }) 
print(t.foo)

__newindex

下一个是newindex, 它和index类似。和 __index一样,它可以包含函数和表。当你给表中不存在

的值赋值时,Lua会在metatable里查找newindex,调用顺序和 index一样。如果__newindex是表,

索引和值会设置到指定的表:

other = {}
t = setmetatable({}, { __newindex = other })
t.foo = 3 --t里没有foo,查看__newindex,并把foo=3传给了other,并没有给t里的foo赋值
other.foo – 3 故为3
t.foo – nil 故为 nil

和期望的一样,__newindex 是函数时,当被调用时会传递表、索引、值三个参数。

t = setmetatable({}, { 
  __newindex = function(t, key, value) 
    if type(value) == "number" then 
      rawset(t, key, value * value) 
    else 
      rawset(t, key, value) 
    end 
  end 
}) 

t.foo = "foo" t.bar = 4 t.la = 10 t.foo -- "foo" t.bar -- 16 t.la -- 100

当在t里创建新的索引时,如果值是number,这个值会平方,否则什么也不做。下面介绍rawget 和rawset。

rawget 和 rawset

有时需要get 和set表的索引,不想使用metatable.你可能回猜想, rawget 允许你得到索引无需__index,

rawset允许你设置索引的值无需__newindex (不,相对传统元表的方式,这些不会提高速度)。为了避免陷

在无限循环里,你才需要使用它们。 在上面的例子里, t[key] = value * value将再次调用__newindex

函数,这让你的代码陷入死循环。使用rawset(t, key, value * value) 可以避免。

你可能看到,使用这些函数, 我们必须传递参数目标table, key, 当你使用rawset时还有value。

操作符

许多元表的索引是操作符 (如, +, -, 等),允许你使用表完成一些操作符运算。例如,我们想要一个表支持

乘法操作符(*), 我们可以这样做:

t = setmetatable({ 1, 2, 3 }, { 
  __mul = function(t, other) , 
    new = {} 

    for i = 1, other do 
      for _, v in ipairs(t) do table.insert(new, v) end 
    end 

    return new 
  end 
}) 

t = t * 2 -- { 1, 2, 3, 1, 2, 3 }

这允许我们创建一个使用乘法操作符重复某些次数的新表。你也看的出来, __mul和乘法相当的索引是,

index、 newindex 不同,操作符索引只能是函数。 它们接受的第一个参数总是目标表, 接着

是右值 (除了一元操作符“-”,即索引__unm)。下面是操作符列表:

add: 加法(+) sub: 减法(-) mul: 乘法(*) div: 除法(/) mod: 取模(%) unm: 取反(-), 一元操作符 concat: 连接(..) eq: 等于(==) lt: 小于(<) le:小于等于(<=) (只有==, <, <= ,因为你能通过上面的实现所有操作,事实上== 和<就足够了)

__call

接下来是__call 索引, 它允许你把表当函数调用,代码示例:

t = setmetatable({}, {
  __call = function(t, a, b, c, whatever)
    return (a + b + c) * whatever
  end
})

t(1, 2, 3, 4) –- 24 ,表t在调用时先查找call,调用里面的函数,t便相当于函数了 和通常一样在call里的函数,被传递了一个目标表,还有一些参数。call 非常有用,经常用来在表和它

里面的函数之间转发调用(原文it's used for is forwarding a call on a table to a function inside

that table.)。 kikito的 tween.lua 库就是个例子tween.start可以被自身调用(tween). 另一个例子是

MiddleClass, 类里的new函数可以被类自身调用。

__tostring

最后一个是 __tostring。如果实现它,那么tostring 可以把表转化为string, 非常方便类似print的函数

使用。 一般情况下,当你把表转为string时, 你需要"table: 0x<hex-code-here",但是你可以仅用

__tostring来解决。示例:

t = setmetatable({ 1, 2, 3 }, {
  __tostring = function(t)
    sum = 0
    for _, v in pairs(t) do sum = sum + v end
    return "Sum: " .. sum
  end
})

print(t) -- prints out "Sum: 6"

创建一个向量类

下面我们来封装一个2D 向量类(感谢 hump.vector 的大量代码)。代码太长你可以查看gist #1055480,

代码里有大量的metatable概念,(注意,如果你之前没接触面向对象可能会有点难)。

Vector = {} Vector.index = Vector 首先声明了一个Vector class, 设置了index 索引指向自身。 这在干啥呢?你会发现我们把所有的元表

放到Vector类里了。你将看到在Lua里实现OOP (Object-Oriented Programming)的最简单方式。Vector

表代表类, 它包含了所有方法,类的实例可以通过Vector.new (如下) 创建了。

function Vector.new(x, y)
  return setmetatable({ x = x or 0, y = y or 0 }, Vector)
end

它创建了一个新的带有x、y 属性的表, 然后把metatable设置到Vector 类。我们知道Vector 包含了所有的

元方法,特别是 __index。这意味着我们通过新表可以使用所有Vector里方法。

另外重要的一行是:

setmetatable(Vector, { _call = function(, ...) return Vector.new(...) end }) 这意味着我们可以创建一个新的Vector 实例通过 Vector.new或者仅Vector。

最后重要的事,你可能没注意冒号语法。当我们定义一个带有冒号的函数时,如下:

function t:method(a, b, c) -- ... end 我们真正定义的是这个函数:

function t.method(self, a, b, c) -- ... end 这是一个语法糖,帮助我们使用OOP。当调用函数时,我们可以这样使用冒号语法:

-- these are the same t:method(1, 2, 3) t.method(t, 1, 2, 3) 我们如何使用 Vector 类? 示例如下:

a = Vector.new(10, 10)
b = Vector(20, 11)
c = a + b
print(a:len()) -- 14.142135623731
print(a) -- (10, 10)
print(c) -- (30, 21)
print(a < c) -- true
print(a == b) -- false

因为Vector里有__index,我们可以在实例里使用它的所有方法。

shownb commented 7 years ago

http://www.jellythink.com/archives/882