Lua学习笔记

387 阅读7分钟

Lua简介

轻量小巧、脚本语言、C语言开发、开源、可拓展

设计目的:为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能

快速入门

参考资料

核心内容精简

  1. 注释-- --[[]]--
  2. 数据类型nil/number/boolean/string/function/userdata/thread/table
  3. 全局变量、局部变量local、表中的域
  4. 循环while do end for i=1,10,1 do end for i,v in ipairs(a) do end repeat until break goto
  5. 条件if then elseif then else end(只有nil和false为假)
  6. 函数 以end结束,无{} 两种定义、多返回值、可变参数arg = {...}
  7. 特殊的运算符~= and or not 连接字符串.. 返回串或表长度#
  8. 字符串‘’ “” [[]] 转义字符、string类包含字符串操作
  9. 数组,定义array={} 取值赋值array[x]=x 索引从1开始
  10. 迭代器,泛型for 无状态 多状态
  11. 表table,可以实现数组、哈希表、对象、集合、键的类型自由度高、table类下包含表的操作
  12. 模块,其实就是拥有自定义属性和方法的表、定义模块return 表名 、加载模块require("表名")、需添加加载路径
  13. 元表metatable,自定义操作方法、设置元表setmetatable(表,元表)、返回元表getmetatable(表),元表中要包含元方法,没有元方法的元表是无意义的
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
  1. 元方法,

    __index若是表,则相当于扩展了原表的属性和方法,若是函数,则元表查不到的话就执行该函数

    __newindex,如上,只不过是针对对表中不存在的索引赋值时调用

    __call,调用值时用的,如table(xxx)

    __tostring,输出用的,如print(table)

    其他就是一些运算与比较操作了

  2. 协同程序coroutine,四种状态,用来解决生产者-消费者问题

  3. 错误处理,assert(判断语句,报错信息)中止正在执行的函数error(报错信息)

  4. 垃圾回收,系统自动,赋值nil为手动

  5. 面向对象,

    封装:定义属性table={名=值,名=值...}、定义方法function table:名(xxx) end 、创建对象(前提有一个生成对象的方法)调用方法即可、访问属性o.属性、访问方法o:方法()

    继承:派生类 = 基类构造的对象,再重写new方法(创建对象的方法)

    派生类重写基础类的函数:再定义一次函数就行

-- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end
-- 基础类方法 printArea
function Shape:printArea ()
  print("面积为 ",self.area)
end

-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()

Square = Shape:new()
-- 派生类方法 new
function Square:new (o,side)
  o = o or Shape:new(o,side)
  setmetatable(o, self)
  self.__index = self
  return o
end

-- 派生类方法 printArea
function Square:printArea ()
  print("正方形面积为 ",self.area)
end

-- 创建对象
mysquare = Square:new(nil,10)
mysquare:printArea()

Rectangle = Shape:new()
-- 派生类方法 new
function Rectangle:new (o,length,breadth)
  o = o or Shape:new(o)
  setmetatable(o, self)
  self.__index = self
  self.area = length * breadth
  return o
end

-- 派生类方法 printArea
function Rectangle:printArea ()
  print("矩形面积为 ",self.area)
end

-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()

XLua

参考资料

核心内容精简

  1. 文件加载

    执行字符串luaenv.DoString("print('hello world')")

    加载Lua文件DoString("require 'byfile'")

    自定义Loader???

  2. C#访问Lua

    获取全局基本数据类型luaenv.Global.Get<数据类型>("a")

    其他 同样用Get方法,但要映射一下

  3. Lua访问C#

    lua里头没有new关键字;

    所有C#相关的都放到CS下,包括构造函数,静态成员属性、方法;

Intellij IDEA

常用的编辑Lua的工具

常用快捷键

  • 查询一个方法在哪些地方被调用:ctrl+点击方法
  • 撤销、回退:ctrl+z ctrl+shift+z
  • 当前文本查找:ctrl+f
  • 所有文本查找:ctrl+shift+r
  • 当前文本替换:ctrl+r
  • 递进式选择代码:ctrl+w
  • 最近打开的文件记录:ctrl+e
  • 查询类名:ctrl+n
  • 查询所有:右上角搜索符号
  • 方法参数提示:ctrl+p

过滤指定的后缀文件

File>Settings>Editor>File Types

最下方输入,注意星号和分号

显示未保存星号

File>Settings>Editor>General>Editor Tabs

遇到的坑

函数定义和调用的两种形式

1. 点号

