Lua入门

Lua安装及编译环境

安装

window

  • 菜鸟教程 下载地址LuaForWindows_v5.1.4-46.exe
  • Github 下载地址:https://github.com/rjpcomputing/luaforwindows/releases
  • Google Code下载地址 : https://code.google.com/p/luaforwindows/downloads/list

使用默认方式一步一步安装

Win + R 输入cmd 打开命令窗口,

输入 lua

显示 lua 版本号 版权等信息 表示安装成功

注:如果不成功,需要在环境变量添加Lua路径

在任务栏 左下角 点击放大镜 搜索 环境变量

进入后 点击环境变量,在系统变量 Path 中 添加 Lua 安装路径

快捷方式: Win + R 输入 SystemPropertiesAdvanced 打开环境变量设置

编译环境

Visual Studio Code

下载并安装

安装完成之后,点击左侧快捷工具栏 方块 图标

输入lua 安装对应版本lua

输入 Code Runner 安装 Code Runner 插件

输入lua Debug 安装调试插件

点击左下角 齿轮 图标, 点击 setting 搜索Run in Terminal 勾选

在Terminal运行

注:如果提示 “无法将“***”项识别为 cmdlet、函数、脚本文件或可运行程序的名称”

解决方法:右键vscode 点击属性 在兼容性中选中【以管理员权限运行此程序】

IntelliJ IDEA

下载并安装

安装完成后打开

File → Settings → Plugins

搜索Lua 安装 EmmyLua插件并重启IDEA即可

简介

Lua的特性

  • 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
  • 可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
  • 其它特性:
    • 支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
    • 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
    • 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
    • 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。

工作原理

  • Lua使用虚拟堆栈向C传递值。此堆栈中的每个元素表示Lua值(nil,number,string等)。API中的函数可以通过它们接收的Lua状态参数访问此堆栈。
  • Lua运行代码时,首先把代码编译成虚拟机的指令(“opcode”),然后执行它们。Lua编译器为每个函数创建一个原型(prototype),这个原型包含函数执行的一组指令和函数所用到的数据表。

基础语法

注释

-- 两个减号,单行注释
--[[
多行注释
--]]

标识符

Lua标识符由任意字母、数字和下划线组成,不能以数字开头。

Lua区分大小写,不允许使用特殊字符。

关键字

andbreakdoelse
elseifendfalsefor
functionifinlocal
nilnotorrepeat
returnthentrueuntil
whilegoto

全局变量

定义变量时,默认为全局变量,未初始化时,其结果是nil

Lua将所有的全局变量保存在一个常规的table中,这个table称之为环境(_G)

-- 打印当前环境中所有全局变量的名称
for n in pairs(_G) do
    print(n)
end

local 用来定义局部变量,其作用域与其定义的位置有关:

  • 如果定义在一个函数体中, 那么作用域就在这个函数中.
  • 如果定义在一个控制结构中, 那么就在这个控制结构中.
  • 如果定义在一个文件中, 那么作用域就在这个文件中.

数据类型

Lua是动态类型的语言,变量不要类型定义,只需要为变量赋值即可。

Lua有 8 个基础类型:nil(空)、boolean、number、string、 table(表)、userdata(自定义类型)、function和thread 。

注:lua不需要定义数据类型就可以赋值,因为在解析器中会根据值的类型来进行初始化。

具体流程:

在赋值的时候,会调用函数expr解析表达式,=号右边的值,它最终会走入 函数simpleexp中,在simpleexp中会根据 expr解析出来的 expdesc结构体里的 t.token,用一个switch判断该表达式的类型,初始化expdesc结构体,将具体的数据赋值给expdesc结构体中的nval。

数据类型描述
nilnil是一种类型,只有值nil,表示一个无效值,用来区别其他任何值(在条件表达式中相当于false)
boolean两个值:false和true,在Lua中将false和nil 视为假
number表示双精度类型浮点数
string字符串由一对双引号或单引号或双方括号[[ ]]来表示
tableLua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。
function由 C 或 Lua 编写的函数
userdata表示任意存储在变量中的C数据结构
thread表示执行的独立线路,用于执行协同程序

type函数

可以让我们知道变量的类型

String 字符串

