当前位置: 首页 > 工具软件 > Torch7 > 使用案例 >

Torch7 系列教程之Lua语言学习教程(一)

计寒
2023-12-01

0.学习导言

在学习Torch7深度神经网络学习库之前,我们首先来学习一些基本的Lua语言基础知识。Lua语言是一种轻量级的脚本语言,它是用标准C语言编写并以源代码形式开放,其设计的基本目的是为了嵌入应用程序中,从而为程序提供灵活的扩展和定制功能。
Lua语言具有以下基本的特性:

  • 轻量级:它用标准C语言编写并以源代码的形式开放,编译之后仅仅一百余K,可以很方便的嵌入别的程序中。
  • 可扩展性:Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C和C++)提供这些功能,Lua可以使用它们,就像本来就内置功能一样。
  • 其他特性:
    • 支持面向过程式编程和函数式编程;
    • 自动内存管理机制:只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
    • 语言内置模式匹配:闭包;函数也可以作为一个值;提供多线程支持;
    • 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,例如数据抽象,虚函数,继承和重载等等。

1.Lua语法基础

1.1 基本语法

注释
单行注释以及多行注释

-- 这里表示单行注释的意思
-- [[
这里表示多行注释的意思
多行注释
-- ]]

标识符

  • 标识符用于定义一个变量,函数获取其他用户定义的项。标识符以一个字母A到Z或者是a到z或者下划线开头加上0个或者多个字母,下划线或者数字
  • 最好不要使用下划线加大写字母的标识符
  • Lua不允许使用特殊字符@,$,和%来定义标识符,Lua是一个大小写区分的编程语言。

关键词
以下例举出了Lua的保留关键字,这些字符不能作为常量或者其他用户定义标识符:

andbreakdoelseelseif
endfalseforfunctionif
inlocalnilnotor
repeatreturnthentrueuntil
whilegoto

一般约定,以下划线开头链接一串大写字母的名字(例如_VERSION)被用作保留用于Lua内部全局变量。

Chunks
Chunk 是一系列语句,Lua 执行的每一块语句,比如一个文件或者交互模式下的每
一行都是一个 Chunk。一个 Chunk 可以是一个语句,也可以是一系列语句的组合,还可以是函数,Chunk可以很大。
每个语句结尾的分号(;)是可选的,但如果同一行有多个语句最好用“;”分开。

变量
变量在使用之前,必须在代码中进行声明变量,即创建该变量。Lua语言中的变量由三种变量:全局变量、局部变量、表中的域
在默认情况下,变量总是认为是全局的。
全局变量不需要声明,给一个变量赋值之后即可以创建这个全局变量,访问一个没有初始化的全局变量会得到结果nil。
局部变量与全局变量不同,局部变量只在被声明的那个代码块内有效。代码块是指:一个控制结构内,一个函数体,或者是一个chunk(变量被声明的那个文件或者文本串)。应该尽可能的使用局部变量,它有两个好处:

  1. 避免命名冲突;
  2. 访问局部变量的速度要比全局变量更快。
    例如以下语句表达了局部变量和全局变量之间的关系
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)

1.2 数据类型

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表示执行的独立线路,用于执行协同程序
tableLua语言中的表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语言中都包含有其他语言中的转义字符,转义字符如下表示:

转义字符串表达的效果
\abell(响铃)
\bback space(后退)
\fform feed(换页)
\nnewline(换行)
\rcarriage return(回车)
\thorizontal tab(水平制表)
\vvertical 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实现的库创建新的类型。之后我们会简单介绍线程的例子。

1.3 表达式

Lua语言中的表达式包括有数字常量、字符串常量、变量、一元和二元运算符、函数调用,还可以是非传统的函数定义和表构造。
算术运算符
二元运算符:+、-、*、/、^
一元运算符:- (负值)
这些运算符之间操作的都是实数类型。
关系运算符
<、>、<=、>=、==、~=
这些运算符返回的结果为false或者true。
注意:Lua通过引用比较tables、userdata、functions,即当且仅当两者表示同一个对象时候相等。Lua比较数字安装传统的数字大小进行比较,比较字符串按照字母的顺序进行,但是字母顺序依赖于本地环境。
逻辑运算符
and、or、not
C语言中的三元运算符

a?b:c

在Lua语言中可以这样实现

(a and b) or c

连接运算符

..		--两个点,用于连接数字或者字符串

优先级顺序

  • ^
  • not、-
  • *、/
  • +、-
  • <、>、<=、>=、~=、==
  • and
  • or
    除了^和…之外,所有的二元运算符都是左连接的。

表的构造
构造器是创建和初始化表的表达式。最简单的表{}表示空表。如下例子表示一个表:

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"}

在构造函数中域分隔符逗号(",")可以用分号(";")替代,通常我们使用分号用来分割不同类型的表元素。

1.4 控制语句

赋值语句
赋值是改变一个变量的值和改变表域的最基本的方法
Lua可以同时对多个变量进行赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋值给左边的变量。

x = 50
a , b  = 10 , 2*x

遇到赋值语句Lua会计算右边所有的值然后再进行执行赋值操作,所以这样可以进行交换变量的值:

x , y = y , x
a[j],a[i] = a[i],a[j]

如果多个变量之间出现值和个数不一致的情况时候,Lua中会以以下的策略进行赋值操作:

  1. 变量个数>值的个数 按照个数补足nil
  2. 变量个数<值得个数 多余的值会被忽略

控制结构语句
控制结构的条件表达式可以是任意值,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
注意:

  1. 三个表达式只会被计算一次,并且是在循环开始前。
  2. 控制变量是局部变量自动被声明,它只作用于循环内部有效。如果需要保留控制变量的值,需要在循环中进行保存。
  3. 循环过程中不要改变变量的值,否则结果是不可以预知的,退出循环可以选择break

类型二,泛型for循环表达

for i,v in ipairs(a) do
	print(i,v)
end

或者

for k in pairs(t) do
	print(k)
end

泛型for和数值for有以下两点相似的地方:

  1. 控制变量是局部变量
  2. 不要修改控制变量的值

break和return语句
break语句用来退出当前的循环(for,repeat,while),在循环外部不能使用
return 用来从函数返回结果,当一个函数自然结束结尾会有一个默认的return。
Lua语法要求break和return只能出现在block的结尾一句。

1.5 函数

函数是对语句或者表达式进行抽象的主要方法,Lua语言中的函数主要包含有两种用途:

  1. 完成指定的任务,这种情况下函数作为调用函数使用;
  2. 计算并返回值,这种情况下函数作为赋值语句的表达式进行使用

函数的基本定义

optional_function_scope function function_name(arg1,arg2,arg3,...,argn)
	function_body
	return result_params_comma_separated
end
  • optional_function_scope表示可选的局部函数还是全局函数,未设置表示全局函数
  • function_name表示函数名称
  • arg1,arg2,arg3,…,argn表示函数的参数,多个参数使用逗号进行分开,也可以不带参数
  • function_body表示函数体,函数中需要执行代码的语句块。
  • result_params_comma_separated表示函数的返回值,可以返回多个函数值,每个值使用逗号进行分开

多个返回值表示

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 的域
例如以下的几个例子:

  1. 表和函数放在一起
Lib = {}
Lib.foo = function (x,y) return x + y end
Lib.goo = function (x,y) return x - y end
  1. 使用表构造函数
Lib = {
	foo = function (x,y) return x+y end
	goo = function (x,y) return x-y end
}
  1. Lua提供另外一种语法方式
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语言的特性。

 类似资料: