lua基础数据类型

nil

一个变量在第一次赋值前的默认值是 nil, 将nil 赋予给一个全局变量就等同于删除它。

boolean

布尔类型, 可选值 true/false; Lua 中 nil 和 false 为“假”, 其它所有值均为“真”。比如 0 和空 字符串就是“真”;

local a = true

if a then
    print("a") -->output:a
else
    print("not a") --这个没有执行
end

number

Number 类型用于表示实数。可以使用数学函数 math.floor( 向下取整) 和 math.ceil( 向上取整) 进行取整操作。

local order = 3.99
local score = 98.01

print(math.floor(order)) -->output:3
print(math.ceil(score)) -->output:99

string

Lua 中有三种方式表示字符串:

  1. 使用一对单引号 'hello'
  2. 使用一对双引号"hello"
  3. 字符串还可以用一种长括号( 即[[ ]]) 括起来的方式定义。

Lua 的字符串是不可改变的值,也不能通过下标来访问字符串的某个字符

local str1 = 'hello world'
local str2 = "hello lua"
local str3 = [["add\name",'hello']]
local str4 = [=[string have a [[]].]=]

print(str1) -->output:hello world
print(str2) -->output:hello lua
print(str3) -->output:"add\name",'hello'
print(str4) -->output:string have a [[]].

table

Table 类型实现了一种抽象的“关联数组”。 “关联数组”是一种具有特殊索引方式的数组, 索引通常是字符串( string) 或者 number 类型, 但也可以是除 nil 以外的任意类型的值。

local corp = {
	web = "www.google.com", --索引为字符串, key = "web",
	                        -- value = "www.google.com"
	telephone = "12345678", --索引为字符串
	staff = {"Jack", "Scott", "Gary"}, --索引为字符串, 值也是一个表
	100876,                            --相当于 [1] = 100876, 此时索引为数字
	                                   -- key = 1, value = 100876
	100191,                            --相当于 [2] = 100191, 此时索引为数字
	[10] = 360,                        --直接把数字索引给出
	["city"] = "Beijing"               --索引为字符串
} 

print(corp.web)                -->output:www.google.com
print(corp["telephone"])       -->output:12345678
print(corp[2])                 -->output:100191
print(corp["city"])            -->output:"Beijing"
print(corp.staff[1])           -->output:Jack
print(corp[10])                -->output:360

function

在 Lua 中, 函数 也是一种数据类型, 函数可以存储在变量中, 可以通过参数传递给其他函数, 还可以作为其他函数的返回值。

local function foo()
	print("in the function")
	--dosomething()
	local x = 10
	local y = 20
	return x + y
end

local a = foo --把函数赋给变量
print(a())

--output:
in the function
30

表达式

逻辑运算符

逻辑运算符

说明

and

逻辑与

or

逻辑或

not

逻辑非

字符串连接

使用操作符“..”( 两个点)连接两个字符串,也可以使用 string 库函数 string.format 连接字符串。

print("Hello " .. "World")    -->打印 Hello World
print(0 .. 1)                 -->打印 01

str1 = string.format("%s-%s","hello","world")
print(str1)                   -->打印 hello-world

str2 = string.format("%d-%s-%.2f",123,"world",1.21)
print(str2)                   -->打印 123-world-1.21

使用table 和 table.concat() 来进行很多字符串的拼接:

local pieces = {}
for i, elem in ipairs(my_list) do
	pieces[i] = my_process(elem)
end
local res = table.concat(pieces)

控制结构

if-else

单个if分支

x = 10
if x > 0 then
	print("x is a positive number")
end

两个分支if-else

x = 10
if x > 0 then
	print("x is a positive number")
else
	print("x is a non-positive number")
end

多个分支if-elseif-else

score = 90
if score == 100 then
	print("Very good!Your score is 100")
elseif score >= 60 then
	print("Congratulations, you have passed it,your score greater or equal to 60")
--此处可以添加多个elseif
else
	print("Sorry, you do not pass the exam! ")
end

while

while 表达式 do
--body
end

Lua 并没有像许多其他语言那样提供类似 continue 这样的控制语句用来立即进入下一个循环迭代( 如果有的话) 。 因此, 我们需要仔细地安排循环体里的分支, 以避免这样的需求。 lua提供了break语句,可以跳出当前循环。

repeat

