Lua语法深入1

1. table构造器中,可以混用记录式(record-style)的和列表式(list-style)创建属性字段:

polyline = {color="blue",
                thickness=2,
                npoints=4, 
                {x=0,        y=0},            --polyline[1]
                {x=-10,    y=0},           --polyline[2]
                {x=-10,    y=1},           --polyline[3]
                {x=0,    y=1},                --最后的这个“,”有也不报错
                }    

如上的构造器中,有局限,就是不能使用负数index和使用非规范的属性名,如果有这样的需要,要使用方括号“[ ]”:

opnames={["+"]="add",  [“-”]="sub", ["*"]="mul",  [“/”]="div"}

i=20;  s="-"
a = {[i+0]=s, [i+1]=s..s, [i+2]=s..s..s }

使用#获得中间有nil值得table时,获得的结果并不可靠,此时需要将table长度显式地保存起来。

pairs遍历获得的元素顺序每次是随机的,ipairs遍历则是每次按固定顺序的。但实测中:前者遇到nil会跳过往下走,后者遇到第一个nil就结束遍历。

第3种方式就是用"for i=1, #t do"的数值型for循环遍历,这种方式将只输出列表式属性,无法输出记录式属性字段。

a={"Mon","Tus","Wed","Thurs","Fri", details={color="blue"}}

