Lua编程

一.lua环境安装

1.SciTE安装包

Github 下载地址:https://github.com/rjpcomputing/luaforwindows/releases

2.LuaDist(官方推荐,但不是很好用)

http://luadist.org/

二.lua中的注释

1.单行注释--

--这里是单行注释

2.多行注释

--[[
这里是多行注释
]]--

小技巧:在多行注释中,--[[添加一个短横线变成---[[就可以取消多行注释

三.标示符命名规则

使用大小写字母或下划线开头,加上0个或若干个大小写字母、数字或下划线。

注意:lua中内部全局变量基本使用下划线加上大写字母命名,因此不推荐这种方式命名标示符,避免冲突。

四.lua中的数据类型

1.数据类型nil

lua中的空值类型,删除变量时将变量设置为nil值即可。没有声明的变量的值都是nil。

b = 1
print(b)
b = nil
print(b)

 

 2.数据类型boolean

布尔值只有两个值true和false,表示真和假。值得注意的是,lua中在作逻辑判断时(如分支结构或循环结构中的真假判断),false和nil都视为假,true和其他值都视为真。

if nil then
    print(' ')
else
    print(false)
end

if 1 then
    print(true)
end

 

 3.数据类型number

lua中的数字都是number类型,可以理解为就是c中的double类型。number有如下写法:

print(2)
print(2.2)
print(2e3)
print(2.34e5)
print(3.1e-2)

 

 4.数据类型string

字符串类型使用单引号或双引号引用都可以,也可以使用两个中括号[[]]来引用字符串。

print('我是字符串')
print("我是字符串")
print([[我是字符串]])

 

 字符串的拼接使用..,不能使用+号。

print('lua中的+号会自动将'..'字符串两边的字符串转化为数字')
print('2'..3)
print('2'+'3')
print(2+3)
print('2'+3)
print('2+3')
print('a'+3)

 

 使用#获得字符串的字节数。

print(#'aaa aa')
print(#'中文一个字符占用两个字节')

 

 5.数据类型table

表的构造和基本使用。当定义table时直接定义值,不定义键时,table和string一样,可以使用#获得table中值的个数(本质上是取得最大索引)。

tab1 = {}  --空表  {}构造表达式
tab2 = {key1='value1',key2='value2'}  --使用键值对的形式构造表(和map或dictionary等类似)
tab3 = {'value1','value2','value3'}  --直接给出表的值,默认索引为1,2,3,4...,和数组或list等的性质类似

print(tab1)  --直接打印表,打印的是表的地址
print(tab1.key)  --表中没有的键对应值为nil

print(tab2.key1)  --获取表中的某个索引对应值的方法一
print(tab2['key1'])  --获取表中某个索引对应值的方法二

print(tab3[2])  --没有给出索引的表默认索引是1,2,3,4...,不能使用方法一获取索引对应的值
print(tab3['1'])  --索引的类型是number,使用字符1进行取值不能取得值

--使用循环遍历表
for k,v in pairs(tab3) do
    print(k..':'..v)
end

 表的添加值

tab = {}
tab.key1 = 'value1'
tab.key2 = 'value2'  --索引是字符串使用.赋值,使用中括号赋值会报错
tab[10] = 100  --索引是number及其他类型使用中括号进行赋值,使用.赋值同样报错。nil不能作为索引。
tab[false] = 10

print(tab.key1)  --同样的,在获取值时也需要注意取值的方式
print(tab[key2])
print(tab[10])
print(tab[false])

 

 6.数据类型function

使用function定义函数,不需要返回值,参数也不需要定义类型。

function fact(n)
    if n==1 then
        return n
    else
        return n*(fact(n-1))
    end
end

print(fact(4))

 

 函数同样可以作为值进行传递。

function fact(n)
    if n==1 then
        return n
    else
        return n*(fact(n-1))
    end
end

print(fact(4))

fact2 = fact

print(fact2(5))

 

 函数也可以作为参数传递,甚至可以定义匿名函数(类似于委托)。

--定义测试函数
function testFun(tab,fun)
    for k,v in pairs(tab) do
        fun(k,v)
    end
end

--定义表和参数中的函数
tab = {key1='value1',key2='value2'}
function f(k,v)
    print(k..':'..v)
end

--调用测试函数
testFun(tab,f)

--定义测试函数
function testFun(tab,fun)
    for k,v in pairs(tab) do
        fun(k,v)
    end
end

--定义
tab = {key1='value1',key2='value2'}

--调用测试函数,以匿名函数的形式传递函数参数
testFun(tab,
    function (k,v)
        print(k..' '..v)
    end
)

 

 7.数据类型thread

thread(线程)本质上是协程。

8.数据类型userdata

userdata是用户自定义的数据类型,通常由C/C++创建。

五.语法

1.全局变量和局部变量

lua中的变量不需要声明类型,默认情况下,变量都是全局变量。可以使用local关键字声明局部变量,局部变量只在当前代码块中起作用。一般情况下, 访问局部变量的速度更快。

function test()
    a = 5
    local b = 6
end

test()

print(a)
print(b)

 2.变量的赋值

变量可以单个赋值,也可以多个同时赋值,赋值的数据类型也可以不同。也可以使用多变量赋值方便地交换变量的值。

--可以单变量赋值,也可以多变量赋值
a = 1
a,b = 1,2
a,b,c = 1,'a',3

print(a,b,c)

--交换两个变量的值
a,b = b,a
print(a,b,c)

--交换三个变量的值
a,b,c = c,a,b
print(a,b,c)

--变量的个数多于值,多出来的变量会被自动忽略;值的个数多于变量,多出来的值会被自动忽略
c,d,e = 1,2
f,g = 4,5,6
print(c,d,e,f,g)

 

 3.循环

while循环。

i = 1
while i<=5 do
    print(i)
    i=i+1
end

 

 for循环。之前也有用到过遍历表的for循环,类似于foreach的使用。

--i从1自增到5,每次自增1
for i=1,5 do
    print(i)
end

--i从1自增到10,每次自增2
for i=1,10,2 do
    print(i)
end

 

 repeat until循环。这个循环相当于do...while,但是和do...while有所不同。do...while是先执行一次do中的代码块,再判断while的逻辑语句,如果满足条件继续执行;repeat until是先执行一次until中的代码块,再判断until中的逻辑语句,如果不满足条件继续执行。

i = 1
repeat
    print(i)
    i=i+2
until i>10

 

 4.分支结构

--if语句
if 0 then
    print(0)
end

--if else语句
i = 1
if i<=0 then
    print(0)
else
    print(1)
end

--if else if ...语句
j = -2
if j <0 then
    print(-1)
elseif j==0 then
    print(0)
else
    print(1)
end

 注:循环结构和分支结构中的,while、for、if等语句后面跟上逻辑语句,这些逻辑语句可以使用括号括起来,也可以使用空格隔开,不使用括号,这里都没有使用括号。

 

 5.函数

在lua中,函数可以作为数据赋值,也可以作为参数传递。

local function max(num1,num2)
    if num1 < num2 then
        return num2
    else
        return num1
    end
end

print(max(3,-5))

--函数作为数据赋值
temp = max
print(temp(1,8))

myprint = function (param)
    print('this is my print function '..param)
end

myprint('ww')

--函数作为参数传递
function add(num1,num2,printFun)
    res = num1+num2
    printFun(res)
end

add(40,50,myprint)

--lua中的函数可以返回多个值
function temp()
    return 10,20,30,40
end

res1,res2,res3,res4 = temp()
print(res1,res2,res3,res4)

--lua中的函数参数个数可变,如print函数
--...代表可变的参数,这些参数会被封装为一个名称为arg的表,使用表的方式访问这些参数,在使用for循环遍历表时arg会多一个值表示参数个数
--可变参数前可以有其他参数,但是可变参数一定在参数列表的最后,封装时只有可变参数部分会被封装到表arg中
function test(a,...)
    for k,v in pairs(arg) do
        print(v)
    end
end

test(1,2,3)

 六.lua中的运算符

1.算数运算符+、-、*、/、%、^(幂运算符在c#语言中不存在,c#中使用函数求幂)

2.关系运算符==、~=(不等于、 <、>、<=、>=

3.逻辑运算符and、or、not

print(3>2 and 4>3)
print(false or false)
print(not true)

 

 七.lua中的常见API

1.string有关的API

常见转义字符:\n 换行、\\ 一个反斜杠、\" 双引号、\' 单引号

a = 'hello\n\world my name is \'Micheal\''
print(a)

 

string.upper 将字符串转换为全部大写 string.lower 将字符串转化为全部小写

str = 'Hello everybody'
str2 = string.upper(str)
print(str,str2)
str3 = string.lower(str)
print(str,str3)

 

 string.gsub 将指定的字符替换为其他指定的字符

str = 'Hello everybody'
--将str中的字符e替换为字符8
str2 = string.gsub(str,'e','8')
print(str,str2)
--将str中的字符e替换为字符123,最多替换1次
str3 = string.gsub(str,'e','123',1)
print(str,str3)

 

 string.find 查找指定字符的第一个索引

str = 'Hello everybody'

--从头查找'every'字符的索引(索引从1开始)
index = string.find(str,'every')
print(index)
--从第6个字符开始茶轴'o'字符的索引
index2 = string.find(str,'o',6)
print(index2)

 

 string.reverse 反转字符串

str = 'Hello everybody'

str2 = string.reverse(str)
print(str2)

 string.format 字符串格式化输出(未知number使用%d代替,未知string使用%s代替,其他代替格式可以自行查阅)

num1 = 5
num2 = 10
str = string.format('加法:%d+%d=%d',num1,num2,num1+num2)
print(str)
date,month,year = 30,1,2021
d = string.format('日期:%02d/%02d/%03d',date,month,year)
print(d)

 

 string.char 将指定的数字转化为对应字符 string.byte 将字符转化为数字(默认转化字符串第一个字符,也可以指定转化第几个字符)

print(string.char(97,98,99,100))
print(string.byte('abcd'))
print(string.byte('abcd',3))

 

 string.len 获得指定字符串的长度(和#的结果相同)

print(string.len('我有一个梦想'))

 

 string.rep 得到指定字符串重复指定次后的字符串

print(string.rep('我有一个梦想',5))

 

 string.gmatch 正则表达式匹配,返回一个迭代器

for word in string.gmatch('Hello lua user','%a+') do
    print(word)
end

 

 2.table有关的API

lua中array本质上是table。因为table是动态的,因此lua中的array也是动态的。值得注意的是,table中的默认索引是从1开始的,因此array中的默认下标也是从1开始的。下面是一个二维数组的例子:

array = {}
for i=1,4 do
    array[i] = {}
    for j=1,3 do
        array[i][j]=i*j
    end
end

for i=1,4 do
    for j=1,3 do
        print(array[i][j])
    end
end

 数组的遍历。

array = {'lua','c#','java'}

--迭代函数一pairs:遍历table中的key和value
for k,v in pairs(array) do
    print(k,v)
end

--迭代函数二ipairs:从索引1开始,递增遍历,遇到nil就停止
for k,v in ipairs(array) do
    print(k,v)
end

array[2] = nil

for k,v in ipairs(array) do
    print(k,v)
end

表可以当作引用类型使用,表赋值给其他表赋值的是地址引用。

table = {'a','b','c'}
newtable = table

print(table[2])
print(newtable[2])

newtable[2] = 'd'

print(table[2])
print(newtable[2])

 

 表的数据拼接。

table1 = {'a','b','c'}

--直接拼接
str = table.concat(table1)
print(str)

--使用,间隔开拼接的数据
str = table.concat(table1,',')
print(str)

--使用,间隔开数据,指定拼接的索引
str = table.concat(table1,',',2,3)
print(str)

 

 表的数据插入和移除。

table1 = {'lua','c#','java','php'}
print(table.concat(table1,','))

--直接插入到末尾
table.insert(table1,'javascript')
print(table.concat(table1,','))

--指定插入到哪一位,后面的数据依次向后移动一位
table.insert(table1,2,'c++')
print(table.concat(table1,','))

--移除表一位数据
table.remove(table1)
print(table.concat(table1,','))

--移除指定位置的数据
table.remove(table1,3)
print(table.concat(table1,','))

 

表的排序。

table1 = {'lua','c#','java','php','c++','javascript'}
print(table.concat(table1,','))

--排序,按照ASCII码表顺序排列
table.sort(table1)
print(table.concat(table1,','))

 

八.面向对象编程及其他特性

1.模块与包

lua中的模块相当于c#的命名空间或java的包。

--定义模块,这个模块保存为文件Module.luavar = 'movin'

func1 = function()
    print('这是一个函数')
end

return module
--引入模块
require 'Module'

--使用模块中的变量
print(var)
func1()

 lua中的包是指使用C包,lua和C很容易结合,lua就是使用C写成的。

2.元表

lua提供了元表允许我们改变table的行为,每种行为关联了对应的元方法。

mytable = {'lua','java','c#','c++'}  --普通表
mymetatable = {}  --元表  拓展了普通表的行为
mytable = setmetatable(mytable,mymetatable)  --关联元表和普通表

1)__index元方法,这是metatable中最常用的键。当通过键访问table的时候,如果这个键没有值,那么lua就会寻找该table的metatable中的__index键。如果__index包含一个表格,lua会在表中查找相应的键,如果__index包含一个函数,lua就会调用那个函数。

mytable = {'lua','java','c#','c++'}

mymetatable = {
__index=function (tab,key)
    return 'javascript'
end
}

mytable = setmetatable(mytable,mymetatable)

print(mytable)
print(mymetatable)  --表和元表关联,但是两个表并不相同,元表是表的拓展
print(mytable[1])  --存在的键值
print(mymetatable[1])
print(mytable[10])  --不存在的键值,调用元表中__index键对应的函数
print(mymetatable[10])

 

 2)__newindex元方法,当对表的新索引进行赋值时会起作用,并且会取消赋值操作。

mytable = {'lua','java','c#','c++'}

mymetatable = {
__newindex = function (tab,key,value)
    print(string.format('我们要修改的key为%s,value为%s',key,value))
end
}

mytable = setmetatable(mytable,mymetatable)

--下面这些操作都不会触发__newindex对应的方法
mytable[1] = 'javascript'
print(table.concat(mytable,'  '))
table.insert(mytable,'lua')
print(table.concat(mytable,'  '))
table.insert(mytable,3,'c')
print(table.concat(mytable,'  '))
table.remove(mytable,1)
print(table.concat(mytable,'  '))
table.remove(mytable)
print(table.concat(mytable,'  '))

mytable[5] = 'lua'  --对新索引进行赋值时会调用__newtable对应的方法,而且不会进行赋值操作
print(table.concat(mytable,'  '))

mytable = {'lua','java','c#','c++'}

mymetatable = {
__newindex = function (tab,key,value)
    rawset(tab,key,value)  --由于调用了__newindex对应的函数后不会进行赋值,若想赋值,可以使用rawset函数(如果直接赋值会产生死循环)
    --mytable[5] = 'lua'  这里采用这种方式赋值会产生死循环
end
}

mytable = setmetatable(mytable,mymetatable)


mytable[5] = 'lua'  
print(table.concat(mytable,'  '))

mytable = {'lua','java','c#','c++'}

newtable = {}
mymetatable = {
__newindex = newtable  --__newindex对应的是一个表
}

mytable = setmetatable(mytable,mymetatable)


mytable[5] = 'javascript'   --设置不存在的索引的值时会设置到__newindex对应的表中

print(mytable[5])
print(newtable[5])  --在__newindex对应的表中,索引值还是5

 

 3)操作符元方法。

mytable = {'lua','java','c#','c++'}

mymetatable = {
__add = function(tab,newtab)  --__add索引的值定义了表的加法操作,这里将第二个相加的表的所有键值对拼接到第一个表的最后
    for k,v in pairs(newtab) do
        table.insert(tab,v)
    end

    return tab
end
}

mytable = setmetatable(mytable,mymetatable)

newtable = {'python','php'}

print(table.concat(mytable+newtable,' '))

 

除了__add外,对应其他运算的元方法有:__sub(减法)__mul(乘法)__div(除法)__mod(求模)__pow(乘方)__unm(取负)__concat(连接)__eq(是否相等)__lt(小于)__le(小于等于)

 4)__call元方法

将表当作函数调用时调用__call对应的函数。

mytable = {'lua','java','c#','c++'}

mymetatable = {
__call = function(tab,arg)
    print(arg)
end
}

mytable = setmetatable(mytable,mymetatable)

mytable('我是大帅比')

 

 5)__tostring元方法

将表当作字符串使用(如print(table))时定义表对应的字符串。

mytable = {'lua','java','c#','c++'}

mymetatable = {
__tostring = function(tab)
    str = ''
    for k,v in pairs(tab) do
        str = str..k..' '..v..','
    end
    return str
end
}

mytable = setmetatable(mytable,mymetatable)

print(mytable)

 

 3.协程

定义和启动协程。

方式一:

--定义协同函数
co = coroutine.create(
    function (a,b)  --必须是匿名函数
        print(a+b)
    end
)

--运行协同函数
coroutine.resume(co,20,40)

 

 方式二:

--定义协同函数
co = coroutine.wrap(
    function (a,b)  --必须是匿名函数
        print(a+b)
    end
)

--运行协同函数
co(20,40)

 

 协程的挂起和继续运行。

--定义协同函数
co = coroutine.create(
    function (a,b)
        print(a+b)
        coroutine.yield()  --挂起协同函数
        print(a-b)
    end
)

--运行协同函数
coroutine.resume(co,20,40)

--这句代码的位置在运行协同函数的代码后,在重启协同函数的代码前,因此挂起协同函数后会运行这段代码
print("I'm here")

--继续运行协同函数
coroutine.resume(co)

 

 协同程序的返回值。

co = coroutine.create(
    function (a,b)
        return a+b,a-b
    end
)

--协同函数默认有一个boolean类型的返回值表示是否启动成功,自己定义的其他返回值作为第二个、第三个......等返回值
res1,res2,res3 = coroutine.resume(co,20,40)

print(res1,res2,res3)

co = coroutine.create(
    function (a,b)
        print('........................')
        coroutine.yield(a+b,a-b)
        print('........................')
        return a*b,a/b
    end
)

--当协同函数中间被暂停时,可以分阶段返回不同的返回值,使当前阶段暂停的yield函数括号中的参数作为当前阶段的返回值
res1,res2,res3 = coroutine.resume(co,20,40)

print(res1,res2,res3)

--最后一个运行阶段的返回值为return返回的内容
res4,res5,res6 = coroutine.resume(co)

print(res4,res5,res6)

 

 协程的状态(3种)。

co = coroutine.create(
    function (a,b)
        print(coroutine.status(co))  --running运行
        print('........................')
        coroutine.yield()
        print('........................')
        print(coroutine.status(co))  --running运行
    end
)

print(coroutine.status(co))  --suspended暂停
coroutine.resume(co,20,40)
print(coroutine.status(co))  --suspended暂停
coroutine.resume(co)
print(coroutine.status(co))  --dead死亡

 

 获取正在运行的协程。

co = coroutine.create(
    function (a,b)
        print(coroutine.running())  --获取正在运行的协程
    end
)

coroutine.resume(co,20,40)

 

 4.文件I/O

文件的简单读取和写入。

f = io.open('iotest.txt','r')  --打开文件
io.input(f)  --创建输入流
print(io.read())  --read函数读取一行
print(io.read())
print(io.read())
print(io.read())
io.close()  --关闭流

 open函数的第一个参数是文件的相对地址和名称,第二个参数是可选参数,对应打开方式。打开方式有:r(只读,文件必须存在)、w(只写,写入的文件原有数据会被清空,文件不存在会自动创建文件)、a(以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留)、r+(以可读写方式打开文件,该文件必须存在)、w+(打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件)、a+(与a类似,但此文件可读可写)、b(二进制模式,如果文件是二进制文件,可以加上b)

f = io.open('iotest.txt','a')  --打开文件,a为追加的形式只写,w为清空后只写
io.output(f)  --创建输出流
print(io.write(''))  --write函数写入内容,返回值代表是否写入成功
io.close()  --关闭流

 

 read函数的参数。参数有:"*n"(读取一个数字并返回)、"*a"(从当前位置读取整个文件)、"*l"(默认参数,读取下一行)、number(返回指定个数的字符串)。

f = io.open('iotest.txt','r')
io.input(f)
print(io.read("*l"))  --读取一行
print(io.read("*n"))  --读取一个数字
print(io.read(10))  --读取10个字符
print(io.read("*a"))  --读取剩下的所有内容
io.close()

 

 完全模式。完全模式下,可以同时处理多个文件。

f = io.open('iotest.txt','r')

--使用f:read代替io.read
print(f:read())

file.close()

 

 5.lua实现面向对象编程

lua中并没有直接实现面向对象编程,但是可以使用表间接实现面向对象。

--定义一个人的对象
person = {name='movin',age=18}

person.eat = function ()
    print(person.name..'在吃饭')
end

person.eat()

 

 优化面向对象的实现。

--定义一个人的对象
person = {name='movin',age=18}

--将对象自身作为变量传递,否则这里对象的名称不能修改
person.eat = function (self)
    print(self.name..'在吃饭')
end

person.eat(person)

--定义一个人的对象
person = {name='movin',age=18}

--使用冒号定义和调用,不用传递self参数,其中self就指代调用者
function person:eat()
    print(self.name..'在吃饭')
end

person:eat()

 

 根据模板创建新对象。

Person = {name='movin',age=18}

function Person:eat()
    print(self.name..'在吃饭')
end

--创建新的对象的new方法
function Person:new()
    local t = {}
    --使用元表的__index元方法指向Person对象
    setmetatable(t,{__index=self})
    return t
end

person1 = Person:new()
person2 = Person:new()

print(person1.name)
person2:eat()

--修改对象中的属性值相当于在对象中设置了新值,再查找这个索引对应的值时就不会在Person中查找,相当于实现了修改属性和重写方法的效果
person1.name = 'ww'

print(person1.name)

 

 __index元方法相当于实现了继承,元表相当于父表。当子表中没有某个属性或方法时,从父表中查找;当子表中重新赋值了某些属性或重写了某些方法时,就直接从子表中调用。

 

posted @ 2021-01-30 12:40  movin2333  阅读(376)  评论(0编辑  收藏  举报