Lua语言学习笔记

0. 环境准备

0.1 简介

Lua是由标准C编写而成的脚本语言,诞生于1993年,具有高效性可移植性可嵌入性简单强大小巧轻便免费开源等诸多优点,主要应用场景包括做为嵌入脚本、做为独立脚本、用于应用程序的动态配置、游戏开发以及Web应用脚本等。

0.2 Windows下载与安装Lua

  1. 选择合适版本的Lua语言开发包下载
  2. 解压下载好的Lua压缩包到合适的路径
  3. 将Lua的解压路径添加到环境变量Path
  4. 打开Windows命令行工具,输入指令lua,命令行输出Lua版本信息,安装成功

0.3 CentOS下载与安装Lua

curl -R -O http://www.lua.org/ftp/lua-5.4.4.tar.gz  # 拉取合适的Lua包
tar -C /usr/local -zxvf lua-5.4.4.tar.gz  # 解压到合适的目录下
cd /usr/local/lua-5.4.4/  # 进入Lua包解压路径
make all test  # 开始安装
ln -s /usr/local/lua-5.4.4/src/lua /usr/bin/lua  # 建立连接(可选步骤)
lua -v  # 查看版本,输出Lua版本信息

0.4 推荐IDE

  1. VSCode + Lua扩展插件(Tencent出品)
  2. 在线IDE

1. 基本数据类型

1.1 table

table类型是一个"关联数组",需要注意:

  1. table的索引可以是数字或者是字符串,所有索引值都需要用[]括起来,如果是字符串,还可以去掉引号和中括号
  2. table的默认初始索引一般以1开始,如果不写索引,则索引会被认为是数字,并按顺序自动从1往后排
  3. table变量只是一个地址引用,对table操作不会产生数据影响
  4. table不固定长度大小,有新数据插入时长度会自动增长
  5. table里保存数据可以是任何类型,包括functiontable
  6. table所有元素之间用逗号,隔开
-- 初始化
mytable = {}
-- 指定值
mytable[1] = "lua"
mytable["name"] = "table"
-- 修改值
mytable[1] = "Lua"
-- 获取值
print(mytable[1]) -- Lua
print(mytable.name) -- table
-- 移除引用
mytable = nil

1.2 string

string类型可以使用双引号""或单引号''声明,如果是块字符串,可以以[[开始,以]]结尾。
字符串不可修改值,可以通过string.gsub函数来替换字符串中的子串。

a = '1024'
b = '3.14159'
c = "Hello"
d = "2022"
e = [[
	
		
		
			Lua
		
	
]]
print(a, b, c, d) 
-- 输出:1024   3.14159 Hello  2022
print(e) 
--[[ 输出:
	
		
		
			Lua
		
	
--]]

1.3 number

number类型只有一种,即双精度浮点double类型

a = 1024
b = 3.14159
print(a, type(a))
-- 输出:1024    number
print(b, type(b))
-- 输出:3.14159 number

1.4 boolean

boolean类型只有两个可选值:true(真)和 false(假)。判断时false和nil都是假,其他都为真

a = true
b = false
print(a, type(a))
-- 输出:true    boolean
print(b, type(b))
-- 输出:false   boolean

1.5 nil

nil类型表示一个无效值,只有值 nil,如果打印没有赋值的变量,则会输出nil。

print("first: ", a)
-- 输出:first:  nil
a = 1024
print("second: ", a)
-- 输出:second:   1024
a = nil
print("third: ", a)
-- 输出:third:  nil
if (type(a) == "nil") then
    print("a is nil")
else
    print("a is not nil")
end
-- 输出:a is nil

注意:要判断变量是否为 nil 的时候,需要使用 type 获取变量的类型,然后与字符串的 nil 进行比较

1.6 function

function类型是由C或Lua编写的完成某一功能的程序指令的集合,称为函数,可分为自定义函数系统函数

function sum(a, b)
    return a + b;
end

result1 = sum(100, 200)
result2 = sum(1024, 99)

print(string.format("result1 = %d, result2 = %d", result1, result2))
-- 输出:result1 = 300, result2 = 1123

1.7 thread

thread类型表示执行的独立线路,用于执行协同程序。

function fun()
    print("hello")
end
cor = coroutine.create(fun)
print(cor, type(cor))
-- 输出:thread: 0x13691f0   thread

1.8 userdata

userdata类型是一种用户自定义数据,用于表示一种由应用程序或C/C++语言库所创建的类型,可以将任意C/C++的任意数据类型的数据存储在Lua变量中调用。

userdata可分为full userdatalight userdata

full userdata light userdata
定义 用户自定义数据 一种表示C指针的值,不用创建
使用 需要显示的创建一块内存,该段内存有Lua垃圾回收器管理,不需要使用者关心 存储在栈上,使用者需要关心内存使用
创建 没有进行参数合法性检查
void *lua_newuserdata(lua State *L, size_t size);
有进行参数合法性检查
void *lua_checkudata(lua State *L, int arg, const char *tname);
void lua_pushlightuserdata(lua_State *L, void *p);
其他 可以指定其metatable和metamethods 不可以指定其metatable和metamethods

2. 注释

2.1 单行注释

-- 这是行注释
print("Hello Lua")
-- 这是行注释

2.2 多行注释

--[[
这是块注释,
块注释可以
注释多行内容
--]]
print("Hello Lua")

3. 变量

变量相当于内存中一个数据存储空间的表示,通过变量名可以访问到变量的具体的值。

Lua的变量在定义时不需要指定明确的类型,而是会根据赋的默认值来断定变量的类型。

3.1 变量赋值

赋值是给已经定义的变量重新设置值的过程。

同时为多个变量赋值时:

  • 当变量个数 > 值的个数时,按变量个数补足nil

  • 当变量个数 < 值的个数时,多余的值会被忽略

a = 1
b = true
c, d = "Hello", 2022
name, age, address = "Xiaoming", 18
day, week = 30, "星期四", 2022
print(a, b)
-- 输出:1   true
print(c, d)
-- 输出:Hello  2022
print(name, age, address)
-- 输出:XiaoMing   18   nil
print(day, week)
-- 输出:30  星期四

3.2 全局变量和局部变量

变量根据作用域可分为全局变量和局部变量,且用local显式声明的变量为局部变量,其余全部为全局变量。

local a = 1024
function hello()
    pi = 3.14159
    local name = "Lua"