repeat 控制结构类似于其他语言中的 do-while,执行 repeat 循环体后, 直到 until 的条件为真时才结束

x = 10
repeat
    print(x)
until false

for

for数字型

for var = begin, finish, step do
--body
end

step参数是可选的,默认为1

for i = 1, 5 do
    print(i)
end

for泛型

泛型 for 循环通过一个迭代器( iterator) 函数来遍历所有值:

-- 打印数组a的所有值
local a = {"a", "b", "c", "d"}
for i, v in ipairs(a) do
	print("index:", i, " value:", v)
end

-- output:
index: 1 value: a
index: 2 value: b
index: 3 value: c
index: 4 value: d

ipairs()是用于遍历数组的迭代器函数。在每次循环中, i 会被赋予一个索引值, 同时 v 被赋予一个对应于该索引的数组元素值。 pairs()用于遍历

break,return关键字

break

语句 break 用来终止 while 、 repeat 和 for 三种循环的执行, 并跳出当前循环体, 继续执行当前循环之后的语句

return

return 主要用于从函数中返回结果, 或者用于简单的结束一个函数的执行 有时候, 为了调试方便, 我们可以想在某个函数的中间提前 return , 以进行控制流的短路。此时我们可以将 return 放在一个 do ... end 代码块中

local function foo()
	print("before")
	do return end
	print("after") -- 这一行语句永远不会执行到
end

函数的参数

按值传递

Lua 函数的参数大部分是按值传递的。在调用函数的时候,若形参个数和实参个数不同时, Lua会自动调整实参个数。 调整规则:

  • 若实参个数大于形参个数, 从左向右, 多余的实参被忽略;
  • 若实参个数小于形参个数, 从左向右, 没有被实参初始化的形参会被初始化为 nil。

变长参数

若形参为 ... , 表示该函数可以接收不同长度的参数。 访问参数的时候也要使用 ...

local function func( ... )                  -- 形参为 ... ,表示函数采用变长参数
	local temp = {...}                      -- 访问的时候也要使用 ...
	local ans = table.concat(temp, " ")     -- 使用 table.concat 库函数对数
											-- 组内容使用 " " 拼接成字符串。
	print(ans)
end

func(1, 2)            -- 传递了两个参数
func(1, 2, 3, 4)      -- 传递了四个参数

-->output
1 2
1 2 3 4

具名参数

Lua 还支持通过名称来指定实参,这时候要把所有的实参组织到一个 table 中, 并将这个table 作为唯一的实参传给函数

local function change(arg) -- change 函数, 改变长方形的长和宽, 使其各增长一倍
	arg.width = arg.width * 2
	arg.height = arg.height * 2
	return arg
end

local rectangle = { width = 20, height = 15 }
print("before change:", "width =", rectangle.width,
						"height =", rectangle.height)
rectangle = change(rectangle)
print("after change:", "width =", rectangle.width,
					   "height =", rectangle.height)
					   
-->output
before change: width = 20 height = 15
after change: width = 40 height = 30

按引用传递

当函数参数是 table 类型时, 传递进来的是 实际参数的引用, 此时在函数内部对该 table 所做的修改, 会直接对调用者所传递的实际参数生效

function change(arg)            --change函数, 改变长方形的长和宽, 使其各增长一倍
	arg.width = arg.width * 2   --表arg不是表rectangle的拷贝, 他们是同一个表
	arg.height = arg.height * 2
end -- 没有return语句了

local rectangle = { width = 20, height = 15 }
print("before change:", "width = ", rectangle.width,
                        " height = ", rectangle.height)
change(rectangle)
print("after change:", "width = ", rectangle.width,
                       " height =", rectangle.height)

--> output
before change: width = 20 height = 15
after change: width = 40 height = 30

函数返回值

函数必须放在调用的代码之前

lua允许函数返回多个值。返回多个值时, 值之间用“,”隔开。

local function swap(a, b) -- 定义函数 swap, 实现两个变量交换值
	return b, a           -- 按相反顺序返回变量的值
end

模块

一个 Lua 模块的数据结构是用一个 Lua 值( 通常是一个 Lua 表或者 Lua 函数) 。 一个 Lua 模块代码就是一个会返回这个 Lua 值的代码块。可以使用内建函数 require() 来加载和缓存模块。 模块加载后的结果通过是一个 Lua table,这个表就像是一个命名空间,其内容就是模块中导出的所有东西, 比如函数和变量。 require 函数会返回Lua 模块加载后的结果, 即用于表示该 Lua 模块的 Lua 值