字符串是由字符或控制字符组成的序列。字符串可以通过以下三种方式进行初始化。

  • 单引号字符串
  • 双引号字符串
  • [[和]]之间的字符串
s1 = 'Lua'
s2 = 'Tutorial'
s3 = [[Lua Tutorial]]
print(s1)
print(s2)
print(s3)
--[[
output:
Lua
Tutorial
Lua Tutorial
--]]

转义字符

转义序列用法
\a响铃
\b退格
\f换页
\n换行
\r回车
\t制表符
\v垂直制表符
\反斜线
"双引号
单引号
[左方括号
]右方括号

常用方法:

函数功能
string.upper(argument)将输入参数全部字符转换为大写并返回。
string.lower(argument)将输入参数全部字符转换为小写并返回。
string.gsub(maingString,findString,replaceString)将 mainString 中的所有 findString 用 replaceString 替换并返回结果。
string.strfind(mainString,findString,optionalStartIndex,optionalEndIndex)在主字符串中查找 findString 并返回 findString 在主字符串中的开始和结束位置,若查找失败则返回 nil。
string.reverse(arg)将输入字符串颠倒并返回。
string.format(…)返回格式化后的字符串。
string.char(arg) 和 string.byte(arg)前者返回输出参数的所代表的字符,后者返回输入参数(字符)的数值。
string.len(arg)返回输入字符串的长度。
string.rep(string,n)将输入字符串 string 重复 n 次后的新字符串返回。
连接两个字符串。
--  ------- 字符串常用方法 -------
-- 大小写
s1 = 'Lua'
print(string.upper(s1))
-- output: LUA
print(string.lower(s1))
-- output: lua

-- 替换子串
s1 = 'Lua Tutorial'
ns = string.gsub(s1, 'Tutorial', 'Language')
print(ns)
-- output: Lua Language

-- 查找, 成功返回起始位置,失败返回nil
s1 = 'Lua Tutorial'
print(string.find(s1, 'Tutorial'))
-- output: 5, 12

-- 颠倒
s1 = 'Lua Tutorial'
rs = string.reverse(s1)
print(rs)
-- output: lairotuT auL

-- 格式化字符串
s1 = 'Lua'
s2 = 'Tutorial'
-- 基本字符串格式
print(string.format('Basci Formatting: %s %s', s1, s2))
-- output: Basci Formatting: Lua Tutorial

-- 日期格式化
year, month, day = 2021, 7, 7
print(string.format('Data Formatting: %03d/%02d/%02d', year, month, day))
-- output: Data Formatting: 2021/07/07

-- 浮点数格式化 %.nf 保留n位小数
print(string.format('%.4f', 1/3))
-- output: 0.3333

-- ------ 字符与ASCII转换 ------
-- 用法:
-- string.byte(string, i) 默认i为1
-- string.byte(string, i, j) 输出 从i到j字符对应的ASII码

s1 = 'Lua'
-- 默认取字符串第一个字符
print(string.byte(s1))
-- output: 76

--取第三个字符
print(string.byte(s1, 3))
-- output: 97

-- 取倒数第2个字符
print(string.byte(s1, -2))
-- output" 117

print(string.byte(s1, 1, 3))
-- output: 76	117	97

-- ASCII 转 字符
print(string.char(97))
-- output: a


-- 字符串连接
s1, s2 = 'Lua', 'Tutorial'
print('Concatenated string: ', s1..s2)
-- output: Concatenated string: 	LuaTutorial


