文章目录- 缘起
- 环境搭建
- 运行方式
- 注释
- 起名字
- 变量
- 数据类型
- table
- function
- 变量
- 索引
- 循环
- 分支语句
- 函数
- 运算符
- 字符串操作
- 数组
- 一维数组
- 多维数组
- 区间迭代器
- 模块与包
- 加载机制
- 面向对象
- 内存管理
- 文件IO
- “线程” -- 协同程序
- table
- function
- 一维数组
- 多维数组
- 加载机制
缘起
缘起:项目要用。 学习时间:懒,所以速战速决吧。 学习方法:直接对标Python。
环境搭建
此处使用Linux环境。
代码语言:javascript复制wget www.lua.org/ftp/lua-5.4.3.tar.gz #版本自己挑
tar -xvf lu[tab]
cd lu[tab]
make linux test
make install
简单粗暴,全程两分钟。
运行方式
先给一行代码去运行一下找找成就感:
代码语言:javascript复制print("hallo world") --这里直接对标Python的print
熟悉的 hallo world。
方式一:起一个 .lua 文件,直接放进去,保存退出来,命令:lua XXX.lua 即可 方式二:起一个 .lua 文件,开头放上:
代码语言:javascript复制#!/usr/local/bin/lua
再放上那一行代码,保存退出,./XXX.lua
方式三:命令:lua -i,开启交互式编程渠道。
注释
这里的注释对标的是 sql 语言。
代码语言:javascript复制--这是单行注释
--[[
这是多行注释
]]--
起名字
名字不可乱起,还记得我教Python课的时候给学员们说起名字的方式嘛:
代码语言:javascript复制--[[
直接英译
多个单词下划线隔开
单个单词末尾加个下划线
尽量不用大写
--]]
妥妥的。
看一下关键字:
一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于 Lua 内部全局变量。
按我的起名法,苹果你就起个 apple_,红苹果你就起个 red_apple。冲突不了。
变量
直接写,默认是全局变量,不用纠结啥的,对标Python。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。 这里跟Python有点不一样,Python访问没有显示定义的变量是要报错的。
删除变量吗?那你想多了。
你用,或者不用,它就在那里,不卑不亢。
不想用了,置空(nil)就好。
数据类型
Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。这点直接对标Python。
在lua里面查看变量类型也是使用type函数。不过我没那个兴趣就是了。
这里面基本都可以对标Python,我只提一下比Python多的部分吧。
1、string,居然支持和数字进行算术运算。不过这里要求这个字符串是可以被转数字的。这个特性其实就是在背地里进行了类型转换而已。在C 里这就是一个运算符重载的事情而已。
2、还是string,可以用 [[[]]]来对标Python中的 ‘’’’’’。
3、依旧是string,可以使用 # 来计算字符串长度。例如:print(#“123456”),输出6。
这里插播一条:字符串的拼接不是 ,而是 … ,下面那个示例有体现。
table
上面没有讲清楚。对标的是字典。不过这个字典可以没有设置键,会有包分配,从1开始。键可以是数字或者是字符串。
代码语言:javascript复制a = {}
a["key"] = "value"
key = 10
a[key] = 22
a[key] = a[key] 11
for k, v in pairs(a) do
print(k .. " : " .. v)
end
代码语言:javascript复制local tbl = {"apple", "pear", "orange", "grape"}
for key, val in pairs(tbl) do
print("Key", key)
end
table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 都是 nil。
要删除键也很简单,将nil赋值给那个键、
常用方法:
tips: 当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。 可以使用以下方法来代替:
代码语言:javascript复制function table_leng(t)
local leng=0
for k, v in pairs(t) do
leng=leng 1
end
return leng;
end
function
这种把函数当成对象的行为还是第一次见哈。
代码语言:javascript复制function factorial1(n)
if n == 0 then
return 1
else
return n * factorial1(n - 1)
end
end
print(factorial1(5))
factorial2 = factorial1
print(factorial2(5))
function 可以以匿名函数(anonymous function)的方式通过参数传递:
代码语言:javascript复制function testFun(tab,fun)
for k ,v in pairs(tab) do
print(fun(k,v));
end
end
tab={key1="val1",key2="val2"};
testFun(tab,
function(key,val)--匿名函数
return key.."="..val;
end
);
不过我想我应该没有吃饱那么撑去炫这个技吧,上面那个性能是比这个要差吗?
线程和自定义类型后面再说吧。
变量
在Python中,函数等块内部的就是局部变量,如果要在其中声明全局变量则需要加 global 关键字。 lua 则相反,默认统统是全局变量,如果要声明局部变量则要加 local 关键字。
局部变量的作用域为从声明位置开始到所在语句块结束。
代码语言:javascript复制a = 5 -- 全局变量
local b = 5 -- 局部变量
function joke()
c = 5 -- 全局变量
local d = 6 -- 局部变量
end
joke()
print(c,d) --> 5 nil
do
local a = 6 -- 局部变量
b = 6 -- 对局部变量重新赋值
print(a,b); --> 6 6
end
print(a,b) --> 5 6
Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
这点可以直接对标Python。 不过呢,当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略:
代码语言:javascript复制a. 变量个数 > 值的个数 按变量个数补足nil
b. 变量个数 < 值的个数 多余的值会被忽略
小tips:多值赋值经常用来交换变量,或将函数调用返回给变量。
应该尽可能的使用局部变量,有两个好处:
代码语言:javascript复制1. 避免命名冲突。
2. 访问局部变量的速度比全局变量更快。
索引
这是 lua 和 Python、C 等语言不同的地方了,lua 是从1开始计数的,回忆一下前面的 table 示例。
循环
示例:
代码语言:javascript复制while( true )
do
print("循环将永远执行下去")
end
lua中只有break,没有continue,不过人的智慧是有无限可能的:
代码语言:javascript复制for i = 10, 1, -1 do
repeat
if i == 5 then
print("continue code here")
break
end
print(i, "loop code here")
until true
end
分支语句
概念直接对标Python,不过需要注意的是,lua 里面,0是true。
代码语言:javascript复制if(0)
then
print("0 为 true")
end
有if else、if… else if… else…写法,不过我不想写而已。
函数
代码语言:javascript复制[local] function function_name([argument1, argument2, argument3..., argumentn])
function_body
return result_params_comma_separated
end
[] 这个是可选的意思,不会有人不知道吧???
代码语言:javascript复制function max(a, b)
if (a > b) then
res = a;
else
res = b;
end
return res;
end
该怎么调用?跟Python一样。
同样的,lua 也支持将函数作为参数进行传参,我更愿意称之为:“函数指针”。
同样,多返回值性质也直接对标Python。
可变参数也一样,对标:
代码语言:javascript复制function add(...)
local s = 0
for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数组
s = s v
end
return s
end
print(add(1,2,3,4))
运算符
该咋样就咋样。我提一下和Python里面不一样的(这里提一下,Python里面的 / 就是除法,不是整除)
1、~=:不等于,检测两个值是否相等,相等返回 false,否则返回 true。 2、… :连接运算符,连接两个字符串。 3、 # :返回字符串或表的长度。
运算符优先级一般我是不管的,只要我括号加的勤。
字符串操作
放一些常用的:
1、
代码语言:javascript复制string.gsub(mainString,findString,replaceString,num)
在字符串中替换。 mainString 为要操作的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换),如:
string.gsub(“aaaa”,“a”,“z”,3); zzza 3
2、
代码语言:javascript复制string.char(arg) 和 string.byte(arg[,int])
char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。
string.char(97,98,99,100) abcd string.byte(“ABCD”,4) 68 string.byte(“ABCD”) 65
3、
代码语言:javascript复制string.len(arg)
计算字符串长度。
string.len(“abc”) 3
4、
代码语言:javascript复制string.rep(string, n)
返回字符串string的n个拷贝
string.rep(“abcd”,2) abcdabcd
5、
代码语言:javascript复制string.format(...)
返回一个类似printf的格式化字符串
string.format(“the value is:%d”,4) the value is:4
数组
一维数组
代码语言:javascript复制array = {"Lua", "Tutorial"}
for i= 0, 2 do
print(array[i])
end
nil Lua Tutorial
多维数组
代码语言:javascript复制-- 初始化数组
array = {}
for i=1,3 do
array[i] = {}
for j=1,3 do
array[i][j] = i*j
end
end
-- 访问数组
for i=1,3 do
for j=1,3 do
print(array[i][j])
end
end
1 2 3 2 4 6 3 6 9
区间迭代器
这个也直接对标Python吧,C 11也引入了。
代码语言:javascript复制array = {"Google", "Runoob"}
for key,value in ipairs(array)
do
print(key, value)
end
模块与包
从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。
代码语言:javascript复制-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
-- 定义一个常量
module.constant = "这是一个常量"
-- 定义一个函数
function module.func1()
io.write("这是一个公有函数!n")
end
local function func2()
print("这是一个私有函数!")
end
function module.func3()
func2()
end
return module
Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。
require("<模块名>") or require “<模块名>”
代码语言:javascript复制-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
print(module.constant)
module.func3()
加载机制
对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。
require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 “~/lua/” 路径加入 LUA_PATH 环境变量里:
代码语言:javascript复制#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"
文件路径以 “;” 号分隔,最后的 2 个 “;;” 表示新加的路径后面加上原来的默认路径。
接着,更新环境变量参数,使之立即生效。
代码语言:javascript复制source ~/.profile
面向对象
其实我个人觉得不是很有必要哈,做好自己分内的事情就好了。还有Python。 但是既然人家有这个功能,那不妨提一提,省的以后看到代码里面写了看不懂就很尬。
不多说,放码过去:
代码语言:javascript复制-- 元类
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()
那继承呢?
代码语言:javascript复制-- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()
Square = Shape:new()
-- 派生类方法 new
function Square:new (o,side)
o = o or Shape:new(o,side)
setmetatable(o, self)
self.__index = self
return o
end
-- 派生类方法 printArea
function Square:printArea ()
print("正方形面积为 ",self.area)
end
-- 创建对象
mysquare = Square:new(nil,10)
mysquare:printArea()
Rectangle = Shape:new()
-- 派生类方法 new
function Rectangle:new (o,length,breadth)
o = o or Shape:new(o)
setmetatable(o, self)
self.__index = self
self.area = length * breadth
return o
end
-- 派生类方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()
函数重写呢?
代码语言:javascript复制-- 派生类方法 printArea
function Square:printArea ()
print("正方形面积 ",self.area)
end
内存管理
对标Python,不用我们管,哈哈
文件IO
基本一个语言学的差不多了都要对文件进行IO操作,C 是这样,Python是这样,lua也一样。
不喜欢废话,直接用案例说话吧:
代码语言:javascript复制-- 以只读方式打开文件
file = io.open("test.lua", "r")
-- 设置默认输入文件为 test.lua
io.input(file)
-- 输出文件第一行
print(io.read())
-- 关闭打开的文件
io.close(file)
-- 以附加的方式打开只写文件
file = io.open("test.lua", "a")
-- 设置默认输出文件为 test.lua
io.output(file)
-- 在文件最后一行添加 Lua 注释
io.write("-- test.lua 文件末尾注释")
-- 关闭打开的文件
io.close(file)
io.read() 的参数列表:
“线程” – 协同程序
放到最后什么意思大家也都明白哈。
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
线程 VS 协同
代码语言:javascript复制线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
基本语法与演示:
各方法的演示:
代码语言:javascript复制-- coroutine_test.lua 文件
co = coroutine.create(
function(i)
print(i);
end
)
coroutine.resume(co, 1) -- 1
print(coroutine.status(co)) -- dead
print("----------")
co = coroutine.wrap(
function(i)
print(i);
end
)
co(1)
print("----------")
co2 = coroutine.create(
function()
for i=1,10 do
print(i)
if i == 3 then
print(coroutine.status(co2)) --running
print(coroutine.running()) --thread:XXXXXX
end
coroutine.yield()
end
end
)
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
print(coroutine.status(co2)) -- suspended
print(coroutine.running())
print("----------")
coroutine在底层实现就是一个线程,当create一个coroutine的时候就是在新线程中注册了一个事件,当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。
众所周知,多线程性质没有好弄,所以我从网上多找了几份代码放这儿,看着研究:
代码语言:javascript复制-- 实例
function foo (a)
print("foo 函数输出", a)
return coroutine.yield(2 * a) -- 返回 2*a 的值
end
co = coroutine.create(function (a , b)
print("第一次协同程序执行输出", a, b) -- co-body 1 10
local r = foo(a 1)
print("第二次协同程序执行输出", r)
local r, s = coroutine.yield(a b, a - b) -- a,b的值为第一次调用协同程序时传入
print("第三次协同程序执行输出", r, s)
return b, "结束协同程序" -- b的值为第二次调用协同程序时传入
end)
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
--输出结果:
第一次协同程序执行输出 1 10
foo 函数输出 2
main true 4
--分割线----
第二次协同程序执行输出 r
main true 11 -9
---分割线---
第三次协同程序执行输出 x y
main true 10 结束协同程序
---分割线---
main false cannot resume dead coroutine
---分割线---
resume和yield的配合强大之处在于,resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。
生产-消费者 实例
代码语言:javascript复制local newProductor
function productor()
local i = 0
while true do
i = i 1
send(i) -- 将生产的物品发送给消费者
end
end
function consumer()
while true do
local i = receive() -- 从生产者那里得到物品
print(i)
end
end
function receive()
local status, value = coroutine.resume(newProductor)
return value
end
function send(x)
coroutine.yield(x) -- x表示需要发送的值,值返回以后,就挂起该协同程序
end
-- 启动程序
newProductor = coroutine.create(productor)
consumer()
(慢慢研究,我得去睡了)