end
hello()
print("a = ", a, "pi = ", pi)
-- 输出:a =    1024   pi =   3.14159
print("name = ", name)
-- 输出:name =  nil

3.3 类型转换

Lua中,除了table类型,其他任何类型的变量都可以通过tostring函数转化为字符串类型。

能表示数字的字符串类型的变量可以通过tonumber函数转化为数字类型。

a = 100
b = true
sa = tostring(a)
sb = tostring(b)
print("sa = ", sa, "type(sa) = ", type(sa))
-- 输出:sa =    100 type(sa) =  string
print("sb = ", sb, "type(sb) = ", type(sb))
-- 输出:sb =    true    type(sb) =  string
sc = "3.14"
sd = "0XA"
c = tonumber(sc)
d = tonumber(sd)
print("c = ", c, "type(c) = ", type(c))
-- 输出:c =    3.14    type(c) =  number
print("d = ", d, "type(d) = ", type(d))
-- 输出:d =    10    type(d) =  number

3.4 获取输入

使用io.read函数获取用户输入。

可选参数:

格式 描述
“*n” 读取一个数字
“*a” 从当前位置读取剩余的全部内容
"*l” 读取下一行内容
10 读取指定数字的长度
local name = io.read()
print("name = ", name, "type(name) = ", type(name))
-- 用户输入:Lua
-- 输出:name =   lua type(name) =   string

local num = io.read("*n")
print("num = ", num, "type(num) = ", type(num))
-- 用户输入:1024
-- 输出:name =   1024 type(name) =   number

3.5 格式化输出

name = "lua"
age = 18
print(string.format("name = %s, age = %d", name, age))
-- 输出:name = lua, age = 18

4. 循环控制

循环控制就是让程序满足一定的条件就一直循环的去执行,直到条件不满足,则跳出循环继续执行循环以外的语句。

4.1 while

while循环当型循环,先判断条件,满足则执行循环,否则不进入循环。

-- 求和
local num = 0
local sum = 0
while (num <= 100) do
    sum = sum + num
    num = num + 1
end
print(string.format("sum = %d", sum))
-- 输出:sum = 5050

-- 打印乘法表
local i = 1
while (i <= 9) do
    local j = 1
    while (j <= i) do
        io.write(string.format("%d * %d = % -6d", j, i, j * i))
        j = j + 1
    end
    i = i + 1
    print()
end
--[[ 输出:
1 * 1 =  1    
1 * 2 =  2    2 * 2 =  4    
1 * 3 =  3    2 * 3 =  6    3 * 3 =  9    
1 * 4 =  4    2 * 4 =  8    3 * 4 =  12   4 * 4 =  16   
1 * 5 =  5    2 * 5 =  10   3 * 5 =  15   4 * 5 =  20   5 * 5 =  25   
1 * 6 =  6    2 * 6 =  12   3 * 6 =  18   4 * 6 =  24   5 * 6 =  30   6 * 6 =  36   
1 * 7 =  7    2 * 7 =  14   3 * 7 =  21   4 * 7 =  28   5 * 7 =  35   6 * 7 =  42   7 * 7 =  49   
1 * 8 =  8    2 * 8 =  16   3 * 8 =  24   4 * 8 =  32   5 * 8 =  40   6 * 8 =  48   7 * 8 =  56   8 * 8 =  64   
1 * 9 =  9    2 * 9 =  18   3 * 9 =  27   4 * 9 =  36   5 * 9 =  45   6 * 9 =  54   7 * 9 =  63   8 * 9 =  72   9 * 9 =  81
--]]

4.2 repeat … until

repeat until循环直到型循环,后判断条件,满足则跳出循环,不满足则进入循环,循环至少会执行1次

-- 求和
local num = 1
local sum = 0
repeat
    sum = sum + num
    num = num + 1
until (num > 100)
print(string.format("sum = %d", sum))
-- 输出:sum = 5050

-- 打印乘法表
local i = 1
repeat
    local j = 1
    repeat
        io.write(string.format("%d * %d = %-6d", j, i, j * i))
        j = j + 1
    until j > i
  	i = i + 1
    print()
until (i > 9)
--[[ 输出:
1 * 1 =  1    
1 * 2 =  2    2 * 2 =  4    
1 * 3 =  3    2 * 3 =  6    3 * 3 =  9    
1 * 4 =  4    2 * 4 =  8    3 * 4 =  12   4 * 4 =  16   
1 * 5 =  5    2 * 5 =  10   3 * 5 =  15   4 * 5 =  20   5 * 5 =  25   
1 * 6 =  6    2 * 6 =  12   3 * 6 =  18   4 * 6 =  24   5 * 6 =  30   6 * 6 =  36   
1 * 7 =  7    2 * 7 =  14   3 * 7 =  21   4 * 7 =  28   5 * 7 =  35   6 * 7 =  42   7 * 7 =  49   
1 * 8 =  8    2 * 8 =  16   3 * 8 =  24   4 * 8 =  32   5 * 8 =  40   6 * 8 =  48   7 * 8 =  56   8 * 8 =  64   
1 * 9 =  9    2 * 9 =  18   3 * 9 =  27   4 * 9 =  36   5 * 9 =  45   6 * 9 =  54   7 * 9 =  63   8 * 9 =  72   9 * 9 =  81
--]]

4.3 for

如果循环的语句是for i = 1, 9, 1 do ...,表示从1开始,大于9结束,每次步进1。如果是步进1,最后1可以省略,变为for i = 1, 9 do ...

-- 求和
local sum = 0
for num = 0, 100, 1 do
	sum = sum + num
end
print(string.format("sum = %d", sum))
-- 输出:sum = 5050

-- 打印乘法表
for i = 1, 9 do
  for j = 1, i do
    io.write(string.format("%d * %d = %-6d", j, i, j * i))
  end
  print()
end
--[[ 输出:
1 * 1 =  1    
1 * 2 =  2    2 * 2 =  4    
1 * 3 =  3    2 * 3 =  6    3 * 3 =  9    
1 * 4 =  4    2 * 4 =  8    3 * 4 =  12   4 * 4 =  16   
1 * 5 =  5    2 * 5 =  10   3 * 5 =  15   4 * 5 =  20   5 * 5 =  25   
1 * 6 =  6    2 * 6 =  12   3 * 6 =  18   4 * 6 =  24   5 * 6 =  30   6 * 6 =  36   
1 * 7 =  7    2 * 7 =  14   3 * 7 =  21   4 * 7 =  28   5 * 7 =  35   6 * 7 =  42   7 * 7 =  49   
1 * 8 =  8    2 * 8 =  16   3 * 8 =  24   4 * 8 =  32   5 * 8 =  40   6 * 8 =  48   7 * 8 =  56   8 * 8 =  64   
1 * 9 =  9    2 * 9 =  18   3 * 9 =  27   4 * 9 =  36   5 * 9 =  45   6 * 9 =  54   7 * 9 =  63   8 * 9 =  72   9 * 9 =  81
--]]

