Lua基础

这篇博客详细介绍了Lua的基础知识,包括全局变量、控制语句、表达式、操作符、函数、闭包、协同程序和C API的使用。讲解了Lua中的数据类型、变量、赋值、函数定义以及特殊特性如闭包和协同程序。此外,还提到了C API中的堆栈管理和环境表等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Lua基础

  • Lua没有main的概念,只能嵌入到一个宿主程序中工作,这个宿主程序被称作embedding program 或 host.
  • \ddd: ddd是一串最多三位的十进制数字, 用来描述一个字符. \10, 如果该字符后面接一个数字5,则\0105
  • 下面五种描述了完全相同的字符串:
a = “abc\n123”;
a = [[abc
123]];
a = [==[  -- [后面立刻跟了一个换行符被忽略
abc
123]==];
  • 长注释
--[  .......
... ] 


  • 换行

这种类型的字符串可以包含多行,且不会解释转义序列;如果第一个是换行符会自动忽略;

[[
/type
...
]]   
  • 基础数据类型 nil, number, string, boolean (nil, false为假其它都为真包括0), function, userdata

    userdata特性:

    • 用来将任意C数据保存在Lua变量中,该类型仅被定义了赋值和相同性判断;
    • 使用metatable, 可以为userdata自定义一组操作,;
    • 不能在Lua中创建出来, 也不能在Lua中修改, 只能通过API;
  • thread:

  • table:

    • 可以用任何东西做索引, 可以包含任何类型的值(nil除外);
    • a.name 来表示a[“name”];

  • table, function, userdata, thread变量本身不会真正的存放值, 仅仅放了一个对象的引用;
  • 数字与字符串之间的自动转换, 需要完全掌握数字怎么转换成字符串, 可以使用字符串库中的format函数;
  • 三种变量: 全局, 局部, table域
    对全局和table域的访问的含义可以通过metatable来改变,如t[i]等效于gettable_event(t,i)
全局变量:
  • 是放在一个特定的lua table域中, 这个table叫做environment table简称环境, 每一个函数都有对一个环境的引用, 函数可见的全局变量都放在这个函数所引用的环境表里, 可以调用getfenv取得环境,setfenv改变环境。
  • 对全局变量x的访问等价于_env.x , 又等价于gettable_event(_env, x)
  • Chunk
    Lua的一个执行单元, 一个Chunk就是一串语句段, 每个语句段以分号结束;
    Lua把一个Chunk当作一个拥有不定参数的匿名函数, 因此Chunk可以定义局部变量, 接收参数, 和返回值;
  • Block
    语句块, 语法上说,语句块就是语句段, 一个语句块可以被显式的写成一个单独的语句段 do block end
  • assignment赋值
    多个变量以,分开, 左边多的变量会被赋成nil, 右边多的变量会忽略, 赋值是先运算完再进行赋值;
  • metatable
    改变全局table域中的赋值操作: t[i] = val; 等价于settable_event(t,i,val); 对于全局变量的赋值x=val; 等价于_env.x = val;settable_event(_env, “x”, val);
控制语句
  • while exp do block end;
  • repeat block until exp;
  • if exp then block {else if exp then block} end;
  • return [explist]
    用于从函数或chunk中返回值, 且返回的值个数不止一个;
  • break用于从while/for/repeat中跳出循环;


break/return必须用于一个语句块的最后一句,即chunk最后一句enduntilelse前, 如果要在一个语句块的中间returnbreak, 则可以do break end;


  • for

  • 数字形式
    for v= start, end, step do block end;
    step没给出的情况为1, start end step 三个表达式只运算一次, 计算只在循环开始之前,且必须为数字; v的值是局部变量, 当for循环结束后就不能再使用;
  • 迭代器函数形式(泛型for):
    for namelist in explist do block end
    namelist如: var1, var2, var3,…;
    explist只会被计算一次, 返回三个值: 迭代器函数, 状态, 迭代器初始值;
    循环变量是局部变量, 循环结束后不能再使用;
    for i,v in ipairs(a) do print(v) end –遍历值
    for k in pairs(t) do print(j) end –遍历键
表达式
  • 如果一个表达式做为一个独立的语句出现,则其返回值将会被对齐到零个元素, 即忽略所有的返回值;
  • 如果表达式作为列表的最后一个元素,则不会有任何对齐;
  • 任何情况下,Lua将表达式看成单一元素, 忽略掉除第一个元素外的任何值;
    如:
    f(); 返回0个结果
    g(f(), x);f()仅取第一个元素
    g(x, f());f()取所有的元素
    a, b, c = x, f();f()调整为2个结果
    a, b, c = f(), x;f()取1个结果, c = nil
    a, b = ... ;a, b分别取可变参数的第1, 2个元素
    a, b, c = f();f()调整为3个元素

  • {f()};f()的所有元素创建一个列表; {...}可变参数创建一个列表;
    {f(), nil};f()被调整为1个结果;

  • 被括号括起来的表达式永远被当作一个值, 除非函数调用(如上面)才可能会是多个值. (f(x,y)) 无论f(x,y)返回多少个值,永远只取第一个值!

总结

出现f()在最后一个表达式时才取全部数据, 否则只取1个元素

操作符
  • 比较操作符
    数字和字符串的比较与常规的比较一样; 对table, userdata, thread以及function的比较是以引用的方式进行: 两个对象只有在指向同一个的东西时才会相等;

  • 逻辑操作符
    and, or, notfalse nil为假其它都为真;
    andor遵循短路原则, and在第一个为falsenil时返回第一个,否则返回第二个; or 在第一个不为falsenil时返回第一个,否则返回第二个; (该方法在完成真假判断的同时还有一定的选择功能)

  • ..字符串连接操作符, 数字后面加..时必须先加上空格以防解释成小数; 10 .. 2

  • # 取长度操作符, 长度是字节数, 一个字符一个字节;

    • table的长度n是以最后一个非nil所在的下标来计算的, 即t[n] ~=nil
      t[n+1] = nil;
    • 数组是1...n, 如果数组中存在nil值则#t则可能得到任意一个nil值的下标;
  • table构造
    每次table构造子被执行,就会造出一个新的table
    table实现一个list:

list = nil;
local i = 0;
for line in io.lines() do
-- 逆向构建一个list, next指向之前的list
list = {next = list, value = line}
i = i + 1;
if(i == 5) then break end
end
-- 遍历
while list ~= nil do
print(list.value)
list = list.next
函数
  • 函数调用
    prefixexp arg
    prefixarg是先被求值, 如果prefixexpfunction, 则这个函数就会被参数调用, 否则prefixexp的元方法会被调用, 元方法的第一个参数就是prefixexp的值, 后面的参数就是arg;
    参数的构造: args = ([explist]); = tableconstructor; = string;

  • 调用方法
    predixexp : name args,类似v.name(args)等效v.name(v.args)
    f{fields}等效f({fields})
    而这样的形式:f’string’f”string”/ f[[string]] 都等效(‘string’),这里的参数列表仅仅是一个字符串;

  • 尾调用return functioncall;
    尾调用: 被调用的函数将重用调用它的函数的堆栈, 因此无论多少此尾调用层次都不会造成过多的资源消耗; 同时尾调用将删除调用它的函数的任何调试信息;

  • 函数定义
    function funcname funcbody;
    local function name funcbody;
    function t.a.b.c.f () body end 等效于t.a.b.c.f = function () body end;
    local function f () body end等效于local f; f = function () body end;

  • C++中可以通过函数指针来调用不同的函数,但函数指针的参数是不变的, 而Lua中是可以调用可变参数的可变函数,通过unpack()来实现可变参数:

func(unpack(a)); --a为可变参数


  • ...变长参数
    变长参数... Lua中将参数存放在一个arg的表中;
  • 函数返回多个值时, 可以用select(index, func)来选择值,选择的范围是第index个到最后一个;
  • 命名参数:

即函数可以直接用一个表来作为参数, 同时在表中对参数进行命名,这样在函数定义中容易应用

function f(arg)
return arg.v1 + arg.v2
end
print(f{v1 = 4,v2 = 5}); --> 9
  • 形参列表
    如果实参该表达式处于另一表达式的中间,则表达式只返回一个值, 如果放于最后一个, 则不会做调整, 参考前面的表达式
    ...变长形参
    实参中存在表达式, 则表达式放于最后位置时会做相应的调整,多余的实参传入...

  • 可视规则
    变量的局部性类似C++

函数是first class即函数和其它值一样可以被存放在变量中,也可以放在表中,函数的参数和函数的返回值;

闭包 closure

词法界定: 一个函数嵌套另一个函数时, 内部的函数可以访问外部的函数的局部变量; 这些局部变量在内部函数中被称为upvalue外部局部变量; 这个功能与C++不同,如下例:

function out()
local i = 1;
return function () local j = 0; j = j + 1; i = i + 1; return i,j; end
end
print(out()());
print(out()());  --两个函数生成了不同的函数对象, out()返回不同的函数指针
local j = out(); --一个函数指针, i为堆栈中的局部变量
print(j()); --此时i = 1 + 1;j = 0 + 1; <-->2,1
print(j());  -- 见下面说明 

上面第二句print(j()),表现出了与C++的不同处,C++上面j()返回后内存被销毁,此处i将重新分配内存;而Lua的闭包功能就是函数仍然保留了前面j对象的内存, 则局部变量i=2保留到了当前的函数, 而j是局部变量,函数会重新得到所以不一样,因此 i = 2 + 1;j = 0 + 1; <–>3,1

  • 函数重定义
    函数可以存储在普通的变量内, 可以很方便的重定义或预定义函数;通过do ..end来封装则可以创建安全的运行环境–也称作沙箱;
function add(a,b) return a + b end
do
local he= add;
add = function (a,b) return a*b end; -- 函数重定义
print(he(2,3)); -- he保留了旧的函数运算:2 + 3
print(add(2,3)); -- 重定义函数:2 * 3
end
print(add(2,3)); -- 重定义:2*3  

上面的例子中, 将旧的add函数保存在了he局部变量中, 通过do..end包装, 可以保证he的局部性, 同时如果在end前不执行add = he, 则add函数会被永久替换;

协同程序
  • 协同程序类似多线程,但其实现并不是多线程的机制,而只是类似非抢占线程, 同一时刻只有有一个协程在运行(没有真正实现并发), 只能被显示的挂起coroutine.yield(co),没参数时挂起自己和恢复coroutine.resume(co);

  • 协程相对多线程的优点:

    • 1.协程是单线程, 协程的切换仅仅是程序控制,没有线程上下文的切换问题, 所以执行效率高;
    • 2.协程不需要锁机制, 也不需要处理互斥访问, 不会有死锁;

协程注意点:

  • co = coroutine.create(function()...end) 协程创建的参数只有一个: 将要运行的协同程序封装成的函数, 返回的cothread类型
  • 三种状态: 挂起, 运行, 停止;
    协同程序刚开始被创建时是挂起状态resume终止状态下的协同程序时会返回falseerror information

  • resume传递额外的参数:

thd = function(a)  --协同程序
while true do
if not a then a = 0 end
print(a .. ‘\n’)
coroutine.yield()
end
end
--每次通过thread类型创建协程时都会生产新的协程
co1 = coroutine.create(thd)
print(coroutine.resume(co1))   -->thd , nil
print(coroutine.resume(co1, 4)) -->thd, nil  

这里第一次启动协程的参数才是里面的a,后面传人的参数没有用,因为协程一直在while循环中运行或挂起,外部传人的变量并实际上不会改变局部变量a;

不对称性: 即挂起一个正在执行的协程的函数与使一个挂起的协程再次执行的函数不是同一个函数, 所以由执行到挂起之间状态的转换函数是不同的


  • yieldresume之间的数据传递:

yield可以传人参数, 参数会返回给下一次对该函数使用resume的协程, 如协程p中执行挂起时有yield(a,b), 那么如果下次有local status, v1, v2 = coroutine.resume(p);v1 = a, v2 = b

co = coroutine.create(
function () 
  local x = 0;
  while true do
    x = io.read()
    coroutine.yield(x+1, x+2)
  end
end
)

print(coroutine.resume(co));  
--可以看成是函数调用,然后返回了相应的值
-- 如果输入2; 则打印: true 3   4
  • 环境
    thread, function, userdata除了metadata外还关联一个环境表,多个对象可以共享一个环境表;

  • Coroutine
    类似多线程,就具体以后再看

C API
  • 堆栈

    1. LuaC传递值是通过一个虚拟栈来实现,栈上的每一个元素都是lua值(nil, string, number..); 每次lua调用C都会得到一个新的栈, 该栈独立于C函数本身, 也独立于以前的栈, 里面包含lua传给C的数据也包含C返回给lua的结果;
    2. 该堆栈的操作可以看成一个数组, 可以通过索引来查找内容, 1~n表示栈上的绝对位置即数组的位置; 而栈顶位于n的位置;因此-1~-n表示栈顶到栈底的过程;0不能作为索引;
  • 伪索引
    函数可以接受一个有效的索引–伪索引, 通过它可以访问一些不在栈上的lua值, 可以用来引用线程环境, 函数环境, 注册表等,
    线程的环境通常在伪索引LUA_GLOBALSINDEX, C环境则放在伪索引LUA_ENVIRONINDEX处;
    只要给定环境表的位置就可以访问该表,如访问全局变量的值
    lua_getfield(L, LUA_GLOBALSINDEX, varname)

  • C closure
    C函数被创建出来, 可能需要一些值关联起来, 从而生成一个C closure, 这些被关联的值叫做upvalue, 这些值对应的伪索引用lua_upvalueindex宏生产;

  • 注册表
    用于保存任何C代码想要保存的lua值, 伪索引LUA_REGISTRYINDEX

### Lua基础语法概述 Lua 是一种轻量级脚本语言,具有简单而强大的特性。以下是关于 Lua 的基本语法介绍: #### 变量与数据类型 Lua 支持八种原生数据类型[^2],其中包括四种简单的变量类型(`nil`, `number`, `string`, 和 `boolean`),以及四种复杂的变量类型(`table`, `function`, `thread`, 和 `userdata`)。 - **Nil**: 表示无值的状态。 - **Number**: 用于表示整数和浮点数。 - **String**: 字符串可以通过双引号或单引号定义。 - **Boolean**: 包含两个值:`true` 和 `false`。 例如: ```lua a = nil -- Nil 类型 b = 10 -- Number 类型 c = "Hello" -- String 类型 d = true -- Boolean 类型 ``` #### 关键字 Lua 中的关键字列表如下[^3],这些关键字不能被用作变量名或其他标识符名称: - `and`, `break`, `do`, `else`, `elseif`, `end`, `false`, `for`, `function`, `if`, `in`, `local`, `nil`, `not`, `or`, `repeat`, `return`, `then`, `true`, `until`, `while`, `goto`. #### 字符串操作 字符串的操作是 Lua 编程中的重要部分之一。通过内置的 `string` 库可以实现各种字符串处理功能。例如,使用 `string.sub()` 函数可以从指定位置提取子字符串[^1]。 代码示例: ```lua print(string.sub("Hello Lua", 4, 7)) --> 输出 'lo Lu' print(string.sub("Hello Lua", 2)) --> 输出 'ello Lua' print(string.sub("Hello Lua", -3, -1)) --> 输出 'ua' ``` #### 函数定义 函数在 Lua 中既可以作为普通的语句块存在,也可以作为一个表达式的返回值。匿名函数通常用于回调或者闭包场景中[^4]。 示例代码展示如何创建并调用带参数的函数: ```lua -- 定义一个加法器函数 function add(a, b) return a + b end result = add(5, 3) -- 调用该函数并将结果赋给 result print(result) --> 输出 8 ``` 对于更高级的应用场合,则可能涉及嵌套函数甚至闭包结构的设计思路。下面是一个利用闭包特性的实例演示: ```lua function createCounter(startValue) local count = startValue or 0 -- 初始化计数值,默认为零 return function() count = count + 1 -- 更新内部状态 return count -- 返回当前计数值 end end counterA = createCounter(10) -- 创建基于初始值 10 的计数器 A print(counterA()) --> 输出 11 print(counterA()) --> 输出 12 ``` --- #### 控制流语句 控制流程主要包括条件判断 (`if`)、循环迭代(`for`, `while`)等逻辑构建单元。这里给出几个典型片段供参考学习: ##### 条件分支 ```lua value = 5 if value > 0 then print("Positive number") --> 当 value>0 执行此条目 elseif value == 0 then print("Zero") else print("Negative number") end ``` ##### 循环遍历 ```lua for i=1,5 do -- 数值范围内的固定次数执行动作 print(i) end --> 分别输出 1 到 5 i = 1 -- 使用 while 实现相同效果 while i <= 5 do print(i) i = i + 1 -- 自增操作不可遗漏 end ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值