Chinaunix首页 | 论坛 | 博客
  • 博客访问: 219704
  • 博文数量: 42
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 420
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-09 10:55
个人简介

每天改变一点点,生活充满了惊喜。

文章分类

全部博文(42)

文章存档

2016年(8)

2015年(29)

2014年(5)

我的朋友

分类: C/C++

2015-09-24 23:46:29

最近工作需要,需要学习下Lua,主要用于nginx的扩展,后续也可能会用于实现一些具体的业务。
《Lua程序设计》这本书,目前看了大概四分之一,感觉也差不多入门了,把书上重要的内容记录下,对于自己的一些理解也做下注解,方便以后查阅。
这里主要记录一些Lua语言所独有的特性,C语言以及其他语言通用的一些语言特征就不再赘述了。

每一门语言的入门,大体都要讲,类型、变量、表达式、语句、函数,本篇也是关于这些方面的内容。

Lua(葡萄言语月亮的意思),他一开始设计的目标是能与C语言或其他常用语言编写的软件相互集成。
重点实现C语言不太擅长的部分,如对硬件的高层抽象、动态结构、无冗余(No Redundancy)、简易的测试和调试。
Lua的大部分功能来源于他的标准库,它的主要特性就是可扩展性,另外他还是一门“胶水语言(Glue Language)”,支持基于组建的软件开发方法,
这种方法可以通过黏合现有的高层组件来创建新的应用程序,而且Lua还可以适配和改造组件,以创建新的组件。
Lua在可扩展性、简易性、高效性、可移植性方面表现出色。
Lua可以集成到其它语言代码中,他既可以用Lua代码来扩展,也可以用C代码来扩展。
Lua实现代码只依赖于ANSI (ISO) C,他可以运行在任何平台上。

1. 概念
Lua语句之间不需要分隔符,不过也可以使用分号来分隔,代码中的换行符不起作用。
注:基于代码的可读性,我们一般的编程习惯是一条语句一行,而且有些语言是基于换行来分隔语句的,例如Shell,
但Lua语句即使多条写在一行,也无需显式的分隔符,而且语句中间换行也是可行的,但为了代码可读性,不建议这样写。

