Lua 入门(数据类型与表达式)

前言:本文介绍的主要内容就是lua的基本数据类型

一、Lua语言独立解释器

Lua是用C语言写的,只需要从官网下载解释器源码,然后自己运行便可以得到解释器。

Lua是动态弱类型语言;

Lua的执行有两种方式,一种是保存在文件中然后使用lua命令去执行脚本文件,另一种则是在交互模式下运行独立解释器。

第一种,使用lua命令执行脚本,lua命令的完整参数形式如下,

lua [options] [scripts [args]]

当我们执行一个脚本的时候,最简单直接的就是lua "hello.lua",也就是在当前目录下寻找hello.lua文件然后执行。

使用 lua -i "hello.lua",-i参数会在执行完hello.lua后进入到交互模式,这时我们可以调用脚本中的函数方便调试,调试完成后,可以使用Ctrl+Z或os.exit()命令退出交互模式。

第二种,在交互模式下加载文件,使用dofile("hello.lua"),其实就是加载了库,之后可以在交互模式中调用hello.lua中的函数。注意:我们需要指定文件的名字和路径,并且要使用"\\"代替单斜线,用于告诉Lua运行环境它后面是特殊符号;

二、基本词法规范和语法基础

1.Lua是对大小写敏感的

2.Lua单行注释,使用两个连续的连字符(--)作为开始;多行注释使用(--[[)作为开始,使用(--]])作为结束。

3.需要注意,在lua语言中,连续语句中的分隔符不是必需的,而且表达式之间的换行也不起任何作用,这一点和C等程序语言不一样。

4.代码块(block),一个代码块是一个控制结构的主体或者一个函数主体等。

Lua中的局部变量,全局变量。lua中默认情况下是全局变量,所有的局部变量使用前需要用关键字local声明,同时,局部变量的生肖范围仅限于它的代码块。

Lua中的赋值语句:Lua可以对多个变量同时赋值,遇到赋值语句,lua会首先计算右边所有的值然后再执行赋值操作;所以在交换两个变量的时候有如下特殊用法

x, y = 3, 5
x, y = y, x -- x = 5, y = 3

5.控制结构

--if控制结构,记住控制结构必须以end结束
--第一种,if then
if a<0 then a=0 end
--第二种,if then else
if a<b then return a else return b end
--第三种,if then elseif then else
if a<b then return -1
elseif a==b then return 0
else return 1 end

--while循环
local i=1
while a[i] do 
    printf(a[i])
    i++
end

--for语句,数值型循环与泛型for循环,如下代表从exp1变化到exp2之间执行循环,且每次以步长exp3增加
for var=exp1,exp2,exp3 do
    something
end

三、Lua中的数据类型

Lua语言是一种动态类型语言,每个变量都没有类型定义,根据自身的值来决定自身的类型信息

基本类型一共有八种:nil(空),boolean(布尔),number(数值),string(字符串),userdata(用户数据),function(函数),thread(线程),table(表)。

使用type函数可以获取一个值所对应的类型名称,type的返回值为字符串类型。

第一类值:Lua中所有的值都是第一类值,即所有的值都可以存储在变量中,通过参数传递给函数或作为结果返回;

下面将对各个数据类型进行逐一介绍。

1.nil

nil是一种类型,它的主要功能是用于区别其它任何值,比如可以在异常情况下返回一个nil值来区别于其它正常的返回值;Lua语言中,nil表示无效值,一个全局变量第一次被赋值之前就是nil类型,它的值就是nil,如果将nil赋值给一个有效全局变量,那么就相当于将其删除。关于Lua中全局变量的知识点,请参考Lua全局变量(_G),该篇文章介绍了lua中全局变量的相关机制。

nil在逻辑操作符中只与其自身相等

2.userdata

userdata类型允许把任意的C语言数据保存在Lua语言变量中,在lua语言中,用户数据类型除了赋值和相等性测试外,没有其它预定义的操作,用户数据被用来表示由应用或C语言编写的库所创建的新类型。

3.Boolean