require函数

要加载一个模块, 只需要简单地调用 require "file" 就可以了, file 指模块所在的文件名。这个调用会返回一个由模块函数组成的table, 并且还会定义一个包含该 table 的全局变量。 在 Lua 中创建一个模块最简单的方法是: 创建一个 table, 并将所有需要导出的函数放入其中, 最后返回这个 table 就可以了。 相当于将导出的函数作为 table 的一个字段

#my.lua, 创建了my模块
local foo={}

local function getname()
	return "Lucy"
end

function foo.greeting()
	print("hello " .. getname())
end

return foo
#在main.lua中加载my模块
local fp = require("my")
fp.greeting() -->output: hello Lucy

注: 对于需要导出给外部使用的公共模块, 处于安全考虑, 是要避免全局变量的出现。

String库

string.byte(s[,i[,j]]) 返回字符 s[i]、 s[i + 1]、 s[i + 2]、 ······、 s[j] 所对应的 ASCII 码

string.char (...) 接收 0 个或更多的整数( 整数范围: 0~255) , 返回这些整数所对应的 ASCII 码字符组成的字符串

string.upper(s) 接收一个字符串 s, 返回一个把所有小写字母变成大写字母的字符串

string.lower(s) 接收一个字符串 s, 返回一个把所有大写字母变成小写字母的字符串。

string.len(s) 接收一个字符串, 返回它的长度。

string.find(s, p [, init [, plain]]) 在 s 字符串中第一次匹配 p 字符串。 若匹配成功, 则返回 p 字符串在 s 字符串中出现的开始位置和结束位置; 若匹配失败, 则返回 nil。

string.format(formatstring, ...) 按照格式化参数 formatstring, 返回后面 ... 内容的格式化版本

string.match(s, p [, init]) 在字符串 s 中匹配( 模式) 字符串 p, 若匹配成功, 则返回目标字符串中与模式匹配的子串;否则返回 nil。

string.gmatch(s, p) 返回一个迭代器函数, 通过这个迭代器函数可以遍历到在字符串 s 中出现模式串 p 的所有地方。

string.rep(s, n) 返回字符串 s 的 n 次拷贝。

string.sub(s, i [, j]) 返回字符串 s 中, 索引 i 到索引 j 之间的子字符串

string.gsub(s, p, r [, n]) 将目标字符串 s 中所有的子串 p 替换成字符串 r

string.reverse (s) 接收一个字符串 s, 返回这个字符串的反转

table库

下标从 1 开始。在 Lua 中, 数组下标从 1 开始计数。

table.getn 获取长度

对于常规的数组, 里面从 1 到 n 放着一些非空的值的时候, 它的长度就精确的为 n, 即最后一个值的下标。 如果数组有一个“空洞”( 就是说, nil 值被夹在非空值之间) , 那么 #t 可能是指向任何一个是 nil 值的前一个位置的下标。 不要在 Lua 的 table 中使用 nil 值, 如果一个元素要删除, 直接 remove, 不要用 nil 去代替。

table.concat (table [, sep [, i [, j ] ] ]) 对于元素是 string 或者 number 类型的表 table, 返回 table[i]..sep..table[i+1] ··· sep..table[j] 连接成的字符串

table.insert (table, [pos ,] value) 在( 数组型) 表 table 的 pos 索引位置插入 value, 其它元素向后移动到空的地方

table.maxn (table) 返回( 数组型) 表 table 的最大索引编号; 如果此表没有正的索引编号, 返回 0

table.remove (table [, pos]) 在表 table 中删除索引为 pos( pos 只能是 number 型) 的元素, 并返回这个被删除的元素,它后面所有元素的索引值都会减一

table.sort (table [, comp]) 按照给定的比较函数 comp 给表 table 排序, 也就是从 table[1] 到 table[n], 这里 n 表示 table的长度

##日期时间函数

os.time ([table]) 如果不使用参数 table 调用 time 函数, 它会返回当前的时间和日期( 它表示从某一时刻到现在的秒数)

os.difftime (t2, t1) 返回 t1 到 t2 的时间差, 单位为秒

