Lua极简入门指南(一):基础知识篇_Lua

本文是《Programming in Lua 3rd》读书笔记。

Chunks

一个 Chunk 就是一组被执行的语句,例如一个文件或者交互模式下的一行。

标识符(identifiers)

我们应该避免使用以 _ 开头并跟上一个或者多个大写字母的字符串来作标识符,它们被保留作特殊的用途(例如:_VERSION)。

注释

单行注释使用

复制代码 代码如下:

--

多行注释使用

复制代码 代码如下:

--[[ 和 --]]

类型简介

Lua 存在的数据类型包括:

1.nil。此类型只有一个值 nil。用于表示“空”值。全局变量默认为 nil,删除一个已经赋值的全局变量只需要将其赋值为 nil(对比 JavaScript,赋值 null 并不能完全删除对象的属性,属性还存在,值为 null)

2.boolean。此类型有两个值 true 和 false。在 Lua 中,false 和 nil 都表示条件假,其他值都表示条件真(区别于 C/C++ 等语言的是,0 是真)

3.number。双精浮点数(IEEE 754 标准),Lua 没有整数类型

4.string。你可以保存任意的二进制数据到字符串中(包括 0)。字符串中的字符是不可以改变的(需要改变时,你只能创建一个新的字符串)。获取字符串的长度,可以使用 # 操作符(长度操作符)。例如:print(#”hello”)。字符串可以使用单引号,也可以使用双引号包裹,对于多行的字符串还可以使用 [[ 和 ]] 包裹。字符串中可以使用转义字符,例如 \n \r 等。使用 [[ 和 ]] 包裹的字符串中的转义字符不会被转义

5.userdata。用于保存任意的 C 数据。userdata 只能支持赋值操作和比较测试

6.function。函数是第一类值(first-class value),我们能够像使用其他变量一样的使用函数(函数能够保存在变量中,可以作为参数传递给函数)

7.thread。区别于我们常常说的系统级线程

8.table。被实现为关联数组(associative arrays),可以通过任何值来进行索引(nil 除外)。和全局变量一样,table 中未赋值的域为 nil,删除一个域只需要将其赋值为 nil(实际上,全局变量就是被放置在一个 table 中)

type 函数用于返回值的类型:

复制代码 代码如下:

print(type("Hello World")) --> string
print(type(10.4*3))        --> number
print(type(print))         --> function
print(type(type(X)))       --> string

在 Lua 中,任何的变量都可以保存任何的值。

table 使用简介

使用构造表达式可以创建一个 table:

复制代码 代码如下:

-- 创建一个空的 table
a = {}
 
-- 创建并初始化一个 table,这里
-- days[1] == "Sunday"
-- days[2] == "Monday"
-- ...
days = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }
 
-- 创建并初始化一个 table,这里
-- a["x"] == 10
-- a["y"] == 20
a = { x = 10, y = 20 }

使用 [] 操作符访问 table 的域:

复制代码 代码如下:

a = {}
k = "x"
a[k] = 10
a["x"] = 20
print(a["y"]) --> nil
a.x = 30

注意,a.name 的语法等价于 a["name"]。

table 可以用于表示数组,这时候索引为整数,并且从 1(而非 0)开始,例如:

复制代码 代码如下:

a = { 'a', 'b' }
a[1] == 'a'
a[2] == 'b'

长度操作符可以获取 table 数组部分的长度:

复制代码 代码如下:

