Lua学习总结

Stella981
• 阅读 604

简述

=====

Lua是一个很小的编程语言,很多人将其与Python高级语言进行比较。
Lua有以下的特点:
(1)纯C语言实现,源码小,可以很好地与C/C++融合。可自行编译,生成静态库。
(2)语法简单,灵活,易学。

我也同时学习了Python,相比之下,Lua精简,功能简单,可用的库少,但是语言的实现写得如此精简也很不错了!与C/C++程序结合就能显示它的强大能力,适用于要求可配置性很高的C/C++程序中,比如游戏。
如果想写纯脚本程序,那就选Python吧,它是强大,库多,喜欢单干。

好,我们学习Lua吧!

基础

数据类型

Lua的常用数据类型有:
(1)Nil  (空) <==> C语言中的NULL
(2)number     所有数据,包含整数小数
(3)string      字串类型
(4)boolean    布尔类型 true, false
(5)function    函数类型
(6)table        表类型

其实,Lua中的数据类型远不止上面的这几个,比如file.
table在Lua中是最重要的数据类型,Lua的很多思想都是基于表来实现的,比如模块、对象。
boolean    除Nil与false为假以为,其它都为真。
string
    \    转义字符
    \[
    \]
    \ddd    3个十进制的数据表示一个字符,高位填0
    [[ ... ]] 表示多行字符串,其中字串无转义