shape = {side = 4}
function shape.set_side(shape, side)
    shape.side = side
end

function shape.print_area(shape)
    print(shape.side * shape.side)
end

print(shape.side)
shape.set_side(shape, 5)
print(shape.side)
shape.print_area(shape)

输出
4
5
25

2. 冒号

冒号其实和点号类似,只是把第一个隐藏参数省略了,而 self 则是指向调用者自身

shape = {side = 4}
function shape:set_side(side)
    self.side = side
end

function shape:print_area()
    print(self.side * self.side)
end

print(shape.side)
shape:set_side(5)
print(shape.side)
shape:print_area()

输出
4
5
25

更深入

可以用点号“ . ”来定义函数,冒号“ :”来调用函数

可以用冒号“ :”来定义函数,点号“ . ”来调用函数

shape = {side = 4}
function shape.set_side(shape, side)
    shape.side = side
end

function shape.print_area(shape)
    print(shape.side * shape.side)
end

print(shape.side)
shape:set_side(5)
print(shape.side)
shape:print_area()
shape = {side = 4}
function shape:set_side(side)
    self.side = side
end

function shape:print_area()
    print(self.side * self.side)
end

print(shape.side)
shape.set_side(shape, 5)
print(shape.side)
shape.print_area(shape)

self是个啥

把上面的函数的定义和调用的方式看懂了就知道了

就是用冒号定义函数后,self表示调用者

实现private和public

Lua中没有private和public,继承的表中的所有属性和方法都可访问

实现:

function GetPerson()

	-- 要封装的类
	local Person =
	{
		ID= 0,			-- ID
		name= "",			-- 名字
	}

	--------------------------	    封装的操作        ---------------------------------
	-- 设置ID
	local function SetID(ID)
		Person.ID = ID
	end

	-- 获取ID
	local function GetID()
		return Person.ID
	end

	-- 设置名字
	local function SetName(name)
		Person.name = name
	end

	-- 获取名字
	local function GetName()
		return Person.name
	end

	-- 对外提供的接口
	-- 原理:新建一个临时表,外面只能操作这里定义的操作
	return {SetID = SetID, GetID = GetID, SetName = SetName, GetName = GetName}
end

------------------------------       测试代码        ------------------------------------

local person = GetPerson()
person.SetID(100)
print(person.GetID())
person.SetName("小明")
print(person.GetName())

神奇的“变量提升”

定义变量,但未赋值,接着就作为函数参数直接使用,到最后才给变量赋值予函数,这样竟然还能成功执行

严格来说不叫变量提升,而是脚本语言的特点

脚本语言

  • 无需编译,程序代码就是最终的可执行程序
  • 由所在的解释器负责解释,或者手动编写一个程序可以解释后缀为.aa的文件,当对.aa的文件内容进行重写时,仍用上述程序进行解释,这就叫脚本语言
  • 脚本语言其实就是解释性语言,翻译一行然后就执行一行,哪怕后面有些内容是有错的也不影响前面语句的执行.非脚本语言就是一定要全部都没错,然后编译通过了然后才能运行

释惑

因为Lua中的变量都是引用类型(这个从视频中看的,待考究),而当作为函数参数使用时它是以变量名的形式传入,真正要取得其中值的时候,已经被赋值了.....

(感觉解释不清楚,参考以下js的吧)

let target = {};
let source = { a: { b: 2 } };
Object.assign(target, source);
console.log(target); // { a: { b: 10 } };
source.a.b = 10;
console.log(source); // { a: { b: 10 } };
console.log(target); // { a: { b: 10 } };

注意,第一次打印的结果target竟然是操作后的值
因为target中含有引用元素,则target和sourse还有联系
而console.log是从内存中查值,改变sourse后内存中两者共有的这个对象元素{b:2}都改变了

通俗地讲:
控制台一开始显示的是{a: {...}}
展开的这个过程是从内存查值的过程,不是相当于照相(固定一瞬间的状态)

深入模块载入

模块载入,立即执行(若重复载入同一模块,只执行第一次):requeire(“model_name”)

模块载入,每次都执行dofile(“my.lua”)

载入时不执行,需要时才执行

local my=loadfile(“my.lua”)
...

my()

深扒模块载入原理:

fp=require(“my”)

-------等价于-------

fp= (function()
--my.lua文件内容--
end)

所以模块定义时可以有返回值,也可以没有

没有返回值那加载模块时就是直接执行整个文件,若有返回值,不仅执行整个文件,还能定义变量拿到该返回值,再次使用该返回值(或其中函数)