lua


简介

lua 是一种轻量级的、嵌入式的、解释型的脚本语言,主要设计目标是嵌入到其他应用程序中作为扩展或脚本语言。它是一个动态类型和垃圾回收语言,通常用于处理数据、脚本化游戏逻辑、配置管理等。

比如某些鼠标就可以通过 lua 来编写宏,自定义一些快捷键啥的。

为啥要学习 lua,每个人都有自己的理由。

对于我来说,仅仅是在人群中多看了它一眼~~~

环境搭建

我所使用的系统是 Ubuntu 22,可以直接通过apt包管理安装 lua。

  • 安装
1
2
apt update
apt install lua
  • 运行

lua 脚本通过 lua 解释器运行,和 Python 差不多。

1
lua demo.lua

基础内容

注释

lua 的注释关键字是--

1
-- 这是一段注释

输出

lua 和 Python 一样通过print函数输出内容

1
print("Hello World!")

变量

  • 默认定义为全局变量

在 lua 中直接定义变量默认会定义为全局变量。

1
num=100
  • 定义局部变量

lua 通过local关键字定义局部变量。

1
local num=100

方法

  • 定义方法

在下例中function是定义方法的关键字,end关键字标记方法体的结束。

自然functionend关键字之间就是方法的主体功能部分了。

demo表示方法名,这是我们可以自定义的。

1
2
3
function demo()

end
  • 传参

lua 的传参和其它编程语言都差不多。

在下例中方法功能为打印传入的name参数。

1
2
3
function demo(name)
print(name)
end

控制结构

条件

以下例子就是一个条件判断语句,其中if a > b就是条件判断部分,它的结果是布尔值(truefalse)。

then关键字用于表示条件成立时要执行的代码块的开始,else及以后部分自然就是条件不成立时要执行的代码。

最后和定义方法一样,使用end关键字表示条件判断语句的结束。

1
2
3
4
5
if a>b then

else

end

循环

在下例中,for是定义循环的关键字,var = 1, 100是控制条件,它将变量var的初始值设置为1,并指定循环到var = 100结束。每次循环时,var会按默认步长自增。

do关键字表示循环体的开始,循环体中的代码将在每次循环时执行,直到遇到end关键字循环结束。

1
2
3
for var=1,100 do
print(var)
end

数据结构

在 lua 中,表是最基本且功能最强大的数据结构。表用于实现数组、字典(哈希表)、对象等多种数据结构。

  • 创建表
1
2
3
4
5
6
7
8
9
-- 创建一个空表
local tbl = {}

-- 创建并初始化表
local person = {name = "Alice", age = 30, city = "New York"}

-- 通过中括号或 . 号访问表元素
print(person["name"]) -- 通过中括号访问
print(person.name) -- 通过 . 号访问

数组

数组是一个特殊类型的表,它的键通常是整数。在 lua 中,数组的行为与普通表一样,只是它使用整数作为键来访问元素。

并且在 lua 中,数组的索引从 1 开始,而不是从 0 开始,感觉这比较反人类。。。

  • 创建数组
1
2
3
4
5
6
-- 创建一个数组
local arr = {1, 2, 3, 4, "hello"}

-- 访问数组元素
print(arr[1]) -- 输出 1
print(arr[5]) -- 输出 "hello"
  • 遍历数组

下面例子中的ipairs函数专用于遍历具有连续整数索引的数组,它返回数组的索引和元素。

1
2
3
4
-- i 变量是数组的索引,v 变量是数组的元素
for i, v in ipairs(arr) do
print(i, v)
end

哈希表

哈希表是通过键(可以是任何类型,除了 nil)来存储数据的一种表。lua 表通过哈希机制来实现键值对的存储,通常用于实现字典。

  • 创建哈希表
1
2
3
4
5
6
-- 创建哈希表
local person = {name = "Alice", age = 30, city = "New York"}

-- 访问哈希表中的元素
print(person.name) -- 输出 "Alice"
print(person["age"]) -- 输出 30

在哈希表中,键可以是任意类型的值,包括字符串、数字、布尔值等。但是唯独不能是nilnil在 lua 中表示空,类似于 C 语言中的NULL

  • 遍历哈希表

下面例子中的pairs函数用于遍历哈希表,它返回每一对键值对。

1
2
3
4
-- key 为健,value为值
for key, value in pairs(person) do
print(key, value)
end

数组与哈希表的区别:

  • 数组:键是连续的整数,并且是有序的。
  • 哈希表:键可以是任意类型的值,通常没有顺序。

