Lua从入门到放弃--语句

时间:2022-07-03 13:08:27

lua的语句包括赋值、控制结构、函数调用、变量声明。


lua的一个执行单元被称为语句组。一个语句组就是一串语句段,它们会被循序执行。每个语句段可以以一个分号结束。在lua中并不强行要求一定以一个分号来结束一个语句,也可以用回车来作为一条语句的结束标识。

lua把一个语句组当做一个拥有不定参数的匿名函数。语句组内可以定义局部变量,接收参数,并且返回值。语句组可以被保存在一个文件中,也可以保存在宿主程序的一个字符串中。当一个语句组被执行时,首先它会被编译成虚拟机中的指令序列,然后被虚拟机解释运行这些指令。语句组也可以被编译成二进制形式。用源代码提供的程序和被编译过的二进制形式的程序是可以相互替换的,lua会自动识别文件类型并做正确的处理。(具体编译和转换细节将会在后续的章节中讲解)


赋值:lua允许多重赋值,例如:

local x, y = 10, 20;   此时x = 10, y = 20

local x, y = 10;        此时x = 10, y = nil

local x, y = 10, 20, 30;    此时x = 10, y = 20, 30这个值被舍弃掉。

多重赋值的原则,从左到右依次赋值。右值会对应左边的个数,多余的会被舍弃,不足的用nil来填充。如果表达式列表以一个函数调用为结束,这个函数所返回的所有值都会在对齐操作之前被置入右值序列中。例如

function getFrame()

local x = 0;

local y = 0;

local w = 320;

local h = 568;

return x, y, w, h;

end

local x, y, w, h = getFrame()     -- 此处的 x = 0, y = 0, w = 320, h = 568


赋值段首先会做运算所有的表达式,然后仅仅做赋值操作。例如

local a = {}

local i = 3

i , a[i] = i + 1, 20

这个例子会把a[3]设置为20, 而不会影响到a[4]。因为a[i]中的i在被赋值为4之前(i = 3时)就被拿出来了。

使用下面的表达式可以交换两个值:

x, y = y, x


对于全局变量以及table中的域的赋值操作的含义可以通过metatable来改变。对变量下标指向的赋值,即t[i] = val等价于settable_event(t, i, val)。

对于全局变量的赋值x = val等价于_env.x = val, 这个又等价于settable_event(_env, x, val)。(settable_event函数在之前的章节中也有提到过,本章只是拿来举例,不做具体的说明)


控制结构:在lua中的控制结构包含if、while、repeat、for。

while exp do block end

repeat block until exp

if exp then block {elseif exp then block} else block end

控制结构中的条件表达式可以返回任何值。false和nil两者都被认为是假条件,除此之外的其他值被认为是真(数字0和空字符串也被认为是真)。

在repeat-until循环中,内部语句块的结束点不是在until这个关键字处,它还包括了其他的条件表达式。因此,条件表达式中可以使用循环内部语句块中的定义的局部变量。

return被用于从函数或者是语句组中返回值。函数和语句组可以返回不止一个值,可参考上面getFrame的例子。

break被用来结束while、repeat、for虚幻,它将忽略循环中下面的语句段的运行,break跳出最内层的循环。

可以忽略此括号内的内容(return 和 break 只能被卸载一个语句块的最后一句。如果你真的需要从语句块中间return 或是 break,你可以使用显式的声明一个内部语句块。一般写为do return end或是do break end, 可以这样写是因为现在return 或 break 都成了一个语句块的最后一句了。)编码过程中并不推荐这种写法。


for循环有三种形式:

for v = var, limit, step do block end 

var为起始值,limit为终止值, step为步长。所有三个控制表达式都只被运行一次,表达式的计算在循环开始之前。这些表达式的结果必须为数字。可以使用break用来退出for循环。循环变量v是一个循环内部的局部变量;当循环结束时,v则被释放掉了,如果想在循环外部继续使用这个值,则需要将这个值赋给一个更高层次的局部变量或全局变量。

迭代器:for namelist in explist do block end   通常为一下两种用法

for k, v in pairs(explist) do block end  

for k, v in ipairs(explist) do block end

explist为table类型。k,v为explist表中的key和value。explist只会被计算一次。它返回三个值,一个迭代器函数,一个状态,一个迭代器的初始值。和上面一样,迭代器也可以使用break来跳出for循环。如果想保留v,同样也需要一个高层次的局部变量或全局变量来引用。

pairs和ipairs的区别:

pairs可以遍历表中所有的key,并且除了迭代器本身以及遍历表本身还可以返回nil。但是ipairs则不能返回nil,只能发挥数字0,如果遇到nil则退出。它只能遍历到表中出现的第一个不是整数的key。

举例说明:

local tab = { [3] = "test2", [6] = "test3", [4] = "test1" }

for k, v in ipairs(tab) do

print(k, v)

end

无任何输出结果,原因是当key = 1时,value就是nil,此时就跳出循环不输出任何值。


同样的代码,我们用pairs来输出

local tab = { [3] = "test2", [6] = "test3", [4] = "test1" }

for k, v in pairs(tab) do

print(k, v)

end

结果为

3 test2
6 test3
4 test1


我们将上面ipairs的例子改一下,将tab表中[3]改为[1]

local tab = { [1] = "test1", [6] = "test2", [4] = "test3" }

for k, v in ipairs(tab) do