os.date ([format [, time]]) 把一个表示日期和时间的数值, 转换成更高级的表现形式

格式字符

含义

%a

一星期中天数的简写( 例如: Wed)

%A

一星期中天数的全称( 例如: Wednesday)

%b

月份的简写( 例如: Sep)

%B

月份的全称( 例如: September)

%c

日期和时间( 例如: 07/30/15 16:57:24)

%d

一个月中的第几天[01 ~ 31]

%H

24小时制中的小时数[00 ~ 23]

%I

12小时制中的小时数[01 ~ 12]

%j

一年中的第几天[001 ~ 366]

%M

分钟数[00 ~ 59]

%m

月份数[01 ~ 12]

%p

“上午( am) ”或“下午( pm) ”

%S

秒数[00 ~ 59]

%w

一星期中的第几天[1 ~ 7 = 星期天 ~ 星期六]

%x

日期( 例如: 07/30/15)

%X

时间( 例如: 16:57:24)

%y

两位数的年份[00 ~ 99]

%Y

完整的年份( 例如: 2015)

%%

字符'%'

数学库

函数名

函数功能

math.rad(x)

角度x转换成弧度

math.deg(x)

弧度x转换成角度

math.max(x, ...)

返回参数中值最大的那个数, 参数必须是number型

math.min(x, ...)

返回参数中值最小的那个数, 参数必须是number型

math.random ([m[, n]])

不传入参数时, 返回 一个在区间[0,1)内均匀分布的伪随机实数; 只使用一个整数参数m时, 返回一个在区间[1, m]内均匀分布的伪随机整数; 使用两个整数参数时, 返回一个在区间[m, n]内均匀分布的伪随机整数

math.randomseed(x)

为伪随机数生成器设置一个种子x, 相同的种子将会生成相同的数字序列

math.abs(x)

返回x的绝对值

math.fmod(x, y)

返回 x对y取余数

math.pow(x, y)

返回x的y次方

math.sqrt(x)

返回x的算术平方根

math.exp(x)

返回自然数e的x次方

math.log(x)

返回x的自然对数

math.log10(x)

返回以10为底, x的对数

math.floor(x)

返回最大且不大于x的整数

math.ceil(x)

返回最小且不小于x的整数

math.pi

圆周率

math.sin(x)

求弧度x的正弦值

math.cos(x)

求弧度x的余弦值

math.tan(x)

求弧度x的正切值

math.asin(x)

求x的反正弦值

math.acos(x)

求x的反余弦值

math.atan(x)

求x的反正切值

文件操作

io.open (filename [, mode]) 按指定的模式 mode, 打开一个文件名为 filename 的文件, 成功则返回文件句柄, 失败则返回 nil 加错误信息

模式

含义

文件不存在时

"r"

读模式 (默认)

返回nil加错误信息

"w"

写模式

创建文件

"a"

添加模式

创建文件

"r+"

更新模式, 保存之前的数据

返回nil加错误信息

"w+"

更新模式, 清除之前的数据

创建文件

"a+"

添加更新模式, 保存之前的数据,在文件尾进行添加

创建文件

file:close () 关闭文件。 注意: 当文件句柄被垃圾收集后, 文件将自动关闭。 句柄将变为一个不可预知的值。

io.close ([file]) 关闭文件, 和 file:close() 的作用相同。 没有参数 file 时, 关闭默认输出文件。

file:flush () 把写入缓冲区的所有数据写入到文件 file 中。

io.flush () 相当于 file:flush(), 把写入缓冲区的所有数据写入到默认输出文件

io.input ([file]) 当使用一个文件名调用时, 打开这个文件( 以文本模式) , 并设置文件句柄为默认输入文件; 当使用一个文件句柄调用时, 设置此文件句柄为默认输入文件; 当不使用参数调用时,返回默认输入文件句柄。

file:lines () 返回一个迭代函数, 每次调用将获得文件中的一行内容, 当到文件尾时, 将返回 nil, 但不关闭文件。

io.lines ([filename]) 打开指定的文件 filename 为读模式并返回一个迭代函数, 每次调用将获得文件中的一行内容,当到文件尾时, 将返回 nil, 并自动关闭文件。 若不带参数时 io.lines() 等价于 io.input():lines()读取默认输入设备的内容, 结束时不关闭文件。

