https://blog.csdn.net/thiousthious/category_8607984.html

运行

交互式模式
lua
lua -i 文件名 //执行完指定程序段后进入交互模式
调用函数dofile:dofile("文件名.lua") //会立即执行一个文件
luajit


注释

--单行注释

--[[ 多行注释 多行注释 --]]
io.read()


字符串

可以用 2 个方括号 "[[]]" 来表示"一块"字符串。

字符串连接使用的是 ..

使用 # 来计算字符串的长度,放在字符串前面

Lua 把 false 和 nil 看作是 false,其他的都为 true,数字 0 也是 true:


table(表)

table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。

Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。

在 Lua 里表的默认初始索引一般以 1 开始。table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 都是 nil。

string.rep(s, n)
解释:返回字符串s串联n次的所组成的字符串,参数s表示基础字符串,参数n表示赋值的次数。
使用函数string.rep()和使用操作符..效果是一样的,但是推荐使用函数string.rep(),因为查过相关的资料说大量迭代使用操作符..费时费力。
string.rep()在遇到\0时不会认为是字符串结尾,而是把字符串的所有内容都拼接起来。

当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。


全局变量

在默认情况下,变量总是认为是全局的。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。 如果你想删除一个全局变量,只需要将变量赋值为nil。当且仅当一个变量不等于nil时,这个变量即存在。


Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。变量的默认值均为 nil。

Lua 可以对多个变量同时赋值,遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:
x, y = y, x -- swap 'x' for 'y'
a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]'

多值赋值经常用来交换变量,或将函数调用返回给变量:
a, b = f()
应该尽可能的使用局部变量,有两个好处:

  1. 避免命名冲突。
  2. 访问局部变量的速度比全局变量更快。

索引

对 table 的索引使用方括号 []。Lua 也提供了 . 操作。

t[i] t.i -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用


函数

Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 ... 表示函数有可变的参数。

function add(...)  
local s = 0  
  for i, v in ipairs{...} do   --> {...} 表示一个由所有变长参数构成的数组  
    s = s + v  
  end  
  return s  
end  
print(add(3,4,5,6,7))  --->25

我们可以将可变参数赋值给一个变量。