-- 遍历table
weekdays = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
for i, v in ipairs(weekdays) do
    print(string.format("%s --> %s", i, v))
end
--[[ 输出:
1 --> Sunday
2 --> Monday
3 --> Tuesday
4 --> Wednesday
5 --> Thursday
6 --> Friday
7 --> Saturday
--]]

4.4 break

终止循环的继续运行,如果有多层循环,只能终止当层循环,无法终止外层循环。

for i = 0, 1 do
    for j = 0, 20 do
        if (j >= 2) then
            break
        end
        print(string.format("i = %d, j = %d", i, j))
    end
end
--[[ 输出:
i = 0, j = 0
i = 0, j = 1
i = 1, j = 0
i = 1, j = 1
--]]

Lua中没有continue语句,但是可以借助for循环以及repeat until循环实现continue功能。

for i = 0, 4 do
    repeat
        if i == 2 then
            break
        end
        print(string.format("i = %d", i))
    until true
end
--[[ 输出:
i = 0
i = 1
i = 3
i = 4
--]]

4.5 return

使用return语句,终止循环、函数的执行。

for i = 0, 5, 1 do
    if (i == 3) then
        return
    end
    print(string.format("i = %d", i))
end
--[[ 输出:
i = 0
i = 1
i = 2
--]]

function check(value)
    if (value <= 0) then
        print("value <= 0")
        return
    end
    print("value > 0")
end
check(12)
check(-1)
--[[ 输出:
value > 0
value <= 0
--]]

4.6 goto

允许将控制流程无条件地转到被标记的语句处,仅lua5.2以上版本支持

local a = 1
::label:: 
print("-- goto label --")
a = a+1
if a < 3 then
    goto label
end

--[[ 输出:
-- goto label --
-- goto label --
--]]

4.7 pairs和ipairs

pairs和iparis都是能遍历集合(表、数组),但是iparis仅仅遍历值,按照索引升序遍历,索引中断停止遍历,即不能返回nil,只能返回数字0,如果遇到nil则退出。只能遍历到集合中出现的第一个不是整数的key。pairs能遍历集合的所有元素。

local table = {"A", "B", [5] = "yes", ["t"] = "no"}
for i, v in pairs(table) do
    print(i, table[i])
end
print()
for i, v in ipairs(table) do
    print(i, table[i])
end
--[[ 输出:
1   A
2   B
5   yes
t   no

1   A
2   B
--]]

5. 分支控制

分支控制就是让程序有选择的执行,主要分为:单分支双分支多分支形式。

5.1 if

local a = 10
local b = 5
if (a > b) then
    print("a > b")
end
print("Out If")
--[[ 输出:
a > b
Out If
--]]

5.2 else

local a = 10
local b = 5
if (a > b) then
    print("a > b")
else
    print("a <= b")
end
print("Out If")
--[[ 输出:
Out If
--]]

5.3 elseif

local a = 5
local b = 5
if (a > b) then
    print("a > b")
elseif (a == b) then
    print("a == b")
else
    print("a <= b")
end
print("Out If")
--[[ 输出:
a == b
Out If
--]]

5.4 条件嵌套

local a = 5
local b = 5
if (a > b) then
    print("a > b")
else
    if (a == b) then
    	print("a == b")
    else
    	print("a <= b")
    end
end
print("Out If")
--[[ 输出:
a == b
Out If
--]]

6. 函数

在程序中,编写函数的主要目的是将一个需要很多行代码的复杂问题分解为一系列简单的任务来解决,而且,同一个函数可以被多次调用,有助于代码重用。

6.1 function

function sum(a, b)
    return a + b;
end

result1 = sum(100, 200)
result2 = sum(1, 99)
print(string.format("result1 = %d, result2 = %d", result1, result2));
--输出:result1 = 300, result2 = 100

6.2 多返回值

Lua中的函数可以不返回任何值,也可以返回一个值,也支持返回多个值。

function check_user(score)
    if score > 85 then
        return "A", true
    elseif score > 60 then
        return "B", true
    else
        return "C", false
    end
end
local level1, isOk = check_user(99)
local level2, _ = check_user(50)
print(string.format("level1 = %s, level2 = %s", level1, level2))
-- 输出:level1 = A, level2 = C

6.3 可变参数

函数参数的个数可以是任意的,可变参数使用...来表示,若想要获取用户传入的所有的参数,可以使用arg变量。

function sum(...)
    local result = 0
    local arg = {...}
    for i, v in iparis(arg) do
        result = result + v
    end
    return result
end
result = sum(1, 3, 5, 7, 9)
print(string.format("result = %d", result))
-- 输出:result = 25

6.4 匿名函数

function test_func(tab, fun)
    for k, v in pairs(tab) do
        print(fun(k, v))
    end
end

tab = {key1 = "val1", key2 = "val2"}
test_func(tab, function(key, val) return key .. " = " .. val end)
--[[ 输出:
key1 = val1
key2 = val2
--]]

6.5 闭包

闭包的主要作用:

  • 简洁,不需要在不使用时生成对象,也不需要函数名
  • 可以捕获外部变量行成不同的调用环境
function func()
    local index = 0
    return function ()
        index = index + 1
        return index
    end
end
local inner1 = func()
print(inner1(), inner1())
-- 输出:1   2
local inner2 = func()
print(inner2(), inner2())
-- 输出:1   2

7. 运算符

7.1 算术运算符

算术运算符是对数值类型的变量进行运算的。

运算符 说明 范例 结果
+ 正号 +3 3
- 负号 -4 -3
+ 加法运算 5 + 5 10
- 减法运算 10 - 5 5
* 乘法运算 5 * 2 10
/ 除法运算 10 / 3 3.3
% 取余运算 10 % 3 1
^ 幂运算 2^3 8
a, b = -20, 3
local c = a + b
local d = a - b
local e = a * b
local f = a / b
local g = a % b
local h = a ^ b
print(string.format("c = %d, d = %d, e = %d, f = %f, g = %d, h = %d", c, d, e, f, g, h))
-- 输出:c = -17, d = -23, e = -60, f = -6.666667, g = 1, h = -8000