io.output ([file]) 类似于 io.input, 但操作在默认输出文件上。

file:read (...) 按指定的格式读取一个文件。 按每个格式将返回一个字符串或数字, 如果不能正确读取将返回nil, 若没有指定格式将指默认按行方式进行读取。

格式

含义

"*n"

读取一个数字

"*a"

从当前位置读取整个文件。 若当前位置为文件尾, 则返回空字符串

"*l"

读取下一行的内容。 若为文件尾, 则返回nil。 (默认)

number

读取指定字节数的字符。 若为文件尾, 则返回nil。 如果number为0,则返回空字符串, 若为文件尾,则返回nil

io.read (...) 相当于 io.input():read

io.type (obj) 检测 obj 是否一个可用的文件句柄。 如果 obj 是一个打开的文件句柄, 则返回 "file" 如果 obj是一个已关闭的文件句柄, 则返回 "closed file" 如果 obj 不是一个文件句柄, 则返回 nil。

file:write (...) 把每一个参数的值写入文件。 参数必须为字符串或数字, 若要输出其它值, 则需通过 tostring 或 string.format 进行转换。

io.write (...) 相当于 io.output():write

file:seek ([whence] [, offset]) 设置和获取当前文件位置, 成功则返回最终的文件位置(按字节, 相对于文件开头),失败则返回 nil 加错误信息。

whence

含义

"set"

文件开始

"cur"

文件当前位置(默认)

"end"

文件结束

file:setvbuf (mode [, size]) 设置输出文件的缓冲模式

模式

含义

"no"

没有缓冲, 即直接输出

"full"

全缓冲, 即当缓冲满后才进行输出操作(也可调用flush马上输出)

"line"

以行为单位, 进行输出

元表

元表 (metatable) 的表现行为类似于 C++ 语言中的操作符重载 Lua 提供了两个十分重要的用来处理元表的方法, 如下:

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

元方法

含义

"__add"

+ 操作

"__sub"

- 操作 其行为类似于 "add" 操作

"__mul"

* 操作 其行为类似于 "add" 操作

"__div"

/ 操作 其行为类似于 "add" 操作

"__mod"

% 操作 其行为类似于 "add" 操作

"__pow"

^ ( 幂) 操作 其行为类似于 "add" 操作

"__unm"

一元 - 操作

"__concat"

.. ( 字符串连接) 操作

"__len"

# 操作

"__eq"

== 操作 函数 getcomphandler 定义了 Lua 怎样选择一个处理器来作比较操作 仅在两个对象类型相同且有对应操作相同的元方法时才起效

"__lt"

< 操作

"__le"

<= 操作

"__index"

取下标操作用于访问 table[key]

"__newindex"

赋值给指定下标 table[key] = value

"__tostring"

转换成字符串

"__call"

当 Lua 调用一个值时调用

"__mode"

用于弱表(week table)

"__metatable"

用于保护metatable不被访问

面向对象

可以使用表和函数实现面向对象。 将函数和相关的数据放置于同一个表中就形成了一个对象。

下面的代码,创建了account类: account.lua

local _M = {}

local mt = { __index = _M }

function _M.deposit (self, v)
	self.balance = self.balance + v
end

function _M.withdraw (self, v)
	if self.balance > v then
		self.balance = self.balance - v
	else
		error("insufficient funds")
	end
end

function _M.new (self, balance)
	balance = balance or 0
	return setmetatable({balance = balance}, mt)
end

return _M

下面代码是使用account类示例:

local account = require("account")

local a = account:new()
a:deposit(100)

local b = account:new()
b:deposit(50)

print(a.balance) --> output: 100
print(b.balance) --> output: 50

继承

继承可以用元表实现, 它提供了在父类中查找存在的方法和变量的机制。 在 Lua 中是不推荐使用继承方式完成构造的, 这样做引入的问题可能比解决的问题要多

---------- s_base.lua
local _M = {}

local mt = { __index = _M }

function _M.upper (s)
	return string.upper(s)
end

return _M

---------- s_more.lua
local s_base = require("s_base")

local _M = {}
_M = setmetatable(_M, { __index = s_base })

function _M.lower (s)
	return string.lower(s)
end

return _M

---------- test.lua
local s_more = require("s_more")

print(s_more.upper("Hello")) -- output: HELLO
print(s_more.lower("Hello")) -- output: hello