print(#a)
for i=1,#a do
    print(a[i])
end
print(string.rep("*",30))    --******************************
a[2]=nil
a[3]=nil
print(#a)
for k,v in pairs(a) do
    print(k,v)
end
for k,v in ipairs(a) do
    print(k,v)
end
E={}
color = ((a or E).details1 or E).color1    --避免属性不存在时报错的一种写法(如例:不存在details1),好比c#中的安全访问符"?."。这样写可减少对表元素验证是否存在时的每次重读
print(color) print(#a)

2. table.insert()在省略指定位置时,会插元素在table的最后。table.remove()不指定位置时,会删除最后一个元素。指定位置时,table.remove()会删除并返回该元素,并将其后的元素向前移动填充。

借助这2个函数,可以容易实现Stack、Queue和Double Queue(双端队列)。

如Stack的操作可以直接用来对栈顶元素做增加和删除。

Lua5.3新增的table.move(a, f, e, t)可以直接将表a中从索引f到e的元素(包括两端的元素本身)移动到索引t位置上。

table.move(a, 2, #a, 1)
a[#a]=nil      -- 往前复制覆盖时,要注意删除掉尾端相同数量的原位置上的元素擦屁股。 或者用table.remove(a)

table.move(a, 1, #a, 1, {}) --copy 表a的所有元素到新表,返回新表。即制作a的备份 table.move(a, 1, #a, #b+1, b) --copy表a的所有元素到表b末尾

 

3. Lua函数:

在多重赋值中,如果函数调用位于一个表达式的最后,则该函数调用将产生尽可能多的返回值,否则(不是最后)只能返回一个结果:

function foo0() end
function foo1() return "a" end
function foo2() return "a", "b" end

x,y = foo0()        --x=nil, y=nil
x,y = foo1()        --x="a", y=nil
x,y = foo2()        --x="a", y="b"
x = foo2()        --x="a", "b"被丢弃
x,y,z = foo2()        --x="a", y="b", z=nil
x,y,z = 10, foo2()        --x=10, y="a", z="b"

x,y=foo2(), 20        --x="a", y=20
x,y=foo0(), 20, 30        --x=nil, y=20, 30被丢弃
t={foo0(), foo2(), 4}    -- t[1]=nil, t[2]="a", t[3]=4

将函数调用用一对() 括起来可以强制其只返回一个结果:

print((foo0()))            --nil
print((foo1()))            --a
print((foo2()))            --a

参数列表中的...称为“可变长参数表达式”,在函数中,既可以用{...}包装参数列表,也可以"local a,b =..."来赋值。

具有可变长参数的函数也可以具有任意数量的固定参数,但是固定参数必须放在变长参数之前

function fwrite(fmt, ...)
    return io.write(string.format(fmt, ...))
end

如果可变长参数中包含无效的nil,那{...}获得的列表可能不再是一个有效的序列。Lua5.2开始提供了函数table.pack(),它除了像{}一样保存所有参数外,还额外提供有一个属性n,记录了参数数量。

function nonils (...)
    local arg=table.pack(...)
    for i=1,arg.n do
        if arg[i]==nil then return false end
    end
    return true
end

print(nonils(2,3,nil))        -->false
print(nonils(2,3))        -->true
print(nonils())            -->true
print(nonils(nil))            -->false

另一种白能力可变长参数的方法是使用函数select,它的第一个参数selector可以是数值n(表示返回参数列表中第n个参数后的所有参数)或者字符串的“#”(取得额外参数的总数):

print(select(1, "a", "b", "c"))        --> a b c
print(select(2, "a", "b", "c"))        --> b c
print(select(3, "a", "b", "c"))        --> c
print(select("#", "a", "b", "c"))        --> 3

通常在需要把返回值个数调整为1的地方使用select(),此时就是把select(n,...)看成是返回第selector个额外参数的表达式:

function add (...)
    local s = 0    
    for i=1,select("#",...) do
        s=s+select(i,...)    -- 这里是只返回一个结果值
        print(s)
    end
    return s
end

print(add(10,25,34,21,12))

table.unpack()是pack的反操作,它通常使用长度操作符#获取返回值的个数,因为该函数只能用于序列。可以显式地控制返回元素的范围:

print(table.unpack({"Sun", "Mon", "Tue", "Wed", "Sat"}, 2, 3))        -->Mon Tue

4. Lua的递归调用中,只有最后是形如 return func(args) 的调用才是尾调用。

 

5. Lua输入输出

5.1 简单I/O模型

io.input(filename) / io.output

io.read()    -- "a"、"l"、"L"、"n"、num,后两个见如下示例

io.write(a, b, c)

--读取一个文件,文件中每行由3个数字组成
while true do
    local n1, n2, n3 = io.read("n", "n", "n")
    if not n1 then break end
    print(math.max(n1,n2,n3))
end

--指定为参数num,读取块
while true do
    local block = io.read(2^13)
    if not block then break end
    io.write(block)
end

5.2 复杂I/O模型

file=io.open(filename, "r")    --模式参数也可以"w"

其它文件操作: file.flush/seek
os.rename/remove
其它系统调用:
os.exit(0)、os.getenv("PATH")
运行系统命令:
os.execute("mkdir "..dirname)    --执行新建文件夹 os.popen("dir /B", "r")    --读取当前目录下/B下的文件列表(Win系统)
os.popen("mail -s subject mailAddress""w")  --发送邮件的命令(POSIX系统)

 

6. 可以使用__newindex指向当访问的属性不存在时的处理函数,不存在就报写入错等;也可以用rawset函数直接绕过__newindex设置新属性。

 

7. C API,举例:一个独立解释器:

#include <stdio.h>
#include <string.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

int main (void) {
    char buff[256];
    int error;
    lua_State *L = luaL_newstate();       /* 打开Lua */
    luaL_openlibs(L);                 /* 打开标准库 */

    while (fgets(buff, sizeof(buff), stdin) != NULL) {
        error = luaL_loadstring(L, buff) || lua_pcall(L, 0, 0, 0);  //编译用户输入内容,无错返回0并向栈中压入编译得到的函数,然后调用lua_pcall弹获函数并以保护模式运行,无错返回0。有错这两个函数都会压栈一条错误信息
        if (error) {
            fprintf(stderrᵣ "%s\n", lua_tostring(L, -1));  //调用lua_tostring()获取错误信息,并打印
       lua_pop(L, 1);               /*从栈中弹出错误信息,即从栈中删除*/ 
     }
  }
  lua_close(L);
  
return 0;
}

  Lua与C的互操作都是通过栈执行:针对每一种能用C语言直接表示的Lua数据类型,CAPI中都有一个对应的压栈函数。

void lua_pushnil (lua_State *L);                                    //常量nil使用lua_pushnil
void lua_pushboolean (lua_State *L, int bool);                      //布尔值(在C语言中是整型)使用lua_pushboolean
void lua_pushnumber (lua_State *L, lua_Number n);                   //双精度浮点数使用 lua_pushnumber
void Lua_pushinteger (lua_State *L, lua_Integer n);                 //整型使用 lua_pushinteger
void lua_pushlstring (lua_State *L, const char *s, size_t len);     //任意字符串(一个指向char的指针,外加一个长度)使用lua_pushlstring
void lua_pushstring (lua_State *L, const char *s);            //以\0终止的字符串使用lua_pushstring

 b. 栈查询:lua_to*系列函数用于从栈中获取一个值:

int lua_toboolean (lua_State *L, int index);
const char *lua_tolstring (lua_State *L, int index, size_t *len); 
lua_State *lua_tothread (lua_State *L, int index); 
lua_Number lua_tonumber (lua_State *L, int index); 
lua_Integer lua_tointeger (lua_State *L, int index);

这些函数不会因为参数类型不正确而报错。比如函数lua_toboolean适用于所有类型,它可将nil和false转换为0,所有其它的Lua值转换为1。对于类型不正确的值,函数lua_tolstring和lua_tothread返回NULL。

不过,数值相关的函数都无法提示数值的类型错误,因此只能简单地返回0。以前我们需要调用函数lua_isnumber来检查类型,但Lua5.2引入了如下的新函数:
Lua_Number lua_tonumberx (lua_State *L, int idx, int *isnum);
lua_Integer lua_tointegerx (lua_State *L, int idx, int *isnum); //出口参数isnum返回了一个布尔值,来表示Lua值是否被强制转换为期望的类型。

c. 对栈进行Dump:从栈底向栈顶遍历:

static void stackDump (lua_State *L){
    int i;
    int top = lua_gettop(L); /* 栈的深度 */
    for(i= 1;i <= top;i+){/*循环 */
        int t = lua_type(L, i);
        switch (t) {
            case LUA_TSTRING:{/* 字符串类型 */
                printf("'%s'", lua_tostring(L, i)); break;
            case LUA_TBOOLEAN:{/*布尔类型*/
                printf(lua_toboolean(L, i)? "true": "false"); break;
            }
            case LUA_TNUMBER:{/*数值类型*/
                printf("%g", lua_tonumber(L, i)); break;
            }
            default:{/* 其他类型 */
                printf("%s", lua_typename(L, t)); break;
            }
        printf("");/* 输出分隔符*/
        }
    printf("\n");/* 换行符 */
}

d. 除了上述在C语言和栈之间交换数据的函数外,c API还提供了下列用于通用栈操作的函数:

int lua.gettop (lua_State *L);
void lua.settop (lua.State *L, int index);
void lua_pushvalue (lua_State *L, int index);  //将指定索引上的元素的副本压入栈
void lua.rotate (lua.State *LZ int index, int n);//将指定索引的元素向栈顶转动n个单位,n为正时;若n为负,则是向栈底,这是个很有用的函数 void lua_remove (lua_State *L, int index); void lua_insert (lua.State *L, int index); void lua_replace (lua.State *L, int index); void lua.copy (lua.State *Lr int fromidx, int toidx);

函数lua_gettop返回栈中元素的个数,也即栈顶元素的索引。函数lua_settop将栈顶设置为一个指定的值,即修改栈中的元素数量。如果之前的栈顶比新设置的更高,那么高出来的这此元素就会被丢弃;反之,该函数会向栈中压人向来补足大小。特别的,函数lua_settop(L,0)用于清空栈。在调用函数lua_settop时也可以使用负数索引;基于这个功能,C API提供了下面的宏,用于从栈中弹出n个兀素:

#define lua_pop(L, n)  lua_settop(L, -(n) - 1)

操作示例:

#include <stdio.h>
#include "lua.h"
/include "lauxlib.h"

static void stackDump (lua_State *L) { //见前面示例 }

int main (void) {
    lua_State *L = luaL_newstate();

    lua_pushboolean(L, 1);
    lua_pushnumber(L, 10);
    lua_pushnil(L);
    lua_pushstring(L, “hello");

    stackDump(L);
    /* 将输出: true 10 nil 'hello' */


    lua_pushvalue(L, -4); stackDump(L);
    /*将输出:true 10 nil 'hello' true */


    lua_replace(L, 3); stackDump(L);
    /* 将输出:true 10 true 'hello' */

    lua_settop(L, 6); stackDump(L);
    /* 将输出:true   10 true 'hello' nil nil */

    lua_rotate(L, 3, 1); stackDump(L);
    /* 将输出:true   10 ni^ jrue 'hello' nil */

    lua.removed, -3); stackDump(L);
    /* 将输出:true 10 nil 'hello' ml */

    lua_settop(L, -5); stackDumpd);
    /*将输出:true */

    lua_close(L);
    return 0;
}

e. 内存分配

lua_State *lua_newstate (lua_Alloc f, void *ud)

f. lua 读取配置文件,从配置文件中获取用户信息:

int getglobint (lua_State *L, const char *var)  
    int isnum, result;
    lua_getglobal(L, var);
    result = (int)lua_tointegerx(Lᵣ -1, &isnum);
    if (!isnum)
        error(L, "'%S' should be a number\n", var);
    lua_pop(L, 1);  /*从栈中移除结果*/
    return result;
)

void load (lua_State *L, const char *fname, int *w, int *h) {
    if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0))
        error(L, "cannot run config. file: %s", lua_tostring(L, -1));
    *w = getglobint(L, "width");
    *h = getglobint(L, "height");
)

--以下是配置文件内容
width = 200
height = 300

 

g. 所有在Lua中注册的函数都必须使用一个相同的原型,就是定义在Lua.h中的lua_CFunction:

typedef int (*lua_CFunction) (lua_State *L)

posted @ 2025-12-12 22:30  云山漫卷  阅读(22)  评论(0)    收藏  举报