7.2 关系运算符

关系运算符结果要么是真,要么是假。

运算符 说明 范例 结果
== 相等 4 == 3 false
~= 不等于 4 ~= 3 true
< 小于 4 < 3 false
> 大于 4 > 3 true
<= 小于等于 4 <= 3 false
>= 大于等于 4 >= 3 true
a = 100
b = 99
local c = a == b
local d = a ~= b
local e = a > b
local f = a < b
local g = a >= b
local h = a <= b
print("c = ", c, "d = ", d, "e = ", e, "f = ", f, "g = ", g, "h = ", h)
-- 输出:c =     false   d =     true    e =     true    f =     false   g =     true    h =     false

7.3 逻辑运算符

逻辑运算符用来连接多个条件,最终返回是true或false,使用逻辑运算符可以模拟三目运算符。

运算符 说明 范例
and 逻辑与 A and B,如果A的值为假,则不会再计算B的值
or 逻辑或 A or B,如果A的值为真,则不会再计算B的值
not 逻辑非 nor A
local age = 40
local b = age > 30 and age < 50
local c = age > 40 or age < 30
local d = not age
local e = not nil
local f = not 0
local g = age > 30 and age or 30
print("b = ", b, "c = ", c, "d = ", d, "e = ", e, "f = ", f, "g = ", g)
-- 输出:b =     true    c =     false   d =     false   e =     true   f =     false   g = 40

7.4 其他运算符

