1.Lua是如何进行热更的?
由于Lua不需要编译,因此Lua代码可以直接在Lua虚拟机里运行。而C#代码在开始运行之前,都会一起装在到内存的代码段,没有办法更新新的代码。
动态装载:app + 内置脚本解释器,由这个脚本解释器动态的执行脚本代码
Lua = Lua解释器 + Lua脚本
2.Lua有哪些数据类型
nil(空类型),boolean,number(数字类型,不细分),string,function,userdata,thread,table
3.Lua中的string
Lua的字符串分为短字符串和长字符串。
短字符串: 长度小于等于40为短字符串。相同字符的短字符串是共用同一个的。
长字符串: 长度大于40为长字符串。相同的两个长字符串是两个独立的字符串。
4.Lua中table的内部数据结构
Lua table实际内部是包含一个数组 (array) 和hashtable来共同存储所有数据的。其中array用于存储键值为1 ~ n的数据,且1 ~ n必须连续。而其他的数据,会被存放在hashtable内。注意Lua数组要求key必须是连续的整数(1 ~ n),如果中间有空洞,那么可能出现的情况是后面的数据会被放到hashtable存储。
除此之外,如果数字键值对的数量过大,也会存储到hash表上(简单的说就是数组容量不够了)
5.Lua中table如何解决冲突
开放定址法
6.Lua中GC
标记清除式(Mark and Sweep)GC算法,每次GC的时候,对所有对象进行一次扫描,如果该对象不存在引用,则被回收。从Lua 5.1开始,采用三色增量标记清除算法。好处:它不必再要求GC一次性扫描所有的对象,这个GC过程可以是增量的,可以被中断再恢复并继续进行的。3种颜色分类如下:
白色:表示对象还没有被GC标记过,这也是任何一个对象创建后的初始状态。换言之,如果一个对象在结束GC扫描过程后仍然是白色,则说明该对象没有被系统中的任何一个对象所引用,可以回收其空间了。(白色又分为白1、白2)。
灰色:当前对象为待扫描状态,表示对象已经被GC访问过,但是该对象引用的其他对象还没有被访问到。
黑色:当前对象为已扫描状态,表示对象已经被GC访问过,并且该对象引用的其他对象也被访问过了。
备注:白色分为白1和白2。原因:在GC标记阶段结束而清除阶段尚未开始时,如果新建一个对象,由于其未被发现引用关系,原则上应该被标记为白色,于是之后的清除阶段就会按照白色被清除的规则将新建的对象清除,这是不合理的。于是lua用两种白色进行标识,如果发生上述情况,lua依然会将新建对象标识为白色,不过是“当前白”(比如白1)。而lua在清扫阶段只会清扫“旧白”(比如白2),在清扫结束之后,则会更新“当前白”,即将白2作为当前白。
7.pairs和ipairs的区别?
pairs遍历table里的所有元素,ipairs只遍历前面连续的元素
--下面lua代码运行的结果是
local a = { [1]=2,[2]=3,[4]=a,[5]=c }
for i,v in ipairs(a) do
print(v)
end
结果
2
3
ipairs顺序遍历数组的部分,遍历到3找不到就自动退出了
8.怎么获取table的长度?
如果tabl内容是连续的可以用#,存在key-value形式的就需要遍历计数
9.table的排序
按照value值来排序,使用 table.sort(needSortTable, func) 即可,可以根据自己的需要重写func,否则会根据默认来,默认的情形之下,如果表内既有string,number类型,则会因为两个类型直接compare而出错,所以需要自己写func来转换一下。
local test_table = {2,1,3,"SORT","sort"}
table.sort(test_table , function(a , b)
return tostring(a) > tostring(b)
end)
10.如何拼接字符串?
使用table.concat或者…
“…”每次拼接都会产生一个新的字符串,而在lua中每产生一个新的字符串都需要将该字符串存放在全局状态表(global_State)的strt域中,随着拼接次数增大,就会需要更大的空间存储新的字符串,当达到一定大小时,旧的字符串就需要GC,伴随着不断的开辟新空间和GC,就导致性能降低。
table.concat将table中的元素转换成字符串,再用分隔符连接起来。table.concat没有频繁申请内存,只有当写满一个8192的BUFF时,才会生成一个TString,最后生成多个TString时,会有一次内存申请并合并。在大规模字符串合并时,应尽量选择这种方式。
11.如何判断一个table是否为nil或者空表
--next查找下一个值,默认从第一个值开始
function IsTableEmpty(t)
return t == nil or next(t) == nil
end
12.__index,__newindex,__call,__tostring方法用法?
local mytable = {1, 2, 3}
local mymetatable = {
__index = {b = 6},
__newindex = function(table, key)
print(__newindex ..key)
end
,
__call = function(self, newtable)
sum = #newtable
return sum
end
,
__tostring = function(self)
return #self
end
}
--设置元表
mytable = setmetatable(mytable, mymetatable)
--获取对象元表
getmetatable(mytable)
--元表设置了__index方法,mytable找不到b元素(也可以是方法)就会去元表的__index中找
print(mytable.b)
--表里添加新元素时会调用__newindex方法
mytable.c = 3
--当表作为函数形式被调用时,进入__call方法
newtable = {1,2,3}
print(mytable(newtable))
--直接调用mytable,进入__tostring方法
print(mytable)
13.Lua如何设置只读表
--在元表的__newindex中抛出异常
function table_read_only(t)
local temp= t or {}
local mt =
{
__index = function(t,k)
return temp[k]
end,
__newindex = function(t, k, v)
error("attempt to update a read-only table!")
end
}
setmetatable(temp, mt)
return temp
end
--用法:
local ta = {1,2,3}
local tb = table_read_only(ta) --tb为只读
tb[5] = 1 --对表进行更新,会报错:attempt to update a read-only table!
14.__rawset 和 __rawget 有什么区别?
如果对一个表进行查找的时候,若表中不存在该值,则会查找该表的元表访问其元表__index字段来解决。而若对表输入一个新值,则会查找该表的元表访问其元表__newindex字段来解决。而rawset & rawget则是绕过元表这一过程,把操作这个表的结果直接输出。
rawset(table, key, value),为table加入一对key,value
key必须为字符串格式,输入其他格式会报错,添加时不会触发元表的__newindex
rawget(table, index)
获取table[index],不会触发元表的__index
15.Lua和C#底层如何交互
C#与Lua进行交互主要通过虚拟栈实现,栈的索引分为正数与负数,若果索引为正数,则1表示栈底,若果索引为负数,则-1表示栈顶。
C# Call Lua:C#把请求或数据放在栈顶,然后lua从栈顶取出该数据,在lua中做出相应处理(查询,改变),然后把处理结果放回栈顶,最后C#再从栈顶取出lua处理完的数据,完成交互
Lua Call C#:(1)Wrap方式:首先生成C#源文件所对应的Wrap文件,由Lua文件调用Wrap文件,再由Wrap文件调用C#文件。
(2)反射方式:当索引系统API、dll库或者第三方库时,如果无法将代码的具体实现进行代码生成,可采用此方式实现交互。缺点:执行效率低。
16.运行Lua的三种方式
1.执行字符串
void Start()
{
//lua环境对象,一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一。
LuaEnv luaenv = new LuaEnv();
//注意这里hello world使用单引号,这样符合C#规则
luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
//调用lua的print方法,前面会有LUA: 标识
luaenv.DoString("print('hello world2')");
luaenv.Dispose();
}
2.加载lua脚本运行
void Start()
{
LuaEnv luaenv = new LuaEnv();
//加载lua脚本,文件放在Resources下,因为Resource只支持有限的后缀,
//放Resources下的lua文件得加上txt后缀,如LuaTestScript.lua.txt
//TextAsset ta = Resources.Load<TextAsset>("LuaTestScript.lua");
//luaenv.DoString(ta.text);
//require加载文件必须放在Resources下,可以不用写.lua后缀
//require实际上是调一个个loader去加载,有一个成功就不再往下,
//全失败则报文件找不到。
luaenv.DoString("require 'LuaTestScript'");
luaenv.Dispose();
}
3.通过自定义loader方式加载文件
void Start()
{
LuaEnv luaenv = new LuaEnv();
//自定义loader加载的lua脚本可以放在任意合法路径,且不需要添加.txt后缀
luaenv.AddLoader(MyLoader);
//调用require时会优先使用自定义的MyLoader加载文件
//如果自定义loader中有返回值,就不会再去执行系统的loader调用
luaenv.DoString("require 'Test'");
luaenv.Dispose();
}
private byte[] MyLoader(ref string fileName)
{
//加载streamingAssets目录下的文件
string absPath = Application.streamingAssetsPath + "/" + fileName + ".lua";
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(absPath));
}
17.C#调用lua
1.获取一个全局基本数据类型
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//获取lua文件中的全局变量
int a = luaenv.Global.Get<int>("a");
Debug.Log(a);
luaenv.Dispose();
}
2.访问一个全局的table
<1>映射到普通class或struct
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//xLua会帮你new一个实例,并把对应的字段赋值过去。
//table的属性可以多于或者少于class的属性。可以嵌套其它复杂类型。
//要注意的是,这个过程是值拷贝,如果class比较复杂代价会比较大。
Person p = luaenv.Global.Get<Person>("person");
print(p.name + "-" + p.age);
//修改class的字段值不会同步到table,反过来也不会。
p.name = "haha";
luaenv.DoString("print(person.name)");
luaenv.Dispose();
}
class Person
{
public string name; //必须是public且名称一致
public int age;
}
/* Lua中的表
person = {
name = "siki",
age = 100,
Eat = function(self, a, b)
print(a + b)
end
}
*/
<2>利用接口做映射,推荐使用
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),
//代码生成器会生成这个interface的实例。如果get一个属性,生成代码会get对应的table字段,
//如果set属性也会设置对应的字段。也可以通过interface的方法访问lua的函数。
IPerson p1 = luaenv.Global.Get<IPerson>("person");
print(p1.name + "-" + p1.age);
//通过接口访问,相当于引用,会修改lua中的值
p1.name = "heihei";
luaenv.DoString("print(person.name)");
p1.Eat(1, 2);
luaenv.Dispose();
}
[CSharpCallLua]
public interface IPerson
{
string name { get; set; }
int age { get; set; }
void Eat(int a, int b);
}
<3>通过Dictionary,List做映射
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//Dictionary只能接受表里键值对形式的
Dictionary<string, object> dict = luaenv.Global.Get<Dictionary<string, object>>("person");
foreach (string key in dict.Keys)
{
print(key + "-" + dict[key]);
}
print("-----------");
//List只能接受表里非键值对形式的
List<object> list = luaenv.Global.Get<List<object>>("person");
foreach (object obj in list)
{
print(obj);
}
luaenv.Dispose();
}
<4>通过LuaTable获取,速度慢,不常用
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
LuaTable tab = luaenv.Global.Get<LuaTable>("person");
print(tab.Get<string>("name"));
print(tab.Get<int>("age"));
luaenv.Dispose();
}
3.访问一个全局的function
<1>映射到delegate,性能好,类型安全,推荐使用
[CSharpCallLua]
public delegate int Add(int a, int b, out int sub);
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
//要生成代码(否则会抛InvalidCastException异常)
Add add = luaenv.Global.Get<Add>("add");
int sub;
int res = add(12, 24, out sub);
print(res);
print(sub);
//引用不置空,Dispose时会报错
add = null;
luaenv.Dispose();
}
/* Lua中的方法
function add(a, b)
print(a + b)
return a + b, a - b
end
*/
<2>映射到LuaFunction,性能差
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaTestScript'");
LuaFunction func = luaenv.Global.Get<LuaFunction>("add");
object[] os = func.Call(12, 24);
foreach (object o in os)
{
print(o);
}
luaenv.Dispose();
}
18.Lua调用C#
1.通过CS.访问不同命名空间下构造函数,静态属性,方法
local newGameObj = CS.UnityEngine.GameObject()
--读静态属性
CS.UnityEngine.Time.deltaTime
--写静态属性
CS.UnityEngine.Time.timeScale = 0.5
--调用静态方法
CS.UnityEngine.GameObject.Find('helloworld')
--调用成员方法,建议用冒号语法糖
testobj:DMFunc(100)
--xlua只一定程度上支持重载函数的调用,C#的int,float,double都对应于lua的number,
--上面例子中DMFunc如果有这些重载参数,如DMFunc(int x) DMFunc(float x)将无法区分开来,只能调用生成代码中排前面的
2.将table映射到参数为结构体,类,接口,List,Dictionary的方法
--[[
C#中定义如下结构体,class和接口也支持,接口需要打标签[CSharpCallLua]
public struct A
{
public int a;
}
public struct B
{
public A b;
public double c;
}
成员函数如下:
public void Foo(B b)
--]]
--在lua可以这么调用
obj:Foo({b = {a = 100}, c = 200})
3.将function映射到委托,需要打标签
--[[
C#中定义如下委托
[CSharpCallLua]
public delegate void MyDelegate(int num);
成员函数
public void Foo(MyDelegate p)
--]]
myDelegate = function(num)
print("lua中对应的委托方法"..num)
end
obj:foo(myDelegate)
19.Lua如何实现面向对象?
Lua中的类通过table和function模拟出来,继承通过 __index和setmetatable模拟出来,多态通过重写父类的方法模拟。
-- 定义基类
Base = { name = "base", age = 99 }
function Base:showInfo()
print(self.name .." 年龄 "..self.age)
end
-- 定义基类的new方法,用于构造子类
function Base:new()
local sub = {}
--写法1
--setmetatable(sub, { __index = self })
--写法2
self.__index = self
setmetatable(sub, self)
return sub
end
-- 创建对象
Sub = Base:new()
Sub:showInfo()
-- 多态
function Sub:showInfo()
print(self.name)
end
Sub:showInfo()
20.点调用和冒号调用区别?
person = { name = "aaa", age = 10}
person.sleep = function()
print("在睡觉")
end
function person:eat()
print(self.name.."在吃饭")
end
--没用到self的方法,冒号调用和点调用都一样
person.sleep()
person:sleep()
--用到self的方法,通过点调用第一个参数为当前的table
--冒号是语法糖,通过冒号调用系统会自动传递当前的table给self
person.eat(person)
person:eat()