a = {}
a[1] = 1
a[2] = 2
print(#a) --> 2
 
a.a = 1
a.b = 2
print(#a) --> 2
 
a = {}
a.a = 1
a.b = 2
print(#a) --> 0

表达式

算术操作符

1.+(加)
2.-(减)
3.*(乘)
4./(除)
5.^(幂)
6.%(取模)

任何算术操作符都试图将操作数转换为数值类型,例如:

复制代码 代码如下:

print(10 + '1') --> 11

关系操作符

1.<(小于)
2.>(大于)
3.<=(小于等于)
4.>=(大于等于)
5.==(等于)
6.~=(不等于)

两个不同类型的值是不相等的,例如:

复制代码 代码如下:

nil ~= false

table、userdata 类型是通过引用进行比较的,例如:

复制代码 代码如下:

a = {}; a.x = 1; a.y = 0
b = {}; b.x = 1; b.y = 0
c = a

这里 a 和 c 引用一个相同的对象,因此 a == c,但是 a ~= b(即便 a、b 内容相同)。

逻辑操作符

1.and
2.or
3.not

逻辑操作符有返回值。对于 and 操作来说,如果第一个操作数为 false 时返回此操作数,否则返回第二个操作数。对于 or 操作来说,如果第一个操作数不为 false 时返回此操作数,否则返回第二个操作数。

连接操作符

字符串连接可以使用连接操作符 “..”,例如:

复制代码 代码如下:

print("Hello " .. "World")

连接操作符试图将操作数转化为字符串,例如:

复制代码 代码如下:

print("number: " .. 1)

语句

多赋值(multiple assignment)支持,例如:

复制代码 代码如下:

a, b = 1, 2
print(a) --> 1
print(b) --> 2

多赋值的一个惯用法就是交换两个变量的值:

复制代码 代码如下:

x, y = 1, 2
x, y = y, x
print(x) --> 2
print(y) --> 1

创建局部变量使用 local:

复制代码 代码如下:

j = 10       -- 全局变量 j
local i = 10 -- 局部变量 i

局部变量的作用域限制于他们声明的块(block)。块(block)包括:

1.控制结构的主体部分
2.函数体
3.chunk
4.do-end

范例:

复制代码 代码如下:

if true then
    local x = 20
    print(x) --> 20
end
 
print(x) --> nil

我们可以使用 do-end 关键字来构造一个块:

复制代码 代码如下:

do
    local x = 20
    print(x) --> 20
end
 
print(x) --> nil

访问局部变量要快于访问全局变量。在 Lua 中有一个习惯用法:

复制代码 代码如下:

local foo = foo

用于创建一个局部变量并初始化为同名的全局变量。这样做常常出于两个原因:

1.避免某些类型的全局变量被修改
2.提高访问速度

控制结构

if then elseif else

复制代码 代码如下:

if a < 0 then
    a = 0
end
 
if a < b then
    return a
else
    return b
end
 
if op == '+' then
    r = a + b
elseif op == '-' then
    r = a - b
elseif op == '*' then
    r = a * b
elseif op == '/' then
    r = a / b
else
    error('invalid operation')
end

Lua 中没有 switch 语句。

while

复制代码 代码如下:

local i = 1
while a[i] do
    print(a[i])
    i = i + 1
end

repeat

复制代码 代码如下:

repeat
    line = io.read()
until line ~= ''
print(line)

区别于 while,repeat 会先执行循环体,然后判断测试条件。

数值型 for(numeric for)

for 有两种:

数值型 for(numeric for)

1.泛型 for(generic for)
2.数值型 for 的语法如下:

复制代码 代码如下:

for var = exp1, exp2, exp3 do
    <something>
end

这里 exp1 作为 var 的初始值,exp2 为 var 的最大值,exp3 为 var 每次递增的值,exp3 是可选的,默认为 1。范例:

复制代码 代码如下:

-- 输出 1 2 3
for i = 1, 3 do
    print(i)
end

有一些需要注意的地方:

1.for 中的 exp1、exp2、exp3 只会被计算一次值,例如:

复制代码 代码如下:

for i = 1, f(x) do print(i) end

这里的 f(x) 只会被调用一次

2.控制变量 var 只是一个局部变量
3.不要尝试去修改控制变量 var 的值(结果是未知的)

泛型 for

泛型 for 通过一个迭代器函数来实现遍历,例如:

复制代码 代码如下:

for k, v in pairs(t) do
    print(k, v)
end

这里的 pairs 就是一个迭代器函数,此 for 循环遍历 table t,每次获取到的 key 保存在变量 k 中,获取到的 value 保存在变量 v 中。除了 pairs 还有其他的迭代器可以用:

1.io.lines 可用于迭代文件中的行
2.ipairs 可用于迭代 table 的数组部分

我们还可以自己编写迭代器。

break、return、goto

break 语句用于跳出一个循环(for、repeat、while)。

return 语句用于为函数返回结果。在 Lua 中,return 语句必须是一个块的最后一条语句,看一个例子:

复制代码 代码如下:

function foo()
    -- 语法错误
    return
    local i = 1
end

有时候,我们出于某些原因(例如为了 debug),我们需要在一个函数中插入一个 return 语句,这时候可以这么做:

复制代码 代码如下:

function foo()
    -- ...
    do return end
    -- ...
end

goto 语句用于在函数中跳转。goto 语句可以让执行跳转到特定的标签(label)处,例如:

复制代码 代码如下:

goto quit
print('come on')
::quit::
print('quit')

这里输出 quit。正如我们看到的,标签的写法为 ::name::。goto 跳转也是存在限制的:

1.不允许跳转到一个块中去
2.不允许跳转到函数之外去
3.不允许跳入局部变量的作用域中

对于第三点,看一个例子:

复制代码 代码如下:

goto quit
local a
::quit::
print('quit')

这里,会出现语法错误(jumps into the scope of local 'a')。但是,有一个细节需要注意,我们先修改上面的例子:

复制代码 代码如下:

goto quit
local a
::quit::

执行成功,没有语法错误。这是因为局部变量的作用域结束于变量定义的块的最后一个非 void 语句,而标签被认为是一个 void 语句,对于上面的例子来说,a 的作用域在 ::quit:: 之前就结束了,因此 goto quit 并没有跳入局部变量 a 的作用域中。

利用 goto 可以比较方便的编写状态机,例如(s1、s2 为状态):

复制代码 代码如下:

::s1:: do
    local c = io.read(1)
    if c == '0' then goto s2
    elseif c == nil then print'ok'; return
    else goto s1
    end
end
 
::s2:: do
    local c = io.read(1)
    if c == '0' then goto s1
    elseif c == nil then print'not ok'; return
    else goto s2
    end
end
 
goto s1

时间: 2024-07-30 04:16:59

Lua极简入门指南(一):基础知识篇_Lua的相关文章

Lua极简入门指南:全局变量_Lua

全局环境 Lua 把全局变量放在一个 table _G 中,这个 table 被叫做全局环境(global environment).打印所有的全局变量名: 复制代码 代码如下: for n in pairs(_G) do print(n) end _ENV(Lua 5.2 开始支持) 对于一个 free name(名字没有绑定任何声明)var 实际上会被转换为 _ENV.var(每个 chunk 中都会存在一个名为 _ENV 的变量): 复制代码 代码如下: v1 = 1 local v2 =

Lua极简入门指南(一):函数篇_Lua

Lua 和其他很多语言一样,函数调用时参数列表被包裹在括号中: 复制代码 代码如下: print('Hello World') 特别的情况是,如果函数调用时只有一个参数,并且此参数为字符串 literal(字面量)或者 table 构造器(constructor)时,包裹参数的括号可以省略: 复制代码 代码如下: print 'Hello World' <--> print('Hello World') type{}              <--> type({}) Lua 为

Lua 极简入门指南(七):面向对象编程_Lua

类 在很多面向对象的语言中有类(class)的概念,对象是类的实例.Lua 中不存在类的概念.Lua 就像 JavaScript 一样是面向原型的语言(http://en.wikipedia.org/wiki/Prototype-based_programming),这类语言使用一个对象表示一个"类",其他对象(此类的实例)使用此对象作为原型.我们有两个 table p 和 obj,将 p 设置为 obj 的原型(回顾:http://www.jb51.net/article/56690

Lua极简入门指南(三): loadfile和错误处理_Lua

编译 Lua 虽然是解释性语言,但 Lua 源码总是被编译为中间形式后再执行. dofile 用于载入并执行一个 Lua 文件,相比之下,loadfile 用于载入一个 Lua 文件,但并不执行,确切的说 loadfile 编译了一个 chunk,并返回此被编译的 chunk(被作为一个函数): 复制代码 代码如下: c = loadfile('./test.lua') c() dofile 可以被实现为: 复制代码 代码如下: function dofile(filename)     loc

Lua极简入门指南(六):模块_Lua

从用户的角度来看,一个模块能够通过 require 加载并返回一个 table,模块导出的接口都被定义在此 table 中(此 table 被作为一个 namespace).所有的标准库都是模块.标准库被预先加载了,就像这样: 复制代码 代码如下: math = require 'math' string = require 'string' require 函数 使用 require 函数加载模块能够避免多次重复加载模块.加载一个模块: 复制代码 代码如下: require 'modulena

JavaScript的RequireJS库入门指南_基础知识

 简介 如今最常用的JavaScript库之一是RequireJS.最近我参与的每个项目,都用到了RequireJS,或者是我向它们推荐了增加RequireJS.在这篇文章中,我将描述RequireJS是什么,以及它的一些基础场景. 异步模块定义(AMD) 谈起RequireJS,你无法绕过提及JavaScript模块是什么,以及AMD是什么. JavaScript模块只是遵循SRP(Single Responsibility Principle单一职责原则)的代码段,它暴露了一个公开的API.

MaxCompute(原ODPS)开发入门指南——数据开发工具篇

MaxCompute(原ODPS)开发入门指南--数据开发工具篇 写在最前面 >>>进入了解更多>>>阿里云数加·MaxCompute大数据计算服务. 大家在使用大数据计算服务MaxCompute时,最头疼就是我现在已有的数据如何快速上云?我的日志数据如何采集到MaxCompute上?等等...具体详见<MaxCompute(原ODPS)开发入门指南--数据上云篇>. 但是数据在MaxCompute上了之后,问题又来了,我怎么基于上面进行快速的数据开发,构建

MaxCompute(原ODPS)开发入门指南——数据上云篇

MaxCompute(原ODPS)开发入门指南--数据上云篇 写在最前面 >>>进入了解更多>>>阿里云数加·MaxCompute大数据计算服务. 根据<MaxCompute(原ODPS)开发入门指南--计量计费篇>的了解,大家清楚了MaxCompute可以做什么,计费模式如何,想必大家也开通了MaxCompute想进行一次POC,但是大家遇到第一个问题一定是我的数据如何上云? 可通过多种方式数据流入MaxCompute MaxCompute(原ODPS)提

JavaScript的函数式编程基础指南_基础知识

引言 JavaScript是一种强大的,却被误解的编程语言.一些人喜欢说它是一个面向对象的编程语言,或者它是一个函数式编程语言.另外一些人喜欢说,它不是一个面向对象的编程语言,或者它不是一个函数式编程语言.还有人认为它兼具面向对象语言和函数式语言的特点,或者,认为它既不是面向对象的也不是函数式的,好吧,让我们先搁置那些争论. 让我们假设我们共有这样的一个使命:在JavaScript语言所允许的范围内,尽可能多的使用函数式编程的原则来编写程序. 首先,我们需要清理下脑子里那些关于函数式编程的错误观