程序块(chunk

一个程序块就是一连串的语句或命令。可以只包含一条语句,也可以包含多条语句及函数定义构成,可以任意大小。
需注意在交互模式中,解释器通常将每行内容作为一个完成的程序快来解释,如果检测到该行的内容不足以构成一个完成的程序块,他会等待输入直至完成程序块的输入。
这会影响到后续讲到的局部变量的作用域问题,从而导致相同的代码在交互模式和脚本文件模式下执行的结果不一致。
解决这个问题的办法是用 do-end显式的界定一个块(类似于C语言中的{})。

词法规范
Lua中的标识符是由字母、数字和下划线构成,不以数字开头,
以下划线开头并跟着大写字母串的标识符应避免使用,因为这类标识符的部分会被Lua用作预留全局变量(如_G)。
另外,通常保留单下划线标识作为“哑变量(Dummy Variable)”。
注:Lua中函数可以返回多重值,有时你可能只需要其中的部分返回值,此时就可以把其他不需要的值赋给哑变量。(具体看函数部分)
标识符中的“字母”概念依赖于环境变量Locale的设置,通过设置Locale,非英文字母也可以作为变量名,但这样做,可能会导致可移植性问题。
Lua语言是大小写敏感的。

-- 行注释符
--[[ ]] 块注释符
调试中的注释技巧,注释可写成:
--[[
print(10)
--]]
当需要重新启用这段代码时,改成:
---[[
print(10)
--]]
(此时,上下两行都变为了行注释)

变量
Lua中为初始化变量值为nil,访问一个未初始化的变量不会引发错误,如果要删除一个变量,为其赋值nil即可。

解释器程序(dtand-slone interpreter
如果在Lua脚本文件开始处加上 #!/usr/local/bin/lua,就可以直接执行脚本,而无需显式地调用Lua解释器。

解释器程序选项:
-e 在命令行中执行Lua代码
# lua -e "print(10)"

-l 加载库文件
-i 运行完命令行其他参数后进入缴入模式
# lua -i -l lib -e "x = 10"
以上命令行,先加载库文件lib,然后执行赋值语句,最后进入交互模式。

_PROMPT 全局变量用来定义交互模式的命令行提示符:
# lua -i -e "_PROMPT = 'lua>'"
lua>

交互模式如果要打印表达式的值,可以使用等号开头,并跟随表达式。这个功能有助于将Lua作为一个计算器来使用。

LUA_INIT环境变量可以用来配置解释器,解释器在执行器参数前会首先执行该变量的内容,如果值为“@文件名”,则按照文件执行,否则当作Lua代码执行。
例如可以通过设置LUA_INIT来预先加载一个程序包、修改命令行提示符和路径、定义函数、对函数进行改名或删除等。

arg全局变量用来检索脚本的启动参数
解释器在运行脚本前,会创建一个名为“arg”的table(Lua唯一的数据结构,见类型与值部分)。
# lua -e "sin=math.sin" script  a 
arg 内容为:
arg[-3] = "lua"
arg[-2] = "-e"
arg[-1] = "sin=math.sin"
arg[0] = "script"
arg[1] = "a"
Lua 5.1 中,脚本还可以通过“变成参数语法(vararg syntax)”来检索其参数。(参考函数部分变长参数)

2. 类型与值
Lua为动态类型语言,值本身“携带”了它的类型信息。
Lua有8种基础类型:
nil、boolean、number、string、userdata(自定义类型)、function、thread、table。
函数type可以返回值的类型。

nil类型
Lua将nil用于表示一种“无效值(non-value)”的情况,主要的功能就是用于区别其他任何值。
boolean类型
Lua将false和nil视为假,除此之外的其他值都视为“真”,包括0和空字符串(PHP中为假)。
number类型
Lua没有整型,number 类型表示实数,支持科学计数的写法,如 0.3e-2
string类型
Lua完全采用8位编码(所以像unicode,utf-16默认是不支持的)。可以通过数值来指定字符串中的字符,数值以转义序列“\<ddd>”给出。
    字符串是不可变值(immutable values)。Lua能高效的处理长字符串。
    当字符串包含多行时,还可以使用双方括号来界定一个字符串(类似于其他脚本语言中的Heredoc),如:
    page = [[
    <html>
    ...
    <\html>
    ]]
    如果字符串中包含方括号,可以使用[===[   ]===] 来界定,开始符号和结束符号中的等号格式需一致。这个机制同样适用于注释。
Lua会根据代码做相应的字符与数字的转换,但建议代码不要依赖这个特性,可以使用tosting、tonumber函数做显式的转换。
长度操作符#,可以返回字符串长度:
lua>print(#"hello")
5
table类型
Lua中table既不是值也不是“值”也不是“变量”,而是“对象”。table类型实现了“关联数组”(nil不能作为key,它是Lua中主要(目前也是仅有)的数据结构。
table通过“构造表达式(constructor expression”来创建。
Lua不会主动为table产生副本,一个持有table的变量只是指向table的一个引用。
当table中的某个元素未初始化时,它的内容为nil。这与全局变量一致,因为Lua中的全局变量就是存储在一个普通的table中的。
Lua中可以使用两种方式访问table中的元素:
t["name"] 和 t.name (如果索引是整型,是不能用后面的方法来访问的),注意有无引号的区别。

如果要表示传统的数组和线性表,只需以整数作为key来使用table即可。建议用1作为Lua数组的起始索引,因为Lua标准库中很多函数基于这个机制实现的。

#操作符也可以用于table,但要注意是返回一个数组和线性表的最后一个索引值(并不是元素个数)。
注意:Lua将nil作为界定数组结尾的标志,当一个数组有“空隙(Hole)”时(中间含有nil),#操作符会认为nil元素就是结尾标记,
    此时可以使用函数table.maxn来返回table的最大整数索引。

3.表达式
Lua中有别于传统的是,表达式中可以包含函数定义和table构造式。
算术操作符
+、-(减法、负号)、*、/、^(指数)、%
“%
”取模运算符是 Lua5.1 版本中新增。它根据以下规则定义:
a % b == a - floor(a / b) * b
对于整数,以上算式通常都是有意义的,计算结果的符号永远与第二个参数一致。
对于实数,x % 1得到的是x的小数部分,x - x % 1 得到x的整数部分,而x - x % 0.01 是x精确到小数点后两位的结果。

关系操作符
<、>、<=、>=、==、~=
Lua中
只能对数字、字符串做大小比较。其他类型只能进行相等或不等比较。
Lua按照字母次序(alphabetical order)比较字符串,具体的字母次序取决于Locale

“~=”不等于运算符,在Lua中如果两个值类型不一致,表达式值为false。
nil 只与其自身相等。
对于table、userdata、函数,只有它们引用同一个对象时,Lua才认为它们是相等的。

逻辑操作符
and、or、not
与条件控制语句一样,所有的逻辑操作符将false和nil视为false,其他任何值视为true。
and和or使用“短路求值(short-cut evaluation)”,即在有需要时才去评估第二个操作数。
短路求值可以确保类似(type(v) == "table" and v.tag == "body")这样的表达式不会产生运行时错误。

对于and操作符,如果第一个操作数为假,返回第一个操作数,否则返回第二个操作数。
对于or操作符,如果第一个操作数为真,返回第一个操作数,否则返回第二个操作。
注意:表达式的值,不是ture和false,是操作数。

Lua 习惯写法:
x = x or var”,它等价于 “if not x then x = v end”
用于在没有设置变量时,给变量设置一个默认值。

(a and b) or c“,类似于C语言中的”a ? b : c“,但前提是b为true(注:如果b为false,表达式值始终为c)。
利用上式,求x和y中的较大者可以写成:”max = (x>y) and x or y

not操作符,永远只返回true和false。

字符串连接符 
“..”字符串连接符两旁需加上 空格,因为如果连接的是数值,可能会被解释成小数点符号。

优先级
^
not、#、-(一元
*、/、%
+、-(二元
..
<、>、=<、>=、~=、==
and
or

在二元操作符中,除了指数操作符和字符串连接操作符是”右结合“,其他操作符都是”左结合(left associative)“。

table 构造式
table构造式用于创建和初始化table,是Lua特有的一种表达式,也是Lua中最有用、最通用的机制之一。

关于table的初始化:
如果没有显式的key,Lua默认使用整数索引,从1开始递增。
如果提供key,则输入的key会被当作字符串,不能是number类型(会报运行错误),但要注意,字符串无需加引号,否则报运行错误。
(注:key的组成需要符合Lua标识符的规定,参考词法规范部分。)

上面的规则,好像会导致,在初始化时,不能显式的设置数字类型的key,Lua提供了一种更通用的写法:
给key加上方括号,此时,字符串类型key需要加上引号,而且对字符串的组成没有限制,实际上此时的key可以是任意的表达式:
lua>a = {["x"] = 2, ["d%"] = "y", ["x" .. "y"] = "xy"; [1] = "x", [2] = "89", [1.1] = 1.0001, }
(注:推荐这种方案,消除了局限性,而且具有通用性,可读性。)
建议在最后一个元素后面加上逗号,很多情况下,这样有助于Lua代码再处理table无需将最后一个元素作为特例来处理。
注意,上面的表达式中出现了分号,它的作用主要用于对table中元素做区分。

关于table中元素引用:
有两种方案,a["x"]和a.x,第二种方式有局限性,数字类型的key不能使用这种方案。

4. 语句
赋值
Lua中赋值的不同是支持”多重赋值“,即将多个值赋予对个变量。
a , b = 10 , 11
在多重赋值中,Lua先对等号右边的所有表达式求值,然后执行赋值。
基于这样的处理方式,Lua中交换变量值可写成:
x , y = y , x

另外Lua总会将右边值的个数调整到与左边变量个数一致,规则是:
若值个数少于变量个数,多余的变量赋值nil;
若值的个数更多的话,多余的值不使用。

建议:
多重赋值并不比多条单一变量赋值语句要快,所以不建议把毫无关联的变量放在一起使用多重赋值,
多重赋值在Lua中常用的场景除了上面的交换变量值,还有用来收集函数返回的多个值。

局部变量与快(block
”尽可能使用局部变量“是一种良好的变成风格。

Lua中的局部变量使用local来创建,local i = 1,它的作用域仅限于声明它的那个块。
这里块的概念指:一个控制结构的执行体、一个函数的执行体、一个程序块(chunk)。
如果要在代码中严格控制一个局部变量的作用域,可以使用do-end显式的界定一个程序块。

Lua将局部变量的声明当作语句来处理,因此可以再任何允许书写语句的地方声明局部变量,声明的局部变量作用域从声明开始,直至块结尾。
local foo = foo
上面语句是创建一个局部变量foo,用全局变量foo的值初始化它。
当我们需要使用全局变量的值,而不希望去修改它,就需要这样做,而且这种方式还可以加速在当前作用域中对foo的访问。

建议:没有必要在一个块起始处声明所有的局部变量,在需要时才声明。可以使整个变量在初始化时刻就拥有一个有意义的初值。

控制结构
if-then-elseif-else-end
Lua不支持switch,所以一连串的if-else-if是比较常见的。

while-do-end
与C语言一致

repeat-until
重复执行循环体,直到条件为真时结束(与C语言中do-while相反
注意:在Lua中,一个声明在循环体中的局部变量的作用域包含条件测试部分(Lua 5.1版本

for-do-end
for循环有两种形式:数字型for(numeric for) 和 泛型for(generic for)
数字型for
for var = exp1, exp2, exp3 do
end
var从exp1递增值exp2,每次以exp3作为步长,exp3可选,默认1。例如:
for i = 1, f(x) do print(i) end
for i = 9, 1, -1 do print(i) end
for的三个表达式是在循环开始前一次性求值的,控制变量会默认声明为for语句的局部变量,并在循环体内可见。谨记在循环结束后,改局部变量就不存在了。
注意:不要在循环过程中,修改控制变量的值,否则可能会导致不可预知的结果。
如果不想给循环设置上限,可以设置exp2为常量math.huge。

泛型for:
通过迭代器函数遍历所有值。如,打印a数组的所有值:
for i, v in ipars(a) do print(v) end 
Lua标准库提供了以下迭代器:
io.lines 文件内容行迭代器
    for line in io.lines("text.t") do - end
pairs table元素迭代器
ipairs 数组元素迭代器
string.gmatch 字符串单词迭代器
    for char in  self.gmatch(str, ".") do - end
另外,还可以自定义迭代器。

break与return
break和return语句用于跳出当前的块,break用于结束循环块,而return从函数中返回结果,或结束函数执行。
注意:由于语法构造的原因,break和return只能是一个块的最后一句。即它们应是程序块的最后一条语句,或是end、else、until前的一条语句。
然而有时候希望在一个块的中间插入一个return或break。例如可能在调试一个函数,此时可以使用显式的do-end块来包住一条return语句。

注:我在机上了做了一些测试,如果在函数中间return,当return不返回值时,执行时会忽略,而当有返回值时,会报运行时错误。

阅读(1644) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~