在学习Torch7深度神经网络学习库之前,我们首先来学习一些基本的Lua语言基础知识。Lua语言是一种轻量级的脚本语言,它是用标准C语言编写并以源代码形式开放,其设计的基本目的是为了嵌入应用程序中,从而为程序提供灵活的扩展和定制功能。
Lua语言具有以下基本的特性:
注释
单行注释以及多行注释
-- 这里表示单行注释的意思
-- [[
这里表示多行注释的意思
多行注释
-- ]]
标识符
关键词
以下例举出了Lua的保留关键字,这些字符不能作为常量或者其他用户定义标识符:
and | break | do | else | elseif |
---|---|---|---|---|
end | false | for | function | if |
in | local | nil | not | or |
repeat | return | then | true | until |
while | goto |
一般约定,以下划线开头链接一串大写字母的名字(例如_VERSION)被用作保留用于Lua内部全局变量。
Chunks
Chunk 是一系列语句,Lua 执行的每一块语句,比如一个文件或者交互模式下的每
一行都是一个 Chunk。一个 Chunk 可以是一个语句,也可以是一系列语句的组合,还可以是函数,Chunk可以很大。
每个语句结尾的分号(;)是可选的,但如果同一行有多个语句最好用“;”分开。
变量
变量在使用之前,必须在代码中进行声明变量,即创建该变量。Lua语言中的变量由三种变量:全局变量、局部变量、表中的域
在默认情况下,变量总是认为是全局的。
全局变量不需要声明,给一个变量赋值之后即可以创建这个全局变量,访问一个没有初始化的全局变量会得到结果nil。
局部变量与全局变量不同,局部变量只在被声明的那个代码块内有效。代码块是指:一个控制结构内,一个函数体,或者是一个chunk(变量被声明的那个文件或者文本串)。应该尽可能的使用局部变量,它有两个好处:
x = 10
local i = 1 -- local to the chunk
while i<x do
local x = i*2 -- local to the chunk
print (x) -- 2,4,6,8,...
i = i + 1
end
if i > 20 then
local x -- local to the "then" body
x = 20
print(x + 2)
else
print(x) -- 10 (the global value)
end
print(x)
Lua是一个动态类型的语言,变量不要定义类型,只需要为变量赋值即可。值可以存储在变量中,作为参数传递或者在结果中返回。
Lua中有8个基本类型,分别为:nil、boolean、number、string、userdata、function、thread和table。关于这几种数据类型的描述,可以由以下列表进行描述:
数据类型 | 描述 |
---|---|
nil | 这个类型只有nil值属于该类型中,表示一个无效值(在条件表达式中表示false) |
boolean | 包含有两个值:false和true |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或者单引号来表示 |
function | 由C或者Lua编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同程序 |
table | Lua语言中的表table实际上表达为一个关联数组,数组的索引可以是数字、字符串或者表类型。table的创建时通过构造表达式来完成的,最简单的表达式{}表示创建一个空表 |
nil(空)
Lua中特殊的类型,只有一个值nil;一个全局变量中没有被赋值以前默认为nil;给全局变量赋值nil可以删除该变量
注意:nil做比较的时候应该加上双引号“:
>type(X)
nil
>type(X) == nil
false
>type(X) == “nil”
true
boolean布尔值
两个取值false和true。Lua语言中所有的值都可以作为条件中逻辑运算值,在控制结构中的逻辑值除了false和nil为假,其他值都为真。所以Lua认为0和空串均表示真。
Numbers(实数)
Lua语言中没有整数变量和浮点数变量之分,和其他语言类似,数字常量的小数部分和指数部分都是可选的,数字常量有以下几个例子:
4 0.4 4.57e-3 0.3e12 5e+20
Strings(字符串类型)
Lua中的字符串是不可以修改的,这意味着需要创建一个新的变量来存放你要的字符串。
string和其他对象一样,Lua自动进行内存的分配和释放,可以使用双引号或者单引号来表示字符串。Lua语言中都包含有其他语言中的转义字符,转义字符如下表示:
转义字符串 | 表达的效果 |
---|---|
\a | bell(响铃) |
\b | back space(后退) |
\f | form feed(换页) |
\n | newline(换行) |
\r | carriage return(回车) |
\t | horizontal tab(水平制表) |
\v | vertical tab(垂直制表) |
\\ | “\” |
\" | double quote(双引号) |
\’ | single quote(单引号) |
\[ | left square bracket(左中括号) |
\] | right square bracket(右中括号) |
另外,Lua语言中的字符串可以使用[[…]]来表达字符串,这种字符串可以包含多行,也可以嵌套而且不会解释转移序列
字符串在运行过程中Lua会自动在string和numbers之间进行自动类型转换,当一个字符串使用算术操作符时候,string就会被转化为数字。反过来,Lua期望一个string二碰到数字时候,会将数字转为string。
… 在 Lua 中是字符串连接符,当在一个数字后面写…时,必须加上空格以防止被解释错。
另外可以使用tostring()和tonumber()函数进行数字和字符串之间的转换。
Functions函数
Lua语言中,函数是可以存储在变量中,可以作为函数的参数,也可以作为函数的返回值。这个特性具有这样的一个优点,即一个程序可以重新定义函数增加新的功能或者是为了避免运行不可靠代码创建安全运行环境而隐藏函数,后面会讲到函数的使用特性和面向对象的函数的特点和特性。
Lua可以调用lua或者C实现的函数,Lua所有的标准库都是使用C语言实现的。标准库包括string库、table库、I/O库、OS库、算术库、debug库等等。
Userdata和Thread
userdata可以将C数据存放在Lua变量中,userdata在Lua中除了赋值和相等比较之外,没有预定义的操作。userdata用来描述应用程序或者使用C实现的库创建新的类型。之后我们会简单介绍线程的例子。
Lua语言中的表达式包括有数字常量、字符串常量、变量、一元和二元运算符、函数调用,还可以是非传统的函数定义和表构造。
算术运算符
二元运算符:+、-、*、/、^
一元运算符:- (负值)
这些运算符之间操作的都是实数类型。
关系运算符
<、>、<=、>=、==、~=
这些运算符返回的结果为false或者true。
注意:Lua通过引用比较tables、userdata、functions,即当且仅当两者表示同一个对象时候相等。Lua比较数字安装传统的数字大小进行比较,比较字符串按照字母的顺序进行,但是字母顺序依赖于本地环境。
逻辑运算符
and、or、not
C语言中的三元运算符
a?b:c
在Lua语言中可以这样实现
(a and b) or c
连接运算符
.. --两个点,用于连接数字或者字符串
优先级顺序
表的构造
构造器是创建和初始化表的表达式。最简单的表{}表示空表。如下例子表示一个表:
days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Staturday"}
Lua语言中表的索引从1开始,即表的第一个元素的索引为1
print(days[4]) --输出Wednesday
构造函数可以使用任何表达式进行初始化:
tab = {cos(0),cos(1),cos(2)}
表可以通过以下方式进行初始化:
a = {x=0,y=0} -- 相当于a = {};a.x=0;a.y=0
构造复杂的数据结构形式:
tab = {color="blue",thickness=2,
{x=0,y=0},
{x=-10,y=20}
}
print(tab[2].x) -- 输出结果为-10
下面介绍更一般形式的初始化表达方式,这里我们使用[expression]表示将被初始化的索引序列:
opnames = {["+"] = "add" , ["-"] = "sub",
["*"] = "mul" , ["/"] = "div"
}
a = 20 ; s = "-"
a = {[i+0] = s , [i+1] = s..s , [i+2] = s..s..s}
print(opnames[s]) -- 输出打印sub
print(a[22]) -- 输出打印 ---
其实,可以由以下的例子表示这样的输出序列的例子:
{x=20,y=10} -- 相当于{["x"] = 10 , ["y"] = 20}
{"red","white","yellow"} -- 相当于{[1] = "red" , [2] = "white" , [3] = "yellow"}
在构造函数中域分隔符逗号(",")可以用分号(";")替代,通常我们使用分号用来分割不同类型的表元素。
赋值语句
赋值是改变一个变量的值和改变表域的最基本的方法
Lua可以同时对多个变量进行赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋值给左边的变量。
x = 50
a , b = 10 , 2*x
遇到赋值语句Lua会计算右边所有的值然后再进行执行赋值操作,所以这样可以进行交换变量的值:
x , y = y , x
a[j],a[i] = a[i],a[j]
如果多个变量之间出现值和个数不一致的情况时候,Lua中会以以下的策略进行赋值操作:
控制结构语句
控制结构的条件表达式可以是任意值,Lua认为false和nil为假,其他均为真值
if语句有三种形式:
-- 形式一:
if conditions then
then-part
end
-- 形式二
if conditions then
then-part
else
else-part
end
if conditions then
then-part
elseif conditions then
elseif-part
... -- 多个elseif
else
else-part
end
while语句:
while conditions do
statements;
end
repeat-untl语句
repeat
statements;
until conditions
for语句包含有两大类:
类型一,数值for循环:
for var = exp1,exp2,exp3 do
loop-part
end
for 将采用exp3作为step从exp1(初始值)到exp2(终止值),执行loop-part。其中exp3可以省略,默认step=1
注意:
类型二,泛型for循环表达
for i,v in ipairs(a) do
print(i,v)
end
或者
for k in pairs(t) do
print(k)
end
泛型for和数值for有以下两点相似的地方:
break和return语句
break语句用来退出当前的循环(for,repeat,while),在循环外部不能使用
return 用来从函数返回结果,当一个函数自然结束结尾会有一个默认的return。
Lua语法要求break和return只能出现在block的结尾一句。
函数是对语句或者表达式进行抽象的主要方法,Lua语言中的函数主要包含有两种用途:
函数的基本定义
optional_function_scope function function_name(arg1,arg2,arg3,...,argn)
function_body
return result_params_comma_separated
end
多个返回值表示
Lua 函数可以返回多个结果值,返回的结果值用逗号进行分开
可变参数表示
Lua 函数可以接受可变数目的参数,和 C 语言类似在函数参数列表中使用三点(…)
表示函数有可变的参数。Lua 将函数的参数放在一个叫 arg 的表中,除了参数以外,arg表中还有一个域 n 表示参数的个数。
例如,重写print函数,这里我们使用另一个函数名print_new代替print函数
local function print_new(...)
prnitResult = ""
for i,v in ipairs(arg) do
printResult = printResult .. tostring(v) .. "\t"
end
printResult = printResult .. "\n"
return printResult
end
有时候可能会加上几个固定参数加上可变参数
function g (a,b, ...) end
有时候我们只选取函数调用中多个返回值中的后面的返回值,可以用下划线代替(虚变量)
local _, x = string.find(s,p)
命名参数
Lua的函数参数是和位置相关系的,调用时候就会依照顺序的形式依次传入形式参数。有时候用名字指定参数是很有用途的,所以这个时候我们可以使用传入表的形式进行修改,例如
function rename(oldname,newname)
os.rename(oldname,newname)
end
修改为
function rename(arg)
os.rename(arg.old,arg.new)
end
函数中的一些特有的性质
Lua函数中是带有词法定界的第一类值
第一类值是指在Lua语言中函数和其他值一样,函数可以被存放在变量中,也可以存放在表当中,可以作为函数的参数,还可以作为函数的返回值。
词法定界是指被嵌套的函数可以访问它外部函数的中的变量。这样Lua就拥有强大的编程能力,因此提出了函数中的一种形式,即函数闭包
例如动态绑定函数
a = {p = print}
a.p("hello world!")
sin_new = math.sin
a.p(sin_new(1))
变量函数定义下的函数表达形式
myfunc = function(x)
return 2*x
end
myfunc(5)
又例如匿名函数的表达形式:
network = {
{name = "grauna", IP = "210.26.30.34"},
{name = "arraial", IP = "210.26.30.23"},
{name = "lua", IP = "210.26.23.12"},
{name = "derain", IP = "210.26.23.20"},
}
table.sort(network,function (a,b)
return (a.name > b.name)
end)
函数闭包
函数闭包即为词法定界一种应用方式,例如
function newCounter()
local i = 0
return function()
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) -- 输出1
print(c1()) -- 输出2
c2 = newCounter()
print(c2()) -- 输出1
print(c1()) -- 输出3
print(c2()) -- 输出2
表中创建函数的例子:
function digitButton (digit)
return Button{label = digit,
action = function()
add_to_display(digit)
end
}
end
非全局函数
Lua中函数可以作为全局变量也可以作为局部变量,函数可以作为table 的域
例如以下的几个例子:
Lib = {}
Lib.foo = function (x,y) return x + y end
Lib.goo = function (x,y) return x - y end
Lib = {
foo = function (x,y) return x+y end
goo = function (x,y) return x-y end
}
Lib = {}
function Lib.foo (x,y)
return x+y
end
function Lib.goo (x,y)
return x-y
end
局部函数的一些定义方式,当我们将函数保存在一个局部变量内时候,我们得到一个局部函数,即在一定的范围内(chunk内部可见),词法定界保证了包内的其他函数可以调用此函数。局部函数的两种声明方式如下表示:
方式一:
local f = function (params)
...
end
local g = function (params)
...
f() -- 外部的局部函数f在这里是可见的
...
end
方式二:
local function f(params)
...
end
请注意递归局部函数的表达方式,如果直接声明一个函数为局部函数的时候,在Lua编译的过程中会找不到局部函数,例如以下错误的表达方式
local fact = function (n)
if n == 0 then
return 1
else
return n*fact(n-1)
end
end
需要在定义函数之前,声明函数变量
local fact
fact = function (n)
if n == 0 then
return 1
else
return n*fact(n-1)
end
end
又例如下面的例子
local f,g
function g()
...
f()
...
end
function f()
...
g()
...
end
函数的尾调用
Lua中还有一个最有趣额特征是尾调用,尾调用是一种类似于函数结尾的goto调用,当函数最后的一个动作是调用另外一个函数时候,我们称这种调用方法为尾调用,例如以下调用方法:
function f(x)
return g(x)
end
在状态机的编程领域,尾调用是非常有用的。状态机的应用要求函数记住每一种状态,改变状态只需要goto一个特定的函数。例如以下的迷宫游戏,每一个函数代表了一种当前的状态,我们可以使用尾调用使得从一个状态转移到另一种状态。
function room1 ()
local move io.read()
if move == "south" then
return room3()
elseif move == "east" then
return room2()
else
print("invalid move!")
return room1()
end
end
function room2()
local move = io.read()
if move = "south" then
return room4()
elseif move == "west" then
return room1()
else
print("invalid move")
return room2()
end
end
function room3 ()
local move = io.read()
if move == "north" then
return room1()
elseif move == "east" then
return room4()
else
print("invalid move")
return room3()
end
end
function room4 ()
print("congratilations!")
end
本文基本讲述了Lua语言基础语法,新手的知识点大概就这么多,接下来会讲述一些有关于Lua语言进阶阶段的知识,下一节将会进一步讲述Lua语言的特性。