Boolean类型具有两个值,true和false。Boolean值主要用于条件测试,但是其它任何值也都可以表示一个条件,在lua中,除了boolean值false和nil外,其它所有值都是true,甚至于值类型0和空字符串都是true。

Lua所支持的常见逻辑运算符有:and,or和not,and以及or都遵循短路求值原则,即只有在必要时才会对第二个操作数进行求值。

两个不同类型的值一定是不相等的,所以及时nil也可以被视为false,但是nil==false是不成立的

4.number

lua语言的数值类型分为interger的64位整形和float的双精度浮点类型(都是8字节的),使用math.type函数可以区分整型和浮点型。

算术运算:运算规则和C语言类似,这里主要叙述几个需要注意的地方。首先是浮点型数据和整型数据一起计算,如果两个操作数都是整型,则结果也是整型,否则就是浮点型。而,两个整数相除的结果不一定是整数,所以除法运算不遵循上述规则,在除法运算中,操作的永远是浮点数而且结果是浮点型。

另一种除法:floor除法,它会对得到的商向负无穷取整,从而保证结果是整数,所以它遵循规则:如果操作数都是整数,则结果是整数,否则结果是浮点数。

取模运算:对于整型操作数,它的结果永远与第二个操作数的符号保持一致,对于任意特定的正常量K,即使x是负数,表达式x%K的结果也永远在[0,K-1]之间;对于浮点操作数来说,x-x%0.01恰好是x保留两位小数的结果,x-x%0.001恰好是x保留三位小数的结果。

关系运算:<,>,<=,>=,==,~=。这些运算符不仅仅用于数值的比较,它可以用于所有值得比较,当两个值得类型不同时,它们就是不相等的。在比较数值时,需要忽略数值子类型,及不考虑它们是整型还是浮点型。

数学库:Lua提供了标准数学库math,标准数学库math由标准的数学函数和常量组成,包括三角函数、指数函数、取整函数、最大和最小函数、用于生成伪随机数和伪随机函数以及常量pi和huge等等。所有的三角函数都以弧度为单位,并使用deg和rad进行角度和弧度的转换。

--随机数发生器的三种调用方式
--1.不带参数的调用,返回一个在[0,1)范围内的伪随机数
math.random() 
--2.带一个整型n的参数调用,返回一个在[1,n]内的伪随机整数
math.random(n)
--3.使用两个整型l和m的参数调用,返回在[l,m]内的伪随机整数
math.random(l,m)
--4.使用种子得到伪随机数
math.randomseed(os.time()) //使用当前系统的时间作为种子初始化随机数发生器

取整函数,floor、ceil和modf,floor向负无穷取整,ceil向正无穷取整,modf向零去整,其中modf除了会返回取整后的值以外,还会返回小数部分作为第二个结果,在lua中,是支持一个函数有多个返回值的。

5.string

