Lua中的“数组”就是特殊方式使用的表。像lua-settable和lua-gettable这种用来操作表的通用函数,也可用于操作数组。不过,CAPI为使用整数索引的表的访问和封信提供了专门的函数。 12void lua_geti (lua_State *L, int index, int key);void lua_seti (lua_State *L, int index, int key); Lua5.3之前的版本只提供了这些函数的原始版本,即lua_rawgeti和lua_rawseti。这两个函数类似于lua_geti和lua_seti,但进行的是原始访问。当区别并不明显时,那么原始版本可能会稍微快一点。lua_geti和lua_seti的描述有一点令人困惑,因为其用了两个索引:index表示在栈中的位置,key表示元素在表中的位置。当t为正数时,那么调用lua_geti(L,t,key)等价于如下的代码:12lua_pushnumber(L,key);lua_gettable(L,t);调用lua_seti(L,t,key)等价于如下的代码:123lua_pushnumbe ...
编程语言
未读我们说用Lua可以调用C语言函数,但这并不意味着Lua可以调用所有的C函数。当C语言调用Lua函数时,该函数必须遵循一个简单的规则来传递参数和获取结果。 (adsbygoogle = window.adsbygoogle || []).push({}); 同样,当Lua调用C函数时,这个C函数也必须遵循某种规则来获取参数和返回结果。此外,当Lua调用C函数时,我们必须注册该函数,即必须以一种恰当的方式为Lua提供该C函数的地址。Lua调用C函数时,也使用一个与C语言调用Lua函数时相同类型的栈,C函数从栈中获取参数,并将结果压入栈中。此处的重点在于,这个栈不是一个全局结构;每个函数都有其私有的局部栈。当Lua调用一个C函数时,第一个参数总是位于这个局部栈中索引为1的位置。即使一个C函数调用了Lua代码,而且Lua代码又再次调用了同一个C函数,这些调用每一次都只会看到本次调用自己的私有栈,其中索引为1的位置上就是一个参数。 C函数先举一个例子,让我们实现一个简化版本的正弦函数,该函数返回某个给定数的正弦值:12345static int l_sin(lua_State ...
Lua是一种嵌入式语言,这就意味着Lua并不是一个独立运行的应用,而是一个库,它可以链接到其他应用程序,将Lua的功能融入这些应用。 (adsbygoogle = window.adsbygoogle || []).push({}); 因为能够当作库来扩展某个应用程序,所以Lua是一种嵌入式语言。同时,使用了Lua语言的程序也可以在Lua环境中注册新的函数,比如用C语言实现函数,从而增加一些无法直接用Lua语言编写的功能。因此Lua也是一种可扩展的语言。上述两种对Lua语言的定位分别对应C语言和Lua语言之间的两种交互形式。在第一种形式中,C语言拥有控制权,而Lua语言被用作库,这种交互形式中的C代码被称为应用代码。在第二种形式中,Lua语言拥有控制权,而C语言被用作库,此时的C代码被称为库代码。应用代码和库代码都适用相同的API与Lua语言通信,这些API被称为C API。C API是一个函数、常量和类型组成的集合,有了它,C语言代码就能与Lua语言交互。C API包括读写Lua全局变量的函数、调用Lua函数的函数、运行Lua代码段的函数,以及注册C函数的函数等。 ...
编程语言
未读协程能够实现一种协作式多线程。每个协程都等价于一个线程。一对yield-resume可以将执行权在不同线程之间切换。 (adsbygoogle = window.adsbygoogle || []).push({}); 不过,与普通的多线程的不同,协程是非抢占的。当一个协程正在运作时,是无法从外部停止它的。只有当协程显式地要求时它才会挂起执行。对于有些应用而言,这并没有问题,而对于另外一些应用则不行。当不存在抢占时,编程简单得多。由于在程序中所有的线程间同步都是显式的,所以我们无须为线程同步问题抓狂,只需要确保一个协程只在它的临界区之外调用yield即可。 不过,对于非抢占式多线程来说,只要有一个线程调用了阻塞操作,整个程序在该操作完成前都会阻塞。对于很多应用来说,这种行为是无法接受的,而这也正是导致许多程序员不把协程看作传统多线程的一种实现的原因。让我们假设一个典型的多线程的场景:我们希望通过HTTP下载多个远程文件。为了下载多个远程文件,我们必须先知道如何下载一个远程文件。要下载一个文件,必须先打开一个到对应站点的连接,然后发送下载文件的请求,接收文件,最后关闭 ...
编程语言
未读反射是程序用来检查和修改其自身某些部分的能力。像Lua语言这样的动态语言支持几种反射机制:环境允许运行时观察全局变量; (adsbygoogle = window.adsbygoogle || []).push({}); 诸如type和pairs这样的函数允许运行时检查和遍历未知数据结构;诸如load和require这样的函数允许程序在自身中追加代码或更新代码。不过,还有很多方面仍然是缺失的:程序不能检查局部变量,开发人员不能跟踪代码的执行,函数也不知道是被谁调用的,等等。调试库填补了上述缺陷。调试库是由两类函数组成:自省函数和钩子。自省函数允许我们检查一个正在运行中的程序的几个方面,例如活动函数的额栈、当前正在执行的代码行、局部变量的名称和值。钩子则允许我们跟踪一个程序的执行。虽然名字里带有”调试“的字眼,但调试库提供的并不是Lua语言的调试器。不过,调试库提供了编写我们自己的调试器所需要的不同层次的所有底层机制。调试库与其他库不同,必须被慎重地使用。首先,调试库中的某些功能的性能不高。其次,调试库会打破语言的一些固有规则,例如不能从一个局部变量的词法定界范围外访 ...
协程可以颠倒调用者和被调用者的关系,而且这种灵活性解决了软件架构中被称为“谁是老大”或者”谁拥有主循环“的问题。这正是对诸如事件驱动编程、通过构造器构建迭代器和协作式多线程等几个看上去并不相关的问题的泛化,而协程以简单和高效的方式解决了这些问题。 (adsbygoogle = window.adsbygoogle || []).push({}); 从多线程的角度看,协程与线程类似:协程是一系列的可执行语句,拥有自己的栈、局部变量和指令指针,同时协程又与其他协程共享了全局变量和其他几乎一切资源。线程与协程的主要区别在于,一个多线程程序可以并行运行多个线程,而协程却需要彼此协作地运行,即在任意指定的时刻只能有一个协程运行。且只有当正在运行的协程显式地要求被挂起时其执行才会暂停。 协程基础Lua语言中协程相关的所有函数都被放在表coroutine中。函数create用于创建新协程,该函数只有一个参数,即协程要执行的代码的函数。函数create返回一个”thread”类型的值,即新协程。通常,函数create的参数是一个匿名函数,例如:12co = coroutine.cr ...
Lua语言使用自动内存管理。程序可以创建对象,但却没有函数来删除对象。Lua语言通过垃圾收集自动删除称为垃圾的对象,从而将程序员从内存管理的绝大部分负担中解放出来。 (adsbygoogle = window.adsbygoogle || []).push({}); 更重要的是,将程序员从与内存管理相关的大多数Bug中解放出来。例如无效指针和内存泄露等问题。在一个理想的环境中,垃圾收集器对程序员来说是不可见的,就像一个好的清洁工不会和其他工人打交道一样。不过,有时即使是智能的垃圾收集器也会需要我们的辅助。在某些关键的性能阶段,我们可能需要将其停止,或者让只在特定的时间运行。另外,一个垃圾收集器只能收集它确定是垃圾的内容,而不能猜测我们把什么当作垃圾。没有垃圾收集器能够做到让我们完全不用操心资源管理的问题,比如驻留内存和外部资源。弱引用表、析构器和函数collectgarbage是在Lua语言中用来辅助垃圾收集器的主要机制。弱引用表允许收集Lua语言中还可以被程序访问的对象;析构器允许收集不在垃圾收集器直接控制下的外部对象;函数collectgarbage则允许我们控 ...
全局变量在大多数变成语言中是让人爱恨交织又不可或缺的。一方面,使用管全局变量会明显地使无关的代码部分纠缠在一起,容易导致代码复杂。 (adsbygoogle = window.adsbygoogle || []).push({}); 另一方面,谨慎地使用全局变量又能更好地表达程序中真正的全局概念;此外,虽然全局常量看似无害,但像Lua语言这样的动态语言是无法区分常量和变量的。像Lua这样的嵌入式语言更复杂:虽然全局变量时再整个程序中均可见的变量,但由于Lua语言是由宿主应用调用代码段的,因此“程序”的概念不明确。Lua语言通过不使用全局变量的方法来解决这个难题,但又不遗余力地在Lua语言汇总对全局变量进行模拟。在第一种近似的模拟中,我们可以认为Lua语言把所有的全局变量保存在一个称为全局环境的普通表中。由于不需要再为全局变量创造一种新的数据结构,因此使用一个表来保存全局变量的一个优点是简化了Lua 语言的内部实现。另一个优点是,可以像操作其他表一样操作这个表。为了便于实现这种操作方式,Lua语言将全局环境自身保存在全局变量_G中。例如,如下代码输出了全局环境中所有全 ...
打乱有序数组,生成随机数组 (adsbygoogle = window.adsbygoogle || []).push({}); 1234567891011121314151617local function randomTable(_table, _num) local _result = {} local _index = 1 local _num = _num or #_table while #_table ~= 0 do local ran = math.random(0, #_table) if _table[ran] ~= nil then _result[_index] = _table[ran] table.remove(_table,ran) _index = _index + 1 if _index > _num then break ...
从很多意义上讲,Lua语言中的一张表就是一个对象。首先,表与对象一样,可以拥有状态。其次,表与对象一样,拥有一个与其无关的标识(self); (adsbygoogle = window.adsbygoogle || []).push({}); 特别地,两个具有相同值的对象(表)是两个不同的对象,而一个对象可以具有多个不同的值;最后,表与对象一样,具有创建者和被创建位置无关的声明周期。对象有其自己的操作。表也可以有自己的操作,例如:1234Account = {balance = 0}function Account.withdraw(v) Account.balance = Account.balance - vend上面的代码创建了一个新函数,并将该函数存入Account对象的withdraw字段。然后,我们就可以进行如下的调用:1Account.withdraw(100.00)这种函数差不多就是所谓的方法了。不过,在函数中使用全局名称Account是一个非常槽糕的编程习惯。首先,这个函数只能针对特定对象工作。其次,即使针对特定的对象,这个函数 ...
编程语言
未读通常,Lua语言中的每种类型的值都有一套可预见的操作集合。例如,我们可以将数字相加,可以连接字符,还可以在表中插入键值对等。但是,我们无法将两个表相加,无法对函数做比较,也琺调用一个字符串,除非使用元表。 (adsbygoogle = window.adsbygoogle || []).push({}); 元表可以修改一个值在面对一个未知操作时的行为。例如,假设a和b都是表,那么可以通过元表定义Lua语言如何计算表达式a+b。当Lua语言试图将两个表相加时,它会先检查两者之一是否有元表且该元表中是否有__add字段。如果Lua语言找到了该字段,就调用该字段对应的值,即所谓的元方法,在本例中就是用于计算表的和的函数。可以认为,元表是面向对象领域中的受限制类。像类一样,元表定义的是实例的行为。不过,由于元表只能给出预先定义的操作集合的行为,所以元表被类更受限;同时,元表也不支持继承。Lua语言中的每一个值都可以有元表。每一个表和用户数据类型都具有各自独立的元表,而其他类型的值则共享对应类型所属的同一个元表。Lua语言在创建新表时不带元表:12t = { ...
Lua 中引入一个模块,可以采用两种方式:import和require方式,具体的区别在于: (adsbygoogle = window.adsbygoogle || []).push({}); 载入一个模块import()与 require()功能相同,但具有一定程度的自动化特性。 假设我们有如下的目录结构: 1234567app/app/classes/app/classes/MyClass.luaapp/classes/MyClassBase.luaapp/classes/data/Data1.luaapp/classes/data/Data2.lua MyClass 中需要载入 MyClassBase 和 MyClassData。如果用 require(),MyClass 内的代码如下: 1234567local MyClassBase = require("app.classes.MyClassBase")local MyClass = class("MyClass", MyClassBase)local Data1 ...