-- 字符串长度,使用len函数 或者 # 来获取
s1 = 'Lua'
print('The length of string: ', string.len(s1))
-- output: The length of string: 	3
print('The length of string: ', #s1)
-- output:The length of string: 	3

-- 重复字符串
s1 = 'Lua'
repeatS = string.rep(s1, 3)
print(repeatS)
-- output: LuaLuaLua


table(表)

Lua 用构造表达式 {} 创建一个空表,与哈希表、字典类似。

Lua的table是由数组部分(array part)和哈希部分(hash part)组成。数组部分索引的key是1~n的整数,哈希部分是一个哈希表(open address table),哈希表本质是一个数组,它利用哈希算法将键转化为数组下标,若下标有冲突(即同一个下标对应了两个不同的键),则它会使用链地址法将冲突的下标上创建一个链表,将不同的键串在这个链表上。

table 当成字典来用,它的 key 值可以是除了 nil 之外的任何类型的值,可以使用 pairs函数来进行遍历,使用pairs遍历时的顺序是随机的,事实上相同的语句执行多次得到的结果是不一样的。

table 当成数组来用, key 为整数。这个数组是一个 索引从1开始 ,没有固定长度,可以根据需要自动增长的数组,可以使用ipairs 对数组进行遍历。

向table中插入数据时,如果已经满了,Lua会重新设置数据部分或哈希表的大小,容量是成倍增加的,哈希部分还要对哈希表中的数据进行整理。需要特别注意的没有赋初始值的table,数组和部分哈希部分默认容量为0。

resize 代价高昂,当我们把一个新键值赋给表时,若数组和哈希表已经满了,则会触发一个再哈希(rehash)。再哈希的代价是高昂的。首先会在内存中分配一个新的长度的数组,然后将所有记录再全部哈希一遍,将原来的记录转移到新数组中。新哈希表的长度是最接近于所有元素数目的2的乘方。

-- ------ table 表 ------
--
student = {}
print('Type: ', type(student))
-- output: Type: 	table

-- 创建条目
student['name'] = 'Jerry'
student['age'] = 10
print(student['name'])
print(student['age'])

-- 创建pupil引用, 同时指向student对应的内存地址,引用计数此时为2
pupil = student

-- 移除引用
student = nil
-- 住:Lua使用引用计数来确定是否回收内存,
--    当没有引用时,垃圾回收机制负责回收

常用方法

方法功能
table.concat(table[, sep [, i[,j]]])根据指定的参数合并表中的字符串。具体用法参考下面的示例。
table.insert(table,[pos,]value)在表中指定位置插入一个值。
table.maxn(table)返回表中最大的数值索引。
table.remove(table[,pos])从表中移出pos位置的值, pos参数可选, 默认为table长度, 即从最后一个元素删起。
table.sort(table[,comp])根据指定的(可选)比较方法对表进行排序操作。
-- ------ table 表 ------
-- 空表
student = {}
print('Type: ', type(student))
-- output: Type: 	table

-- 创建条目
student['name'] = 'Jerry'
student['age'] = 10
print(student['name'])
print(student['age'])
-- 注:不指定key时,默认用下标作为key,下标从1开始

-- 创建pupil引用, 同时指向student对应的内存地址,引用计数此时为2
pupil = student

-- 移除引用
student = nil
-- 住:Lua使用引用计数来确定是否回收内存,
--    当没有引用时,垃圾回收机制负责回收


-- 表连接
-- 用法: table.concat(list, seq, i, j)
--       对list 从位置i到j以字符串seq进行连接
fruits = {'banana', 'orange', 'apple'}
-- 直接返回字符串连接的结果
print('Concatenated string: ', table.concat(fruits))
-- output: Concatenated string: 	bananaorangeapple

-- 以seq连接所有字符串
print('Concatenated string: ', table.concat(fruits, ','))
-- output: Concatenated string: 	banana,orange,apple

-- 以seq连接从位置i到j的字符串
print("Concatenated string ",table.concat(fruits,',', 2,3))
-- output: Concatenated string 	orange,apple


-- 插入、最大索引、删除
fruits = {'banana', 'orange', 'apple'}
-- 插入
-- 用法:table.insert(table, pos, value)
--      在table中的pos位置插入value, pos默认为table长度

-- 默认方式插入
table.insert(fruits, 'mango')
print("Fruit at index 4 is ",fruits[4])

for k,v in ipairs(fruits) do
    print(k,v)
end
--[[
output:
Fruit at index 4 is 	mango
1	banana
2	orange
3	apple
4	mango
--]]

-- 在索引2位置插入
table.insert(fruits, 2, 'grapes')
print("Fruit at index 2 is ",fruits[2])
for k,v in ipairs(fruits) do
    print(k,v)
end

--[[
output:
Fruit at index 2 is 	grapes
1	banana
2	grapes
3	orange
4	apple
5	mango
--]]

-- 最大索引
print("The maximum elements in table is",table.maxn(fruits))
-- output:The maximum elements in table is	5

-- 删除
-- 用法:table.remove(table, pos)
--      移除table中pos位置的值,pos默认为table长度
table.remove(fruits)
print("The previous last element is",fruits[5])
for k,v in ipairs(fruits) do
    print(k,v)
end
--[[
output:
The previous last element is	nil
1	banana
2	grapes
3	orange
4	apple
--]]


-- 表排序
-- 用法:table.sort(table, comp)
--      使用comp方法对table进行排序

fruits = {"banana","orange","apple","grapes"}
for k,v in ipairs(fruits) do
    print(k, v)
end

--[[
output:
1	banana
2	orange
3	apple
4	grapes
--]]

-- 默认按照升序排序
table.sort(fruits)
for k,v in ipairs(fruits) do
    print(k, v)
end

--[[
output:
1	apple
2	banana
3	grapes
4	orange
--]]

function 函数

function factorial(n)
    if n == 0 then
        return 1
    else
        return n * factorial(n-1)
    end
end
print(factorial(5))
-- output: 120

算术运算符

设 A 的值为10,B 的值为 20:

运算符描述实例
+加法A + B 输出结果 30
-减法A - B 输出结果 -10
*乘法A * B 输出结果 200
/除法B / A 输出结果 2
%取余B % A 输出结果 0
^乘幂A^2 输出结果 100
-负号-A 输出结果 -10

关系运算符

设定 A 的值为10,B 的值为 20:

操作符描述实例
==等于,检测两个值是否相等,相等返回 true,否则返回 false(A == B) 为 false。
~=不等于,检测两个值是否相等,不相等返回 true,否则返回 false(A ~= B) 为 true。
>大于,如果左边的值大于右边的值,返回 true,否则返回 false(A > B) 为 false。
<小于,如果左边的值大于右边的值,返回 false,否则返回 true(A < B) 为 true。
>=大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false(A >= B) 返回 false。
<=小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false(A <= B) 返回 true。

逻辑运算符

设定 A 的值为 true,B 的值为 false:

操作符描述实例
and逻辑与操作符。 若 A 为 false,则返回 A,否则返回 B。(A and B) 为 false。
or逻辑或操作符。 若 A 为 true,则返回 A,否则返回 B。(A or B) 为 true。
not逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false。not(A and B) 为 true。

字符串连接

操作符描述实例
连接两个字符串a…b ,其中 a 为 "Hello " , b 为 “World”, 输出结果为 “Hello World”。
#一元运算符,返回字符串或表的长度。#“Hello” 返回 5

运算符优先级

从高到低的顺序:

^
not    - (unary)
*      /       %
+      -
..
<      >      <=     >=     ~=     ==
and
or

除了 ^ 外所有的二元运算符都是左连接的。

a+i < b/2+1          <-->       (a+i) < ((b/2)+1)
5+x^2*8              <-->       5+((x^2)*8)
a < y and y <= z     <-->       (a < y) and (y <= z)
-x^2                 <-->       -(x^2)
x^y^z                <-->       x^(y^z)  -- 右连接

控制结构

判断控制

语句描述
if 语句if 语句中包括一个布尔表达式作为判断条件。
if…else 语句if 语句也可以选择和 esle 语句一起使用。当条件为假时,则执行 else 语句。
嵌套 if 语句在 if 语句或者 else if 语句内使用 if 或者 else if。

if 语句包括一个布尔表达式和一个或多个语句

Lua 语言中所有布尔真与非 nil 的组合的结果被当作真,而布尔假与 nil 组合被当作假。值得注意的是,Lua 中零被当作真。

age = 18
if (age < 18) then
    print('you\'re a minor.')

else
    print('you\'re an adult.')
end


print('Please input your score:')
score = io.read('*number')

if (score >= 90) then
    print('A')
elseif (score >= 80) then
    print('B')
elseif (score >= 70) then
    print('C')
elseif (score >= 60) then
    print('D')
else
    print('E')
end

循环控制

for 循环 执行一个语句序列多次,可以简化管理循环变量的代码。

while 循环 先检测条件,条件为真时再执行循环体,直到条件为假时结束。

repeat…until 循环 重复执行一组代码语句,直到 until 条件为真为止。

循环类型描述
for 循环执行一个语句序列多次,可以简化管理循环变量的代码。
while 循环先检测条件,条件为真时再执行循环体,直到条件为假时结束。
repeat…until 循环重复执行一组代码语句,直到 until 条件为真为止。
嵌套循环可以在一个循环语句中再使用一个循环语句。
for

用法:

-- var从start变到over,变化以step为步长递增var
for var=start, over, step do
	do something
end

-- 输出10 - 1for i=10, 1, -1 do    print(i)end-- 泛型for-- 打印数组a的所有值  a = {"one", "two", "three"}for i, v in ipairs(a) do    print(i, v)end --[[output:1	one2	two3	three--]]
while

while 在判断条件为 true 时会重复执行循环体语句

用法:

while(condition)do   statementsend
-- 输出 10 - 19
a = 10
while (a < 20)
do
    print('The value of a is:', a)
    a = a + 1
end
repeat … until

repeat…until 先执行循环再检测条件判断是否再次执行,一定会执行一次。

用法:

repeat
   statements
until( condition )
-- 输出10 - 15
a = 10
repeat
    print('The value of a is:', a)
    a = a + 1
until (a > 15)

控制语句

break 用于结束当前循环或语句

return 用于从函数中返回结果,或者简单结束一个函数的执行

迭代器
无状态迭代器
a = {"one", "two", "three"}
for i, v in ipairs(a) do
    print(i, v)
end
--[[
output:
1	one
2	two
3	three
--]]

自身不保存任何状态的迭代器,典型例子:pairs,ipairs。

pairs: 迭代 table,可以遍历表中所有的 key 可以返回 nil

ipairs: 迭代数组,不能返回 nil,如果遇到 nil 则退出

local a = {    [1] = 'one',    [3] = 'three',    [4] = 'four'}for i,v in pairs(a) do    print(i, v)end--[[output:1	one4	four3	three--]]-- k=2 时断开for i,v in ipairs(a) do    print(i, v)end-- output: 1	one
-- 迭代器 实现 n的平方function square(iteratorMaxCount, currentNumber)    if currentNumber < iteratorMaxCount then        currentNumber = currentNumber + 1        return currentNumber, currentNumber * currentNumber    endendfor i,n in square, 3, 0 do    print(i, n)end--[[output:1	12	43	9--]]

有状态迭代器

很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包。

-- 有状态迭代器array = {'Lua', 'Tutorial'}function elementIterator(collection)    local index = 0    local length = #collection    -- 返回闭包函数    return function()        index = index + 1        if index <= length then            -- 返回迭代器的当前元素            return collection[index]        end    endendfor element in elementIterator(array) do    print(element)end

函数

在Lua中,函数是对语句和表达式进行抽象的主要方法。

Lua 函数主要有两种用途:

  • 1.完成指定的任务,这种情况下函数作为调用语句使用;
  • 2.计算并返回值,这种情况下函数作为赋值语句的表达式使用。

函数定义

Lua 编程语言函数定义格式如下:

optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
    function_body
    return result_params_comma_separated
end

解析:

  • optional_function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local
  • function_name: 指定函数名称。
  • argument1, argument2, argument3…, argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。
  • function_body: 函数体,函数中需要执行的代码语句块。
  • result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。

函数作为参数传递给函数

myPrint = function(param)
    print('This is print function - ## ', param, '##')
end

function add(num1, num2, func)
    res = num1 + num2
    -- 调用传递的函数参数
    func(res)
end

add(2, 5, myPrint)

-- output: This is print function - ## 	7	##

可变参数

函数参数列表中使用三点 表示函数有可变的参数

function add(...)
    local sum = 0
    -- {...} 表示一个由所有变长参数构成的数组 
    for i, v in ipairs{...} do
        sum = sum + v
    end
    return sum
end

print(add(1, 2, 3, 4))
-- output: 10

如果有固定参数,固定参数必须放在变长参数之前。

select 函数来访问变长参数了:select(’#’, …) 或者 select(n, …)

  • select(’#’, …) 返回可变参数的长度。
  • select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。

具名实参

Lua 中参数传递具有位置性,实参通过在参数表中的位置与形参进行匹配。

Lua不支持通过名称来指定实参,但是可以将函数参数设置为Table来实现这种机制。

function rename(arg)
    return os.rename(arg.old, arg.new)

end

-- 当实参只有一个table时,可以省略圆括号
rename({old = 'temp.lua', new = 'temp1.lua'})

当函数拥有大量参数,且其中大部分参数是可选的,使用Table这种方式会特别有用。

深入函数

在lua中,函数是一种第一类值,具有特定的词法域。

第一类值,在Lua中函数和其他类型的值具有相同的权利,可以作为实参传递,可以作为返回值。
函数和其他值一样是匿名的,一个函数定义其实是一条赋值语句

-- 两种写法 等价
function foo(x) return 2*x end
foo = function(x) return 2*x end
print(foo(2))
-- output: 4

词法域,指一个函数嵌套在另一个函数时,内部函数可以访问外部函数的局部变量。这两种特征都是c#没有的,c#中只能使用委托和匿名委托来实现类似的机制。

匿名函数排序

network = {
    {name = 'grauna', IP = '210.26.30.34'},
    {name = 'arraial', IP = '210.26.30.23'},
    {name = 'lua', IP = '210.26.30.12'},
    {name = 'derain', IP = '210.26.30.20'},
}

table.sort(network, function(a, b) return (a.name > b.name) end)

for i, n in ipairs(network) do
    print(n.name, n.IP)
end
--[[
output:
lua	210.26.30.12
grauna	210.26.30.34
derain	210.26.30.20
arraial	210.26.30.23
--]]

闭包 closure

-- 闭包
function newCounter()
    local i = 0
    -- 匿名函数访问非局部变量i
    return function()
        i = i + 1
        return i
    end
end

c1 = newCounter()
print(c1())
-- output: 1
print(c1())
-- output: 2
c2 = newCounter()
print(c2())
-- output: 1
-- c1和c2是同一个函数所创建的两个不同的闭包closure,各自拥有独立的局部变量i
print(c1())
-- output: 3

closure 用处

例如:十进制计算器,创建10个数字按钮,按钮间区别并不大,仅需改变对应操作即可。

function digitButton(digit)
    -- Button用于创建新按钮
    -- lable 是按钮的标签
    -- action 动作,每当按下按钮时就会调用closure
    return Button{label = tostring(digit),
                  action = function()
                              add_to_display(digit)
                           end
                  }
end

例如:访问控制

-- 访问控制,以访问a.txt为例,文本内容为access ok!
do
    local oldOpen = io.open
    local accessOK = function(filename, mode)
        -- <检查访问权限>
        if filename == 'a.txt' then
            return true
        else
            return false
        end
    end
    io.open = function(filename, mode)
        if accessOK(filename, mode) then
            return oldOpen(filename, mode)
        else
            return nil, 'access denied'
        end
    end
end

print(io.open('a.txt', 'r'))
-- output:file (53AC1BD8)
print("io.open(file_path,".."r"..")--->"..tostring(io.open('a.txt', 'r'):read("*a")))
-- output:io.open(file_path,r)--->access ok!
print(io.open('b.txt', 'r'))
-- output: nil	access denied

通过闭包closure可以创建一个安全的运行环境,即沙盒sandbox,使外部无法直接访问到原来的版本。

非全局函数

将函数存储在table中,具体代码如下

-- 基础版
Lib = {}
Lib.add = function(x, y) return x + y end
Lib.sub = function(x, y) return x - y end

-- 构造式
Lib = {
    add = function(x, y) return x + y end,
    sub = function(x, y) return x - y end
}

-- 另一种方法
Lib = {}
function Lib.add(x, y) return x + y end
function Lib.sub(x, y) return x - y end

定义递归的局部函数

-- 错误写法
local fact = function(n)
    if n == 0 then return 1
    else
        -- 由于局部fact未定义完毕,此时调用的是全局的fact
        return n * fact(n - 1)
    end
end

-- 正确写法
-- 先定义局部变量fact,再定义函数本身
local fact
fact = function(n)
    if n ==0 then return 1
        else return n * fact(n - 1)
    end
end
-- 另一种方式,与上面等价
-- 对于间接递归无效,间接递归的情况,需要使用前向声明
local function fact(n)
    if n == 0 then return 1
        else return n * fact(n - 1)
    end
end

尾调用

尾调用就是指一个函数的最后一步是return另一个函数

-- 尾调用
function f(x) return g(x) end
-- 不是尾调用
function f(x) g(x) end

出现尾调用后,程序不会保存尾调用所在的函数的栈信息,因为没有必要。这种现象称为尾调用消除。基于这种机制,尾调用永远不会导致栈溢出
尾调用的典型应用就是状态机。用一个函数来表示一个状态。

元表

通过设置元方法可以让表具有加减乘除等算术操作。

两个用来处理元表的方法,如下:

  • setmetatable(table,metatable):此方法用于为一个表设置元表。
  • getmetatable(table):此方法用于获取表的元表对象。
-- 为指定的表设置元表
mytable = {}
mymetatable = {}
setmetatable(mytable, mymetatable)

-- 简写版
mytable = setmetatable({}, {})

__index

__index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。

-- __index 实例
mytable = setmetatable({key1 = 'value1'},
        {
	        --mytable 设置了元表,元方法为 __index
            __index = function(mytable, key)
	            -- 查找索引key2 返回
                if key == 'key2' then
                    return 'metatableValue'
                else
                	-- 否则返回mytable中索引对应的值
                    return mytable[key]
                end
            end
        })

print(mytable.key1, mytable.key2)
-- output: value1	metatableValue

-- 简写版
mytable = setmetatable({key1 = 'value1'}, {__index = {key2 = 'metatableValue'}})
print(mytable.key1, mytable.key2)

Lua 查找表元素的步骤

  • 1.在表中查找,如果找到,返回该元素,找不到则继续
  • 2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
  • 3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。

__newindex

__newindex 元方法用来对表更新

如果键存在于主表中,只会简单更新相应的键值。如果键不在表中时,会在元表 mymetatable 中添加该键值对。

-- __newindex
mymetatable = {}
mytable = setmetatable({key1 = 'value1'}, {__newindex = mymetatable
})

print(mytable.key1)

mytable.newkey = 'new value 2'
print(mytable.newkey, mymetatable.newkey)

mytable.key1 = 'new key'
print(mytable.key1, mymetatable.newkey)

--[[
output:
value1
nil	new value 2
new key	new value 2

--]]

rawset更新表

用 rawset 函数在相同的表(主表)中更新键值,而不再是将新的键添加到元表中

-- rawset 更新表
mytable = setmetatable({key1 = 'value1'}, {
    __newindex = function(mytable, key, value)
        rawset(mytable, key, "\""..value.."\"")
    end
})

mytable.key1 = 'new value'
mytable.key2 = 4
print(mytable.key1, mytable.key2)
-- output: new value	"4"

rawset 函数设置值时不会使用元表中的 __newindex 元方法,

rawget 方法,该方法访问表中键值时也不会调用 __index 的元方法。

__add

使用 + 操作符完成两个表组合

-- __add, 两表组合
mytable = setmetatable({1, 2, 3},{
    __add = function(mytable, newtable)
        -- #newtable 获取newtable 长度
        for i = 1, #newtable do
            table.insert(mytable, #mytable + 1, newtable[i])
        end
        return mytable
    end
})

secondtable = {4, 5, 6}
mytable = mytable + secondtable
for k, v in ipairs(mytable) do
    print(k, v)
end

--[[
output:
1	1
2	2
3	3
4	4
5	5
6	6
--]]
描述
__add改变加法操作符的行为。
__sub改变减法操作符的行为。
__mul改变乘法操作符的行为。
__div改变除法操作符的行为。
__mod改变模除操作符的行为。
__unm改变一元减操作符的行为。
__concat改变连接操作符的行为。
__eq改变等于操作符的行为。
__lt改变小于操作符的行为。
__le改变小于等于操作符的行为。

__call

使用 __call 可以使表具有像函数一样可调用的特性

-- __call元方法
mytable = setmetatable({10},{
    __call = function(mytable, newtable)
        sum = 0
        for i = 1, #mytable do
            sum = sum + mytable[i]
        end

        for i = 1, #newtable do
            sum = sum + newtable[i]
        end

        return sum

    end
})

newtable = {10, 20, 30}
print(mytable(newtable))
-- output: 70

__tostring 方法

__tostring 元方法用于修改表的输出行为

mytable = setmetatable({10, 20, 30}, {
    __tostring = function(mytable)
        sum = 0
        for k, v in pairs(mytable) do
            sum = sum + v
        end
        return 'The sum of values in the table is '..sum
    end
})

print(mytable)
-- output:The sum of values in the table is 60

面向对象

面向对象的特征

  • 类(class):类是可以创建对象,并为状态(成员变量)提供初值及行为实现的可扩展模板。
  • 对象(objects):对象是类的实例,每个对象都有独立的内存区域。
  • 封装(encapsulation):封装是指将数据和函数组织在一个类中。外部可以通过类的方法访问内中的数据。封装也被称之为数据抽象。
  • 继承(inheritance):继承用于描述一个类的变量和函数被另一个类继承的行为。
-- 面向对象
-- 元类
Rectangle = {area = 0, length = 0, breadth = 0}
--
-- 继承类的方法 new
function Rectangle:new (o, length, breadth)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    self.length = length or 0
    self.breadth = breadth or 0
    self.area = length * breadth
    return o
end

-- 继承类的方法 printArea
function Rectangle:printArea()
    print('The area of Rectangle is:', self.area)
end


-- 创建对象
r = Rectangle:new(nil,10,20)
--print(r)
-- 访问属性
print(r.breadth)
-- output: 20

-- 访问成员方法
r:printArea()
-- output:The area of Rectangle is:	200

内存管理

垃圾回收机制

  • 在Lua5.0及其更早的版本中,Lua的GC是一次性不可被打断的过程,使用的++Mark算法是双色标记算法(Two color mark)++,这样系统中对象的非黑即白,要么被引用,要么不被引用,这会带来一个问题:在GC的过程中如果新加入对象,这时候新加入的对象无论怎么设置都会带来问题,如果设置为白色,则如果处于回收阶段,则该对象会在没有遍历其关联对象的情况下被回收;如果标记为黑色,那么没有被扫描就被标记为不可回收,是不正确的。
  • 为了降低一次性回收带来的性能问题以及双色算法的问题,在Lua5.1后,Lua都采用分布回收以及++三色增量标记清除算法(Tri-color incremental mark and sweep)++
  • 将所有对象分成三个状态:
  • White状态 ,也就是待访问状态。表示对象还没有被垃圾回收的标记过程访问到。(白色又分为White0和White1,主要为了解决上面所说到的在GC过程中新加入的对象的处理问题)
  • Gray状态 ,也就是待扫描状态。表示对象已经被垃圾回收访问到了,但是对象本身对于其他对象的引用还没有进行遍历访问。
  • Black状态 ,也就是已扫描状态。表示对象已经被访问到了,并且也已经遍历了对象本身对其他对象的引用。
  • GC流程 :
每个新创建的对象颜色设置为White

//初始化阶段

遍历root节点中引用的对象,从白色置为灰色,并且放入到Gray节点列表中

//标记阶段

while(Gray集合不为空,并且没有超过本次计算量的上限):

	从中取出一个对象,将其置为Black

	遍历这个对象关联的其他所有对象:

        if 为White
            标记为Gray,加入到Gray链表中

//回收阶段

遍历所有对象:
    if 为White,
        没有被引用的对象,执行回收

    else
        重新塞入到对象链表中,等待下一轮GC
  • 在每个步骤之间,由于程序可以正常执行,所以会破坏当前对象之间的引用关系。black对象表示已经被扫描的对象,所以他应该不可能引用到一个white对象。当程序的改变使得一个black对象引用到一个white对象时,就会造成错误。解决这个问题的办法就是设置barrier。barrier在程序正常运行过程中,监控所有的引用改变。如果一个black对象需要引用一个white对象,存在两种处理办法:
  • 将white对象设置成gray,并添加到gray列表中等待扫描。这样等于帮助整个GC的标识过程向前推进了一步。
  • 将black对象改回成gray,并添加到gray列表中等待扫描。这样等于使整个GC的标识过程后退了一步。

这种垃圾回收方式被称为"++Incremental Garbage Collection++"(简称为"IGC",Lua所采用的就是这种方法。使用"IGC"并不是没有代价的。IGC所检测出来的垃圾对象集合比实际的集合要小,也就是说,有些在GC过程中变成垃圾的对象,有可能在本轮GC中检测不到。不过,这些残余的垃圾对象一定会在下一轮GC被检测出来,不会造成泄露。

参考资料:

Lua 程序设计

Lua教程

Lua教程-菜鸟

Lua基础知识总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值