局部变量

在一个 block 中的变量, 如果之前没有定义过, 那么认为它是一个全局变量, 而不是这个 block 的局部变量 Lua 中的局部变量要用 local 关键字来显式定义, 不使用 local 显式定义的变量就是全局变量 局部变量的生命周期是有限的, 它的作用域仅限于声明它的块( block) 。 一个块是一个控制结构的执行体、 或者是一个函数的执行体再或者是一个程序块( chunk) “尽量使用局部变量”是一种良好的编程风格

判断数组大小

table.getn(t) 等价于 #t 但计算的是数组元素, 不包括 hash 键值。 而且数组是以第一个 nil 元素来判断数组结束。 # 只计算 array 的元素个数, 它实际上调用了对象的 metatable 的__len 函数。 对于有 __len 方法的函数返回函数返回值, 不然就返回数组成员数目。

注意: 一定不要使用 # 操作符或 table.getn 来计算包含 nil 的数组长度, 这是一个未定义的操作, 不一定报错, 但不能保证结果如你所想。 如果你要删除一个数组中的元素, 请使用remove 函数, 而不是用 nil 赋值。

非空判断

对于table型变量,可以如下判断非空:

local person = {name = "Bob", sex = "M"}
if person ~= nil and person.name ~= nil then
	print(person.name)
end

对于简单类型的变量, 我们可以用 if (var == nil) then 这样的简单句子来判断。 但是对于 table型的 Lua 对象, 就不能这么简单判断它是否为空了。 一个 table 型变量的值可能是 {} , 这时它不等于 nil

正则表达式(openresty中的正则表达式规范 -- ngx.re.*)

下面是使用openresty中正则的例子:

location /test {
	content_by_lua_block {
	local regex = [[\d+]]
	
	-- 参数 "j" 启用 JIT 编译, 参数 "o" 是开启缓存必须的
	local m = ngx.re.match("hello, 1234", regex, "jo")
	if m then
		ngx.say(m[0])
	else
		ngx.say("not matched!")
	end
	}
}

正则中使用了jo 两个参数:

  • ngx.re.* 中的 o 选项, 指明该参数, 被编译的 Pattern 将会在工作进程中缓存, 并且被当前工作进程的每次请求所共享
  • ngx.re.* 中的 j 选项, 指明该参数, 如果使用的 PCRE 库支持 JIT, OpenResty 会在编译 Pattern 时启用 JIT。即使运行在不支持 JIT 的 OpenResty 上, 加上 j 选项也不会带来坏的影响

lua正则汇总:

符号

匹配次数

匹配模式

+

匹配前一字符 1 次或多次

非贪婪

*

匹配前一字符 0 次或多次

贪婪

-

匹配前一字符 0 次或多次

非贪婪

?

匹配前一字符 0 次或1次

仅用于此, 不用于标识是否贪婪

符号

匹配模式

.

任意字符

%a

字母

%c

控制字符

%d

数字

%l

小写字母

%p

标点字符

%s

空白符

%u

大写字母

%w

字母和数字

%x

十六进制数字

%z

代表 0 的字符

虚变量

当一个方法返回多个值时, 有些返回值有时候用不到, 要是声明很多变量来一一接收, 显然不太合适( 不是不能) 。 Lua 提供了一个虚变量(dummy variable), 以单个下划线( “_”) 来命名, 用它来丢弃不需要的数值, 仅仅起到占位的作用。

-- string.find (s,p) 从string 变量s的开头向后匹配 string
-- p, 若匹配不成功, 返回nil, 若匹配成功, 返回第一次匹配成功的起止下标。
local start, finish = string.find("hello", "he") --start 值为起始下标, finish
                                                 --值为结束下标
print ( start, finish )                          --输出 1 2
local start = string.find("hello", "he")         -- start值为起始下标
print ( start )                                  -- 输出 1
local _,finish = string.find("hello", "he")      --采用虚变量( 即下划线) , 接收起
											     --始下标值, 然后丢弃, finish接收
												 --结束下标值
print ( finish )                                 --输出 2

点号与冒号操作符的区别

冒号操作会带入一个 self 参数, 用来代表 自己 。 而点号操作, 只是 内容 的展开。

local str = "abcde"
print("case 1:", str:sub(1, 2))
print("case 2:", str.sub(str, 1, 2))