Lua基本学习理解(001)

0、可以将一个table想象成一种动态分配的对象,程序仅仅持有一个对他们的引用(或指针),Lua不会暗中产生table的副本或创建新的table。table永远是匿名的(anonymous),一个持有table的变量与table自身没有固定的关联性。感觉与python完全类似。


     Lua中怎么分配一块大的连续的数组,只用语言本身。table.insert不行。
     table中有那些接口,查一下?可以直接通过for k,v in pairs(table) do print(k,v) end得到。
     Lua中获取某个变量的地址?
     
     1、注意,print({} == {}) 输出false。因为table、userdata和函数,Lua是作引用比较的。也就是说,只有当他们引用一个对象时,才认为他们相等。而print("abcdef" == "abcdef")会返回true,因为在Lua在VM内对相同的string永远只保留一份copy。table例子:
 
          
     
     2、通常写法:x = x or v,将x设置一个默认值v。


     3、Lua允许多重赋值,比如local a,b = 1,2,则变量a和b都是局部变量,值分别为1,2。在多重赋值中,Lua先对等号右边的所有元素求值,然后才执行赋值。Lua总是会将等号右边值的个数调整到与左边的个数相一致。特殊例子:
      


     4、Lua会调整一个函数的返回值数量以适应不同的调用情况。若将函数调用作为一条单独语句时,Lua会丢弃函数的所有返回值。若将函数作为表达式的一部分来调用时,Lua只保留函数的第一个返回值。只有当一个函数调用是一系列表达式中的最后一个元素(或仅有一个元素)时,才能获得它的所有返回值。这里所谓的“一系列表达式”在Lua中表现为4中情况:多重赋值、函数调用时传入的实参列表、table的构造式和return语句。特殊例子:


     5、setfenv(f,table):Sets the environment to be used by the given function. f can be a Lua function or a number that specifies the function at that stack level: Level 1 is the function calling setfenv. setfenv returns the given function.As a special case, when f is 0 setfenv changes the environment of the running thread. In this case, setfenv returns no values.


     6、与python类似,函数与一般数据对象类似,可以赋值给一般变量。从技术上来讲,Lua中只有closure,而不存在函数。因为,函数本身就是一种特殊的closure。当Lua展开局部函数定义的“语法糖”时,并不是使用基本函数定义语法。而对于局部函数的定义:
                         local function foo(<参数>) <函数体> end
Lua将其展开为:
                         local foo 
                         foo = function (<参数>) <函数体> end


     7、在Lua中,只有“return <func>(<args>)”这样的调用形式才算是一条“尾调用”。Lua会在调用前对<func>以其参数求值,所以他们可以是任意复杂的表达式。


     8、跟通常所说的迭代器(又叫生成器)类似,就是用来保存状态和获取当前状态的值。实现迭代器,就是实现这个两个功能,在Lua中,当返回nil时,表示迭代器结束。泛型for在循环过程内部保存了迭代器函数。实际上它保存着3个值:一个迭代器函数(_f)、一个恒定状态(_s)和一个控制变量(_var)。ipairs和pairs就是迭代器工厂函数,在for循环中创建了其相应的无状态的迭代器。无状态迭代器可以理解为没有upvalue(非局部变量)的闭包,而一般的迭代器通常是有upvalue的闭包。所谓的具有复杂状态的迭代器是指,其upvalue的值为一个table。
                                             
     9、在调用next(t,k)时,k是table t的一个key。此调用会以table中的任意次序返回一组值:此table的下一个key和这个key(返回的key)所对应的值。而调用next(t,nil)时,则返回table的第一组值。若没有下一组值时,next返回nil。


     10、类似于create,wrap创建一个新的协同程序。但不同的是,wrap并不是返回协同程序本身,而是返回一个函数。每当调用这个函数,即可唤醒一次协同程序,实质上就是对resume函数进行了包装。但这个函数与resume的不同之处在于,它不会返回错误代码。当遇到错误时,它会引发错误。


     11、协同程序相当于非抢占式的线程,这样一个好处了无须同步,降低bug的可能性。
     
     12、Lua中的每一个值都有一个元表。table和userdata可以有各自独立的元表,而其他类型的值则共享其类型所属的单一元表。Lua在创建新的table时不会创建元表。当然元表也是一个常规的table。在lua代码中,只能设置table的元表。若要设置其他类型的值的元表,则必须通过C代码来完成。


     13、使用算术类元方法和关系类元方法可以给table定义许多类似于number类型的操作。


     14、Lua中各种程序库在元表中定义它们自己的字段是很普遍的方法。它会检测一个操作中的值是否有元表,这些元表中是否定义了关于此操作的元方法。比如函数tostring,它能将各种类型的值表示为一中简单的文本格式:
     
