本博客采用创作共用版权协议, 要求署名、非商业用途和保持一致. 转载本博客文章必须也遵循署名-非商业用途-保持一致的创作共用协议.

工作需要用到Lua做一些脚本, 所以学习一下这个在游戏开发应用广泛的语言, 当然, 我更喜欢称之为撸啊撸语言…

简介

  • Lua是一种轻量语言,它的官方版本只包括一个精简的核心和最基本的库,
  • 性能方面, Lua比Python快(这也是脚本语言选型中不用python的原因)
  • Lua与C/C++交互方便, 易于扩展
  • Lua是一个动态弱类型语言, 支持增量式垃圾收集策略, 支持协程
  • 支持REPL(Read-Eval-Print Loop, 交互式解释器)
1
2
3
4
5
6
7
8
-- 命令行中输入lua, 使用REPL
andrew_liu:Lua/ $ lua
Lua 5.2.4 Copyright (C) 1994-2015 Lua.org, PUC-Rio
> print "hello world" -- 国际编程学习惯例
hello world
> print("hello world")
hello world
>

安装

已经懒得再安利brew了(不好用你干我), 一行命令完成安装

1
2
# 安装路径为/usr/local/Cellar/lua
$ brew install lua

基础语法

注释

单行注释使用--, 多行注释使用--[[--]]

1
2
3
4
5
6
7
-- 这种为单行注释
-- Date: 16/8/27
-- Time: 22:33
--[[
这里是多行注释
--]]

变量定义

  • lua中的所有变量默认为全局变量, 定义局部变量需要使用local关键字(是不是很变态)
  • 所有的数字都是双精度浮点型(double)
  • 字符串可以使用单引号和双引号, 类似于python中的字符串表示形式
  • 使用一个未定义的变量不会报错, 而是返回nil(类似于C/C++中的NULL)
  • 一行语句可以同时定义多个变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- variable
age = 0 -- 全局变量
sum = 2 -- 所有数字都是double型
str = '蛤丝' -- 字符串可以是单引号和双引号
str1 = "还是蛤丝"
-- 使用`[[]]`定义多行字符串时, 其空格都会被保留
str2 = [[多行的字符串
以两个方括号
开始和结尾。]]
sum = nil -- 撤销sum的定义
test = not_define
print(test)
name, age, sex = "andrew", 18, "male", "蛤丝" -- 最后一个变量会被丢弃, 而不会报错(闷死发大财才是最吼得)

if-else

if-else与python有些像, 不过需要注意if和elseif最后的then和末尾的end

1
2
3
4
5
6
7
8
9
10
11
-- if-else
age = 100
if age <= 40 and age >= 20 then
print("young man!")
elseif age > 40 and age < 100 then
print("old man")
elseif age < 20 then
print("too young")
else
print("monster")
end

循环

  • Lua同样支持三种循环, while循环, for循环, until循环(可以理解为C/C++中的do-while循环)

while循环

当条件满足, 会一直执行循环体

1
2
3
4
5
6
-- while
num = 0
while num < 10 do
print(num)
num = num + 1
end

for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- for
sum = 0
-- 1表示从1开始, 10表示最大为10(包含10)
-- C++: for (int i = 1, i <= 10; ++i)
for i = 1, 100 do -- 包含1和100
sum = sum + i
end
-- 类似于Python中的range操作, 2表示, 每次循环后i执行+2操作
-- C++: for (int i = 1, i <= 10; i = i + 2)
for i = 1, 10, 2 do -- 1,3,5,7,9
print(i)
sum = sum + i
end
-- ..操作符用于连接字符串, 可以理解为python中字符串的+
操作
print("sum = " .. sum)

until循环, 类似于C/C++中的do-while语句

1
2
3
4
5
6
7
-- until(do-while)
sum = 2
-- 中文: 直到sum的值大于1000时, 才终止循环
repeat
sum = sum ^ 2 -- 幂操作
until sum > 1000
print("do-while, sum = "..sum)