lua中的字符串是一串字节的序列,字符使用8个比特位也就是1个字节来存储,可以使用长度操作符(#)获取字符串的长度,也就是一个字符串拥有的字节数(包括空字符,但没有结束字符)。lua中的字符串是不可变值,如果要修改字符串,就只能通过创建一个新的字符串。

lua中使用连接操作符..来进行字符串的连接,如果操作数中存在数值,则会先把数值转换为字符串类型,连接后的字符串则是一个新的字符串常量。注意:..操作符在连接数值和字符串时,需要在操作数和操作符之间添加空格,否则lua可能会将操作符视为小数点,从而报出"malformed number near"的错误(这个错误在table中也会有);

字符串标准库:

--返回字符串s的长度
string.length(s)
--返回将字符串s重复n次的结果
string.rep(s,n)
string.rep("a",2^20)
--字符串的反转
string.reverse(s)
--将s中所有大小字母转换为小写字母
string.lower(s)
--将s中所有大小字母转换为小写字母
string.upper(s)
--从字符串中提取第i到第j个字符,该函数支持负数索引(-1为倒数第一个字符)
string.sub(s,i,j)
--转换字符及其内部数值表示
string.char(97)
--string.byte(s,i,j)返回索引i到j之间所有字符的数字表示,支持负数索引
string.byte("abc",1,3)
--用于进行字符串格式化和将数值输出为字符串的工具
string.format()

string.format()函数比较繁琐,具体细节即用即查即可。

还有一个重要的库函数就是用于模式匹配的函数,可以帮助我们在指定字符串中进行模式搜索,当我们需要在字符串类型的值中提取数据的时候,它是相当重要的,具体使用可以参考Lua字符串模式匹配

split,字符串分割,lua中并没有提供分割函数,需要自行实现

function split(str, reps)
    local resultStrList = {}
    string.gsub(str, '[^' .. reps .. ']+', function(w)
        table.insert(resultStrList,w)
    end)
    return resultStrList
end

string.dump

参考:简书:Lua string.dump() 

6.Table

表是lua中最主要的也是唯一的数据结构;table既不是值,也不是变量,而是对象,它是一种动态分配的对象,程序只能操作指向表的引用,表本身和保存表的变量(也就是引用)之间没有固定的关系,Lua不会暗中产生table的副本或创建新的table,所以table永远是匿名的,一个持有table引用的变量和table自身没有固定的关联性。对于一个表而言,当程序中不再有指向它的引用时,垃圾收集器会最终删除这个表并回收内存。Lua也是通过table来表示模块(module)、包(package)和对象(object)的;

表索引:同一个表中存储的值可以具有不同的类型索引,并可以按需增长以容纳新的元素。当我们把表当做一个结构体来使用的时候,可以把索引当做成员名称来使用,a.name和a["name"]等价。

表构造器:用来创建和初始化表的表达式,最简单的构造器就是{},表构造器也可以用来初始化列表,lua还提供了一种初始化记录式表(record-table)的特殊语法,a={x=1,y=2},a.x=1,a.y=2;  在同一个构造器中可以混用记录式和列表式,如下:

attribute={color="red",
        weight=5,
        {width=2,height=4},
        {quality=best}
}

通用构造器:无论是列表式构造器还是记录式构造器都有各自的局限,通用构造器就是要通过方括号括起来的表达式来显示指定每一个元素的索引。例如列表式构造器a={o,p,q},转换为通用表达式就是a={[1]=o,[2]=p,[3]=q};例如记录式构造器,{x=0,y=1},就要写成{["x"]=0,["y"]=1}。

表的分类:数组、列表、序列;如果想表示常见的数组或列表,那么只需使用整型作为索引的表即可,如下

a={}
for i=1,10 do
    a[i]=io.read()
end

序列则被定义为所有元素都不为nil的数组,lua中有很多关于带有空洞(存在值为nil的元素)的列表的讨论, 但是程序中大多数列表其实都是序列,所以这里不作讨论。

遍历表:

--第一种遍历方式,使用pairs迭代器遍历表中的键值对
--受限于表在lua底层的实现机制,这种遍历过程元素出现的先后顺序是随机的,即使相同的程序在每次运行
--也会出现不同的顺序
t={m,l,x=2,y="ok"}
for k,v in pairs(t) do
    print(k,v)
end

--对于列表而言,就可以使用ipairs迭代器,这里lua会确保遍历按顺序执行
t={m,l,x=2,y="ok"}
for k,v in ipairs(t) do
    print(k,v)
end

--还有一种遍历序列的方法是使用数值型for循环:
t={m,l,2,"ok"}
for k=1,#t do
    print(k,t[k])
end

注意:pairs, 迭代 table,可以遍历表中所有的 key 可以返回 nil;ipairs: 迭代数组,不能返回 nil,如果遇到 nil 则退出

表标准库:

--table.insert向序列的指定位置插入一个元素,其它元素依次后移,如果未指定位置,则在最后插入
t={2,4,5}
table.insert(t,1,1)
for k=1,#t do
	print(k,t[k])
end 
--t={1,2,4,5}

--table.remove从序列的指定位置删除一个元素,其它元素依次前移,如果未指定位置,则删除最后一个
t={2,4,5,8}
table.remove(t,3)
for k=1,#t do
	print(k,t[k])
end 
--t={2,4,8}

--table.move(t,s,e,p)将表t从索引f到e的元素移动到位置p上
--需要明白,这里的移动只是对元素的拷贝,如果把第一个值移动到第二个位置,那么第一个位置的值是不变的。
--它仅仅是被拷贝了,第二个值会被改变。
--如下代码用来在列表a的开头插入一个元素
table.move(t,1,#t,2)
t[1]=newElement
--如下代码用来删除列表a的开头第一个元素
table.move(t,2,#t,1)
t[#t]=nil

--table.move还支持使用一个表作为可选的参数,当带有可选的表作为参数时,表示函数将第一个表的元素移
动到第二个表中,table.move(a,1,#a,#b+1,b)表示将列表a所有元素复制到列表b的末尾。

7.function

一个lua程序既可以通过Lua语言编写的函数,也可以调用C语言编写的函数。Lua语言标准库中的所有函数都是使用C语言编写的。调用函数时使用的参数个数可以与定义函数时使用的参数个数不一致,Lua通过抛弃多余参数和将不足的参数设为nil的方式来调整参数的个数。

多返回值:lua语言编写的函数同样可以返回多个结果,只需在return关键字后列出所有要返回的值即可。lua语言根据函数的被调用情况调整返回值的数量,在多重赋值中,如果一个函数没有返回值或者返回值个数不够多,那么lua语言会用nil来补充缺失的值。当一个函数调用是另一个函数调用的最后一个或唯一一个实参时,第一个函数的所有返回值都会被作为实参传给第二个函数。

可变长参数函数:lua可以支持数量可变的参数,语法上只需要使用表达式{...}来表示一个可变长参数列表,当然一个函数也可以拥有任意数量的固定参数,但是固定参数必须放在可变长参数之前。当通过表达式传入了一个可变长参数列表以后,就需要遍历该列表以获取相应的数据,第一种,使用table.pack(),该函数可以将所有的可变长参数打包成一个表,而且这个表还保存了一个额外字段"n",用于存储表中元素个数。第二种遍历方式是使用select,select函数拥有一个固定参数selector和一个数量可变的参数;具体使用方法参考代码,如下所示

--使用table.pack()来遍历,下面这个函数用来判断可变长参数列表中是否有nil值
function nonils(...)
    local arg=table.pack(...)
    for i=1,arg.n do
        if arg[i]==nil then return false end
    end
    return true
end

--select的固定参数如果是数值n,则返回第n个参数后的所有参数
select(n,...)
select(2,"a","b","c")    --b    c
--如果select的固定参数是"#",则返回可变长参数列表中的参数总数
select("#","a","b","c)    --3

table.unpack与table.pack,pack是把参数列表转换成lua语言中真实的列表,也就是table,而unpack则是把lua语言中的一个表转换成一组返回值,从而可以作为另一个函数的参数使用。

四.表达式

逻辑操作符

and和or都使用“短路求值”,短路求值可以确保像type(v)=="table" and v.tag=="h1"这样的表达式不会导致运行错误(虽然可以使用if-else,但是使用and更加简洁);

  • 对于and,当第一个操作数为假时,返回第一个操作数,当第一个操作数为真时,返回第二个操作数;
  • 对于or,当第一个操作数为真时,返回第一个操作数,当第一个操作数为假时,返回第二个操作数;

一种习惯用法:x=x or v,它返回第一个不为假的操作数,如果都为假,则返回最后一个数;

另一种:x=a and b or c,等同与三元表达式x=a>b?c,前提是要保证b不为假;它其实就相当于if x then b else c;也可以直接使用x = a and b; b也可以是一个表达式等;

not操作符的返回值永远是true或false,也即是boolean型数据;

五.总结

1.还要一个数据类型thread,后面会单独介绍,而且关于前面几种数据类型,在之后的学习过程中也会慢慢补充。

2.本文主要内容参考资料《Lua程序设计》一书(侵删),自己只是稍微提炼总结了一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值