OpenResty使用Lua大全(一)Lua语法入门实战
一)lua介绍1993 年在巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro in Brazil)诞生了一门编程语言,发明者是该校的三位研究人员,他们给这门语言取了个浪漫的名字——Lua,在葡萄牙语里代表美丽的月亮。事实证明她没有糟蹋这个优美的单词,Lua 语言正如它名字所预示的那样成长为一门简洁、优雅且富有乐趣的语言。
文章目录
系列文章索引
OpenResty使用Lua大全(一)Lua语法入门实战
OpenResty使用Lua大全(二)在OpenResty中使用Lua
OpenResty使用Lua大全(三)OpenResty使用Json模块解析json
OpenResty使用Lua大全(四)OpenResty中使用Redis
OpenResty使用Lua大全(五)OpenResty中使用MySQL
OpenResty使用Lua大全(六)OpenResty发送http请求
OpenResty使用Lua大全(七)OpenResty使用全局缓存
OpenResty使用Lua大全(八)OpenResty执行流程与阶段详解
OpenResty使用Lua大全(九)实战:nginx-lua-redis实现访问频率控制
OpenResty使用Lua大全(十)实战: Lua + Redis 实现动态封禁 IP
OpenResty使用Lua大全(十一)实战: nginx实现接口签名安全认证
OpenResty使用Lua大全(十二)实战: 动手实现一个网关框架
一、OpenResty使用Lua入门
1、hello world
nginx.conf修改成这样:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type text/html;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
charset utf-8;
location /lua {
# 直接将脚本写在这里,会执行
content_by_lua 'ngx.say("hello world")';
}
location / {
root html;
index index.html index.htm;
}
}
}
执行nginx -t
检查配置,执行nginx -s reload
更新配置。
访问:
2、nginx内部变量
名称 说明
$arg_name 请求中的name参数
$args 请求中的参数
$binary_remote_addr 远程地址的二进制表示
$body_bytes_sent 已发送的消息体字节数
$content_length HTTP请求信息里的"Content-Length"
$content_type 请求信息里的"Content-Type"
$document_root 针对当前请求的根路径设置值
$document_uri 与$uri相同; 比如 /test2/test.php
$host 请求信息中的"Host",如果请求中没有Host行,则等于设置的服务器名
$hostname 机器名使用 gethostname系统调用的值
$http_cookie cookie 信息
$http_referer 引用地址
$http_user_agent 客户端代理信息
$http_via 最后一个访问服务器的Ip地址。
$http_x_forwarded_for 相当于网络访问路径
$is_args 如果请求行带有参数,返回“?”,否则返回空字符串
$limit_rate 对连接速率的限制
$nginx_version 当前运行的nginx版本号
$pid worker进程的PID
$query_string 与$args相同
$realpath_root 按root指令或alias指令算出的当前请求的绝对路径。其中的符号链接都会解析成真是文件路径
$remote_addr 客户端IP地址
$remote_port 客户端端口号
$remote_user 客户端用户名,认证用
$request 用户请求
$request_body 这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义
$request_body_file 客户端请求主体信息的临时文件名
$request_completion 如果请求成功,设为"OK";如果请求未完成或者不是一系列请求中最后一部分则设为空
$request_filename 当前请求的文件路径名,比如/opt/nginx/www/test.php
$request_method 请求的方法,比如"GET"、"POST"等
$request_uri 请求的URI,带参数; 比如http://localhost:88/test1/
$scheme 所用的协议,比如http或者是https
$server_addr 服务器地址,如果没有用listen指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费)
$server_name 请求到达的服务器名
$server_port 请求到达的服务器端口号
$server_protocol 请求的协议版本,"HTTP/1.0"或"HTTP/1.1"
$uri 请求的URI,可能和最初的值有不同,比如经过重定向之类的
写个配置文件,测试一下$uri变量
location /test_url {
echo "url:$uri";
}
location /test_url {
echo "url:$uri";
echo "full url : $host$request_uri";
}
重启nginx,访问
再此基础上测试$args变量
location /test_url {
echo "url:$uri----args:$args";
}
再此基础上测试$arg_name变量
location /test_url {
echo "url:$uri----args:$args--------arg_name:$arg_name";
}
说明一下,$arg_name表示取名为name的参数,如果想取其他名称的参数可以对应的取该参数名
location /test_url {
echo "url:$uri --- args:$args --- arg_name:$arg_name <br/>";
echo "arg_user:$arg_user --- arg_age:$arg_age<br/>";
echo "arg_test:$arg_test";
}
test参数名不存在,则为空。
自定义变量
location /test_def {
set $name "rainbow";
echo $name;
}
set 设置的变量 为局部变量,其他请求无法获取此$name的值
location /test_def {
set $name "rainbow";
echo_exec /test_def2; ##内部跳转,可以传递变量
}
location /test_def2 {
echo $name;
}
二、Lua入门
1、简介
一)lua介绍
1993 年在巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro in Brazil)诞生了一门编程语言,发明者是该校的三位研究人员,他们给这门语言取了个浪漫的名字——Lua,在葡萄牙语里代表美丽的月亮。事实证明她没有糟蹋这个优美的单词,Lua 语言正如它名字所预示的那样成长为一门简洁、优雅且富有乐趣的语言。
Lua 从一开始就是作为一门方便嵌入(其它应用程序)并可扩展的轻量级脚本语言来设计的,因此她一直遵从着简单、小巧、可移植、快速的原则,官方实现完全采用 ANSI C 编写,能以 C 程序库的形式嵌入到宿主程序中。LuaJIT 2 和标准 Lua 5.1 解释器采用的是著名的 MIT 许可协议。正由于上述特点,所以 Lua 在游戏开发、机器人控制、分布式应用、图像处理、生物信息学等各种各样的领域中得到了越来越广泛的应用。其中尤以游戏开发为最,许多著名的游戏
Lua 和 LuaJIT 的区别
Lua 非常高效,它运行得比许多其它脚本(如 Perl、Python、Ruby)都快,这点在第三方的独立测评中得到了证实。尽管如此,仍然会有人不满足,他们总觉得“嗯,还不够快!”。LuaJIT 就是一个为了再榨出一些速度的尝试,它利用即时编译(Just-in Time)技术把 Lua 代码编译成本地机器码后交由 CPU 直接执行。LuaJIT 2 的测评报告表明,在数值运算、循环与函数调用、协程切换、字符串操作等许多方面它的加速效果都很显著。凭借着 FFI 特性,LuaJIT 2 在那些需要频繁地调用外部 C/C++ 代码的场景,也要比标准 Lua 解释器快很多。目前 LuaJIT 2 已经支持包括 i386、x86_64、ARM、PowerPC 以及 MIPS 等多种不同的体系结构。
LuaJIT 是采用 C 和汇编语言编写的 Lua 解释器与即时编译器。LuaJIT 被设计成全兼容标准的 Lua 5.1 语言,同时可选地支持 Lua 5.2 和 Lua 5.3 中的一些不破坏向后兼容性的有用特性。因此,标准 Lua 语言的代码可以不加修改地运行在 LuaJIT 之上。LuaJIT 和标准 Lua 解释器的一大区别是,LuaJIT 的执行速度,即使是其汇编编写的 Lua 解释器,也要比标准 Lua 5.1 解释器快很多,可以说是一个高效的 Lua 实现。另一个区别是,LuaJIT 支持比标准 Lua 5.1 语言更多的基本原语和特性,因此功能上也要更加强大。
2)应用场景
1、在很多时候,我们可以将Lua直接嵌入到我们的应用程序中,如游戏、监控服务器等。这样的应用方式对于程序的最终用户而言是完全透明的,但是对于程序本身,其扩展性将会得到极大的增强。
2、将Lua视为一种独立的脚本语言,通过它来帮助我们完成一些软件产品的辅助性工具的开发。比如在我们之前的数据分析产品中,我们通过编写Lua脚本,将每个用户不同格式的数据重新格式化为我们的软件平台能够读取的格式,之后再将格式化的后的数据加载到数据库中,或者是写入我们的分析引擎可以识别的数据分析文件中。这其中Lua仅仅用于文件格式的规格化过程,至于此后的操作,都是通过Lua调用我们的C语言导出函数来完成的。
3、将Lua应用于应用程序的动态配置部分。比如移动智能设备或嵌入式设备,它们的显示分辨率在很多情况下都是非标准的,如果我们为每一款设备都维护一套相关的配置信息,这无疑会加大我们程序的维护开销,如果我们将这段动态配置逻辑交由Lua脚本完成,那么这对于程序配置的灵活性而言,将会得到很大的提高。甚至可以是这样,运行在移动终端设备上的应用程序,在启动主窗体之前先和服务器建立连接,在服务器确认设备的各种参数后,再将和该设备显示相关的Lua脚本发送给设备客户端,这样客户端在得到Lua脚本之后,就可以立刻执行它以得到最新的动态配置信息。
3)主要优势
1. 高效性:
作为一种脚本语言,Lua的高效是众所周知的,因此在实际应用中,很多大型程序都会考虑将代码中易变的部分用Lua来编写。这不但没有明显降低系统的运行效率,反而使程序的稳定性和可扩展性得到了显著的提升。
2. 可移植性:
在官方网站中提供了基于多种平台的发布包,如Linux/Unix、Windows、Symbian和Pocket PC等。
3. 可嵌入性:
在语言设计之初,Lua就被准确的定位为嵌入式脚本语言,因此Lua的设计者们为Lua提供了与其他编程语言之间的良好交互体验,这特别体现在和C/C++之间的交互上。对于其他语言,如Java和C#,也可以将Lua作为其嵌入式脚本引擎,并在代码中进行直接的交互。
4. 简单强大:
尽管是过程化脚本语言,但由于Lua的设计者们为Lua提供了meta-mechanisms机制,这不仅使Lua具备了一些基本的面向对象特征,如对象和继承,而且仍然保持了过程化语言所具有的语法简单的特征。
5. 小巧轻便:
在最新版本(5.2.0)的Lua中,仅仅包含了大约20000行的C语言代码,编译后的库文件大小约为240K左右,因此这对于很多资源有限的平台有着极强的吸引力。
6. 免费开源:
MIT Licence可以让Lua被免费的用于各种商业程序中。
1、hello world
# 1、编写
vi helloworld.lua
# 内容
print("hello world");
# 2、运行,使用我们之前安装OpenResty自动安装的luajit
[root@localhost lua]# /usr/local/openresty/luajit/bin/luajit helloworld.lua
hello world
2、基本语法
(1)注释
-- 两个减号是单行注释
--[[
多行注释
多行注释
--]]
(2)数据类型
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
数据类型 描述
nil 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false),类似java的null。
boolean 包含两个值:false和true。
number 表示双精度类型的实浮点数
string 字符串由一对双引号或单引号来表示
function 由 C 或 Lua 编写的函数
userdata 表示任意存储在变量中的C数据结构
thread 表示执行的独立线路,用于执行协同程序
table Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 类似java的Map。
print(type("Hello world")) --> string
print(type(10.4*3)) --> number
print(type(print)) --> function
print(type(type)) --> function
print(type(true)) --> boolean
print(type(nil)) --> nil
print(type(type(X))) --> string
-- 对 table 的索引使用方括号 []。Lua 也提供了 . 操作
t[i]
t.i -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用
一)nil类型
> print(type(a))
nil
-------------------------
对于全局变量和 table,nil 还有一个"删除"作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉,执行下面代码就知:
tab1 = { key1 = "val1", key2 = "val2" }
for k, v in pairs(tab1) do
print(k .. " - " .. v)
end
print('---------')
tab1.key1 = nil
for k, v in pairs(tab1) do
print(k .. " - " .. v)
end
-----------------------------
判断nil类型 作比较时应该加上双引号 ":
type(X) ---> 返回的类型 其实是string
> type(X)==nil
false
> type(X)=="nil"
true
一)boolean(布尔)
布尔类型,可选值 true/false;
Lua 中 nil 和 false 为“假”,其它所有值均为“真”。比如 0 和空字符串就是“真”;
local a = true
local b = 0
local c = nil
if a then
print("a") -->output:a
else
print("not a") --这个没有执行
end
if b then
print("b") -->output:b
else
print("not b") --这个没有执行
end
if c then
print("c") --这个没有执行
else
print("not c") -->output:not c
end
二)number(数字)
Number 类型用于表示实数,和 C/C++ 里面的 double 类型很类似。
可以使用数学函数 math.floor(向下取整)和 math.ceil(向上取整)进行取整操作。
local count = 10
local order = 3.99
local score = 98.01
print(math.floor(order)) -->output:3
print(math.ceil(score)) -->output:99
三)string(字符串)
Lua 中有三种方式表示字符串:
1、使用一对匹配的单引号。例:'hello'。
2、使用一对匹配的双引号。例:"abclua"。
local str1 = 'hello world'
local str2 = "hello lua"
print(str1) -->output:hello world
print(str2) -->output:hello lua
转义字符
string = hello\',\"\",\\n,\\t
3、字符串还可以用一种长括号(即[[ ]])括起来的方式定义。整个词法分析过程将不受分行限制,不处理任何转义符。
在 Lua 实现中,Lua 字符串一般都会经历一个“内化”(intern)的过程,
即两个完全一样的 Lua 字符串在 Lua 虚拟机中只会存储一份。
每一个 Lua 字符串在创建时都会插入到 Lua 虚拟机内部的一个全局的哈希表中。
另外,Lua 的字符串是不可改变的值,不能像在 c 语言中那样直接修改字符串的某个字符,而是根据修改要求来创建一个新的字符串。Lua 也不能通过下标来访问字符串的某个字符。
它支持一些转义字 符,列表如下
\a 响铃
\b 退格 (back space)
\f 提供表格(form feed)
\n 换行(newline)
\r 回车(carriage return)
\t 水平tab(horizontal tab)
\v 垂直tab(vertical tab)
\\ 反斜杠(backslash)
\" 双引号(double quote)
\' 单引号(single quote)
定义:"add\name",'hello' 字符串
local str3 = [["add\name",'hello']]
print(str3) -->output:"add\name",'hello'
4、字符串连接使用的是 ..
> print("a" .. 'b')
ab
5、字符串与number类型转换
print(tonumber("10") == 10)
print(tostring(10) == "10")
6、使用 # 来计算字符串的长度,放在字符串前面
print(#"this is string")
=====================================================
注意:string不可修改
local a= "this is a";
a = "this is new a ";
local b= "this is new a ";
1)在 Lua 实现中,Lua 字符串一般都会经历一个“内化”(intern)的过程,即两个完全一样的 Lua 字符串在 Lua 虚拟机中只会存储一份。
每一个 Lua 字符串在创建时都会插入到 Lua 虚拟机内部的一个全局的哈希表中。
2)Lua 的字符串是不可改变的值,不能像在c语言中那样直接修改字符串的某个字符,而是根据修改要求来创建一个新的字符串。
Lua 也不能通过下标来访问字符串的某个字符。
Lua的字符串和其它对象都是自动内存管理机制所管理的对象,不需要担心字符串的内存分配和释放提供了效率,安全
s='123456789';
s1 = s;
s='abcdwff';
print(s);
print(s1);
--------------------
s='123456789'
local s1 = string.sub(s, 2, 5)
print(s)
(3)变量
-- test.lua 文件脚本
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
(4)函数
一)function (函数)
有名函数:
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
optional_function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local。
function 函数定义关键字
function_name: 指定函数名称。
argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。
function_body: 函数体,函数中需要执行的代码语句块。
result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开
end:函数定义结束关键字
1)函数实例
--[[ 函数返回两个值的最大值 --]]
function max(num1, num2)
if (num1 > num2) then
result = num1;
else
result = num2;
end
return result;
end
-- 调用函数
print("两值比较最大值为 ",max(10,4))
print("两值比较最大值为 ",max(5,6))
=============================
匿名函数
optional_function_scope function_name = function (argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
有名函数的定义本质上是匿名函数对变量的赋值。为说明这一点,考虑
function foo()
end
等价于
foo = function ()
end
类似地,
local function foo()
end
等价于
local foo = function ()
end
==============================
local function 与 function 区别
1 使用function声明的函数为全局函数,在被引用时可以不会因为声明的顺序而找不到
2 使用local function声明的函数为局部函数,在引用的时候必须要在声明的函数后面
function test()
test2()
test1()
end
local function test1()
print("hello test1")
end
function test2()
print("hello test2")
end
test()
-----------------
local function test1()
print("hello test1")
end
function test()
test2()
test1()
end
function test2()
print("hello test2")
end
test()
==========================
函数参数
1) 将函数作为参数传递给函数
local myprint = function(param)
print("这是打印函数 - ##",param,"##")
end
local function add(num1,num2,functionPrint)
result = num1 + num2
functionPrint(result)
end
add(2,5,myprint)
2)传参数,lua参数可变
local function foo(a,b,c,d)
print(a,b,c,d)
end
a、若参数个数大于形参个数,从左向右,多余的实参被忽略
b、若实参个数小于形参个数,从左向右,没有被初始化的形参被初始化为nil
------------------
c、Lua还支持变长参数。用...表示。此时访问参数也要用...,如:
function average(...)
result = 0
local arg={...}
for i,v in ipairs(arg) do
result = result + v
end
print("总共传入 " .. #arg .. " 个数")
return result/#arg
end
print("平均值为",average(1,2,3,4,5,6))
==============================
3)返回值
Lua函数允许返回多个值,返回多个值时,中间用逗号隔开
函数返回值的规则:
1)若返回值个数大于接收变量的个数,多余的返回值会被忽略掉;
2)若返回值个数小于参数个数,从左向右,没有被返回值初始化的变量会被初始化为nil
local function init()
return 1,"lua";
end
local x,y,z = init();
print(x,y,z);
注意:
1)当一个函数返回一个以上的返回值,且函数调用不是一个列表表达式的最后一个元素,那么函数只返回第一个返回值
local function init()
return 1,"lua";
end
local x,y,z = 2,init(); --local x,y,z = init(),2;
print(x,y,z);
2)如果你确保只取函数返回值的第一个值,可以使用括号运算符
local function init()
return 1,"lua";
end
print((init()))
(5)table
一)table (表)
Table 类型实现了一种抽象的“关联数组”。即可用作数组,也可以用作map。
lua中没有数组和map,都是用table这个类型
--数组
java int[] intArr = new int[]{1,2,3,4,5,6};
intArr[0]
intArr[1]
--map----> key value
HashMap map
map.add(key,value)
-- 初始化表
mytable = {}
-- 指定值
mytable[1]= "Lua"
mytable[2]= "Lua2"
mytalbe["k1"] = v1;
-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存
lua类似数组的table ,索引值从1开始,,而不是0
mytable={1,2,3,4,5}
mytalbe[1]
mytable={"a","b","hello","world"}
mytable1 = {key1 = "v1",k2="v2",k3="v3"}
mytable2 = {"a",key1 = "v1","b",k2="v2",k3="v3","hello","world"}
print(mytable[1],mytable[2],mytable[3],mytable[4]);
print("------------------")
print(mytable1["key1"],mytable1["k2"],mytable1["k3"]);
print("------------------")
print(mytable2[1],mytable2["key1"],mytable2[2],mytable2["k2"],mytable2[3],mytable2[4]);
talbe key可以为 number 或字符串,,也可以是其他类型
table 是内存地址 赋值给变量
二)table进行赋值给变量,其实是把内存地址给了变量,变量只是引用了内存地址
local mytable1 = {"a",key1 = "v1","b",k2="v2",k3="v3","hello","world"}
local mytable2 = mytable1
mytable2[1] = "aa"
print(mytable2[1])
print(mytable1[1])
mytable2 = nil --移除的是引用
print("-------------")
print(mytable1[1])
内存指针--》C++
3、运算符
一)算术运算符
+ 加法
- 减法
* 乘法
/ 除法
% 取余
^ 乘幂
- 负号
print(1 + 2) -->打印 3
print(5 / 10) -->打印 0.5。 这是Lua不同于c语言的
print(5.0 / 10) -->打印 0.5。 浮点数相除的结果是浮点数
-- print(10 / 0) -->注意除数不能为0,计算的结果会出错
print(2 ^ 10) -->打印 1024。 求2的10次方
local num = 1357
print(num % 2) -->打印 1
print((num % 2) == 1) -->打印 true。 判断num是否为奇数
print((num % 5) == 0) -->打印 false。判断num是否能被5整数
二)关系运算符
== 等于,检测两个值是否相等,相等返回 true,否则返回 false
~= 不等于,检测两个值是否相等,相等返回 false,否则返回 true 不想java !=,,~=
> 大于,如果左边的值大于右边的值,返回 true,否则返回 false
< 小于,如果左边的值大于右边的值,返回 false,否则返回 true
>= 大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false
<= 小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false
print(1 < 2) -->打印 true
print(1 == 2) -->打印 false
print(1 ~= 2) -->打印 true
local a, b = true, false
print(a == b) -->打印 false
注意:table, userdate 和函数;判断的是引用地址的判断
local a = { x = 1, y = 0}
local b = { x = 1, y = 0}
if a == b then
print("a==b")
else
print("a~=b")
end
三)逻辑运算符
and 逻辑与操作符。(A and B) 若 A 为 假,则返回 A,否则返回 B
or 逻辑或操作符。(A or B) 若 A 为 真,则返回 A,否则返回 B
not 逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false。 永远只返回 true 或者 false
local c = nil
local d = 0
local e = 100
print(c and d) -->打印 nil
print(c and e) -->打印 nil
print(d and e) -->打印 100
print(c or d) -->打印 0
print(c or e) -->打印 100
print(d or e) -->打印 0
print(not c) -->打印 true
print(not d) -->打印 false
短路取值 原理
and 与,,,if(a and b and c)
a and b and c a为真,b为真,c为真 返回c
a为真,b为假,c为真 返回b
a为假 bc不管bc为什么值 返回a
一路去判断变量值是否为真,一旦遇到某个变量为假,就立刻短路返回 返回值就是 假的变量值
a or b or c 一路判断变量值是否为真,一但遇到某个变量为真,就立刻短路返回,返回值就是 真的变量值
a为真,bc 返回a
a为假,b为真,不管c是什么值 返回b
四)其他运算符
.. 连接两个字符串 a..b ,其中 a 为 "Hello " , b 为 "World", 输出结果为 "Hello World"。
# 一元运算符,返回字符串或表的长度。 #"Hello" 返回 5
很多字符串连接 我们如果采用..这个运算符 ,性能是很低
推荐使用 table 和 table.concat() 来进行很多字符串的拼接
五)优先级
^
not # -
* / %
+ -
..
< > <= >= == ~=
and
or
------------------------
local a, b = 1, 2
local x, y = 3, 4
local i = 10
local res = 0
res = a + i < b/2 + 1 -->等价于res = (a + i) < ((b/2) + 1)
res = 5 + x^2*8 -->等价于res = 5 + ((x^2) * 8)
res = a < y and y <=x -->等价于res = (a < y) and (y <= x)
4、控制语句
一)条件 - 控制结构 if-else
if-else 是我们熟知的一种控制结构。Lua 跟其他语言一样,提供了 if-else 的控制结构。
1)单个 if 分支 型
if 条件 then
--body
end
条件为真 ,执行if中的body
-----------------------
x = 10
if x > 0 then
print("分支一")
end
----
x = 10
if (x > 0) then
print("分支一")
end
运行输出:分支一
2)两个分支 if-else 型
if 条件 then
--条件为真 执行此body
else
--条件为假 执行此body
end
----------
x = 10
if x > 0 then
print("分支一")
else
print("分支二")
end
运行输出:分支一
3)多个分支 if-elseif-else 型
if 条件一 then
--条件为真 执行此body
elseif 条件二 then
.....
elseif 条件三 then
.....
else
--条件为假 执行此body
end
score = 90
if score == 100 then
print("分支一")
elseif score >= 60 then
print("分支二")
--此处可以添加多个elseif
else
print("分支三")
end
运行输出:分支二
与 C 语言的不同之处是 else 与 if 是连在一起的,若将 else 与 if 写成 "else if" 则相当于在 else 里嵌套另一个 if 语句,如下代码:
score = 0
if score == 100 then
print("分支一")
elseif score >= 60 then
print("分支二")
else
if score > 0 then
print("分支三")
else
print("分支四")
end --与上一示例代码不同的是,此处要添加一个end
end
运行输出:分支四
二)循环 - while 型控制结构
Lua 跟其他常见语言一样,提供了 while 控制结构,语法上也没有什么特别的。
但是没有提供 do-while 型的控制结构,但是提供了功能相当的 repeat。
while 型控制结构语法如下,当表达式值为假(即 false 或 nil)时结束循环。也可以使用 break 语言提前跳出循环。
while 条件表达式 do
--body
end
示例代码,求 1 + 2 + 3 + 4 + 5 的结果
x = 1
sum = 0
while x <= 5 do
sum = sum + x
x = x + 1
end
print(sum) -->output 15
continue继续执行,lua是没有这个概念
break 终端循环,lua是有的
值得一提的是,Lua 并没有像许多其他语言那样提供类似 continue 这样的控制语句用来立即进入下一个循环迭代(如果有的话)。
因此,我们需要仔细地安排循环体里的分支,以避免这样的需求。
没有提供 continue,却也提供了另外一个标准控制语句 break,可以跳出当前循环。
例如我们遍历 table,查找值为 11 的数组下标索引:
local t = {1, 3, 5, 8, 11, 18, 21}
local i = 1
while i < #t do
if 11 == t[i] then
print("index[" .. i .. "] have right value[11]")
break
end
i = i + 1;
end
三)循环 - repeat 控制结构
repeat ---重复执行
--body
until 条件 条件为真时就结束
1)until的条件表达式 为真 就结束
2)repeat until 控制结构 ,他至少会执行一遍
Lua 中的 repeat 控制结构类似于其他语言(如:C++ 语言)中的 do-while,但是控制方式是刚好相反的。
简单点说,执行 repeat 循环体后,直到 until 的条件为真时才结束,
而其他语言(如:C++ 语言)的 do-while 则是当条件为假时就结束循环。
以下代码将会形成死循环:
x = 10
repeat
print(x)
until false
该代码将导致死循环,因为until的条件一直为假,循环不会结束
除此之外,repeat 与其他语言的 do-while 基本是一样的。同样,Lua 中的 repeat 也可以在使用 break 退出。
之前我们介绍了
条件判断的 if
用于循环迭代的 while、repeat
四)for 控制结构
for 语句有两种形式:数字 for 和范型 for。
1)数字型 for 的语法如下:
for var = begin, finish, step do
--body
end
关于数字 for 需要关注以下几点:
1.var 从 begin 变化到 finish,每次变化都以 step 作为步长递增 var
2.begin、finish、step 三个表达式只会在循环开始时执行一次
3.第三个表达式 step 是可选的,默认为 1
4.控制变量 var 的作用域仅在 for 循环内,需要在外面控制,则需将值赋给一个新的变量
5.循环过程中不要改变控制变量的值,那样会带来不可预知的影响
for i = 1, 5 do
print(i)
end
-- output:
1
2
3
4
5
for i = 1, 10, 2 do
print(i)
end
-- output:
1
3
5
7
9
for i = 10, 1, -1 do
print(i)
end
-- output:
10
9
8
7
6
5
4
3
2
1
如果不想给循环设置上限的话,可以使用常量 math.huge:
for i = 1, math.huge do
if (0.3*i^3 - 20*i^2 - 500 >=0) then
print(i)
break
end
end
2)for 泛型
对lua的table类型进行遍历
泛型 for 循环通过一个迭代器(iterator)函数来遍历所有值:
-- 打印数组a的所有值
local a = {"a", "b", "c", "d"}
for i, v in ipairs(a) do
print("index:", i, " value:", v)
end
-- output:
index: 1 value: a
index: 2 value: b
index: 3 value: c
index: 4 value: d
Lua 的基础库提供了 ipairs,这是一个用于遍历数组的迭代器函数。
在每次循环中,i 会被赋予一个索引值,同时 v 被赋予一个对应于该索引的数组元素值。
下面是另一个类似的示例,演示了如何遍历一个 table 中所有的 key
-- 打印table t中所有的key
for k in pairs(t) do
print(k)
end
pairs是可以把数组类型和哈希类型索引值,都会迭代出来
=========================================================
对于泛型 for 的使用,再来看一个更具体的示例。假设有这样一个 table,它的内容是一周中每天的名称:
local days = {
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"
}
k v ===》 v ,k
现在要将一个名称转换成它在一周中的位置。
为此,需要根据给定的名称来搜索这个 table。然而 在 Lua 中,通常更有效的方法是创建一个“逆向 table”。
例如这个逆向 table 叫 revDays,它以 一周中每天的名称作为索引,位置数字作为值:
local revDays = {
["Sunday"] = 1,
["Monday"] = 2,
["Tuesday"] = 3,
["Wednesday"] = 4,
["Thursday"] = 5,
["Friday"] = 6,
["Saturday"] = 7
}
接下来,要找出一个名称所对应的需要,只需用名字来索引这个 reverse table 即可:
local x = "Tuesday"
print(revDays[x]) -->3
当然,不必手动声明这个逆向 table,而是通过原来的 table 自动地构造出这个逆向 table:
local days = {
"Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday","Sunday"
}
local revDays = {}
for k, v in pairs(days) do
revDays[v] = k
end
-- print value
for k,v in pairs(revDays) do
print("k:", k, " v:", v)
end
-- output:
k: Tuesday v: 2
k: Monday v: 1
k: Sunday v: 7
k: Thursday v: 4
k: Friday v: 5
k: Wednesday v: 3
k: Saturday v: 6
这个循环会为每个元素进行赋值,其中变量 k 为 key(1、2、...),变量 v 为 value("Sunday"、"Monday"、...)。
值得一提的是,在 LuaJIT 2.1 中,ipairs() 内建函数是可以被 JIT 编译的,而 pairs() 则只能被解释执行。
因此在性能敏感的场景,应当合理安排数据结构,避免对哈希表进行遍历。
事实上,即使未来 pairs 可以被 JIT 编译,哈希表的遍历本身也不会有数组遍历那么高效,毕竟哈希表就不是为遍历而设计的数据结构。
五)break,return 关键字
1)break
语句 break 用来终止 while、repeat 和 for 三种循环的执行,并跳出当前循环体, 继续执行当前循环之后的语句。
下面举一个 while 循环中的 break 的例子来说明:
-- 计算最小的x,使从1到x的所有数相加和大于100
sum = 0
i = 1
while true do
sum = sum + i
if sum > 100 then
break
end
i = i + 1
end
print("The result is " .. i) -->output:The result is 14
在实际应用中,break 经常用于嵌套循环中。
2)return
return 主要用于从函数中返回结果,或者用于简单的结束一个函数的执行。
return 只能写在语句块的最后,一旦执行了 return语句,该语句之后的所有语句都不会再执行。
执行return方法,如果实在主函数体里面,不在语句块中;执行return 且没有返回值,之后的语句照样会执行
若要写在函数中间,则只能写在一个显式的语句块内,参见示例代码:
local function add(x, y)
return x + y
end
local function is_positive(x)
if x > 0 then
return x .. " is > 0"
else
return x .. " is not > 0"
end
print("function end!")
end
--由于return只出现在前面显式的语句块,所以此语句不注释也不会报错
--,但是不会被执行,此处不会产生输出
sum = add(10, 20)
print("The sum is " .. sum) -->output:The sum is 30
answer = is_positive(-10)
print(answer) -->output:-10 is is not > 0
有时候,为了调试方便,我们可以想在某个函数的中间提前 return,以进行控制流的短路。
此时我们可以将 return 放在一个 do ... end 代码块中,例如:
local function add(x, y)
print(1)
return
print(2)
end
--return 不放在语句块中,return 也没有返回值,不注释该语句,不会报错; 但会执行return之后的业务
local function add(x, y)
print(1)
do return end
print(2)
end
5、正则
与其他脚本语言不同的是,Lua并不使用POSIX规范的正则表达式[4](也写作regexp)来进行模式匹配。主要的原因出于程序大小方面的考虑:实现一个典型的符合POSIX标准的regexp大概需要4000行代码,这比整个Lua标准库加在一起都大。权衡之下,Lua中的模式匹配的实现只用了500行代码,当然这意味着不可能实现POSIX所规范的所有更能。然而,Lua中的模式匹配功能是很强大的,并且包含了一些使用标准POSIX模式匹配不容易实现的功能。
Lua正则中的特殊字符(元字符)包括如下几种:( ) . % + - * ?[ ] ^ $
lua正则支持其大写形式,大写形式代表非,即有%A = ^%a,%A表示与任意非字母配对。
6、string的操作
string的相关操作
1)string.upper(s)
接收一个字符串 s,返回一个把所有小写字母变成大写字母的字符串。
print(string.upper("Hello Lua")) -->output HELLO LUA
2)string.lower(s)
接收一个字符串 s,返回一个把所有大写字母变成小写字母的字符串。
print(string.lower("Hello Lua")) -->output hello lua
3)string.len(s)
接收一个字符串,返回它的长度
print(string.len("hello lua")) -->output 9
使用此函数是不推荐的。推荐使用 # 运算符来获取 Lua 字符串的长度。
print(#("hello lua")) -->output 9
由于 Lua 字符串的长度是专门存放的,并不需要像 C 字符串那样即时计算
因此获取字符串长度的操作总是 O(1) 的时间复杂度。
4)string.find(s, p [, init [, plain]]) --查找子字符串
在 s 字符串中第一次匹配 p 字符串。若匹配成功,则返回 p 字符串中出现的开始位置和结束位置;
若匹配失败,则返回 nil。
第三个参数 init 默认为 1,并且可以为负整数,
当 init 为负数时,表示从后往前数的字符个数;再从此索引处开始向后匹配字符串 p 。
第四个参数默认为 false,当其为 true 时,关闭模式匹配;只会把 p 看成一个字符串对待。
local find = string.find
print(find("abc cba", "ab"))
print(find("abc cba", "ab", 2))
print(find("abc cba", "ba", -1))
print(find("abc cba", "ba", -3))
-->output
1 2
nil
nil
6 7
--------------------------------------
模式匹配--lua正则表达式
local s = "am+df"
print(string.find(s, "m+", 1, false)) -- 2 2、
其中字符 + 在 Lua 正则表达式中的意思是匹配在它之前的那个字符一次或者多次,
也就是说 m+ 在正则表达式里会去匹配 m, mm, mmm ……。
print(string.find(s, "m+", 1, true)) -- 2 3
plain为true,关闭了模式匹配,p参数也就是"m+",当做了是个普通字符串,不进行模式匹配
5)string.format(formatstring, ...) --格式化输出
按照格式化参数 formatstring,返回后面 ... 内容的格式化版本。
编写格式化字符串的规则与标准 c 语言中 printf 函数的规则基本相同:
它由常规文本和指示组成,这些指示控制了每个参数应放到格式化结果的什么位置,及如何放入它们
一个指示由字符 % 加上一个字母组成,这些字母指定了如何格式化参数,
例如 d 用于十进制数、x 用于十六进制数、o 用于八进制数、f 用于浮点数、s 用于字符串等。
在字符 % 和字母之间可以再指定一些其他选项,用于控制格式的细节。
print(string.format("%.4f", 3.1415926)) -- 保留4位小数
print(string.format("%d %x %o", 31, 31, 31))-- 十进制数31转换成不同进制
d = 29; m = 7; y = 2015 -- 一行包含几个语句,用;分开
print(string.format("%s %02d/%02d/%d", "today is:", d, m, y))
-->output
3.1416
31 1f 37
today is: 29/07/2015
6)整型数字 与 字符互换
Lua 字符串总是由字节构成的。下标是从 1 开始的,这不同于像 C 和 Perl
string.byte(s [, i [, j ]])
返回字符 s[i]、s[i + 1]、s[i + 2]、······、s[j] 所对应的 ASCII 码。
i 的默认值为 1,即第一个字节;j 的默认值为 i
print(string.byte("abc", 1, 3))
print(string.byte("abc", 3)) -- 缺少第三个参数,第三个参数默认与第二个相同,此时为 3
print(string.byte("abc")) -- 缺少第二个和第三个参数,此时这两个参数都默认为 1
-->output
97 98 99
99
97
由于 string.byte 只返回整数,而并不像 string.sub 等函数那样(尝试)创建新的 Lua 字符串,
因此使用 string.byte 来进行字符串相关的扫描和分析是最为高效的,尤其是在被 LuaJIT 2 所 JIT 编译之后。
string.char (...)
接收 0 个或更多的整数(整数范围:0~255),返回这些整数所对应的 ASCII 码字符组成的字符串。
当参数为空时,默认是一个 0。
print(string.char(96, 97, 98))
print(string.char()) -- 参数为空,默认是一个0,
-- 你可以用string.byte(string.char())测试一下
print(string.char(65, 66))
--> output
`ab
AB
如果你只是想对字符串中的单个字节进行检查,使用 string.char 函数通常会更为高效。
7)string.match(s, p [, init])--匹配子字符串
在字符串 s 中匹配(模式)字符串 p,若匹配成功,则返回目标字符串中与模式匹配的子串;否则返回 nil。
第三个参数 init 默认为 1,并且可以为负整数,
当 init 为负数时,表示从后往前数的字符个数,在此索引处开始向后匹配字符串 p。
print(string.match("hello lua", "lua"))
print(string.match("lua lua", "lua", 2)) --匹配后面那个lua
print(string.match("lua lua", "hello"))
print(string.match("today is 27/7/2015", "%d+/%d+/%d+"))
-->output
lua
lua
nil
27/7/2015
string.match 目前并不能被 JIT 编译,应 尽量 使用 ngx_lua 模块提供的 ngx.re.match 等接口。
8)string.gmatch(s, p) --匹配多个字符串
返回一个迭代器函数,通过这个迭代器函数可以遍历到在字符串 s 中出现模式串 p 的所有地方。
s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do --匹配最长连续且只含字母的字符串
print(w)
end
-->output
hello
world
from
Lua
----------------------------------------
t = {}
s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%a+)=(%a+)") do --匹配两个最长连续且只含字母的
t[k] = v --字符串,它们之间用等号连接
end
for k, v in pairs(t) do
print (k,v)
end
-->output
to Lua
from world
此函数目前并不能被 LuaJIT 所 JIT 编译,而只能被解释执行。
应 尽量 使用 ngx_lua 模块提供的 ngx.re.gmatch 等接口。
9)string.rep(s, n) --字符串拷贝
返回字符串 s 的 n 次拷贝。
print(string.rep("abc", 3)) --拷贝3次"abc"
-->output abcabcabc
10)string.sub(s, i [, j]) --截取子字符串
返回字符串 s 中,索引 i 到索引 j 之间的子字符串。当 j 缺省时,默认为 -1,也就是字符串 s 的最后位置。
i 可以为负数。当索引 i 在字符串 s 的位置在索引 j 的后面时,将返回一个空字符串。
print(string.sub("Hello Lua", 4, 7))
print(string.sub("Hello Lua", 2))
print(string.sub("Hello Lua", 2, 1)) --看到返回什么了吗
print(string.sub("Hello Lua", -3, -1))
-->output
lo L
ello Lua
Lua
11)string.gsub(s, p, r [, n]) --替换子字符串
将目标字符串 s 中所有的子串 p 替换成字符串r。可选参数n,表示限制替换次数。
返回值有两个,第一个是被替换后的字符串,第二个是替换了多少次。
print(string.gsub("Lua Lua Lua", "Lua", "hello"))
print(string.gsub("Lua Lua Lua", "Lua", "hello", 2)) --指明第四个参数
-->output
hello hello hello 3
hello hello Lua 2
此函数不能为 LuaJIT 所 JIT 编译,而只能被解释执行。一般我们推荐使用 ngx_lua 模块提供的 ngx.re.gsub 函数。
12)string.reverse (s) --反转
接收一个字符串 s,返回这个字符串的反转。
print(string.reverse("Hello Lua")) --> output: auL olleH
7、table的操作
Lua中table内部实际采用哈希表和数组分别保存键值对、普通值;下标从1开始
不推荐混合使用这两种赋值方式。
local color={first="red", "blue", third="green", "yellow"}
print(color["first"]) --> output: red
print(color[1]) --> output: blue
print(color["third"]) --> output: green
print(color[2]) --> output: yellow
print(color[3]) --> output: nil
一)table.getn 获取长度
相关于取长度操作符写作一元操作 #。
字符串的长度是它的字节数(就是以一个字符一个字节计算的字符串长度)。
对于常规的数组,里面从 1 到 n 放着一些非空的值的时候,它的长度就精确的为 n,即最后一个值的下标。
local tblTest1 = { 1, a = 2, 3 }
print("Test1 " .. table.getn(tblTest1))
此table的长度为2,可是明明是三个数值啊,这里要说明一下,getn 只能执行数值型的table,即数值索引的值.
忽略哈希值类型的键值对map,即不包含字符索引值
local tblTest1 = { b=1, a = 2, 3 }
print("Test1 " .. table.getn(tblTest1)) #输出长度为1
local tblTest1 = { b=1, a = 2, c=3 }
print("Test1 " .. table.getn(tblTest1)) #输出长度为0
local tblTest1 = { 1, 2, 3 }
print("Test1 " .. table.getn(tblTest1)) #输出长度为3
在有个地方说明一下
--此table 虽然有[3]=6,也是数组型,但是不连续的,缺少了2的索引,所以输出长度为1
local tblTest1 = { [3] = 6,c = 1, a = 2, [1]=3 }
print("Test1 " .. table.getn(tblTest1))
--此table 数值索引是连续 到2,所以输出长度为2
local tblTest1 = { [2] = 6,c = 1, a = 2, 3 }
print("Test1 " .. table.getn(tblTest1))
=============================
获取table长度,不区分数组和键值对
function table_length(t)
local leng=0
for k, v in pairs(t) do
leng=leng+1
end
return leng;
end
=================
特殊说明:
如果数组有一个“空洞”(就是说,nil 值被夹在非空值之间),
那么 #t 可能是指向任何一个是 nil 值的前一个位置的下标(就是说,任何一个 nil 值都有可能被当成数组的结束)。
这也就说明对于有“空洞”的情况,table 的长度存在一定的 不可确定性。
关于nil的特别说明
local tblTest2 = { 1,nil,2}
对一个table中有nil值 取长度,会有很多不确定性,不同的luajit版本输出的结果也不一样
不要在 Lua 的 table 中使用 nil 值,如果一个元素要删除,直接 remove,不要用 nil 去代替。
二)table.concat (table [, sep [, i [, j ] ] ])
对于元素是 string 或者 number 类型的表 table,
返回 table[i]..sep..table[i+1] ··· sep..table[j] 连接成的字符串。填充字符串 sep 默认为空白字符串。
起始索引位置 i 默认为 1,结束索引位置 j 默认是 table 的长度。如果 i 大于 j,返回一个空字符串。
local a = {1, 3, 5, "hello" }
print(table.concat(a)) -- output: 135hello
print(table.concat(a, "|")) -- output: 1|3|5|hello
print(table.concat(a, " ", 2, 4)) -- output: 3 5 hello
print(table.concat(a, " ", 4, 2)) -- output:
在介绍string字符串那边有个字符串拼接,是用.. 这个符号进行的
local str = "a" .. "b" .. "c".......................... 推荐用concat
三)table.insert (table, [pos ,] value)
在(数组型)表 table 的 pos 索引位置插入 value,其它元素向后移动到空的地方。
pos 的默认值是表的长度加一,即默认是插在表的最后。
local a = {1, 8} --a[1] = 1,a[2] = 8
table.insert(a, 1, 3) --在表索引为1处插入3
print(a[1], a[2], a[3])
table.insert(a, 10) --在表的最后插入10
print(a[1], a[2], a[3], a[4])
-->output
3 1 8
3 1 8 10
四)table.remove (table [, pos])
在表 table 中删除索引为 pos(pos 只能是 number型)的元素,并返回这个被删除的元素,
它后面所有元素的索引值都会减一。pos 的默认值是表的长度,即默认是删除表的最后一个元素。
local a = { 1, 2, 3, 4}
print(table.remove(a, 1)) --删除速索引为1的元素
print(a[1], a[2], a[3], a[4])
print(table.remove(a)) --删除最后一个元素
print(a[1], a[2], a[3], a[4])
-->output
1
2 3 4 nil
4
2 3 nil nil
五)table.sort (table [, comp])
local a = { 1, 7, 3, 4, 25}
table.sort(a) --默认从小到大排序
print(a[1], a[2], a[3], a[4], a[5])
-->output
1 3 4 7 25
按照给定的比较函数 comp 给表 table 排序,也就是从 table[1] 到 table[n],这里 n 表示 table 的长度。
比较函数有两个参数,如果希望第一个参数排在第二个的前面,就应该返回 true,否则返回 false。
如果比较函数 comp 没有给出,默认从小到大排序。
local function compare(x, y) --从大到小排序
return x > y --如果第一个参数大于第二个就返回true,否则返回false
end
table.sort(a, compare) --使用比较函数进行排序
print(a[1], a[2], a[3], a[4], a[5])
-->output
25 7 4 3 1
六)table.maxn (table)
返回(数组型)表 table 的最大索引编号;如果此表没有正的索引编号,返回 0。
local a = {}
a[-1] = 10
print(table.maxn(a))
a[5] = 10
print(table.maxn(a))
-->output
0
5
七)table 判断是否为空
大家在使用 Lua 的时候,一定会遇到不少和 nil 有关的坑吧。
有时候不小心引用了一个没有赋值的变量,这时它的值默认为 nil。如果对一个 nil 进行索引的话,会导致异常。
如下:
local person = {name = "Bob", sex = "M"}
-- do something
person = nil
-- do something
print(person.name)
报错person为nil了
然而,在实际的工程代码中,我们很难这么轻易地发现我们引用了 nil 变量。
因此,在很多情况下我们在访问一些 table 型变量时,需要先判断该变量是否为 nil,例如将上面的代码改成:
local person = {name = "Bob", sex = "M"}
-- do something
person = nil
-- do something
if person ~= nil then
print(person.name)
else
print("person 为空")
end
对于简单类型的变量,我们可以用 if (var == nil) then 这样的简单句子来判断。
我们如果要判断table类型的对象是否为空,那如何判断呢?
我们思考一下,判断table是否为空有两种情况:
第一种table对象为nil;
第二种table对象为{},代表没有键值对,但不为nil。
那么我们一般的判断逻辑就应该是 table == nil 或者 table的长度为0 就表示为空
下面我们看看以下例子:
local a = {}
local b = {name = "Bob", sex = "Male"}
local c = {"Male", "Female"}
local d = nil
if a == nil then
print("a == nil")
end
if b == nil then
print("b == nil")
end
if c == nil then
print("c == nil")
end
if d== nil then
print("d == nil")
end
if next(a) == nil then
print("next(a) == nil")
end
if next(b) == nil then
print("next(b) == nil")
end
if next(c) == nil then
print("next(c) == nil")
end
以上有几个注意点,涉及到table类型的长度
(#a) --"#"表示为获取table类型的长度,类似table.getn()
因为a为{},所以长度为0.
我们再看(#b) ,依然输出的是0,但b是有值的啊。
我们再看(#c),输出的是2,这个是怎么回事。这里就是之前在table类型的课程中已经介绍的获取table的长度,
只是获取的是 数组型的长度,不包含map型的。
我们再往下看 if a == nil then 在判断 a是否为nil,明显a不为nil
if next(a) == nil then中的next是什么意思呢?
next (table [, index])
功能:允许程序遍历表中的每一个字段,返回下一索引和该索引的值。
参数:table:要遍历的表
index:要返回的索引的前一索中的号,当index为nil[]时,将返回第一个索引的值,
当索引号为最后一个索引或表为空时将返回nil
next(a) 就是返回第一个索引的值,a的第一个索引是没有值的,那么next(a) 就为nil
所以next方法经常用来判断 table中是否有值。
下面的语句相信大家就能看懂了。
综合以上代码,我们判断table是否为空,就不能简单的判断table长度是否为0,而是判断索引值。
所以要判断table是否为空应该按照以下进行判断
function isTableEmpty(t)
return t == nil or next(t) == nil
end
八)ipairs和pairs的区别
为了看出两者的区别,首先定义一个table:
a={"Hello","World";a=1,b=2,z=3,x=10,y=20;"Good","Bye"}
for i, v in ipairs(a) do
print(v)
end
输出的结果是:
Hello
World
Good
Bye
可见ipairs并不会输出table中存储的键值对,会跳过键值对,然后按顺序输出table中的值。
再使用pairs对其进行遍历:
for i, v in pairs(a) do
print(v)
end
输出的结果是:
Hello
World
Good
Bye
1
10
2
20
3
可见pairs会输出table中的值和键值对,并且在输出的过程中先按顺序输出值,再乱序输出键值对。
这是因为table在存储值的时候是按照顺序的,但是在存储键值对的时候是按照键的哈希值存储的,
并不会按照键的字母顺序或是数字顺序存储。
对于a来说,如果执行print(a[3]),输出的结果也会是Good。也就是说table并不会给键值对一个索引值。
也就是说ipairs只是按照索引值顺序,打印出了table中有索引值的数据,没有索引值的不管。
而pairs是先按照数组索引值打印,打印完成后再按照哈希键值对的键的哈希值打印它的值。
LuaJIT 2.1 新增加的 table.new 和 table.clear 函数是非常有用的。
前者主要用来预分配 Lua table 空间,后者主要用来高效的释放 table 空间,并且它们都是可以被 JIT 编译的。
8、变量详解
一)全局-局部变量
全局变量是指:这个变量在没有被同名局部变量覆盖的时候,所有代码块都是可见的。
局部变量是指:该变量只在被申明的代码块中可见,并且可以覆盖同名全局变量或者外层局部变量。
Lua 中的局部变量要用 local 关键字来显式定义,不使用 local 显式定义的变量就是全局变量:
g_var = 1 -- 全局变量
local l_var = 2 -- 局部变量
1)局部变量作用域
局部变量的生命周期是有限的,它的作用域仅限于声明它的块(block)。
一个块是一个控制结构的执行体、或者是一个函数的执行体再或者是一个程序块(chunk)。
我们可以通过下面这个例子来理解一下局部变量作用域的问题:
x = 10 -- 全局变量
local i = 1 -- 程序块中的局部变量 i
while i <=x do -- 判断条件的x为全局变量的x
local x = i * 2 -- while 循环体中的局部变量 x
print(x) -- output: 2, 4, 6, 8, ...
i = i + 1
end
print("while循环结束");
if i > 20 then
local x -- then 中的局部变量 x
x = 20
print(x + 2) -- 如果i > 20 将会打印 22,此处的 x 是局部变量
else
print(x) -- 打印 10,这里 x 是全局变量
end
print(x) -- 打印 10
2)使用局部变量的好处
局部变量可以避免因为命名问题污染了全局环境
local 变量的访问比全局变量更快
由于局部变量出了作用域之后生命周期结束,这样可以被垃圾回收器及时释放
在生产环境中,我们应该尽可能用 局部变量。
3)全局变量,其实本质上也是一个table,它把我们创建的全局变量都保存到一个table里了。
而这个table的名字是:_G
-- 定义一个全局变量
gName = "我是个全局变量";
-- 用三种方式输出变量的值
print(gName);
print(_G["gName"]);
print(_G.gName);
二)虚变量
当一个方法返回多个值时,有些返回值有时候用不到,要是声明很多变量来一一接收,显然不太合适(不是不能)。
Lua 提供了一个虚变量,以单个下划线(“_”)来命名,用它来丢弃不需要的数值,仅仅起到占位的作用。
local start, finish = string.find("hello", "he") --start 值为起始下标,finish
--值为结束下标
print ( start, finish ) --输出 1 2
local start = string.find("hello", "he") -- start值为起始下标
print ( start ) -- 输出 1
local _,finish = string.find("hello", "he") --采用虚变量(即下划线),接收起
--始下标值,然后丢弃,finish接收
--结束下标值
print ( finish ) --输出 2
代码倒数第二行,定义了一个用 local 修饰的 虚变量(即 单个下划线)。
使用这个虚变量接收 string.find() 第一个返回值,静默丢掉,这样就直接得到第二个返回值了。
虚变量不仅仅可以被用在返回值,还可以用在迭代等。
local t = {1, 3, 5}
print("all data:")
for i,v in ipairs(t) do
print(i,v)
end
print("")
print("part data:")
for _,v in ipairs(t) do
print(v)
end
执行结果:
# luajit test.lua
all data:
1 1
2 3
3 5
part data:
1
3
5
9、时间操作
在 Lua 中,函数 time、date 和 difftime 提供了所有的日期和时间功能。
在 OpenResty 的世界里,不推荐使用这里的标准时间函数,
因为这些函数通常会引发不止一个昂贵的系统调用,同时无法为 LuaJIT JIT 编译,对性能造成较大影响。
推荐使用 ngx_lua 模块提供的带缓存的时间接口,
如 ngx.today, ngx.time, ngx.utctime, ngx.localtime, ngx.now, ngx.http_time,以及 ngx.cookie_time 等。
一)os.time ([table])
它会返回当前的时间和日期的时间戳(精确到秒),如赋值table,表示此table指定日期的时间戳
字段名称 取值范围
year 四位数字
month 1--12
day 1--31
hour 0--23
min 0--59
sec 0--59
isdst boolean(true表示夏令时)
对于 time 函数,如果参数为 table,那么 table 中必须含有 year、month、day 字段。
其他字缺省时段默认为中午(12:00:00)。
print(os.time())
a = { year = 2018, month = 1, day = 30, hour = 0, min = 0, sec = 0 }
print(os.time(a))
时间戳的是以计算机最小时间和指定时间之间相差的秒数,计算机最小时间为1970-1-1 00:00:00(美国时区),
针对中国时区就是1970-1-1 08:00:00
a = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 1 }
print(os.time(a))
输出的就是1秒
二)os.difftime (t2, t1)
返回 t1 到 t2 的时间差,单位为秒。
local day1 = { year = 2018, month = 1, day = 30 }
local t1 = os.time(day1)
local day2 = { year = 2018, month = 1, day = 31 }
local t2 = os.time(day2)
print(os.difftime(t2, t1))
--->output:86400
三)os.date ([format [, time]])
把一个表示日期和时间的数值,转换成更高级的表现形式。
格式字符 含义
%a 一星期中天数的简写(例如:Wed)
%A 一星期中天数的全称(例如:Wednesday)
%b 月份的简写(例如:Sep)
%B 月份的全称(例如:September)
%c 日期和时间(例如:07/30/15 16:57:24)
%d 一个月中的第几天[01 ~ 31]
%H 24小时制中的小时数[00 ~ 23]
%I 12小时制中的小时数[01 ~ 12]
%j 一年中的第几天[001 ~ 366]
%M 分钟数[00 ~ 59]
%m 月份数[01 ~ 12]
%p “上午(am)”或“下午(pm)”
%S 秒数[00 ~ 59]
%w 一星期中的第几天[0 ~ 6 = 星期天 ~ 星期六]
%x 日期(例如:07/30/15)
%X 时间(例如:16:57:24)
%y 两位数的年份[00 ~ 99]
%Y 完整的年份(例如:2015)
%% 字符'%'
print(os.date("today is %A, in %B"))
print(os.date("now is %x %X"))
print(os.date("%Y-%m-%d %H:%M:%S"))
-->output
today is Thursday, in July
now is 07/30/15 17:39:22
2018-03-29 22:36:05
---------------------------
t = os.date("*t", os.time());
for i, v in pairs(t) do
print(i, v);
end
yday 120 --一年中的第几天,一月一日为1
month 4
sec 9
min 9
hour 16
day 30
year 2018
isdst false --是否夏令时
wday 2 --一周第几天 星期日为1
10、模块
从lua5.1开始,Lua 加入了标准的模块管理机制,Lua 的模块是由变量、函数等已知元素组成的 table,
因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。
一)模块定义
模块的文件名 和 模块定义引用名称要一致
-- 文件名为 model.lua
-- 定义一个名为 model 的模块
model = {}
-- 定义一个常量
model.constant = "这是一个常量"
-- 定义一个函数
function model.func1()
print("这是一个公有函数")
end
local function func2()
print("这是一个私有函数!")
end
function model.func3()
func2()
end
return model
二)require 函数
Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
require("<模块名>") 或者 require "<模块名>"
执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。
-- test_model.lua 文件
-- model 模块为上文提到 model.lua
require("model")
print(model.constant)
model.func3()
-------------------------
另一种写法,给加载的模块定义一个别名变量,方便调用
local m = require("model")
print(m.constant)
m.func3()
以上代码执行结果为:
这是一个常量
这是一个私有函数!
如:模块定义的model,为local修饰为局部变量,那只能采用local m = require("model") 引用
三)require 加载机制
我们使用require命令时,系统需要知道引入哪个路径下的model.lua文件。
require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,
当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。
如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
lua文件的路径存放在全局变量package.path中,默认的package.path的值为 print(package.path)
./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua
我们运行require("model");相当于把model替换上面的?号,lua就会在那些目录下面寻找model.lua如果找不到就报错。
所以我们就知道为什么会报错了。
那我们如何解决,我这里介绍常用的解决方案,编辑环境变量LUA_PATH
在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),
例如把 "~/lua/" 路径加入 LUA_PATH 环境变量里:
#LUA_PATH
export LUA_PATH="/usr/local/lua/?.lua;;"
文件路径以 ";" 号分隔,最后的 2 个 ";;" 表示新加的路径后面加上原来的默认路径。
接着,更新环境变量参数,使之立即生效。
source ~/.profile
这时假设 package.path 的值是:
/usr/local/lua/?.lua;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua
那么调用 require("model") 时就会尝试打开以下文件目录去搜索目标。
11、元表
举个例子,在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。
那如何计算两个table的相加操作a+b?
local t1 = {1,2,3}
local t2 = {4,5,6}
local t3 = t1 + t2 ----> {1,2,3,4,5,6}
类似java的一些操作重载
这种类似的需求,lua 提供了元表(Metatable)和元方法,允许我们改变table的行为,每个行为关联了对应的元方法。
1)setmetatable(table,metatable): 对指定table设置元表(metatable),
如果元表(metatable)中存在__metatable键值,setmetatable会失败 。
2)getmetatable(table): 返回对象的元表(metatable)。
mytable = {} -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表
等价于:
mytable = setmetatable({},{})
返回对象元表:
getmetatable(mytable) -- 返回mymetatable
元方法的命名都是以 __ 两个下划线开头。
一)__index 元方法
对表读取索引一个元方法
这是 metatable 最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)
中的__index元方法。如果__index是一个表,Lua会在表格中查找相应的键。
local t = {} -- 普通表t为空
local other = {foo = 2} -- 元表 中有foo值
setmetatable(t,{__index = other}) -- 把 other 设为 t 的元表__index
print(t.foo); ---输出 2
print(t.bar); ---输出nil
t.foo为什么会输出2,就是因为我们重写了__index索引的重载,lua在执行中如果t中没有foo,
就会在他的元表中__index中去找,__index等于other,就输出2。
----------------
如果我们把t设置一下foo的值为3,在看看结果
local t = {foo = 3 } -- 普通表t为空
local other = {foo = 2} -- 元表 中有foo值
setmetatable(t,{__index = other}) -- 把 other 设为 t 的元表__index
print(t.foo); ---输出 3
print(t.bar); ---输出nil
---------------------------------------------
如果__index赋值为一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。
local t = {key1 = "value1" }
local function metatable(mytable,key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
setmetatable(t,{__index = metatable})
print(t.key1);
print(t.key2);
print(t.key3);
分析:
print(t.key1); ---这个输出value1 ,是因为t表中有此key
print(t.key2); ---这个输出metatablevalue,是因为t表中没有此key,就会调用t的元表中的元方法__index,
---这是__index是个函数,就会执行这个函数,传t表和key值这两个参数到此函数,
---函数体中判断有此key2 就输出metatablevalue;
print(t.key3); ---这个输出nil,是因为t表没有,元方法__index函数中 对key3返回nil值
--------------------------------------
总结:lua对表索引取值的步骤
Lua查找一个表元素时的规则,其实就是如下3个步骤:
1.在表中查找,如果找到,返回该元素,找不到则继续步骤2
2.判断该表是否有元表,如果没有元表,返回nil,有元表则继续步骤3。
3.判断元表有没有__index元方法,如果__index方法为nil,则返回nil;
如果__index方法是一个表,则重复1、2、3;
如果__index方法是一个函数,则执行该函数,得到返回值。
二)__newindex 元方法
__newindex 元方法用来对表更新,__index则用来对表访问 。
当你给表进行索引进行赋值,但此索引不存在;lua会查找是否有元表,有元表就会查找__newindex 元方法是否存在:
如果存在则调用__newindex的值进行执行操作,但不会对原表进行赋值操作。
以下实例演示了 __newindex 元方法的应用:
mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })
print("mytable.key1=",mytable.key1)
mytable.newkey = "新值2"
print("mytable.newkey=",mytable.newkey)
print("mymetatable.newkey=",mymetatable.newkey)
mytable.key1 = "新值1"
print("mytable.key1=",mytable.key1)
print("mymetatable.key1=",mymetatable.key1)
以上实例中表设置了元方法 __newindex
在对新索引键(newkey)赋值时(mytable.newkey = "新值2"),会调用元方法,而对mytable原表不进行赋值。
而对mytable已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex。
----------------------------
如果我们要对原来的table进行赋值,那我们就可以用rawset;;__newindex函数会传三个参数,
mytable = setmetatable({key1 = "value1"}, {
__newindex = function(t,k,v) ---第一个参数为table,第二个参数为key,第三个参数为value
rawset(t,k,v);
end
})
mytable.key1 = "new value"
mytable.key2 = 4
print("mytable.key1=",mytable.key1)
print("mytable.key2=",mytable.key2)
key2原本是不再mytable表中的,通过元方法__newindex中函数使用了rawset,就可以对原table进行赋值。
三)为表添加操作符“+”
我们这里定义“+”这元方法,把它定义为两个table相连
如
t1={1,2,3}
t2={4,5,6}
t1 + t2 相加的结果,我们想得到的是 {1,2,3,4,5,6}
那我们如何写元表?
“+”对应的元方法为__add
local function add(mytable,newtable)
local num = table.maxn(newtable)
for i = 1, num do
table.insert(mytable,newtable[i])
end
return mytable
end
local t1 = {1,2,3}
local t2 = {4,5,6}
setmetatable(t1,{__add = add})
t1 = t1 + t2
for k,v in ipairs(t1) do
print("key=",k," value=",v)
end
这样我们就实现了两个table相加
以下是我们的操作符对应关系
模式 描述
__add 对应的运算符 '+'.
__sub 对应的运算符 '-'.
__mul 对应的运算符 '*'.
__div 对应的运算符 '/'.
__mod 对应的运算符 '%'.
__unm 对应的运算符 '-'.
__concat 对应的运算符 '..'.
__eq 对应的运算符 '=='.
__lt 对应的运算符 '<'.
__le 对应的运算符 '<='.
四)__call元方法
__call元方法在 Lua 调用一个值时调用。以下实例演示了计算两个表中所有值相加的和:
类似的 t();类似函数调用
local function call(mytable,newtable)
local sum = 0
local i
for i = 1, table.maxn(mytable) do
sum = sum + mytable[i]
end
for i = 1, table.maxn(newtable) do
sum = sum + newtable[i]
end
return sum
end
local t1 = {1,2,3}
local t2 = {4,5,6}
setmetatable(t1,{__call = call})
local sum = t1(t2)
print(sum)
五)__tostring 元方法
__tostring 元方法用于修改表的输出行为。以下实例我们自定义了表的输出内容,把表中所有的元素相加输出:
local t1 = {1,2,3}
setmetatable(t1,{
__tostring = function(mytable)
local sum = 0
for k, v in pairs(mytable) do
sum = sum + v
end
return "all value sum =" .. sum
end
})
print(t1) ----print方法会调用table的tostring元方法
到此我们的元表 和 元方法 就讲完了,这个是需要大家自己动手去测试体验的。要有领悟能力
六)点号与冒号操作符的区别
local str = "abcde"
print("case 1:", str:sub(1, 2))
print("case 2:", str.sub(str, 1, 2))
执行结果
case 1: ab
case 2: ab
冒号操作会带入一个 self 参数,用来代表 自己。而点号操作,只是 内容 的展开。
在函数定义时,使用冒号将默认接收一个 self 参数,而使用点号则需要显式传入 self 参数。
obj = { x = 20 }
function obj:fun1()
print(self.x)
end
等价于
obj = { x = 20 }
function obj.fun1(self)
print(self.x)
end
注意:冒号的操作,只有当变量是类对象时才需要。
12、面向对象
面向对象编程(Object Oriented Programming,OOP)是一种非常流行的计算机编程架构。
java,c++,.net等都支持面向对象
面向对象特征
1) 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
2) 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,
而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
3) 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,
可以通过指向基类的指针,来调用实现派生类中的方法。
4) 抽象:抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,
并且可以在最恰当的继承级别解释问题。
一)Lua 中面向对象
对象由属性和方法组成,lua中的面向对象是用table来描述对象的属性,function表示方法;
LUA中的类可以通过table + function模拟出来。
一个简单实例
以下简单的类代表矩形类,包含了二个属性:length 和 width;getArea方法用获取面积大小
新建rect.lua脚本
local rect = {length = 0, width = 0}
-- 派生类的方法 new
function rect:new (length,width)
local o = {
--设定各个项的值
length = length or 0,
width = width or 0
}
setmetatable(o, {__index = self})
return o
end
-- 派生类的方法 getArea
function rect:getArea ()
return self.length * self.width
end
return rect
-----------------test.lua------------------------
引用模块
local rect = require("rect")
创建对象
创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。
local rect1 = rect:new(10,20)
local rect2 = rect:new(5,6)
访问属性 ----》 rect1.length
访问成员函数 ----》rect1:getArea()
print("长度1:",rect1.length);
print("面积1:",rect1:getArea()); --- 点号 和 冒号 调用前面课程已经介绍
print("长度2:",rect2.length);
print("面积2:",rect2:getArea());
二)Lua继承
-------------------shape基类---------------------------
local shape = {name = ""}
-- 创建实体对象方法 new
function shape:new (name)
local o = {
name = name or "shape"
}
setmetatable(o, {__index = self})
return o
end
-- 获取周长的方法 getPerimeter
function shape:getPerimeter ()
print("getPerimeter in shape");
return 0
end
-- 获取面积的方法 getArea
function shape:getArea ()
print("getArea in shape");
return 0
end
function shape:getVum ()
print("getVum in shape");
return 0
end
return shape
----------------------triangle继承类----------------------------
local shape = require("shape")
local triangle = {}
-- 派生类的方法 new
function triangle:new (name,a1,a2,a3)
local obj = shape:new(name);
--1)当方法在子类中查询不到时,再去父类中去查找。
local super_mt = getmetatable(obj);
setmetatable(self, super_mt);
--2)把父类的元表 赋值super对象
obj.super = setmetatable({}, super_mt)
--3)属性赋值
obj.a1 = a1 or 0;
obj.a2 = a2 or 0;
obj.a3 = a3 or 0;
setmetatable(obj, { __index = self })
return obj;
end
-- 派生类的方法 getPerimeter
function triangle:getPerimeter()
print("getPerimeter in triangle");
return (self.a1 + self.a2 + self.a3);
end
-- 派生类的方法 getHalfPerimeter
function triangle:getHalfPerimeter()
print("getHalfPerimeter in triangle");
return (self.a1 + self.a2 + self.a3) / 2
end
return triangle
------------------test-----------------------
local rect = require("rect")
--local shape = require("shape")
local triangle = require("triangle")
local rect1 = rect:new(10,20)
local rect2 = rect:new(5,6)
--local shape1 = shape:new();
local triangle1 = triangle:new("t1",1,2,3)
local triangle2 = triangle:new("t2",6,7,8)
print("长度1:",rect1.length);
print("面积1:",rect1:getArea());
print("===============");
print("长度2:",rect2.length);
print("面积2:",rect2:getArea());
print("===============");
--print("shape1 getPerimeter:",shape1:getPerimeter())
print("===============");
----覆盖了shape的方法
print("t1 getPerimeter:",triangle1:getPerimeter())
print("t1 getHalfPerimeter:",triangle1:getHalfPerimeter())
print("t2 getPerimeter:",triangle2:getPerimeter())
print("t2 getHalfPerimeter:",triangle2:getHalfPerimeter())
---- 从shape继承的getVum方法
print("t1 getVum:",triangle1:getVum())
print("t2 getVum:",triangle2:getVum())
print("===============");
print("t2 super getPerimeter:",triangle2.super:getPerimeter())
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)