function average(...)
   result = 0
   local arg={...}    --> arg 为一个表,局部变量
   for i,v in ipairs(arg) do
      result = result + v
   end
   print("总共传入 " .. #arg .. " 个数")
   return result/#arg
end

print("平均值为",average(10,5,3,4,5,6))
我们也可以通过 select("#",...) 来获取可变参数的数量,也可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:

'%' 用作特殊字符的转义字符,因此 '%.' 匹配点;'%%' 匹配字符 '%'。转义字符 '%'不仅可以用来转义特殊字符,还可以用于所有的非字母的字符。


数组

一维数组是最简单的数组,其逻辑结构是线性表。Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。在 Lua 索引值是以 1 为起始,但你也可以指定 0 开始。


模块

Lua 的模块是由变量、函数等已知元素组成的 table,因此就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。

-- 定义一个名为 module 的模块
module = {}
 
-- 定义一个常量
module.constant = "这是一个常量"
 
-- 定义一个函数
function module.func1()
    io.write("这是一个公有函数!\n")
end
 
local function func2()
    print("这是一个私有函数!")
    --声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问
    --模块里的这个私有函数,必须通过模块里的公有函数来调用.
end
 
function module.func3()
    func2()
end
return module

由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。

require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
require("<模块名>") | require "<模块名>"
执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。

函数 require 有它自己的文件路径加载策略。
require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化
如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 "~lua" 路径加入 LUA_PATH 环境变量里:
LUA_PATH
export LUA_PATH="~/lua/?.lua;;"
文件路径以 ";" 号分隔,最后的 2 个 ";;" 表示新加的路径后面加上原来的默认路径。

接着,更新环境变量参数,使之立即生效。
source ~/.profile


C包

与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。

Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。
loadlib 函数加载指定的库并且连接到 Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为 Lua 的一个函数,这样我们就可以直接在Lua中调用他。

如果加载动态库或者查找初始化函数时出错,loadlib 将返回 nil 和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:

local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f()  -- 真正打开库

一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。
将 stub 文件所在的目录加入到 LUA_PATH,这样设定后就可以使用 require 函数加载 C 库了。


面向对象

使用点号(.)来访问类的属性:r.length
使用冒号 : 来访问类的成员函数:r:printArea()
内存在对象初始化时分配。


loadstring

loadstring(string [,chunkname])
解释:函数会从所给的字符串中来加载程序块并运行,常使用这种构造式来调用assert(loadstring(s))(),如果省略参数chunkname,那么它默认为所给的字符串。参数chunkname的作用,就是在错误的提示信息中起到提示作用。

它类似加载,从给定的字符串得到块,要加载和运行一个给定的字符串。

一般如下用法:
assert(loadstring(script))()

f = loadstring("a = 1")
相当于: f = loadstring("function() a = 1 end")

复杂用法:1、下面是动态加载字符串,并执行,结果为一个table

local  script="local ee={[0]={id=0,lv=5,text='yy'},[1]={id=1,lv=3,text='zz'}}  return ee"
local tb=assert(loadstring(script))()
print(tb[0].text)

2、下面是动态加载字符串,并执行,结果为方法

local  addscript="function dadd(a,b) return a+b  end"
assert(loadstring(addscript))()
print(tostring(dadd(2,3)))

lua随机数生成:
https://cloud.tencent.com/developer/article/1693312
https://www.cnblogs.com/SkyflyBird/p/9959990.html

lua全局变量:
https://www.linuxidc.com/Linux/2014-05/102528p2.htm


核心结构(底层)

1 lua_State* luaL_newstate() 生成栈

struct lua_State {
    //全局信息、栈信息、函数、GC、hook
    1全局存储信息,起到类似全局变量的效果
    2栈信息,上下限索引、计数等
    3函数相关,用链表或者其他形式把所有函数存储起来并且能够获取
    4GC相关,用于垃圾回收
    5函数跟踪调试,给出对外的接口以便于调试代码以及打印情况
    6异常抛出处理,用于处理异常情况
    
    *lua_State本身也是一个可以GC的对象
    -------------------------------
    global_state:
    字符串的哈希表
    原表、上值等
    内存分配函数
    GC相关的众多链表及相应变量
    全局统计信息:总内存、正在使用的内存估算
    
    CallInfo:保存当前调用函数的信息,也提供了一系列指针方便索引栈、函数顶、其他函数。
}

2 lua的数据结构

让数值自带类型,再以联合union的形式构建数据结构

定义基础数据类型的type(lua.h)
字节码为字面值时,先检查tt获取类型,再读取Value做对应类型数据的存储和处理,再根据需要做内存回收的数据类型和不需要做数据回收的,封装一个GC头部。

union GCObject
{
    // 需要GC的数值类型
    GCheader gch;
    union TString ts;
    union Udata u;
    union Closure cl;
    struct Table h;
    struct Proto p;
    struct UpVal uv;
    struct lua_State th;  /* thread */
};

typedef union {
  GCObject *gc;
  void *p;
  lua_Number n;
  int b;
} Value;

#define TValuefields  Value value; int tt //tt:获取数据类型

typedef struct lua_TValue {
  TValuefields;
} TValue;


补充与说明:
例:#define LUA_TNIL  0   //lua.h

//tt,即该GC对象的具体类型
//next,指向GCObject的指针,用于GC算法内部实现链表
//marked,用于GC算法内部实现
#define CommonHeader  GCObject *next; lu_byte tt; lu_byte marked

typedef struct GCheader {
  CommonHeader;
} GCheader;

*LUA_TLIGHTUSERDATA和LUA_TUSERDATA一样,对应的都是void*指针,区别在于,LUA_TLIGHTUSERDATA的分配释放是由Lua外部的使用者如C/C++来完成,而LUA_TUSERDATA则是通过Lua内部来完成的。换言之,前者不需要Lua去关心它的生存期,由使用者自己去关注,后者则反之。

字符串:选用了哈希桶+字符串哈希存储,将总表放置在全局变量中方便查找。
唯一比较有特色的地方是有一个dummy,做字节对齐操作。
ypedef struct global_State {
    stringtable strt;  /* hash table for strings (保存了所有字符串的统一集合)*/
}

typedef struct stringtable {
    GCObject** hash; // 存放一系列链表的数组
    lu_int32 nuse;  /* number of elements */
    int size;
} stringtable;

表:为存储键值对,加入一个数组存储k/v型,每个节点Node,均用Tvalue类型的key和value。插入k/v时,对key做哈希(假设为i),然后存入对应的Node数组索引下标为i的地方。
哈希得到的结果会很分散,为了将数据存放在比较规整的数组中,我们可以用哈希对数组大小取余,余数即数组索引。为此我们对key单独创建一个结构体增加链表指针,即通过哈希找到该key所在数组位置后,再根据key的链表取找寻对应的key。(实际Lua会比这个更复杂)
问题:插入和查询都需要去考虑在哪种数组,扩容和缩容亦然。
typedef union TKey {
  struct {
    TValuefields;
    struct Node *next;  /* for chaining */
  } nk;
  TValue tvk;
} TKey;


typedef struct Node {
  TValue i_val;
  TKey i_key;
} Node;

//k/v型数组+纯数字索引数组
typedef struct Table {
  CommonHeader;
  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */ 
  lu_byte lsizenode;  /* log2 of size of `node' array */
  struct Table *metatable;
  TValue *array;  /* array part */
  Node *node;
  Node *lastfree;  /* any free position is before this position */
  GCObject *gclist;
  int sizearray;  /* size of `array' array */
} Table;

上值和函数(闭包):
函数,方便表达层级关系以及传值的数据结构,所以至少需要的基本元素:指令集的存储、局部变量、指针指向栈上的传参及上层函数、全局变量、静态变量等。

对函数外部变量进行结构体封装:
用一个指针pStackVal指向栈,如果栈上的变量回收,则改为指向自身的value结构体获取数值
封装value联合体,若值仍在栈上,则通过pNext进行链表连接后续的外部变量值;若不在栈上,则存储于FixedVal中。
typedef struct ExternalVal
{
    TValue* pStackVal;

    union 
    {
        Tvalue FixedVal;
        struct 
        {
            ExternalVal* pNext;
        }list;
    }value;
}ExternalVal;

typedef struct TFunction
{
    Instruction* pPC;    // 函数指令集
    TValue*     pLocalVal;    // 函数内定义的变量
    Other    otherVal;     // 辅助变量
}TFunction;

//CFunction、LFunction:脚本语言是要和静态语言进行交互的,因此难免会有静态语言的函数调用,而此类函数仅需要存储函数指针及传值,并不需要用到指令集之类
//C仅需要传参和返回值
typedef struct CFunction
{
    CommonFunctionHeader;
    void (func*)(Lua_state* L);
    TValue* pVal;
}CFunction;

typedef struct LFunction
{
    CommonFunctionHeader;
    struct TFunction* pFunction;
    ExternalVal*      pVal;
}

对比Lua,函数原型Proto即为此处的函数通用结构体TFunction,上值UpVal即为外部变量ExternalVal,而上值和函数原型的组合,就称之为闭包Closure。