API 操作

lua 提供了一套丰富的 API 用于操作表、字符串、文件、输入输出等。通过这些 API,我们可以高效地进行开发。

以下是一些常见的 lua API 操作分类及相关示例:

全局函数

这些函数是 lua 的基础功能,可以在任何地方调用。

  • type

返回变量的类型。

1
2
3
print(type(10))        -- 输出 "number"
print(type("hello")) -- 输出 "string"
print(type({})) -- 输出 "table"
  • tostring

将值转换为字符串。

1
2
print(tostring(10))    -- 输出 "10"
print(tostring(true)) -- 输出 "true"
  • tonumber

将字符串转换为数字。

1
2
print(tonumber("123"))    -- 输出 123
print(tonumber("abc")) -- 输出 nil

输入输出

  • io.read

从标准输入读取数据。

1
2
3
-- 从标准输入读取一行
local buf = io.read()
print(buf)
  • io.write

将数据写入标准输出。

1
io.write("Hello, World!\n")

字符串操作

  • string.len

返回字符串的长度。

1
2
print(string.len("hello"))   
-- 输出 5
  • string.sub

截取字符串的子串。

1
2
print(string.sub("hello", 2, 4))  
-- 输出 "ell"
  • string.upperstring.lower

将字符串转换为大写或小写。

1
2
print(string.upper("hello"))   -- 输出 "HELLO"
print(string.lower("HELLO")) -- 输出 "hello"
  • string.find

查找子字符串的位置。

1
2
print(string.find("hello", "ll"))  
-- 输出 3 4(表示 "ll" 在字符串中的位置)

表操作

  • table.insert

向表中插入一个元素,默认添加到末尾。

1
2
3
arr = {1, 2, 3}
table.insert(arr, 4)
print(arr[4]) -- 输出 4
  • table.remove

移除表中的指定元素。

1
2
3
arr = {1, 2, 3, 4}
table.remove(arr, 2) -- 移除索引为2的元素(值为2)
print(arr[2]) -- 输出 3
  • table.concat

将表的元素连接成一个字符串。

1
2
arr = {"Hello", "Lua", "World"}
print(table.concat(arr, " ")) -- 输出 "Hello Lua World"
  • table.sort

对表的元素进行排序。

1
2
3
4
5
arr = {3, 1, 4, 2}
table.sort(arr)
for i, v in ipairs(arr) do
print(v) -- 输出 1 2 3 4
end

面向对象

创建对象

前面我们提到过,表在 lua 中用于实现对象数据结构,在 lua 中对象是由一个包含方法和属性的表表示的。

首先,我们可以创建一个名为people的空表,

1
people = {}

定义对象方法

这段代码定义了一个匿名函数,并将其分配给 people.sayHi 字段。调用 people.sayHi() 时,Lua 会执行该匿名函数。

通过匿名函数定义sayHi方法,将它添加为people表的一个字段。

即该字段的值是一个函数。调用people.sayHi()这个字段就会调用这个函数的功能。

1
2
3
people.sayHi = function()
print("people say hi")
end

以下这段代码重新定义了sayHi方法,使其打印"people say Hi:"和对象的name属性。self参数是 lua 中常见的约定,用来表示方法的调用者(即当前对象)。

1
2
3
function people.sayHi(self)
print("people say Hi:" .. self.name)
end

复制表来创建实例

clone函数通过浅拷贝(pairs遍历)创建了一个新的表ins,然后将people表的所有字段(包括方法)复制到新表中。这个新表就是people类的一个实例。它继承了people类的方法,可以像调用people的方法一样调用实例的方法。

1
2
3
4
5
6
7
function clone(table)
local ins = {}
for key, var in pairs(table) do
ins[key] = var
end
return ins
end

这里通过clone(people)创建了一个新的对象p,并调用了p.sayHi()。由于ppeople的副本,它可以使用 people中定义的sayHi方法。

1
2
local p = clone(people)
p.sayHi()

构造方法

在这段代码中,people.new字段定义了一个构造函数,它创建了一个新的people实例,并为其设置了name属性。这个构造函数使用了之前定义的clone函数来创建一个对象实例,并将name属性添加到该实例上。

1
2
3
4
5
people.new = function(name)
local self = clone(people)
self.name = name
return self
end

使用 people.new("zhanshan") 创建了一个新的对象 p,并为其设置了 name 属性为 "zhanshan"。然后,通过 p:sayHi() 调用sayHi方法,输出 "people say Hi: zhanshan"

