Lua学习笔记——基础(壹)

目录

目录

一、注释

-- 单行注释
 
--[[
    多行注释1
]]
--[[
    多行注释2
]]--
--[[
    多行注释3
--]]

二、变量

1、变量类型

nil、boolean、number、string、userdata、function、thread 、table

Lua变量图

2、几个注意点:

① Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值 —— 自动识别类型,和 python 很像

② 通过 type 函数可以获取变量类型,该函数返回值是 string

③ Lua 中使用未赋值的变量不会报错,默认值是 nil

3、简单的四种变量类型:

nil、boolean、number、string

-- 1、nil 类似c#的null
a = nil
print(type(a)) -- nil
 
-- 2、number,所有数值都是number
a = 1
print(type(a)) -- number
a = 1.23
print(type(a)) -- number
 
-- 3、string,使用双引号或单引号包裹,在lua里没有char
a = "双引号包裹的字符串"
print(type(a)) -- string
a = '单引号包裹的字符串'
print(type(a)) -- string
 
-- 4、boolean,布尔类型,为true或false
a = true
print(type(a)) -- boolean
 
-- 5、使用未赋值的变量
print(b) -- nil

三、字符串操作

1、字符串操作:

-- 1、字符串长度
-- 一个英文字符占1个长度、一个汉字占3个长度
s = "abcdefG"
print(#s) -- 7
s = "abcdefG字符串"
print(#s) -- 16
 
-- 2、多行字符串(多行字符串前面加--便是多行注释)
s = [[这是多行字符串
我可以写很多行]]
print(s)
 
-- 3、字符串拼接
print("Hello" .. " World!") -- Hello World!
 
-- 4、格式化字符串(和C很像)
print(string.format("你好,我今年%d岁了",18)) -- 你好,我今年18岁了
 
-- 5、其他类型转字符串(显示转换)
a = true
print(tostring(a)) -- true
 
-- 6、小写转大写
str = "aBcdEFg"
print(string.upper(str)) -- ABCDEFG
 
-- 7、大写转小写
print(string.lower(str)) -- abcdefg
 
-- 8、字符串翻转
print(string.reverse(str)) -- gFEdcBa
 
-- 9、字符串索引查找(Lua中,索引从1开始)
print(string.find(str,"cdE")) -- 3	5
 
-- 10、截取字符串
print(string.sub(str,3,5)) -- cdE
 
-- 11、字符串重复,获取字符串的n个拷贝
print(string.rep(str,2)) -- aBcdEFgaBcdEFg
 
-- 12、字符串替换(返回值第二个参数代表替换次数)
print(string.gsub(str,"cd","**")) -- aB**EFg	1
 
-- 13、字符转ASCII码
a = string.byte("Lua",1) -- 将字符串第1个字符转成ASCII码
print(a) -- 76
 
-- 14、ASCII码转字符
print(string.char(a)) -- L

2、几个注意点:

① 使用#可以获取字符串长度以及表的长度(后面会学习)

② Lua 中,索引是从 1 开始算的,而不是 0

③ 使用..可以拼接字符串

四、运算符

1、算术运算符:

a = 2
b = 5
-- 加法
print(a+b) -- 7
-- 减法
print(a-b) -- -3
-- 乘法
print(a*b) -- 10
-- 除法
print(a/b) -- 0.4
-- 取余
print(a%b) -- 2
-- 幂运算
print(a^b) -- 32
-- 整除(Lua版本大于等于5.3)
-- 5 // 2
 
-- 注:如果是 string 和 number 进行算术运算符操作,则string会自动转成number
print("123.4" + 1) -- 124.4

2、条件运算符:

-- 大于
print(a>b) -- false
-- 小于
print(a=b) -- false
-- 小于等于
print(a<=b) -- true
-- 不等于(使用~符号,不是!)
print(a~=b) -- true

3、逻辑运算符:

-- 与
print(true and false) -- false
-- 或
print(true or false) -- true
-- 非
print(not false) -- true

4、其他运算符:

.. 连接两个字符串

# 一元运算符,返回字符串或表的长度

5、Lua 不支持的运算符:

① 不支持位运算&|

② 不支持三目运算符? :

③ Lua 没有自增自减 ++ –-,也没有复合运算符 += -= *= /= %=

6、几个注意点:

① 算术运算中,如果是 string 和 number 进行算术运算符操作,则 string 会自动转成 number 进行运算(可以转换的话)

② 条件运算符中,不等于使用~= 而不是!=

③ 逻辑运算符中,逻辑运算符支持 “短路” 原则

五、条件分支语句

1、语法:

-- 1、单分支
if true then
    print("123") -- 123
end
 
-- 2、双分支
if false then
    print("123")
else
    print("456") -- 456
end
 
-- 3、多分支
if false then
    print("123")
elseif true then
    print("456") -- 456
else
    print("789")
end

2、几个注意点:

① elseif 要连着写

② Lua 中没有 switch 和三目运算符

六、循环语句

1、语法:

-- 1、while循环语句
num = 0
while num < 5 do
    print(num) -- 0 1 2 3 4
    num = num + 1
end
 
-- 2、repeat循环语句(do while循环)
num = 0
repeat
    print(num) -- 0 1 2 3 4 5
    num = num + 1
until num > 5

-- 3、for循环语句
for i=2,5 do -- 默认自增1
    print(i) -- 2 3 4 5
end
 
for i=1,5,2 do
    print(i) -- 1 3 5
end

2、几个注意点:

① do…while 循环在 Lua 中是 repeat…until

② for 循环中 3 个参数分别代表起始值,目标值、步长,其中步长默认为1

七、函数

1、函数的写法:

-- 函数的两种写法
function 函数名(参数)
    方法体
end

变量名 = function(参数)
    方法体
end

2、几个函数示例

Lua 的函数非常灵活,通过以下例子可以简单总结函数的几个规律:

(1)函数可接收任意个数量的参数:
如果实参数量多于形参,则多余的实参会被忽略,
如果实参数量少于形参,则缺少的参数会补为 nil

(2)可用多个变量接收函数的返回值:
如果函数内部没有返回值(没有 return),则默认返回 nil
如果外部接收的变量少于返回值,则多余的返回值会被忽略
如果外部接收的变量多于返回值,多余的变量被赋值为 nil

-- 1、无参无返回值函数
function F1()
	print("F1函数")
end
F1() -- F1函数
 
F2 = function()
	print("F2函数")
end
F2() -- F2函数
 
-- 2、有参无返回值函数
function F3(a)
	print(a)
end
F3(1) -- 1
 
-- 几个特殊情况
F3() -- 不传参:参数默认为nil
b = F3(1) -- 接收返回值:返回值为nil
F3(1,2,3) -- 传多个参数:丢弃后面多余的参数,只接受第一个参数
 
-- 3、无参有返回值函数
function F4(a)
	return a
end
temp = F4(5)
print(temp) -- 5
 
-- 可以有多个返回值,外面可用多个变量来接收,如果接收的变量数量不一致,则只接收对应位置的返回值
function F5(a)
	return a,"123",true
end
temp,temp2,temp3 = F5(1) -- temp:1,temp2:"123",temp3:true

3、变长参数

使用即可代表不定长参数,使用变长参数时需要先用表存起来:

function Func(...)
	arg = {...} -- 用表存起来
	for i=1,#arg do
		print(arg[i])
	end
end
Func(1,2,3,4,5) -- 1 2 3 4 5

4、函数嵌套

函数内部可以声明函数:

function Func()
	return function()
		print("你好,我是func!")
	end
end
myfunc = Func()
myfunc() -- 你好,我是func!

5、闭包:

闭包是指一个函数能够访问其外部作用域中的变量,即使该外部函数已经执行完毕并返回

如下面的代码中,改变了外部函数传入参数 x 的生命周期:

function Func(x)
	return function(y)
		return x + y
	end
end
myfunc = Func(10) -- 10这个变量的生命周期被改变
print(myfunc(5)) -- 15

6、几个注意点:

1、函数是一种类型,即 function,因此能赋值给变量

2、Lua 中,不支持函数重载,默认调用最后一次声明的函数

八、表

table 是 Lua 的一种数据结构,是 lua 中非常重要的一个知识点,table 用来帮助我们创建不同的数据类型,如:数组、字典、类等,学习 table 对我们理解 lua 有非常大的帮助。

Lua 中例如字典和类这些并非真正 Lua 的语法,只不过是用 table 的特性模拟的,所以我下面的数据结构都称为 “模拟 XX”。

(一)Lua 模拟数组

使用 {} 包裹起来的数据构成了数组,其中数组中的值可以是任意数据类型,但不要填 nil

使用 {} 包裹多个数组可以构成二维数组。

1、数组的遍历:

Lua 中的数组下标是从 1 开始的,而不是 0!使用#可以获取数组长度,结合这两点就可以做到数组的遍历:
(但实际遍历时不推荐使用#,后面会解释)

-- 数组定义
a = {1,"123",true,2}
print(a[0]) -- nil,数组索引从1开始
print(a[1]) -- 1
print(a[3]) -- true
 
-- 遍历数组
for i=1,#a do
	print(a[i]) -- 1 123 true 2
end
 
-- 二维数组定义
a = {{1,2,3},{4,5,6}}
print(a[2][2]) -- 5
 
-- 遍历二维数组
for i=1,#a do
	b = a[i]
	for j=1,#b do
		print(b[j]) -- 1 2 3 4 5 6
	end
end

2、为什么不推荐使用 #:

先看 table 长度的定义:table 的长度被定义成一个整数下标 n ,它满足 t [n] 不是 nil 而 t [n+1] 为 nil。

当数组中间出现了 nil,即出现了 “空洞”,使用#获取数组的长度是未定义的,即无法知道结果到底是什么。# 获取的结果可能是指向任何一个是 nil 值的前一个位置的下标,获取的长度是不确定的。

详细解释可看下面这篇文章:

lua 中求 table 长度 | 菜鸟教程

3、自定义索引

可以自己在数据前自定义一个索引,当然设置比较奇怪的索引可能会影响#获取数组长度(即出现上述所说的出现空洞的问题)。

a = {[0]=1,2,3,[-1]=4,5} -- 0号索引设置为1,-1号索引设置为4
print(a[-1]) -- 4
print(a[0]) -- 1
print(a[1]) -- 2
print(a[2]) -- 3
print(a[3]) -- 5
print(#a) -- 3,长度计算还是按正常的索引顺序算,即只计算了2、3、5这三个数

4、迭代器遍历

为了解决之前所说的用 #遍历数组带来的问题,接下来介绍两种迭代器遍历方式:

ipairspairs

ipairs 的局限就是只能遍历索引从 1 开始的,而且中间断序了,即获取到 nil 时就结束了

pairs 可以找出表中所有的键,推荐使用这个

arr = {[-1]=1,[0]=2,3,4,5,[5]=6} 
 
-- 1、ipairs遍历
for i,k in ipairs(arr) do
	print("索引:"..i.." 值:"..k) 
	--[[
		索引:1 值:3
		索引:2 值:4
		索引:3 值:5
	]]
end
 
-- 2、pairs遍历
for i,k in pairs(arr) do
	print("索引:"..i.." 值:"..k)
	--[[
		索引:1 值:3
		索引:2 值:4
		索引:3 值:5
		索引:0 值:2
		索引:-1 值:1
		索引:5 值:6
	]]
end

(二)Lua 模拟字典

1、基本操作:

-- 1、字典基本操作
-- 声明
a = {["name"]="Tom",["age"]=12}
-- 访问
print(a["age"]) -- 12
print(a.name) -- Tom,通过类似类变量的方式访问
-- 修改
a["age"] = 18
-- 添加
a["sex"] = false
-- 删除
a["sex"] = nil
 
-- 2、遍历字典
for k,v in pairs(a) do
	print(k,v)
	--[[
		name	Tom
		age	18
	]]
end

2、几个注意点:

① 字典的删除操作可以考虑将值置 nil

② 字典的值可以通过点出来使用,不过只对键是字符串时有效

(三)Lua 模拟类

1、类内部声明:

类的每个属性和方法本质上就是表中的一个键值对,因此根据表的特性(字典的添加和修改操作),类的属性和方法不仅能在表内声明,还能在表外部进行声明,下面先看类内部声明吧。

注意几点:

(1)由于这并非真正的类,因此想在方法内部调用自身的属性,并不能直接调用,Lua 也没有 this 关键字,因此要么通过类名调用(表名),要么通过传参数进来进行调用,详情看下面的 PrintAge 方法。

(2)目前这样声明的类并非完全具有面向对象的特性,更像是拥有静态属性和静态方法的类,而面向对象相关知识点会在后面章节讲述。

-- 类声明
Student = {
	-- 年龄
	age = 18,
	-- 性别
	sex = true,
	-- 成长方法
	Up = function()
		print("Up")
	end,
	-- 打印年龄方法
	PrintAge = function(s)
		-- print(age) 			-- 这个age和表中的age没有任何关系,这代表一个全局变量
		-- print(Student.age)   -- 方法一:必须指定表名才可以调用正确的属性和方法
		print(s.age)		-- 方法二:外部传一个参数进来
	end
}

2、成员和方法调用:

这一小节和第 3 小节都有一些新知识点,不过别急,在第 4 小节会对新知识点进行简单总结。

-- 调用类属性和方法
print(Student.age) -- 18
Student.Up() -- Up
Student.PrintAge(Student) -- 打印18,使用点调用方法必须传入参数
Student:PrintAge() -- 打印18,使用冒号调用方法,默认把调用者作为第一个参数传入方法中

3、类外部声明:

在类外部,可以声明成员变量和方法,而方法的声明有多种方式

-- 外部声明变量或方法
Student.name = "Tom" -- 外部声明成员变量
Student.Func = function() -- 外部声明函数方式1
	print("Func")
end
function Student.Func2() -- 外部声明函数方式2
	print("Func2")
end
function Student:Func3() -- 外部声明函数方式3,使用冒号
	print(self.name .. " Func3") -- lua中,self表示默认传入的第一个参数,和this不是一个东西
end
 
-- 调用类外部声明的方法
Student.Func3(Student) -- Tom Func3,这里必须要传一个参数,因为函数中使用了self这个参数
Student:Func3() -- Tom Func3

4、新知识点总结:

在模拟类中,我们又新学到了几个知识点,都比较重要,下面进行简单的总结:

(1)函数中的冒号与点号

函数调用时,使用点号就是普通的函数调用,而使用冒号调用默认将调用者作为第一个参数传入函数。

为表声明函数时,使用点号代表正常的声明函数,而使用冒号则默认为函数添加一个名为 self 的隐式参数作为第一个参数。

综上,我简单总结一下冒号的特殊之处:冒号代表着是否默认带上一个参数

(2)self

Lua 中的 self 和其他语言的 this 不是同一种概念!self 代表默认传入的第一个参数,一般搭配冒号声明的函数使用。最好理解这一点,方便后续面向对象的学习

(四)表的操作

Lua表操作

下面简单通过代码使用一下:

t1 = {{age=1,name="tom"},{age=2,name="tony"}}
t2 = {name="jany",sex=false}
 
-- 1、插入
table.insert(t1,t2) -- 默认t2插入到t1后面
print(t1[1].name) -- tom
print(t1[2].name) -- tony
print(t1[3].name) -- jany
 
-- 2、移除
table.remove(t1) -- 默认移除最后一个
print(t1[1].name) -- tom
print(t1[2].name) -- tony
print(t1[3]) -- nil
 
-- 3、排序
t3 = {1,5,8,3,6,2}
table.sort(t3)
for _,v in pairs(t3) do
	print(v) -- 1 2 3 5 6 8
end
-- 自定义排序规则
table.sort(t3, function(a,b)
	if a > b then
		return true -- 希望a排b前面则返回true
	end
end)
for _,v in pairs(t3) do
	print(v) -- 8 6 5 3 2 1
end
 
-- 4、拼接
t4 = {"123","456","789"}
str = table.concat(t4, ";")
print(str) -- 123;456;789