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()
应该尽可能的使用局部变量,有两个好处:
- 避免命名冲突。
- 访问局部变量的速度比全局变量更快。
索引
对 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。