1
2
local p = people.new("zhanshan")
p:sayHi()

继承

这里定义了一个名为Man的新表,并在其中定义了new构造函数。Man.new函数通过调用people.new(name)来创建 people类的一个实例,然后返回该实例。这样,Man类的对象就继承了people类的所有属性和方法。

1
2
3
4
5
Man = {}
Man.new = function(name)
local self = people.new(name) -- 继承 people 类
return self
end

多态

通过Man.new("John")创建了一个Man类的实例m,该实例继承了people类的方法sayHi。因此,调用m:sayHi()时,默认执行的是people类中的sayHi方法,输出"people say Hi: John"

如果Man类重写了sayHi方法,那么调用m:sayHi()时,会执行Man类中的重写版本,覆盖父类people中的实现。

如果Man类没有重写sayHi方法,则会调用从people类继承而来的sayHi,表现为父类的行为。

这种机制体现了多态的特性:相同的接口(方法调用)可以根据对象的实际类型表现出不同的行为。

1
2
local m = Man.new("John")
m:sayHi()

和 C 联结

lua 可以与 C 语言进行集成和交互,通过调用 C 语言函数或者将 C 语言库嵌入到 lua 脚本中来实现更高效的功能扩展。lua 提供了丰富的 API 来实现 C 和 lua 的交互,主要包括以下两种方式:

  1. lua 的 C API:通过在 C 语言中使用 lua 提供的 API 来调用 lua 脚本。
  2. C 函数暴露给 lua:将 C 语言函数暴露给 lua 脚本,这样 lua 脚本就可以调用 C 函数。

将 lua 嵌入到 C 程序中

我们可以将 lua 脚本嵌入到 C 程序中并执行 lua 代码。首先需要在 C 代码中包含 lua 库,并使用 lua 提供的函数来初始化 lua 虚拟机、执行 lua 代码、调用 lua 函数等。

  • C 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdio.h>

int main() {
// 创建一个新的 Lua 状态
lua_State *L = luaL_newstate();

// 打开 Lua 标准库
luaL_openlibs(L);

// 运行一个 Lua 脚本
if (luaL_dofile(L, "script.lua") != LUA_OK) {
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
}

// 关闭 Lua 状态
lua_close(L);

return 0;
}
  • lua 代码

创建为script.lua文件。

1
print("hello from lua!")
  • 编译

确保已经安装 lua 库。

如果安装库后,编译失败并且显示缺少库就手动指定库文件路径。

1
gcc -o demo demo.c -llua -lm -ldl
  • 运行输出
1
hello from lua!

从 lua 调用 C 函数

C 函数可以被暴露给 lua,允许 lua 脚本调用这些 C 函数。通过lua_register或其他 C API 函数,你可以将 C 函数注册到 lua 环境中,使得 lua 可以像调用普通函数一样调用 C 函数。

  • C 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

// C 函数
void func(lua_State *L) {
// 获取 lua 传入的参数
const char *str = luaL_checkstring(L, 1);

// 打印输出
printf("Hello from C: %s\n", str);
}

int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);

// 注册 C 函数到 Lua
lua_register(L, "func", func);

// 执行 lua 脚本
if (luaL_dofile(L, "lua_c.lua") != 0) {
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
}

lua_close(L);

return 0;
}
  • lua 代码
1
func("Hello from lua")
  • 编译
1
gcc -o lua_c lua_c.c  -llua5.3 -lm -ldl     
  • 运行输出
1
Hello from C: Hello from lua

从 C 调用 lua 函数

你也可以在 C 代码中调用 lua 函数。首先需要将 lua 函数加载到栈中,然后通过lua_pcall执行它。

  • C 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);

// 加载 lua 脚本
if (luaL_dofile(L, "c_lua.lua") != 0) {
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
lua_close(L);
return -1;
}

// 获取 lua 中的函数
lua_getglobal(L, "func");

// 在栈上压入参数
lua_pushstring(L, "Hello from C");

// 调用 lua 函数
if (lua_pcall(L, 1, 0, 0) != 0) {
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
}

lua_close(L);
return 0;
}
  • lua 代码

创建为c_lua.lua文件。

1
2
3
function func(str)
print("Hello from lua: " .. str)
end
  • 编译
1
gcc -o c_lua c_lua.c -llua5.3 -lm -ldl
  • 运行输出
1
Hello from lua: Hello from C