> a = [[ \t \n [[ ]]
> print(a)
 \t \n [[

当字符串被参于数值运算时,Lua会试图将字串转成数值

> print("10" + 1)  --> 11
> print("123" * "12")  --> 1476

当数值参于字串运算时,Lua也会尝试将其转换成字符串

> print(123 .. 12)  -->12312
>
> print(10 == "10")  --> 永远都是false
> -- 除非
> print(10 == ("10" + 0))  --> true
> print("10" == (10 .. ""))  --> true

总之:操作符决定了两边操作数的转换类型。

表达式

(1)算术运算符  + - * / ^  (加减乘除幂)
(2)关系运算符     <   >   <=   >=   ==   ~= (不等于)
   nil只与自己相等,table, usedate, function, 只比对象不比内容的值,相当于C语言中的指针比较
(3)逻辑运算符     and   or    not
        and, or 的运算结果不是true与false

a and b  -- 如果a为false,则返回a,否则b
a or b   -- 如果a为true,则返回a,否则b
a and b or c   -- 类似C中的 a ? b : c

        not 结果返回true或false

(4)优先级
        ^ 与 .. 是右连接

表结构

> days = {"sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
> print(days[0])  --> nil
> print(days[1])  --> Sun

注意: Lua中,表的索引是从1开始的,不是0!

Polyline = { color = "blue", thickness = 2, npoints = 4,
    {x = 0, y = 0}, 
    {x = -10, y = 0},
    {x = -10, y = 10},
    {x = 0, y = 10}
}
Polyline["color"]  --> "blue"
Polyline.color     --> "blue"
Polyline[1]        --> {x = 0, y = 0}
Polyline[1].x      --> 0

表中的元素都是由key-value组成。如果没有指定key,那就默认以1开始的整数做key,如上4个{x,y}。如果指定了名称,则以string类型为key,如color, thickness, npoints。其实,表的key还可以为table, function, file, boolean任意类型。value可以为任何Lua类型。

Person = {["name"] = "Peter", ["age"] = 28}  -- 等价于
Person = {name = "Peter", age = 28}

{x=0, y=0}  --> {["x"]=0, ["y"]=0}
{"red", "green", "blue"}  --> {[1]="red", [2]="green", [3]="blue"}

基本语法

(1)多元素赋值

a, b = 10, 'Hi'   --> 等价于
a = 10; b = 'Hi'

如果左边与右边个数不一致时,多的忽略,少的填Nil。

(2)代码块chunk

    使用local创建的一个局部变量,可以避免命名冲突,而比全局变量高效。

(3)控制结构

----------------------------------
if condition1 then
    ...
else if condition2 then
    ...
else
    ...
end

----------------------------------
while condition do
    ...
end

----------------------------------
repeat
    ...
until condition

for 语句

for var=exp1, exp2, exp3 do
    ...
end
---> exp1: 初始值
---> exp2: 终止值
---> exp3: 步进

for k in pairs(days) do
    print(k .. ' = ' .. days[key])
end

for i, val in ipairs(days) do
    print(i .. ' = ' .. val)
end

(4)函数

定义格式:

function func_name (arguments_list)
    ...
end

当函数的参数只有一个且为string或table类型时,可以不加括号。如:

dofile "text.lua"
table_print {a=12, b=30}

函数的返回值可以是多个:

function foo(a, b)
    return a+b, a-b
end

aa, bb = foo(32, 11)
print(aa, bb)    -->  43,  21

(5)闭包

Python与Lua都有闭包的特性,我记得好像Java也有,但是C与C++是没有的。

-- 调用newCounter()返回一个function,而这个function引用了newCounter()函数内部的局部变量local i
----------------------------
function newCounter()
    local i = 0
    return function()
        i = i + 1
        return i
    end
end

c1 = newCounter
print(c1())  --> 1
print(c2())  --> 2

newCounter = nil  -- 就算我把这个函数销毁了
print(c1())  --> 3  还是生效

为什么?因为在Lua中,所有的变量都是“指针”。所有的变量都是从堆里分配出来的,不像C与C++,局部变量是从栈里分配,一旦函数退出,所有局部变量都要被释放。Lua中的所有变量都是从堆里申请的。对象的释放是自动进行的,只有这个对象的引用计数为0了才会被释放掉。

为了便于理解,我写一段C++的伪代码:

class CounterFunc
{
public:
    CounterFunc(int i*) {
        m_pCnt = i;
    }
    int operator()() {
        ++i;
        return i;
    }

private:
    int *m_pCnt;
}

CounterFunc* newCounter()
{
    int *i = new int;
    return new CounterFunc(i);
}

int main()
{
    CounterFunc &c1 = *newCounter()
    cout << c1() << endl;
    cout << c1() << endl;
}

熟悉C++的学友们一下就看懂了。

(6)非全局函数

表中的函数实现方式:

Lib = {}
Lib.foo = function(x, y) return x+y end
----------------------------------------------
Lib = {
     foo = function(x, y) return x+y end
}
----------------------------------------------
Lib = {}
function Lib.foo(x, y) return x+y end

局部函数

local foo = function(x, y) 
    return x+y 
end
------------------------------------------------
local function foo (x, y)
    return x+y
end

(7)尾调函数

当函数的最后返回结果是调用另一个函数,称之为尾调函数。Lua的在调用尾调函数时,先是弹出当前函数的栈空间,然后再调用尾调函数,从而降低了函数层层调动过程中的栈消耗,非常适用于函数递归调用。

加载文件与运行

当我们需要加载与运行已有文件中的Lua代码时,可以用以下几种方式:

(1)loadstring( str )  --- 加载字串

f = loadstring "a = 12; print('a=' .. a)"
f()  --> a=12

loadstring()本是函数,由于函数是string,所以没有加括号。loadstring()执行完之后,只是加载解析,并没有执行。返回的是一个function类型。执行该函数便可以运行。相当于:

f = loadstring "function() a = 12; print('a='..a) end"

(2)loadfile( file_name ) --- 加载文件

这个函数相当于从文件里读出string,然后再调用loadstring(file_text)实现加载功能。

(3)dofile( file_name )  --- 加载并执行文件

相当于loadfile()之后,返回一个函数,再调用这个函数。

function dofile( file_name )
    local f = assert(loadfile(file_name))
    f()
end

(4)require( file_name )

这个函数是通过调用dofile()来实现的。不同的是,每次加载执行一个文件时,require()都会记录,避免重复加载。另外,如果给定的路径找不到文件,require()会到指定的路径下去找输到加载的文件。文件名称可以省去.lua后缀。

协同函数

关于这个功能,我的理解是:是非抢占式任务,由任务主动放弃执行权来达到任务切换的目的。

function foo (x, y)
    while ture do
        local a = x + y
        local b = x - y
        x, y = coroutine.yield(a, b)
    end
end
------------------------------------------
co = coroutine.create(foo)
print(coroutine.resume(co, 1, 2))  --> true   3  -1
print(coroutine.resume(co, 7, 1))  --> true   8  6

这里最不好理想的就是yield()与resume()参数与返回值的问题。这里分步骤详细解释一下:
(1)co = coroutine.create(foo),创建一个协同任务,返回co,此时co的状态为suspend。
(2)coroutine.resume(co, 1, 2),程序转而执行foo(x, y),x,y的传入的值正是resume()中的(1, 2)。

(3)foo(1, 2)执行到coroutine.yield(a, b),(a, b)分别为(3,-1)。程序执行yield(3, -1)切换到主任务,resume()函数返回第一个值为bool,后面的正是yield()中传入的参数。
(4)coroutine.resume(co, 7, 1),程序跳到foo()中的coroutine.yield()行继续执行。其中resume()函数中带的参数(7, 1),在foo()函数中,x, y = coroutine.yield() 通过返回值的形式赋给了(x, y)。
(5)跳到(3),如此重复。

可见,Lua并没有实现多任务并行执行,而是任务之间通过resume()与yield()函数进行切换。我想,其主要的作用还是用于将功能以任务的形式进行隔离,便于维护。

Metatables and Metamethods


元表与元方法,这是一个很重要的概念。可以为table设置或指定metatable,用于指定某些运算符对应的操作方法。

* setmetatable(table, meta_table)      设置元表
* getmetatable(table)                       获取元表

这个元素,可以是个函数。

tb = {
    ----> <metatable> = {
              __add = function : xxx   加法操作
              __sub = function : xxx   减法操作
              __mul = function : xxx   乘法操作
              ...
          }

    [1] = 20
    [2] = 40
    ...
}

(1)算术运算

+ --> __add(a, b)
- --> __sub(a, b)
* --> __mul(a, b)
/ --> __div(a, b)
-负 --> __unm(a)
^ --> __pow(a, b)

(2)关系运算

== --> __eq(a, b)
<  --> __lt(a, b)
<= --> __le(a, b)

只要定义上面几个就行了,不用再定义 ~=, >=, >

(3)库定义

__tostring = function(tb)

在print(t)的时候,print会调用元素中的tostring(t)来进行转换,tostring(t)就查t中的metatable中是否有__tostring域,如果有就调用默认是有的。

(4)表相关

__index = function(tb, key)
__index = tb

访问索引,如果执行读取一个表中的操作,而这个表里又没有这个域,那么Lua就去查询metatable中的__index域。如果__index是表,那么就去查__index表中有没有这个域,如果有就从__index这个表里的这个域里去取值。如果__index是函数,那么就调用__index(tb, key)函数。

__newindex = function(tb, key)

更新索引,在更改表域的时候,如果这个表中没有这个域,那么就会从元表的__index里去找。与__index同理。

这两个东西,在类继承里用到。

环境变量

Lua环境放在全局变量 _G 中。可以用以下语句打印全局变量。

> for n in pairs(_G) do print(n) end

> a = 5
> print(_G["a"])  --> 5

其实,所有的全局变量都是放在__G表里。可以对_G表里加metatable,来控制全局变量的形为。如__newindex, __index, 限制直接定义与访问一个变量。

在非全局域,防止访问到全局域

do 
    a = 'Hi'
    setfenv(1, {_G = _G})
    _G.print(_G.a)   --> Hi
end

在局部Chunk中,将_G放入一个空表中,将这个表作为全局表。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
00_设计模式之语言选择
设计模式之语言选择设计模式简介背景设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这