函数print总是调用tostring来格式输出。当格式化任意字符串时,tostring会检查该值是否有一个__tostring的元方法。如果有这个元方法,tostring就用该值作为参数来调用这个元方法。接下来就由这个元方法来完成实现的工作,它返回的结果就是tostring的结果。类似的函数setmetatable和getmetatable对应的值的字段为__metatable。还有__index和__newindex元方法,他们都可以实现一些强大的功能。这些在python中有的。
       
     15、元表首先是一个table,其中可能包含各种元方法,我们可以使用语言自带的元方法名(比如__add,__sub,__index等),可以使用它们默认的行为或重新定义这些元方法。
                                             
                                             
     16、Lua将所有的全部变量保存在一个常规的table中,这个table称为“环境(environment)”。Lua将这个环境table自身保存在一个全局变量_G中,即_G._G等于_G,可以理解这个环境变量用key值为"_G"保存了这个环境table的地址(或引用)了。对应Lua来说,_G只是一个普通的名字。当Lua创建最初的全部table时,只是将这个table赋予了全局变量_G,Lua不会在意这个变量_G的当前值。
       
     17、可以通过函数setfevn来改变一个函数(或程序块)的环境。该函数的参数是一个函数和一个新的环境table。第一个参数除了可以指定为函数本身,还可以指定为一个数字,以表示当前函数调用栈中的层数。数字1表示当前函数(即调用setfevn的函数),数字2表示调用函数的函数,依次类推。设置环境后,后面的未声明的变量都会去这这个环境table中寻找,看是否有这个变量。


     18、每个新创建的函数都继承了创建这个函数的环境。若一个程序块改变自己了自己的环境,那么后续由它创建的函数都将共享这个新环境。也就是后面创建的全局变量,都相当于给这个环境table添加新的成员。


     19、通常,Lua不会设置规则(policy)。但从5.1开始,为模块和包(package,一系列的模块)定义了一系列的规则。这些规则不需要语言引入额外的特性,可以用前面的table、函数、元表和环境来实现这些规则。然而Lua提供了两个函数require(用于使用模块)和module(用于创建模块)来实现这些规则。


     20、从使用的角度来看,一个模块就是一个程序库(也就是一个文件),可以通过require来加载。然后便得到一个全局变量,表示一个table。这个table就像是一个命名空间,其内容就是模块中导出的所有东西,例如函数和常量。例如,有一个模块的名字为mod.lua,可以用require "mod"来加载模块,用mod.foo()使用其中的全局函数,也可以用local m = require "mod";m.foo()来使用模块中的接口。


     21、要加载一个模块,只需要简单地调用函数require "<模块名>"。该函数会返回一个由模块全局变量组成的table,并且还会定义一个包括该table的全局变量。对于标致库,Lua总是会预先加载他们。require实现的伪代码如下:


                                   
require函数首先模块是否已经加载了,加载了模块都保存在package.loaded中,Lua预先加载的模块保存在package.loaded中,值如下:


                                                  
只要一个模块已经加载过了,后续require的调用都将返回同一个值,不会再次加载它。若想再次加载,需要把这个模块对应的table从package.loaded中删除,例如package.loaded["foo"] = nil;require "foo"。若模块没有加载,则require会试着首先从package.preload(初始时为{})中查找传入的模块名,没有找到,就是尝试从Lua文件(使用loadfile函数)或C程序库(使用loadlib)中加载模块。Lua文件的加载路径存储在变量package.path中,C程序库路径保存在变量package.cpath中。package模块内容如下:


               
     22、在开始编写一个模块时,可以通过调用module(...)来表示下面将创建一个模块。这句调用会创建一个新的table,并将其赋予适当的全局变量和package.loaded table,最后还会将这个table设为主程序快的环境。类似于以下代码工作(当不等于,它会最其他的工作):
    
                                                 
默认情况下,module不提供外部访问。必须在调用之前,为需要访问的外部函数或模块声明适当的局部变量。也可以通过继承来实现外部访问,只需调用module时加一个选项package.seeall(是一个函数),即module(...,package.seeall)。这个选项等价于以下代码:setmetatable(M,{__index = _G})。modual函数还提供了一些额外的功能。比如在模块table之前,会先检查package.loaded是否包含了这个模块;还会在创建的模块table中设置一些预定义的变量:_M包括了模块table自身(类似于_G),_NAME,包括了模块名,_PACKAGE,包含了包的名称。


     23、使用self参数是所有面向对象语言的一个核心。大多数面向对象语言都对程序员隐藏部分self参数,从而使得程序员不必显式地声明这个参数。Lua只需使用冒号。冒号的作用在一个方法定义中添加一个额外的隐藏参数,以及在一个方法调用中添加一个额外的实参。冒号只是一种语法便利,并没有引入任何新的东西。


     24、Lua中的类、继承的实现都是通过table和table的元表中的元方法(即__index)来实现的。其本质上,对象(对象在Lua中实质就是table了)是没有类型的(objects have no classes)。而是每个对象都有一个原型(prototype)。原型也是一种常规的对象,当其他对象(类的实例)遇到一个未知操作时,就会查找原型。在Lua中要表示一个类,只需创建一个专用作其他对象(类的实例)的原型。类和对象都是一种组织对象间共享行为的方式。python实质上也是这样做的。所谓的继承、重定义类方法操作都是通过元表方法和其查找方式(即嵌套往上查找要访问的key的值)来实现的。并且可以用闭包,来实现private成员,保证私密性,只能通过接口来访问成员。


     25、弱引用table(weak table)是这样一种机制,用户能用它来告诉Lua一个引用不应该阻碍一个对象的回收。所谓弱引用(weak refernce)就是一种会被垃圾收集器忽视的对象引用。不论哪种类型的弱引用table,只要有一个key或value被回收了,那么它们所在的整个条目都会从table中删除。一个table的弱引用类型是通过其元表中的__mode字段来决定的。注意,Lua只会回收弱引用table中的对象。而像数字和字符串这样的值是不可回收的。


     26、当require一个模块mod.sub时,require会用原始的模块名"mod.sub"作为key来查询table package.loaded 和package.preload,其中,模块名中的点在搜索table中没有任何含义,他只是key的值的一部分。

你可能感兴趣的:(lua)