是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。
lua是西班牙语的月亮
最快的脚本语言
可以编译调试
与C/C++结合容易
Lua 是对性能有要求的必备脚本
魔兽世界、博德之门、仙剑奇侠传五、愤怒的小鸟等游戏的任务脚本和后端服务器。
Google的大量应用,和战胜李世石的AlphaGo
视频服务器、入侵检测系统脚本、防火墙脚本
下载 Lua 5.4.4 http://www.lua.org/versions.html
安装 vs2019
编译Lua为动态库 (不用静态库,影响c++编译速度)
建立Lua编译器项目
编译Lua库为了以后对其拓展(也可以直接下载库)
启动VS2019,创建新项目,选择“Windows桌面向导”,点击“下一步”。
设置项目名称,这里我设置的名称是“lua-5.4.1”,点击“创建”。
此时会弹出一个对话框,下拉应用程序类型选择“静态库”或者动态库,选择“空项目”,点击“确认”,等待项目创建完毕。
创建好项目后,在【头文件】选项上点击鼠标右键,选择【添加】—>【现有项】,然后找到刚刚解压Lua压缩包的目录,选择Lua目录下的src文件夹,这里我的是D:\lua-5.4.1\src目录。然后把src下的所有.h文件添加到头文件。
同上操作,在【源文件】选项上点击鼠标右键,以此选择【添加】—>【现有项】,找到Lua目录。把src下除去“lua.c”和“luac.c”后的所有.c文件都添加到源文件。
注意: 需要注意的是 lua.c 和luac.c 不能拷贝进去,这两个文件不能编译。
导入头文件和源文件之后,右键【项目】—>【属性】,在界面中操作,【C/C++】—> 【高级】—> 【编译为】选择【编译为C代码(/TC)】,最后确定。
添加预处理器 LUA_BUILD_AS_DLL
把Debug替换成Release。
生成静态库或者动态库。
显示生成成功。
打开项目目录–找到release文件夹–找到lua5.3.lib(你自定义项目名称)。
上面我们已经编译好了Lua5.4.1的库文件,下面进行测试lua库是否可以使用。
创建一个控制台应用
右键单击项目–选择属性。
选择C/C+±-常规–附加包含目录–选择lua源码目录的src文件夹。
选择链接器–常规–附加库目录–选择Lua库文件目录
选择链接器–输入–附加依赖项–输入我们所编译的lua5.4.1.lib库文件名称(刚才生成后拷贝到Lua源码目录下的静态链接库)。然后点击应用,确定就好了。
到此Lua开发环境就配置好了,下面测试搭建的Lua环境。
在源代码里创建一个main.lua的文件,如图所示。
创建好后,在里面输入打印代码:
print("Hello Lua")
如图所示。
然后在主函数里面,编写测试代码并运行:
extern "C"
{
#include
#include
#include
}
int main()
{
//lua_State* lua = lua_open();
lua_State* lua = luaL_newstate();
//打开基本库
luaopen_base(lua);
//打开string库(使用string时需要打开)
luaopen_string(lua);
//打开table库
luaopen_table(lua);
//打开IO库
luaL_openlibs(lua);
//打开math库
luaopen_math(lua);
if (luaL_loadfile(lua, "main.lua"))
{
const char* error = lua_tostring(lua, -1);
printf("%s\n 执行脚本文件失败", error);
return -1;
}
if (lua_pcall(lua, 0, 0, 0))
{
const char* error = lua_tostring(lua, -1);
printf("%s\n 执行脚本文件失败", error);
return -1;
}
lua_close(lua); //释放lua内存
return 0;
}
全局变量
b = 2
本地变量 (尽量用本地,保证及时的垃圾回收)
local a = 1
用于区分具有一些数据或没有(nil)数据的值
全局变量设为nil会交给垃圾回收
local a = nil
print(type(a)) -- > nil
注意Lua中所有的值都可以作为条件
除了false和nil为假,其他值都为真,0为真
local a = true
Lua中没有整数,都是用浮点数运算
对应c的double类型
新版Lua中有基于64位的整形
tonumber 转换格式
local a = "123" --定义一个string类型数据
local b= tonumber(a) --转换成number
print(type(b)) --输出:number
print(b) --输出:123
tostring转换格式
local a = 123 -- 定义一个number类型数据
local b= tostring(a) -- 转换成string
[[ ]] 多行字符串赋值
”与C一样的转义\””
local html = [[
]]
print(html)
字符串拼接 ..
local str = "test1" .. "test2"
print(str) -- 输出:test1test2
字符串长度 string.len
字符串子串 string.sub(str,3,5)
local str = "12345678" --定义一个string类型数据
print(string.len(str)) --输出str的长度:8
print("sub string = "..string.sub(str,3,5)) --输出:sub string = 345
字符串查找 local b,e = string.find(str,"HEAD") 支持正则
local str = "12345678"
local b,e = string.find(str,"345"); --查询str中是否有345子串,成功返回子串开始和结束位置,失败返回nil
print("b = "..b .." e="..e); --输出:b = 3 e=5
字符串替换 string.gsub(str,"HEAD","XCJ") 支持正则
local str = "12341234"
local str2 = string.gsub(str,"12","ab") --把str中"12"替换成"ab"
print(str2) --输出:ab34ab34
and or not
if(a==1 or a==3)
if(a==1 and b == 1)
if(not (a==1)) 注意加括号,解决逻辑次序问题
< > <= >= ~= ==
if((1==1 and 1==3) or 3==3) then
print("1==1")
elseif (2==2) then
print("2==2")
else
print("in else")
end
local i = 100
while(not(i <0)) do
print("i="..i)
if(i == 90) then
print("break while") --打印100到90
break
end
i = i-1
end
local i =100
repeat
i = i + 1
print("i = " .. i) --打印101到111
until i>110
数值循环
for var = 1,10 do
print("var = " .. var) --从1循环到10退出,一次默认加1
end
for var = 1,10,2 do
print("var = " .. var) --从1循环到10退出,一次增加2 var值变化为:1,3,5...
end
范型循环
local days={"Sun","Mon","Tue"}
for i,v in pairs(days) do
print(i.."=="..v) --输出:1==Sum 2==Mon 3==Tue
end
local tab = { [1] = "A",[2] = "B",[4] = "D"}
for i,v in ipairs(tab) do
print(i.."--"..v) //用ipairs时只输出1,2 用pairs输出1,2,4
end
表的大小 table.getn(t1)
插入 table.insert(a, pos,line)
不传pos相当于 push_back
删除 table.remove(a,pos) 返回这次删除的值
不传pos相当于 pop_back
-- 数组(保存相同类型)
local tab1 = {"001","002","003"}
table.insert(tab1,2,"002-2") --在第2个元素后面插入一个元素
table.insert(tab1,"004") --没有指定插入位置,默认在最后面插入一个元素
table.remove(tab1,1) --删除指定的第一个元素
table.remove(tab1) --删除最后一个元素
for i,v in ipairs(tab1) do
print("v = ".. v)
end
--哈希表(同时存在不同类型)
local tab2 = {id=123,age=20}
tab2["name"] = "xiaoming" --插入一个元素
tab2["id"] = nil --删除一个元素
for i,v in pairs(tab2) do --注意这里不能用ipairs,ipairs只能遍历数组,在这里遍历不出来
print("v -- ".. v)
end
--多维数组
local tab3 = {}
tab3[1] = {"name1","value1"}
tab3[2] = {"name2","value2"}
for k,v in pairs(tab3) do
for k2,v2 in pairs(v) do
print("k2="..k2,"v2="..v2)
end
end
普通参数
--调用者传参少于定义的参数个数时,没有传的参数默认为nil
--调用者传参多于定义的参数个数时,多余的参数舍弃
function test(p1,p2)
if(p1 == nil) then
p1 = "000"
end
if(p2 == nil) then
p2 = "000"
end
print("p1="..p1,"p2="..p2)
end
--调用上面定义的函数
test(123)
可变参数
function test(...)
local arg={...} --获取参数 {...} 表示一个由所有变长参数构成的数组
print("arg len is "..#arg) --打印参数长度
for i,v in ipairs(arg) do
print(v) --打印传进来的每个参数
end
end
多返回值 (不建议使用,太特殊的用法,可通过直接返回表实现)
function test(p1,p2)
--可以选择返回任意数量返回值
return 11,"22"
end
--可以选择接收几个参数,多接收的值为nil,少接收的参数丢弃
local ret1,ret2 = test()
print(ret1..ret2) --打印:1122
Lua与C++交互次序
Lua栈的访问下标
编写c代码
int CTest(lua_State* L)
{
size_t len;
//获取lua传入的第一个参数(string类型),len为参数长度
const char* name = lua_tolstring(L, 1, &len);
int age = lua_tonumber(L, 2);//获取lua传入的第二个参数(int类型接收)
bool is = lua_toboolean(L, 3);//获取lua传入的第三个参数(bool类型接收)
//打印内容:name = 123abc len = 6 , age = 456 , is = 1
printf("lua name = %s len = %d , age = %d , is = %d\n", name, len, age, is);
return 0;
}
//将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码
//在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。
lua_register(lua, "CTest", CTest);
编写lua代码
--调用c++定义的函数
CTest("123abc", 456, true)
编写c代码
int CTestArr(lua_State* L)
{
printf("int CTestArr ");
int len = luaL_len(L, 1);//获取table长度
printf("len = %d\n", len);
for (int i = 1; i <= len; i++)
{
lua_pushnumber(L, i);//插入数字索引
lua_gettable(L, 1);//将刚刚插入的数字索引出栈(table[i]),然后压入栈顶
size_t size;
const char* str = lua_tolstring(L, -1, &size);//获取压入栈顶的值 获取每个table值
printf("str = %s\n", str);
lua_pop(L, 1); //将刚刚改变的栈恢复
}
return 0;
}
//将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码
//在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。
lua_register(lua, "CTestArr", CTestArr);
编写lua代码
local arr = {"A001","A002","A003","A004"}
--调用c++定义的函数
CTestArr(arr)
编写c代码
int CTestTable(lua_State* L)
{
lua_pushnil(L);//插入一个空的table
while (lua_next(L, 1) != 0)
{
//先push key(先插入栈顶 -2),在push value(后插入栈顶-1)
printf("key = %s", lua_tostring(L, -2));
printf(" value = %s\n", lua_tostring(L, -1));
lua_pop(L, 1);
}
//获取栈底中ket为name的值压入栈顶
lua_getfield(L, 1, "name");
//获取刚刚压入栈顶的值
printf("name = %s\n", lua_tostring(L, -1));
//获取判断第二个参数类型
//luaL_checktype(L, 1, LUA_TTABLE);
if (lua_type(L, 2) != LUA_TNUMBER)
{
printf("para 2 is not number\n");
}
return 0;
}
//将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码
//在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。
lua_register(lua, "CTestTable", CTestTable);
编写lua代码
local tab = {name="xiaoming",age="22",id="003"}
CTestTable(tab,12)
编写c代码
返回普通参数
int CTestReAdd(lua_State* L)
{
//检查栈中的参数是否合法,1表示Lua调用时的第一个参数(从左到右),依此类推。
//如果Lua代码在调用时传递的参数不为number,该函数将报错并终止程序的执行。
double op1 = luaL_checknumber(L, 1);
double op2 = luaL_checknumber(L, 2);
//将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。
lua_pushnumber(L, op1 + op2);
//返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。
return 1;
}
返回表参数
int CTestReAdd(lua_State* L)
{
//创建一个空表
lua_newtable(L);
//插入key value
lua_pushstring(L, "name");
lua_pushstring(L, "xiaoming");
//把值写入刚刚创建的表中
lua_settable(L, -3);//因为刚刚插入了两个值,所以表的位置在-3
//在插入一个key value
lua_pushstring(L, "age");
lua_pushnumber(L, 33);
lua_settable(L, -3);//因为上面使用lua_settable把插入的值出栈了,所以刚刚入栈了两个,现在还是-3
//返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。
return 1;
}
编写lua代码
local re = CTestRe()
--打印接收的普通参数
print(re)
--打印接收的表参数
print("name="..re["name"])
print("age="..re["age"])
编写c代码
//c++给lua传递一个全局变量 需要写在调用脚本前面
lua_pushstring(lua, "hello");//把string数据压入栈顶
lua_setglobal(lua, "ctest");//设置lua全局变量ctest,数据为刚刚压入的栈顶的值
//c++给lua传递一个全局表
lua_newtable(lua);//创建一个表放入栈顶
lua_pushstring(lua, "name");//把key压入栈中
lua_pushstring(lua, "xiaoming");//把value压入栈中(string类型)
lua_settable(lua,-3);//把压入的key和value出栈到刚刚创建的表中,因为刚刚新压入了两个数据,所以表的位置在-3
lua_pushstring(lua, "age");//在压入一个新的key到栈中
lua_pushnumber(lua, 22);//在压入一个新的value到栈中(number类型)
lua_settable(lua, -3);//把压入的key和value出栈到刚刚创建的表中
lua_setglobal(lua, "person");//设置lua全局变量person,数据为刚刚压入的栈顶的值
编写lua代码
//获取打印普通变量
print("c++ test = "..ctest) --打印内容为:c++ test = hello
//获取打印表 下面两种方式都可以获取表中数据
print(person.name)
print(person["age"])
编写c代码
//获取lua中普通变量 写在调用脚本后面
lua_getglobal(lua, "width");//获取lua中定义的width全局变量,压入栈顶
int width = lua_tonumber(lua, -1);//从栈里面取值
lua_pop(lua, 1);//从栈顶弹出,让栈恢复原状
printf("width = %d\n", width); //打印内容为:width = 1920
//c++获取lua表数据
lua_getglobal(lua, "conf");//获取lua中定义的conf全局变量,压入栈顶
lua_getfield(lua, -1, "titlename");//获取conf表中key为titlename的数据压入栈顶
printf("title = %s\n", lua_tostring(lua, -1)); //打印内容为:title = first lua
lua_pop(lua, 1);
lua_getfield(lua, -1, "height");
printf("height = %d\n", (int)lua_tonumber(lua, -1)); //打印内容为:height = 1080
lua_pop(lua, 2);//开头lua_getglobal压入的表和上面lua_getfield压入的值一起出栈清理
编写lua代码
//定义普通全局变量
width = 1920
//定义全局table
conf =
{
titlename = "first lua",
height = 1080
}
编写c代码
//调用lua函数
printf("top is %d\n", lua_gettop(lua));
lua_getglobal(lua, "ferror");//压入一个错误处理函数
int errfun = lua_gettop(lua);//获取此时栈的数量,也是栈底距离刚刚压入的错误处理函数的位置
lua_getglobal(lua, "event");//把要调用的lua函数event压入栈顶
lua_pushstring(lua, "key");//压入一个string类型数据到栈顶,这里压入给函数做参数(做为函数第一个参数)
lua_newtable(lua);//创建一个空表压入栈顶(做为函数第二个参数)
lua_pushstring(lua, "name");//压入一个key值到栈顶
lua_pushstring(lua, "zhangshan");//压入一个value值到栈顶
lua_settable(lua, -3);//将key值和value值给刚刚创建的表并出栈
//上下文 传参的个数(上面压入了几个参数就传几个) 接收返回值的个数 函数出错返回信息
//if (lua_pcall(lua, 1, 1, -3) != 0)
if (lua_pcall(lua, 1, 1, errfun) != 0)//两种方式都可以,区别是错误函数一个从栈顶开始计算,一个从栈底
{
printf("call event failed %s\n", lua_tostring(lua, -1));//没有接收错误信息时,默认把错误信息压入栈顶
lua_pop(lua, 1);//把栈顶错误信息出栈,清理堆栈
}
else
{
//函数执行成功返回值压入栈顶,此时读取
//读取返回的表的返回值
lua_getfield(lua, -1, "id");//把表key为id的值压入栈顶
printf("event success %d\n", (int)lua_tonumber(lua, -1));
lua_pop(lua, 2);
//读取普通返回值
//printf("event success %s\n", lua_tostring(lua, -1));
//lua_pop(lua, 1);//把栈顶返回值出栈,清理堆栈
}
lua_pop(lua, 1);//把压入的错误处理函数出栈
printf("top is %d\n", lua_gettop(lua));
编写lua代码
function event(e,obj)
print("c++ call lua function");
print(e)
print(obj.name)
--return "return lua event" --返回普通返回值
return {id=123} --返回一个表
end
--定义一个错误函数,在lua遇到错误时在c++中调用
function ferror(e)
print("my error:" .. e);
return "lua change error"
end
对于一个table来说,要获得其长度,最简单的应该是直接使用“#”操作符。
注意table.getn函数在lua5.2之后版本已经被移除。
当然,如果table是一个数组,那么长度计算一般是正确的(没有值为nil)
t={1,2,3,4}
print(#t)
--this will output 4
虽然“#”操作符简单,但是,其计数有时候并不准确。
t={[-1]=-1,[0]=0,[1]=1,[2]=2,[4]=5}
print(#t)
--this will output 4
t={[-1]=-1,[2]=1}
print(#t)
--this will output 0
t={a=1,b=2}
print(#t)
--this will output 0
所以,如果我们计算table,最好自己写一个函数
function table_leng(t)
local leng=0
for k, v in pairs(t) do
leng=leng+1
end
return leng;
end
t={2,3,4,5}
t[2]=nil
print(#t) --this will output 4
print(table_leng(t)) --this will output 3, because t[2] was deleted
压栈和出栈操作时要留意是否存在漏了出栈导致堆栈泄露
在操作前和操作后都检查栈的数量:int (lua_gettop) (lua_State *L);
printf("top is %d\n", lua_gettop(lua)); //返回此时栈的数量 输出:top is 4