Lua简明教程
这几天系统地学习了一下Lua这个脚本语言,Lua脚本是一个很轻量级的脚本,也是号称性能最高的脚本,用在很多需要性能的地方,比如:游戏脚本,nginx,wireshark的脚本,当你把他的源码下下来编译后,你会发现解释器居然不到200k,这是多么地变态啊(/bin/sh都要1M,MacOS平台),而且能和C语言非常好的互动。我很好奇得浏览了一下Lua解释器的源码,这可能是我看过最干净的C的源码了。
我不想写一篇大而全的语言手册,一方面是因为已经有了(见本文后面的链接),重要的原因是,因为大篇幅的文章会挫败人的学习热情,我始终觉得好的文章读起来就像拉大便一样,能一口气很流畅地搞完,才会让人爽(这也是我为什么不想写书的原因)。所以,这必然又是一篇“入厕文章”,还是那句话,我希望本文能够让大家利用上下班,上厕所大便的时间学习一个技术。呵呵。
相信你现在已经在厕所里脱掉裤子露出屁股已经准备好大便了,那就让我们畅快地排泄吧……
目录
运行
首先,我们需要知道,Lua是类C的,所以,他是大小写字符敏感的。
下面是Lua的Hello World。注意:Lua脚本的语句的分号是可选的,这个和GO语言很类似。
print("Hello World")
你可以像python一样,在命令行上运行lua命令后进入lua的shell中执行语句。
chenhao-air:lua chenhao$ lua Lua 5.2.2 Copyright (C) 1994-2013 Lua.org, PUC-Rio > print("Hello, World") Hello, World >
也可以把脚本存成一个文件,用如下命令行来运行。
>lua file.lua
或是像shell一样运行:
chenhao-air:lua chenhao$ cat hello.lua #!/usr/local/bin/lua print("Hello, World") chenhao-air:lua chenhao$ chmod +x hello.lua chenhao-air:test chenhao$ ./hello.lua Hello, World
语法
注释
-- 两个减号是行注释
--[[ 这是块注释 这是块注释 --]]
变量
Lua的数字只有double型,64bits,你不必担心Lua处理浮点数会慢(除非大于100,000,000,000,000),或是会有精度问题。
你可以以如下的方式表示数字,0x开头的16进制和C是很像的。
num = 1024 num = 3.0 num = 3.1416 num = 314.16e-2 num = 0.31416E1 num = 0xff num = 0x56
字符串你可以用单引号,也可以用双引号,还支持C类型的转义,比如: ‘\a’ (响铃), ‘\b’ (退格), ‘\f’ (表单), ‘\n’ (换行), ‘\r’ (回车), ‘\t’ (横向制表), ‘\v’ (纵向制表), ‘\\’ (反斜杠), ‘\”‘ (双引号), 以及 ‘\” (单引号)
下面的四种方式定义了完全相同的字符串(其中的两个中括号可以用于定义有换行的字符串)
a = 'alo\n123"' a = "alo\n123\"" a = '\97lo\10\04923"' a = [[alo 123"]]
C语言中的NULL在Lua中是nil,比如你访问一个没有声明过的变量,就是nil,比如下面的v的值就是nil
v = UndefinedVariable
布尔类型只有nil和false是 false,数字0啊,‘’空字符串(’\0’)都是true!
另外,需要注意的是:lua中的变量如果没有特殊说明,全是全局变量,那怕是语句块或是函数里。变量前加local关键字的是局部变量。
theGlobalVar = 50 local theLocalVar = "local variable"
控制语句
不多说了,直接看代码吧(注意:Lua没有++或是+=这样的操作)
while循环
sum = 0 num = 1 while num <= 100 do sum = sum + num num = num + 1 end print("sum =",sum)
if-else分支
if age == 40 and sex =="Male" then print("男人四十一枝花") elseif age > 60 and sex ~="Female" then print("old man without country!") elseif age < 20 then io.write("too young, too naive!\n") else local age = io.read() print("Your age is "..age) end
上面的语句不但展示了if-else语句,也展示了
1)“~=”是不等于,而不是!=
2)io库的分别从stdin和stdout读写的read和write函数
3)字符串的拼接操作符“..”
另外,条件表达式中的与或非为分是:and, or, not关键字。
for 循环
sum = 0 for i = 1, 100 do sum = sum + i end
sum = 0 for i = 1, 100, 2 do sum = sum + i end
sum = 0 for i = 100, 1, -2 do sum = sum + i end
until循环
sum = 2 repeat sum = sum ^ 2 --幂操作 print(sum) until sum >1000
函数
Lua的函数和Javascript的很像
递归
function fib(n) if n < 2 then return 1 end return fib(n - 2) + fib(n - 1) end
闭包
同样,Javascript附体!
function newCounter() local i = 0 return function() -- anonymous function i = i + 1 return i end end c1 = newCounter() print(c1()) --> 1 print(c1()) --> 2
function myPower(x) return function(y) return y^x end end power2 = myPower(2) power3 = myPower(3) print(power2(4)) --4的2次方 print(power3(5)) --5的3次方
函数的返回值
和Go语言一样,可以一条语句上赋多个值,如:
name, age, bGay = "haoel", 37, false, "[email protected]"
上面的代码中,因为只有3个变量,所以第四个值被丢弃。
函数也可以返回多个值:
function getUserInfo(id) print(id) return "haoel", 37, "[email protected]", "https://coolshell.cn" end name, age, email, website, bGay = getUserInfo()
注意:上面的示例中,因为没有传id,所以函数中的id输出为nil,因为没有返回bGay,所以bGay也是nil。
局部函数
函数前面加上local就是局部函数,其实,Lua中的函数和Javascript中的一个德行。
比如:下面的两个函数是一样的:
function foo(x) return x^2 end foo = function(x) return x^2 end
Table
所谓Table其实就是一个Key Value的数据结构,它很像Javascript中的Object,或是PHP中的数组,在别的语言里叫Dict或Map,Table长成这个样子:
haoel = {name="ChenHao", age=37, handsome=True}
下面是table的CRUD操作:
haoel.website="https://coolshell.cn/" local age = haoel.age haoel.handsome = false haoel.name=nil
上面看上去像C/C++中的结构体,但是name,age, handsome, website都是key。你还可以像下面这样写义Table:
t = {[20]=100, ['name']="ChenHao", [3.14]="PI"}
这样就更像Key Value了。于是你可以这样访问:t[20],t[“name”], t[3.14]。
我们再来看看数组:
arr = {10,20,30,40,50}
这样看上去就像数组了。但其实其等价于:
arr = {[1]=10, [2]=20, [3]=30, [4]=40, [5]=50}
所以,你也可以定义成不同的类型的数组,比如:
arr = {"string", 100, "haoel", function() print("coolshell.cn") end}
注:其中的函数可以这样调用:arr[4]()。
我们可以看到Lua的下标不是从0开始的,是从1开始的。
for i=1, #arr do print(arr[i]) end
注:上面的程序中:#arr的意思就是arr的长度。
注:前面说过,Lua中的变量,如果没有local关键字,全都是全局变量,Lua也是用Table来管理全局变量的,Lua把这些全局变量放在了一个叫“_G”的Table里。
我们可以用如下的方式来访问一个全局变量(假设我们这个全局变量名叫globalVar):
_G.globalVar _G["globalVar"]
我们可以通过下面的方式来遍历一个Table。
for k, v in pairs(t) do print(k, v) end
MetaTable 和 MetaMethod
MetaTable和MetaMethod是Lua中的重要的语法,MetaTable主要是用来做一些类似于C++重载操作符式的功能。
比如,我们有两个分数:
fraction_a = {numerator=2, denominator=3} fraction_b = {numerator=4, denominator=7}
我们想实现分数间的相加:2/3 + 4/7,我们如果要执行: fraction_a + fraction_b,会报错的。
所以,我们可以动用MetaTable,如下所示:
fraction_op={} function fraction_op.__add(f1, f2) ret = {} ret.numerator = f1.numerator * f2.denominator + f2.numerator * f1.denominator ret.denominator = f1.denominator * f2.denominator return ret end
为之前定义的两个table设置MetaTable:(其中的setmetatble是库函数)
setmetatable(fraction_a, fraction_op) setmetatable(fraction_b, fraction_op)
于是你就可以这样干了:(调用的是fraction_op.__add()函数)
fraction_s = fraction_a + fraction_b
至于__add这是MetaMethod,这是Lua内建约定的,其它的还有如下的MetaMethod:
__add(a, b) 对应表达式 a + b __sub(a, b) 对应表达式 a - b __mul(a, b) 对应表达式 a * b __div(a, b) 对应表达式 a / b __mod(a, b) 对应表达式 a % b __pow(a, b) 对应表达式 a ^ b __unm(a) 对应表达式 -a __concat(a, b) 对应表达式 a .. b __len(a) 对应表达式 #a __eq(a, b) 对应表达式 a == b __lt(a, b) 对应表达式 a < b __le(a, b) 对应表达式 a <= b __index(a, b) 对应表达式 a.b __newindex(a, b, c) 对应表达式 a.b = c __call(a, ...) 对应表达式 a(...)
“面向对象”
上面我们看到有__index这个重载,这个东西主要是重载了find key的操作。这操作可以让Lua变得有点面向对象的感觉,让其有点像Javascript的prototype。(关于Javascrip的面向对象,你可以参看我之前写的Javascript的面向对象)
所谓__index,说得明确一点,如果我们有两个对象a和b,我们想让b作为a的prototype只需要:
setmetatable(a, {__index = b})
例如下面的示例:你可以用一个Window_Prototype的模板加上__index的MetaMethod来创建另一个实例:
Window_Prototype = {x=0, y=0, width=100, height=100} MyWin = {title="Hello"} setmetatable(MyWin, {__index = Window_Prototype})
于是:MyWin中就可以访问x, y, width, height的东东了。(注:当表要索引一个值时如table[key], Lua会首先在table本身中查找key的值, 如果没有并且这个table存在一个带有__index属性的Metatable, 则Lua会按照__index所定义的函数逻辑查找)
有了以上的基础,我们可以来说说所谓的Lua的面向对象。
Person={} function Person:new(p) local obj = p if (obj == nil) then obj = {name="ChenHao", age=37, handsome=true} end self.__index = self return setmetatable(obj, self) end function Person:toString() return self.name .." : ".. self.age .." : ".. (self.handsome and "handsome" or "ugly") end
上面我们可以看到有一个new方法和一个toString的方法。其中:
1)self 就是 Person,Person:new(p),相当于Person.new(self, p)
2)new方法的self.__index = self 的意图是怕self被扩展后改写,所以,让其保持原样
3)setmetatable这个函数返回的是第一个参数的值。
于是:我们可以这样调用:
me = Person:new() print(me:toString()) kf = Person:new{name="King's fucking", age=70, handsome=false} print(kf:toString())
继承如下,我就不多说了,Lua和Javascript很相似,都是在Prototype的实例上改过来改过去的。
Student = Person:new() function Student:new() newObj = {year = 2013} self.__index = self return setmetatable(newObj, self) end function Student:toString() return "Student : ".. self.year.." : " .. self.name end
模块
我们可以直接使用require(“model_name”)来载入别的lua文件,文件的后缀是.lua。载入的时候就直接执行那个文件了。比如:
我们有一个hello.lua的文件:
print("Hello, World!")
如果我们:require(“hello”),那么就直接输出Hello, World!了。
注意:
1)require函数,载入同样的lua文件时,只有第一次的时候会去执行,后面的相同的都不执行了。
2)如果你要让每一次文件都会执行的话,你可以使用dofile(“hello”)函数
3)如果你要玩载入后不执行,等你需要的时候执行时,你可以使用 loadfile()函数,如下所示:
local hello = loadfile("hello") ... ... ... ... hello()
loadfile(“hello”)后,文件并不执行,我们把文件赋给一个变量hello,当hello()时,才真的执行。(我们多希望JavaScript也有这样的功能(参看《Javascript 装载和执行》))
当然,更为标准的玩法如下所示。
假设我们有一个文件叫mymod.lua,内容如下:
local HaosModel = {} local function getname() return "Hao Chen" end function HaosModel.Greeting() print("Hello, My name is "..getname()) end return HaosModel
于是我们可以这样使用:
local hao_model = require("mymod") hao_model.Greeting()
其实,require干的事就如下:(所以你知道为什么我们的模块文件要写成那样了)
local hao_model = (function () --mymod.lua文件的内容-- end)()
参考
我估计你差不多到擦屁股的时间了,所以,如果你还比较喜欢Lua的话,下面是几个在线文章你可以继续学习之:
- manual.luaer.cn lua在线手册
- book.luaer.cn lua在线lua学习教程
- lua参考手册Lua参考手册的中文翻译(云风翻译版本)
关于Lua的标库,你可以看看官方文档:string, table, math, io, os。
(全文完)
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
《Lua简明教程》的相关评论
恩,读完有晨便的痛快感觉…
\(^o^)/~好
拉完看完,谢了耗子。
“布尔类型只有nil和false是 false” 这句话没错,但是不严谨,nil和boolean是两种不同的类型,用“条件表达式只有nil和false是false”更好。 另外,最后的链接应该加上Programming in Lua, 这本书在Lua的地位相当于K&R在C的地位,基本上普通应用读这一本书就足够了,也不过300多页而已。最新的是第三版讲Lua 5.2, 第一版在官网上是免费的,讲Lua 5.0, 只要注意区别就好了。 链接:http://www.lua.org/pil/contents.html
读完很有收获,谢谢。不过我还是猜测作者便秘,哈哈。
教程相当全面了,基本上只缺coroutine, 迭代器 和 C API,其他方面都有讲解,再次赞一个。这篇文章会推动Lua在国内的普及吧,虽然现在工作中已经不用了,还是相当喜欢Lua的。目标是在Stack Overflow的Lua Top User里排名前五,目前好像第十几。
语法上跟shell还是有不少区别的。因为轻量,所以喜欢。
没什么意思,lua真正核心是coroutine,真正的应用是将lua嵌入到c/c++中发挥协程的优势简化逻辑开发, 变同步逻辑为异步执行。这其中的经验和心得,恐怕只有实践过的同学才能说清道明。
我知道lua在游戏上有大量使用,WOW的所有插件几乎都是lua的。在游戏之外使用lua的不太知道,不过比较神奇的是迅雷貌似也有大量使用lua。如何把wow和迅雷联系到一起……
这个文章长度,肯定会便秘的
不错的教程,对初学者真不错
@独行猫儿
RTMFP 开源P2P服务器Cumulus是用Lua做扩展的
关于闭包中的示例2 测试有误
print(power2(4)) –2的4次方
结果应为4^2 x为2,y为4
因为print(power3(4)) 结果为64
求确认
谢谢查错,是我写反了。Sorry
print(power2(4)) –2的4次方这个似乎有问题?应该是4的2次方。
我尝试print(power2(5))得到的结果是25.
更改函数为
function myPower(x)
return function(y) return x^y end
end
就和之后的表述一样了。
4的2二次方
迅雷是不是用到了openresty框架了?@独行猫儿
打算学习lua或者shell,但是不知道lua一般怎么用。
今天在马桶上看完了
讲得很不错,但是没讲 lua 最核心的 coroutine 啊,我猜楼主要再搞一篇文章吧,楼主再接再厉。
受教了
这个果断要顶
lua和js实在太像了, 赞
楼主为啥没提Coroutine,腹泻的节奏有木有?
号称性能最高的脚本,与编译型的c/c++比较,性能差距有没有1个数量级?
好舒畅的赶脚!
@jxbm
同样的疑问?
闭包,示例二:
print(power2(4)) –2的4次方,是错的。
power2(y)这个函数应该是y的2次方
证明:
>print(power2(4))
16
>print(power2(5))
25
>print(power2(6))
36
我记得好像IPAD上有一款直接开发软件的应用,用的就是LUA
语法上本身和大部分动态语言有很多相似之处,但是与C互call的方式实在太蛋疼了。
顺便发个广告
请大家帮忙投个票吧
Hi guys, After winning AngelHack SH, Startup Weekend SH and got a the hottest booth in TechCrunch SH, we still competing to bring our startup to Silicon Valley, to Google HQ. We compete in Global Startup Battle, you can vote us every 24 hours until Dec 6th to send us to a Google mentorship program in Silicon Valley. Please help us to bring this Beijing entrepreneur to Silicon Valley. We want to win, and we are the only Beijing/Shanghai startup in the competition
Vote us below http://globalstartupbattle.agorize.com/en/juries/11/votables/418
粗略看了一下,感觉很像ruby语言。
很好的教程,lua确实很小巧!
看到半截看不下去了,没学过重载操作符,在metatable那里卡着了,我这算是便秘了吧
多谢lz,扫盲了!
看到 __index 那里略疑惑,此文(http://www.cnblogs.com/simonw/archive/2007/01/17/622032.html)释疑了。在这里#mark#一下……「当表要索引一个值时如table[key], Lua会首先在table本身中查找key的值, 如果没有并且这个table存在一个带有__index属性的Metatable, 则Lua会按照__index所定义的函数逻辑查找.」
闭包之前没概念(不懂javascript),看到知乎一个关于javascript闭包的回答,http://www.zhihu.com/question/19554716,「闭包是指在 JavaScript 中,内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。」结合私有公有对象,有点豁然开朗(七窍通六窍)之感……
再记录一下 mozilla developer network 上一篇 closure 的文章,http://goo.gl/0OsYdB。
再次多谢lz!
P.S. 这文章如果真要一次大便读完,要么深度便秘,要么筋骨惊奇…… Orz
挺不错的解释,我回到文章中。谢谢
a = ‘alo\n123″‘
a = “alo\n123\””
a = ‘\97lo\104923″‘
这里最后一行,\97,是如何保证解释器把9和7连在一起,而不是\9和数字7呢?
还有49,这里为什么要加一个0,同样,解释器依据什么将049做为一个整体的?
此奥,看完第一段就没有看下去的勇气了,太恶心了-_-!!!
为毛大家都看到里面的技术,我只看到耗子老师吐槽金正日。。。
刚刚看完文章,我点击页面右上角”High一下!”的时候。。。魂都给我吓掉了。。。。。。。老师。。。不带这么整人的。。。。。
QQ邮箱不能订阅,
看python,lua这些,和C的感觉很不一样
Lua是类C的么?不能因为仅仅是区分大小写就说是类C的吧。
另,最权威的参考书籍还是Programming in Lua
Lua源代码只有两万余行,非常值得学习
我感觉自己对这个语言不感兴趣..
@独行猫儿
Google拼音 貌似也可以用 Lua 定制
居然看完了,收获很大。
lua必须看云风的blog啊,他搞了好久了。。
haoel = {name=”ChenHao”, age=37, handsome=True} ^_^
我们用Lua开发自动化测试的脚本,小巧却强大。
写得不错 马克下来了