运算符 说明 范例
连接运算符 str1…str2
# 获取字符串长度 #str1
local str1 = "hello"
local str2 = "lua"
local str3 = str1..str2
print(string.format("str3 = %s, #str3 = %d", str3, #str3))
-- 输出:str3 = hellolua, #str3 = 8

8. 字符串

8.1 字符串函数

函数 描述
upper(arg) 字符串全部转为大写字母
lower(arg) 字符串全部转为小写字母
sub(s, i, [,j]) 截取字符串
gsub(mainString, findString, replaceString, num) 在字符串中替换
dump(function) 把函数序列化为字符串来保存
find(str, substr, [init, [end]]) 在字符串中查找,存在返回具体位置,不存在返回nil
reverse(arg) 字符串反转
format(…) 返回一个格式化字符串
char(arg) 将整型数字转化为字符并连接
byte[arg[int,]] byte转换字符为整数值
len(arg) 计算字符串长度
rep(string, n) 返回字符串string的n个拷贝
连接两个字符串
gmatch(str, pattern) 迭代器函数,每次调用返回一个查找到的子串
match(str, pattern, init) 查找第一个配对的子串

8.1 大小写转换

string.upper(s) 用于将字符串中所有字母转化为大写。

string.lower(s) 用于将字符串中所有字母转化为小写。

str1 = "Hello Lua"
str2 = string.upper(str1)
str3 = string.lower(str1)
print(string.format("str1 = %s, str2 = %s, str3 = %s", str1, str2, str3))
-- 输出:str1 = Hello Lua, str2 = HELLO LUA, str3 = hello lua

8.2 字符串截取

string.sub(s, i [, j]) 用于字符串截取,返回字符串s从第i个字符到第j个字符的子串。注意,字符串的第1个字符索引是1。

str1 = "Hello Lua"
str2 = string.sub(str1, 1, 5)
str3 = string.sub(str1, -3)
print(string.format("str1 = %s, str2 = %s, str3 = %s", str1, str2, str3))
-- 输出:str1 = Hello Lua, str2 = Hello, str3 = Lua

8.3 字符串替换

string.gsub(mainString, findString, replaceString, num) 用于字符串替换,num指定替换字符串的次数,默认全部替换。

str1 = "Hello world, Hello Lua"
str2 = string.gsub(str1, "Hello", "Nihao")
str3 = string.gsub(str1, "Hello", "Nihao", 1)
print(string.format("str1 = %s, str2 = %s, str3 = %s", str1, str2, str3))
-- 输出:str1 = Hello world, Hello Lua, str2 = Nihao world, Nihao Lua, str3 = Nihao world, Hello Lua

8.4 序列化与加载

string.dump(function) 用于将函数序列化为字符串,便于函数的保存与传输loadstring(str) 用于将序列化后的函数字符串反序列化加载为函数。

function sum(a, b)
  return a + b
end
str = string.dump(sum)
res = loadstring(str)
print(str, res(1, 2))
-- 输出:uaQ 3

8.5 字符串查找

string.find(str, substr, [init, [end]]) 用于在一个指定的目标字符串中搜素指定的内容,返回其具体位置,不存在返回nil,也支持使用正则匹配查找。

str1 = "Hello Lua"
index1 = string.find("Hello Lua", "Lua")
index2 = string.find("Hello Lua", "Lua", 8)
index3 = string.find("Hello Lua", "%s%u%a.")
print("index1 = ", index1, "index2 = ", index2, "index3 = ", index3)
-- 输出:index1 =   7   index2 =   nil   index3 =   6

8.6 字符串反转

string.reverse(arg) 用于反转字符串。

str = "Hello Lua"
print(string.format("str = %s, res = %s", str, string.reverse(str)))
-- 输出:str = Hello Lua, res = auL olleH

8.7 格式化字符串

格式 描述 格式 描述
%c 接收数字,转化为字符 %d,%i 接收数字,转化为有符号整数
%o 接收数字,转化为八进制数 %u 接收数字,转化为无符号整数
%x 接收数字,转化为十六进制数,使用小写字母 %X 接收数字,转化为十六进制数,使用大写字母
%e 接收数字,转化为科学计数法,使用小写字母e %E 接收数字,转化为科学计数法,使用大写字母E
%f 接收数字,转化为浮点数 %g,%G 接收数字,转化为%e,%f中较短格式
%q 接收字符串,转化为可安全被Lua编译器读入的格式 %s 接收字符串,按给定参数格式化字符串
%+ 表示其后的数字转义符将让正数显示正号 %占位符 在后面指定了字串宽度时占位用
%对齐标识 在指定了字串宽度时, 默认为右对齐, 增加 - 号改为左对齐 %宽度数值 占位宽度
%小数位数/字串裁切 数字保留位数,字符串做裁切
print(string.format("%c", 65))
print(string.format("%+d", 17.0))
print(string.format("%05d", 17))
--[[ 输出:
A
+17
00017
--]]

8.8 数字与字符的转换

string.char(arg) 用于将整数转化为字符并连接。

string.byte(arg[,int]) 用于将字符转化为整数并连接,int表示要转换的字符。

print(string.char(97, 98, 99))
print(string.byte("ABCD", 3))
--[[ 输出:
abc
67
--]]

8.9 字符串长度

string.len()用于获取字符串长度,另外#也可以获取字符串长度。

str1 = "ABC"
str2 = "Hello"
print(string.format("str1's len = %d, str2's len = %d", string.len(str1), #str2))
-- 输出:str1's len = 3, str2's len = 5

8.10 字符串拷贝

string.rep(string, n) 用于将字符串拷贝n次。

str = "ABCD"
print(string.format("str = %s", string.rep(str, 2)))
-- 输出:str = ABCDABCD

8.11 字符串连接

字符串可以使用..连接。

str1 = "hello"
str2 = "lua"
res = str1 .. " " .. str2
print(string.format("res = %s", res))
-- 输出:hello lua

8.12 字符串匹配

string.gmatch(str, pattern) 是一个迭代器函数,每调用一次,返回一个在字符串str查找到的符合pattern描述的子串,如果没找到返回nil,也支持正则匹配。

string.match(str, pattern, init) 只找寻str中第一个配对,搜索起点可配置,也支持正则匹配。

for word in string.gmatch("Hello Lua", "%a+") do
    print(word)
end
print(string.match("I have 2 questions for you.", "%d+ %a+"))
--[[ 输出:
Hello
Lua
2 questions
--]]

8.13 转义字符

转义字符 意义 转义字符 意义
\a 响铃 \b 退格,将当前位置移到前一列
\f 换页,将当前位置移到下页开头 \n 换行,将当前位置移到下一行开头
\r 回车,将当前位置移到本行开头 \t 水平制表,跳到下一个Tab位置
\v 垂直制表 \ 代表一个反斜线字符
代表一个单引号字符 " 代表一个双引号字符
\0 空字符 \ddd 1到3位八进制数所代表的任意字符
\xhh 1到2位十六进制数所代表的任意字符

9. Table

数组与表的类型都为table

数组的语法为arrName = {element1, element2, ....},数组保存的一组数据类型可以不一致,数组的索引值是以1为起始的,也可以人为指定为0开始,如arrName = {[0]=element1, element2, ....}

表是一个 “关联数组”,表的索引可以是数字或者是字符串,所有索引值都需要用 [] 括起来;如果没有 [] 括起,则认为是字符串索引,可以认为,数组是索引为以0或1开始的连续数字的一种特殊的表,因此,表的一些增删改查行为函数也适用于数组。

操作 描述
concat 连接
insert 插入
maxn 最大key
remove 移除
sort 升序排序

9.1 定义

#后面直接加数组名可以获取数组的长度,如果设置了索引从0开始,则获取到的数组长度会比实际的长度少1。

#后面直接加表名无法准确获取数组的长度,会在索引中断的地方停止计数,应该用循环遍历来获取正确长度。

-- 数组
letters = {}
letters[1] = "A"
letters[2] = "B"
letters[3] = "C"
letters[4] = "D"
letters[5] = "E"
days = {[0]="Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
numbers = {1, 2, 3, nil}
all = {1, "A", nil, true}
print(string.format("Len: letters = %d, days = %d, numbers = %d, all = %d", #letters, #days, #numbers, #all))
-- 输出:Len: letters = 5, days = 6, numbers = 3, all = 4

-- 表
mytable1 = {[1] = "Lua", greet = "Hello", [3] = "Count"}
mytable2 = {}
print(string.format("Len: mytable1 = %d, mytable2 = %d", #mytable1, #mytable2))
-- 输出:Len: mytable1 = 1, mytable2 = 0

9.2 遍历

-- 数组
days = {[0]="Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
for i = 0, #days do
    print(string.format("days[%d] = %s", i, days[i]))
end
--[[ 输出:
days[0] = Monday
days[1] = Tuesday
days[2] = Wednesday
days[3] = Thursday
days[4] = Friday
days[5] = Saturday
days[6] = Sunday
--]]

letters = {"A", "B", "C", "D", "E"}
for i, v in pairs(letters) do
  print(string.format("letters[%d] = %s", i, v))
end
--[[ 输出:
letters[1] = A
letters[2] = B
letters[3] = C
letters[4] = D
letters[5] = E
--]]

-- 表
mytable = {[1] = "Lua", greet = "Hello", [3] = "Count"}
for i, v in pairs(mytable) do
  print(i, v)
end
--[[ 输出:
1   Lua
3   Count
greet   Hello
--]]

9.3 连接

table.concat(table [, sep [, start [, end]]]) 函数列出参数中指定table的数组部分从start位置到end位置的所有元素,元素键以指定分隔符sep隔开,start默认为1。注:该函数只处理table下标为数字的数据,且下标是连续的才能被处理,断开就结束了。

fruits = {"orange", "apple", "banana"}
print(string.format("str = %s", table.concat(fruits, ", ")))
-- 输出:orange, apple, banana

letters = {[0]="A", "B", "C", "D", "E"}
print(string.format("str = %s", table.concat(letters, ", ")))
-- 输出:B, C, D, E

mytable = {[1] = "Lua", greet = "Hello", [3] = "Count"}
print(string.format("str = %s", table.concat(mytable, ", ")))
-- 输出:Lua

9.4 插入

table.insert(table [pos,] value) 函数在table指定位置pos插入一个value元素,pos默认为数组部分末尾,即连续下标元素的最后。

fruits = {"orange", "apple", "banana"}
table.insert(fruits, 3, "watermelon")
for k, v in pairs(fruits) do
    print(k, v)
end
--[[ 输出:
1   orange
2   apple
3   watermelon
4   banana
--]]

letters = {[0]="A", "B", "C", "D", "E"}
table.insert(letters, 3, "W")
for i = 0#letters do
    print(i, letters[i])
end
--[[ 输出:
0   A
1   B
2   C
3   W
4   D
5   E
--]]

mytable = {[1] = "Lua", greet = "Hello", [3] = "Count"}
table.insert(mytable, 2, "Table")
for k,v in pairs(mytable) do
    print(k,v)
end
print(#mytable)
--[[ 输出:
1   Lua
2   Table
3   Count
greet   Hello
3
--]]

9.5 最大值

table.maxn(table) 函数返回table的最大正数索引,如果没有正数索引返回0。Lua5.2之后的版本已移除该函数。

fruits = {"orange", "apple", "banana"}
print(table.maxn(fruits))

letters = {[0]="A", "B", "C", "D", "E"}
print(table.maxn(letters))

mytable = {[1] = "Lua", greet = "Hello", [3] = "Count"}
print(table.maxn(mytable))

--[[ 输出:
3
4
3
--]]

9.6 删除

table.remove(table [,pos]) 函数删除指定pos位置的元素,pos默认为table长度。即连续索引的最大值。

fruits = {"orange", "apple", "banana"}
remove1 = table.remove(fruits)
remove2 = table.remove(fruits, 1)
print(string.format("remove1 = %s, remove2 = %s", remove1, remove2))
-- 输出:remove1 = banana, remove2 = orange

mytable = {[1] = "Lua", greet = "Hello", [3] = "Count"}
remove = table.remove(mytable)

print(string.format("remove = %s", remove))
-- 输出:remove = Lua

9.7 排序

table.sort(table [,comp]) 函数用于对给定的table进行升序排序,还支持传入排序规则。

local test0 ={1,9,2,8,3,7,4,6}
table.sort(test0)
for i,v in pairs(test0) do
    io.write(v.." ")
end
print()
local test1 ={
                {id=1, name="deng"},
                {id=9, name="luo"},
                {id=2, name="yang"},
                {id=8, name="ma"},
                {id=5, name="wu"},
}
table.sort(test1, function(a,b) return a.id < b.id end)
for i in pairs(test1) do
   print(test1[i].id, test1[i].name)
end
--[[ 输出:
1 2 3 4 6 7 8 9
1   deng
2   yang
5   wu
8   ma
9   luo
--]]

10. 元素metatable

10.1 定义

Lua的table中可以访问对应的key来得到value值,但是却无法对两个table进行操作。元表允许改变table的行为,每个行为关联了对应的元方法。如两个table相加操作a+b,当Lua尝试对两个表相加时,先检查两者之一是否有元表,之后检查__add字段是否存在,如果操作,则调用相应的值。__add等即时字段其对应的值就是元方法。

有两个重要的函数处理元表:

函数 描述
setmetatable(table, metatable) 对指定的table设置元表,如果元表中存在__metatable键值,则setmetatable会失败
getmetatable(table) 返回对象的元表
mytable = {}      -- 普通表
mymetatable = {}  -- 元表
setmetatable(mytable, mymetatable) -- 设置元表
getmetatable(mytable) -- 返回元表

10.2 __index元方法

__index元方法用来对表访问。

Lua查找一个表元素时的规则:

1 在表中查找,如果找到,返回该元素,找不到继续

2 判断该表是否有元表,如果没有元表,返回nil,有元表则继续

3 判断该表有没有__index方法,如果__index方法为nil,则返回nil,如果__index方法是一个表,则重复1、2、3步;如果__index方法是一个函数,则返回该函数的返回值

mytable = setmetatable({key1 = "value1"}, {
	__index = function(mytable, key)
    	if key == "key2" then
        	return "metatablevalue"
        else
           	return nil
        end
     end
})
print(string.format("key1 = %s, key2 = %s", mytable.key1, mytable.key2))
-- 输出:key1 = value1, key2 = metatablevalue
-- 等价于
mytable = setmetatable({key1 = "value1"}, {__index = {key2 = "metatablevalue"}})
print(mytable.key1, mytable.key2)
-- 输出:value1    metatablevalue

10.3 __newindex元方法

__newindex元方法用来对表更新。

当给表的一个缺少的索引赋值,解释器会查找__newindex元方法,如果存在,则调用这个函数而不进行赋值操作。

mytable = setmetatable({key1 = "value1"}, {
    __newindex = function(mytable, key, value)
        rawset(mytable, key, "\""..value.."\"")
    end
})
mytable.key1 = "new value"
mytable.key2 = 4
print(string.foramt("key1 = %s, key2 = %s", mytable.key1, mytable.key2))
-- 输出:key1 = new value, key2 = "4"

10.4 __call元方法

__call元方法可以让table当做一个函数来使用。

local mt = {}
mt.__call = function(mytable,...)
    for _, v in ipairs{...} do
        print(v)
    end
end
t = {}
setmetatable(t, mt)
t(1, 2, 3)
--[[ 输出:
1
2
3
--]]

10.5 __tostring元方法

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

mytable = setmetatable({10, 20, 30}, {
	__tostring = function(mytable)
        sum = 0
        for k, v in pairs(mytable) do
            sum = sum + v
        end
    	return "sum = " ..sum
    end
})
print(mytable)
-- 输出:sum = 60

10.6 为表添加操作符

模式 等价于运算符 模式 等价于运算符
__add + __sub -
__mul * __div /
__mod % __unm -
__concat __eq ==
__lt < __le <=
function table_maxn(t)
    local mn = 0
    for k, v in pairs(t) do
        if mn < k then
            mn = k
        end
    end
    return mn
end
-- 两表相加
mytable = setmetatable({1, 2, 3}, {
    __add = function(mytable, newtable)
        for i = 1, table_maxn(newtable) do
            table.insert(mytable, table_maxn(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
--[[ 输出:
1   1
2   2
3   3
4   4
5   5
6   6
--]]

11. 模块与包

11.1 模块定义

模块类似于一个封装库,是由变量、函数等已知元素组成的table,因此创建模块就是创建一个table,然后把需要导出的常量、函数放入其中,最后返回这个table即可。如下创建自定义模块module。

-- 文件名 module.lua
-- 定义一个名为module的模块
module = {}
-- 定义一个常量
module.constant = "this is constant"
-- 定义一个函数
function module.func1()
    io.write("This is a public function\n")
end
local function func2()
    io.write("This is a private function")
end
function module.func3()
    func2()
end
return module

11.2 require函数

require("模块名")用来加载模块,执行require后会返回一个由模块常量或函数组成的table,并且还会定义一个包含该table的全局变量。

require("module")
-- 别名变量 m
local m = require("module")
print(m.constant)
m.func3()
--[[ 输出:
this is constant
This is a private function
--]]

require函数会尝试从Lua文件或C程序中加载模块,require用于搜索Lua文件的路径存放在全局变量package_path中,当Lua启动后,会以环境变量LUA_PATH的值来初始化这个环境变量,如果找不到该环境变量,则使用一个编译时定义的默认路径来初始化。

可以自定义设置路况,在当前用户跟目录下打开.profile文件(没有则创建,打开.bashrc文件也可以),例如把"~/lua/"路径加入LUA_PATH环境变量里。

#LUA_PATH
export LAU_PATH="~/lua/?.lua;;"

文件路径以";“号分割,最后两个”;;"表示新加的路径后面加上原来的默认路径。

接着执行指令source ~/.profile更新变量参数使之生效。

如果找到目标文件,则会调用package.loadfile来加载模块,否则就会找C程序库。

搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。

搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。

11.3 module函数

module()函数调用时会创建表并将其赋予给全局变量和loaded table,最后还会将这个表设置为主程序块的环境。

-- 在模块文件在使用module函数
module "module_name"
--[[
等同语法
--]]
-- 定义模块名
local moduleName = "module_name"
-- 定义用于返回的模块表
local M = {}
-- 将模块表加入到全局变量
_G[moduleName] = M
-- 将模块表加入到package.loaded中防止多次加载
package.loaded[moduleName] = M
-- 将模块表设置为函数的环境表,使得模块中的所有操作都是在模块表中,这样定义函数就直接定义在模块表中
setfenv(1, M)

11.4 调用C语言包

Lua和C语言很容易结合,可以使用C语言为Lua写包,C语言包使用前必须先加载并连接,大多数系统是通过动态连接库机制。

Lua在一个加loadlib的函数内提供了所有的动态连接功能。这个函数的两个参数:库的绝对路径和初始化函数,如:

local path = "/usr/local/lua/lib/libluasocket.so"
local f = loadlib(path, "luaopen_socket")

loadlib函数加载指定的库并连接到Lua,然而并没有调用初始化函数,而是返回初始化函数作为Lua的一个函数。

如果加载动态库或者查找初始化函数出错,loadlib将返回nil和错误信息。

local path = "C:\\windows\\luasocket.dll"
local f = loadlib(path, "luaopen_socket")
-- 真正打开库
f()

一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。

将 stub 文件所在的目录加入到 LUA_PATH,这样设定后就可以使用 require 函数加载 C 库了。

12. 协程

12.1 定义

协程与线程比较类似,拥有独立的堆栈、独立的局部变量、独立的指令,同时又与其他协同程序共享全局变量和其他大部分东西。

一个具有多线程的程序可以同时运行几个线程,而协程却需要彼此写作运行,在任一指定时刻只有一个协程在运行,并且这个正在运行的协同程序只有在明确的被要求挂起时才会被挂起。

协程有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协程。

12.2 用法

方法 描述
coroutine.create() 创建coroutine,返回coroutine,参数是一个函数,当和resume配合使用时唤醒函数调用
coroutine.resume() 重启coroutine,和create配合使用
coroutine.yield() 挂起coroutine,将coroutine设置为挂起状态,和resume配合使用能够有很多效果
coroutine.status() 查看coroutine的状态,有dead、suspened、running三种状态
coroutine.wrap() 创建coroutine,返回一个函数,一旦调用这个函数,就进入croutine,和create功能重复
coroutine.running() 返回正在运行的coroutine,一个coroutine就是一个新村,返回一个coroutine的线程号
function foo(a)
    print("foo函数输出", a)
    return coroutine.yield(2 * a)
end

co = coroutine.create(function (a, b) 
    	print("第一次协同程序执行输出", a, b)
        local r = foo(a + 1)
        
    	print("第二次协同程序执行输出", r)
        local r, s = coroutine.yield(a + b, a - b)
        
        print("第三次协同程序执行输出", r, s)
        return b, "结束协同程序"
   	end)

print("main", coroutine.resume(co, 1, 10))
print("--分割线--")
print("main", coroutine.resume(co, "r"))
print("--分割线--")
print("main", coroutine.resume(co, "x", "y"))
print("--分割线--")
print("main", coroutine.resume(co, "x", "y"))
print("--分割线--")

--[[ 输出:
第一次协同程序执行输出 1 10
foo 函数输出 2
main true 4
--分割线--
第二次协同程序执行输出 r
main true 11 -9
--分割线--
第三次协同程序执行输出 x y
main true 10 结束协同程序
--分割线--
main false 10 cannot resume dead coroutine
--]]

12.3 生产者-消费者问题

local newProductor

function productor()
    local i = 0
    while true do
        i = i + 1
        send(i)
    end
end

function consumer()
    while true do
        local i = receive()
        print(i)
    end
end

function receive()
    local status, value = coroutine.resume(newProductor)
    return value
end

function send(x)
    coroutine.yield(x)
end

newProductor = coutine.create(productor)
cosumer()
--[[ 输出:
1
2
3
4
5
6
7
...
--]]

13. 文件IO

Lua文件IO库用于读取和处理文件,分为简单模式完全模式

  • 简单模式:拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作,适合做一些简单的文件操作
  • 完全模式:使用外部的文件句柄来实现,以一种面向对象的形式,将所有的文件操作定义为文件句柄的方法,适合做一些高级的文件操作,如同时读取多个文件

13.1 简单模式

函数 描述
io.input([file]) 设置默认的输入文件,file为文件名,返回文件句柄
io.output([file]) 设置默认的输出文件,file为文件名
io.close([file]) 关闭文件,不带参数的默认文件
io.read(formats) 读取默认文件,formats取值为a*-全读、*n-按数字读入、*l-按行读入,n-读取n个字符
io.lines([fn]) fn文件名,如无文件,取默认文件,返回一个迭代器
io.write(value) 向默认文件写入内容
io.flush() 把文件缓存里的操作立即作用到默认输出文件
-- 以只读方式打开文件
file = io.open("test.lua", "r")
-- 设置默认输入文件
io.input(file)
-- 输出文件第一行
print(string.format("read file: [%s]", io.read()))
-- 关闭文件
io.close(file)
-- 以附加的方式打开只写文件
file = io.open("test.lua", "a")
-- 设置默认输出文件
io.output(file)
-- 在文件最后一行写入
io.write("File content in test.lua")
print("file write success")
-- 关闭文件
io.close(file)

13.2 完全模式

-- 以只读方式打开文件
file = io.open("test.lua", "r")
-- 输出文件第一行
print(file:read())
-- 关闭打开的文件
file::close()
-- 以附加的方式打开只写文件
file = io.open("test.lua", "a")
-- 在文件最后一行写入
file::write("--test")
-- 关闭打开的文件
file::close()
-- 以只读方式打开文件
file = io.open("test.lua", "r")
-- 定位到文件倒数第25个位置
file::seek("end", -25)
-- 从当前(倒数第25个位置)读取整个文件
print(file::read("*a"))
-- 关闭打开的文件
file::close()

14. 错误处理

错误类型分为语法错误运行错误

语法错误通常是对程序的组件使用不当引起,如:

for a = 1, 10
	print(a)
end
-- 输出:lua: main.lua:2: 'do' expected near 'print'

运行错误时程序可以正常运行,但是会输出报错信息,如:

function add(a, b)
    return a + b
end
add(10)
--[[ 输出:
lua: main.lua:2: attempt to perform arithmetic on local 'b' (a nil value)
stack traceback:
    main.lua:2: in function 'add'
    main.lua:4: in main chunk
--]]

14.1 assert

assert(express, message) 断言函数会判断给定express表达式是否成立,成立的话不做任何事情,不成立则以message作为错误信息抛出。

function add(a, b)
    assert(type(a) == "number", "a not a number")
    assert(type(b) == "number", "b not a number")
    return a + b
end
add(10)
--[[ 输出:
lua: main.lua:3: b not a number
stack traceback:
    [C]: in function 'assert'
    main.lua:3: in function 'add'
    main.lua:6: in main chunk
--]]

14.2 error

error(message [,level]) 函数终止正在执行的函数,并返回message的内容作为错误信息,level参数指示获得错误的位置,level=1指出调用error位置,level=2指出调用error函数的函数吗,level=0不添加错误位置信息。

function add(a, b)
    if (type(a) ~= "number") then
        error("a not a number")
    end
    if (type(b) ~= "number") then
        error("b not a number", 2)
    end
    return a + b
end
add(10)
--[[ 输出:
lua: main.lua:10: b not a number
stack traceback:
    [C]: in function 'error'
    main.lua:6: in function 'add'
    main.lua:10: in main chunk
--]]

14.3 pcall

可以使用pcall函数包装需要执行的代码,pcall函数接收一个函数和要传递给后者的参数,并执行该函数,无错误返回true,有错误返回false和错误信息。

a, errorinfo = pcall(function(i) print(i) end, 27)
print(a, errorinfo)
--[[ 输出:
27
true nil
--]]
b, errorinfo = pcall(function(i) print(i) error('error..') end, 27)
print(b, errorinfo)
--[[ 输出:
27
false main.lua1: error..
--]]

14.4 xpcall

xpcall函数相比pcall函数会返回更多的错误调试信息,并执行该函数,无错误返回true,有错误返回false和错误信息。

function myfunction ()
   n = n / nil
end
function myerrorhandler(err)
   print("ERROR:", err)
end
status = xpcall(myfunction, myerrorhandler)
print(status)
--[[ 输出:
ERROR:  main.lua:2: attempt to perform arithmetic on global 'n' (a nil value)
false
--]]

15. 垃圾回收

15.1 机制

Lua运行了一个垃圾收集器收集所有死对象来完成自动内存管理的工作。

Lua实现了一个增量标记-扫描收集器,使用垃圾收集器间歇率和垃圾收集器步进倍率来控制垃圾收集循环。

垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。

垃圾收集器步进倍率控制收集器运作速度相对与内存分配速度的倍率,默认值是200%,即运作速度2倍于内存分配速率。

15.2 垃圾回收器函数

Lua提供一下函数collectgarbage([opt [,arg]])来控制自动内存管理。

函数 功能
collectgarbage(“collect”) 做一次完整的垃圾收集循环
collectgarbage(“count”) 以K字节数为单位返回Lua使用的总内存数
collectgarbage(“restart”) 重启垃圾收集器的自动运行
collectgarbage(“setpause”) 将arg设为收集器的间歇率,返回间歇率的前一个值
collectgarbage(“setstepmul”) 返回步进倍率的前一个值
collectgarbage(“step”) 单步运行垃圾收集器,步长由arg控制
collectgarbage(“stop”) 停止垃圾收集器的运行
mytable = {"apple", "orange", "banana"}
print(collectgarbage("count"))
mytable = nil
print(collectgarbage("count"))
print(collectgarbage("collect"))
print(collectgarbage("count"))
--[[ 输出:
27.6396484375
27.6767578125
0
26.623046875
--]]

16. 面向对象

16.1 面向对象特征

  • 封装:能够把一个实体的信息、功能、响应都装入一个单独的对象中的特征。

  • 继承:基础的方法允许在不改动原程序的基础上对其进行扩充,使得原功能得以保存,新功能也得以扩展,有利于减少重复代码,提高开发效率。

  • 多态:同一操作作用于不同的对象,产生不同的执行结果,可以通过指向基类的指针,来调用实现派生类的方法。

  • 抽象:简化复杂问题的方法,可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。

16.2 类封装

-- 元类
Shape = { area = 0 }

-- 基础类方法 new
function Shape:new (o, size)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    size = size or 0
    self.area = size * size
    return o
end

-- 基础类方法 printArea
function Shape:pirntArea ()
    print("The area = ", self.area)
end

-- 创建对象
myshape = Shape:new(nil, 10)
myshape:printArea()
-- 输出:The area = 100

16.3 继承与多态

-- 元类
Shape = { area = 0 }

-- 基础类方法 new
function Shape:new (o, size)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    size = size or 0
    self.area = size * size
    return o
end

-- 基础类方法 printArea
function Shape:pirntArea ()
    print("The area = ", self.area)
end

-- 创建对象
myshape = Shape:new(nil, 10)
myshape:printArea()
-- 输出:The area = 100

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

-- 派生类方法 printArea
function Square:printArea ()
    print("The Square area = ", self.area)
end

-- 创建对象
mysquare = Square:new(nil, 10)
mysquare:printArea()
-- 输出:The Square area = 100

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

-- 派生类方法 printArea
function Rectangle:printArea ()
    print("The Rectangle area = ", self.area)
end

-- 创建对象
myrectangle = Rectangle:new(nil, 10, 20)
myrectangle:printArea()
-- 输出:The Rectangle area = 200

参考文章

本文是笔者通过下列网站教程学习Lua的记录,有部分修改和补充,转载请注明出处,并附带下面链接。

1.【菜鸟教程Lua教程】

2.【嗨客网Lua教程】

你可能感兴趣的:(Lua语言,lua,学习,开发语言)