函数

  • 函数在Lua中是一等公民
  • 支持有名和匿名函数
  • 函数最后有个end, 写C/C++/Python的可能不太适应
  • 函数支持一次`返回多个变量
  • 局部函数的定义只需要在函数前加个local, 和局部变量类似
1
2
3
4
5
6
7
8
9
10
-- function(return支持返回多个值), 函数是一等公民
function add(a, b)
return a + b
end
print("1 + 2 = "..add(1, 2))
-- 匿名函数
f = function (a, b)
return a * b
end
print ("匿名函数: " .. f(2, 3))

递归(经典: 斐波那契)

1
2
3
4
function fib(n)
if n < 2 then return n end
return fib(n - 2) + fib(n - 1)
end

Table

Table是Lua中核心数据结构, 我理解为其他语言中的Dict/Map数据结构(key-value的数据结构), 而数组等在Lua中都是通过Table来表示的

Table可以使用任何非nil的值作为key

1
2
3
4
5
6
7
8
9
10
-- Lua唯一的组合数据结构
-- 可以使用任何非nil值作为key
-- Table(key-value数据结构)
map = {
name = "andrew", -- 此处name默认使用字符串作为key
age = 18,
sex = "male"
}
-- 上下两种写法等价
--map = {['name'] = 'andrew', ['age'] = 18, ['sex'] = 'male'}

对Table创建, 读取, 更新和删除操作, key-value的读取可以通过.符号

1
2
3
4
5
-- Table的CRUD
map['hobby'] = "膜蛤" -- map.hobby = "膜蛤"
print(map.name)
map.age = 19
map.sex = nil -- 删除key

遍历一个Table

1
2
3
4
-- 遍历一个Table
for k, v in pairs(map) do
print(k, v)
end

数组/列表

  • 数组是通过Table来实现的

Lua中数组的下标是从1开始, 从1开始, 从1开始. 重要的事情说三遍.

1
2
3
4
5
6
7
8
9
-- array, 数组下标从1开始
-- 数组中可以是不同类型的数据
arr = {"string", 100, "andrew", function() print("hello world") end }
-- 等价于
-- arr = {[1]="string", [2]=100, [3]="andrew", [4]=40, [5]=function() print("hello world") end}
for i = 1, #arr do -- #arr表示数组的长度
print(arr[i])
end

元表(metatable)

Table的metatable提供一种类似于操作符重载的机制, 写个python的可能重写个__add__方法, 感觉有点类似

  • 通过setmetatable将自己实现的metamethod绑定到对应的Table对象中
1
2
3
4
5
6
7
8
9
10
11
12
13
-- table的元表提供一种机制,支持操作符重载
data_one = {a = 1, b = 2} -- data_one和data_two 可以看做表示1/2和3/4
data_two = {a = 3, b = 4 }
meta_fraction = {} -- 新定义一个元表
function meta_fraction.__add(f1, f2) -- __add是build-in的原方法
local sum = {}
sum.b = f1.b * f2.b
sum.a = f1.a * f2.b + f2.a * f1.b
return sum
end
setmetatable(data_one, meta_fraction) -- 为之前定义的两个table设置metatable
setmetatable(data_two, meta_fraction)
s = data_one + data_two -- 执行 + 操作
  • 元表的__index 可以重载用于查找的点操作符, 我理解有点类似于python中的__getattr__, 对类中的属性做查找
1
2
3
4
5
6
-- 当表要索引一个值时如table[key], Lua会首先在table本身中查找key的值,
-- 如果没有并且这个table存在一个带有__index属性的Metatable, 则Lua会按照__index所定义的函数逻辑查找
default_map = {name = 'andrew', sex = 'male' }
new_map = {age = 18 }
setmetatable(default_map, {__index = new_map})
print(default_map.name, default_map.age) -- 继承了new_map的属性

元方法(metamethod)

__add这种方法在Lua中被称为元方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- __add(a, b) for a + b
-- __sub(a, b) for a - b
-- __mul(a, b) for a * b
-- __div(a, b) for a / b
-- __mod(a, b) for a % b
-- __pow(a, b) for a ^ b
-- __unm(a) for -a
-- __concat(a, b) for a .. b
-- __len(a) for #a
-- __eq(a, b) for a == b
-- __lt(a, b) for a < b
-- __le(a, b) for a <= b
-- __index(a, b) <fn or a table> for a.b
-- __newindex(a, b, c) for a.b = c
-- __call(a, ...) for a(...)

继承

继承同样是通过元表和元方法来实现的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- table实现继承
Dog = {}
-- 这个new函数我理解为继承类的构造函数, 冒号(:)函数会使参数默认增加一个self作为参数
function Dog:new()
-- 下面这句相当于继承的类增加的新的成员变量
local new_obj = {sound = 'woof'}
self.__index = self
return setmetatable(new_obj, self)
end
function Dog:make_sound() -- 等价于Dog.make_sound(Dog)
print("say " .. self.sound)
end
-- 使用
new_dog = Dog:new() -- new_dog获得Dog的方法和变量列表
new_dog:make_sound()
  1. self.__index = self是防止self被扩展后改写,所以,让其保持原样
  2. setmetatable(new_obj, self)会返回第一个参数, new_obj会得到self的函数
  3. Dog:new()调用方式会增加一个隐式参数self

模块

  1. require函数, 第一次执行后有缓存, 所以载入同样的lua文件时, 只有第一次的时候会去执行, 多次执行同一句require, 只有第一次生效
  2. dofile函数, 类似于require, 但无缓存, 每次语句都会重新执行一次
  3. loadfile加载一个lua文件, 但是并不运行它, 只要在需要的时候再像函数一样执行它
1
2
3
4
5
6
7
-- module
-- 1. require
local test = require('module') -- require文件会被缓存
-- test.say_my_name() -- 不能调用local
test.say_hello()
-- 2. dofile 类似require 但不缓存
-- 3. loadfile加载一个lua文件, 但是并不运行它

参考链接