print(k, v)

end

现在的输出结果显而易见就是 1     test1


表达式:lua基本的表达式包含算术操作符、关系操作符、逻辑操作符、字符串连接、table构造器、取长度操作符、函数等。

算术操作符和关系操作符在之前的章节中有做介绍,本文不做详细的讲解。


逻辑操作符包含and、or、not。与条件操作语句一样,所有的逻辑操作符都将false和nil视为假,其他结果均为真。and和or的运算结果不是true就是false,而是和它的两个操作相关。

a   and   b    -- 如果a为false,则返回a,否则返回b

a    or    b    --如果a为true,则返回a,否则返回b

例如:

print( 4 and 5 )            -->  5

print( nil and 13 )        -->  nil

print( false and 13 )    -->  false

print( 4 or 5 )              -->  4

print( false or 5 )        -->  5

一个很实用的技巧:如果x为false或者nil则给x赋初始值v

x = x or v

等价于

if  not  x  then

       x = v

end

and的优先级比or高

在C语言中的三目运算符 a ? b : c

在lua中可以这样实现   (a and  b) or c

not的结果只返回false或者是true

print( not nil )        -->  true

print( not false )    -->  true

print( not 0 )         -->  false

print( not not nil )  -->  false


字符串连接:在lua中,两个字符串连接可以使用(..)  两个点,称为字符串连接操作符。print( "hello" .. "world" )     -->  helloworld

print( 4 .. 5 )将两个number类型的数字用..进行连接,在lua执行时会将其自动转换为字符串,输出结果为45,但是注意,这个时候的45已经不是number类型而是string类型。


table构造器:构造器用于构建和初始化table的表达式。这是lua特有的表达式,也是lua种最有用、最通用的机制之一。最简单的构造器为空构造器{},用于创建一张空表。我们通过构造器还可以初始化一些数据,数组初始化方式,如

days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }

我们来打印一下这个表

for i = 1, #days do       

print(days[i]);

end

输出结果为:

Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

从输出结果可以看出,days在构造后会将自动初始化,其中days[1]被初始化为"Sunday",以此类推。

lua中还提供了另一种特殊的语法用于初始化一张表,记录初始化方式。如 a = { x = 10, y = 20 },其等价于: a = {}; a.x = 10; a.y = 20;

除了上面两种构造方式之外,lua还提供了一种更为通用的方式,如:

opnames = { ["+"] = "add", ["-"] = "sub", ["*"]  = "mul", ["/"] = "div" }

print(opnames["+"])    -->  add

对于table的构造器,还有两个需要了解的语法规则,如:

a = { [1] = "red", [2] = "green", [3] = "blue", }

这里需要注意最后一个元素的后面仍然可以保留都好(,), 这一点类似于C语言中的枚举。

a = { x = 10, y = 45; "one", "two", "three" }

可以看到上面的声明中同事存在都好(,)和分号(;)两种元素分隔符,这种写法在lua中也是允许的。我们通常会将分号(;)用于分隔不同初始化类型的元素,如上例中分号之前的初始化为记录初始化,而后面的则是数组初始化方式。


取长度操作符:上面我们有用到#,在lua中,#为取长度操作符。字符串的长度是它的字节数。table的长度被定义成一个整数下标n。它满足t[n]不是nil而t[n+1]为nil;此外,如果t[1]为nil,n就可能是零。对于常规的数组,里面从1到n放着一些非空的值的时候,它的长度就精确的为n,即最后一个值的下标。如果数组中有一个“空洞”(就是说,nil值被夹在非空值之间),那么#t可能是指向任何一个是nil值的前一个位置的下标(就是说,任何一个nil值都有可能被当成数组的结束)。


优先级:lua中操作符的优先级从低到高的排序:

     or
     and
     <     >     <=    >=    ~=    ==
     ..
     +     -
     *     /     %
     not   #     - (unary)
     ^
通常你可以使用括号来改变运算的次序。连接操作符(..)和幂操作符(^)是从右至左。其他所有的操作都是从左到右。


函数:在lua中,函数的写法有以下两种:


func = function(params) body end

function func(params) body end

这两种写法是可以相互转换的,后者只是通过语法糖的简化了写法而已。但是函数构成并没有变,仍然是函数名、参数和返回值。在lua中可以使用(...)来描述一组参数,但是必须放到参数列表的末尾。当一个函数被调用,如果函数没有被定义为接收不定长参数,即在形参列表的末尾注明三个点(...),那么实参列表就会被调整到形参列表的长度,变长参数函数不会调整实参列表。举例说明:

function f( a, b ) end

f( 3 )         -->    a = 3, b = nil

f( 3, 4 )     -->     a = 3, b = 4

f( 3, 4, 5 ) -->     a = 3, b = 4

和多重赋值一样,多于的实参会被舍弃掉,少于的实参会使用nil填充

在函数中,可以使用return来返回。如果执行到函数末尾依旧没有遇到任何return语句,函数就不会返回任何结果,也可以认为默认帮我们返回一个nil值。

冒号语法可以用来定义方法,也就是说函数可以有一个隐式的形参self。因此,如下写法:

local meta = {}

function meta:log(params) body end

相当于:

meta.log(self, params) body end

对于函数具体使用和详解,将会在后面的章节中介绍,本文只是讲一些简单的使用。


本文参考lua手册  http://www.lua.org/manual/5.3/manual.html