标准库与文件 I/O

14.1 math 库

常用常量

1
2
3
4
print(math.pi)            --> 3.1415926535898
print(math.huge) --> inf(无穷大)
print(math.maxinteger) --> 9223372036854775807(5.3+)
print(math.mininteger) --> -9223372036854775808(5.3+)

取整函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- floor:向下取整
print(math.floor(3.7)) --> 3
print(math.floor(-3.7)) --> -4

-- ceil:向上取整
print(math.ceil(3.1)) --> 4
print(math.ceil(-3.1)) --> -3

-- 四舍五入(自己实现)
local function round(x)
return math.floor(x + 0.5)
end

print(round(3.4)) --> 3
print(round(3.5)) --> 4

绝对值与符号

1
2
3
4
5
6
7
print(math.abs(-5))       --> 5
-- Lua 没有 sign 函数,自己实现
local function sign(x)
if x > 0 then return 1
elseif x < 0 then return -1
else return 0 end
end

幂与根

1
2
3
4
5
print(math.sqrt(16))      --> 4
print(math.pow(2, 10)) --> 1024(等价于 2^10)
print(math.exp(1)) --> 2.718281828459(e^1)
print(math.log(100)) --> 4.6051701859881(自然对数)
print(math.log(100, 10)) --> 2.0(以 10 为底的对数)

三角函数

1
2
3
4
5
6
7
8
9
10
11
12
local angle = math.pi / 4  -- 45 度
print(math.sin(angle)) --> 0.7071
print(math.cos(angle)) --> 0.7071
print(math.tan(angle)) --> 1.0

-- 反三角函数
print(math.asin(0.5)) --> 0.5236(弧度 ≈ 30°)
print(math.acos(0.5)) --> 1.0472(弧度 ≈ 60°)

-- 弧度 ↔ 角度转换
local deg = math.deg(math.pi / 3) --> 60
local rad = math.rad(60) --> 1.0472

随机数

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
-- 初始化种子
math.randomseed(os.time())

-- 生成随机浮点数 [0, 1)
print(math.random())

-- 生成随机整数 [1, n]
print(math.random(100))

-- 生成随机整数 [m, n]
print(math.random(10, 20))

-- 注意:Windows 上 os.time() 粒度不够
-- 更好的种子策略
math.randomseed(os.time() + os.clock() * 1000)

-- 打乱数组
local function shuffle(t)
for i = #t, 2, -1 do
local j = math.random(i)
t[i], t[j] = t[j], t[i]
end
return t
end

local cards = {"A", "2", "3", "4", "5"}
shuffle(cards)

14.2 os 库

时间与日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 当前时间戳(秒)
local now = os.time()
print(now) --> 例如 1756627200

-- 将时间表转为时间戳
local t = {year = 2026, month = 6, day = 1, hour = 8, min = 0, sec = 0}
print(os.time(t)) --> 对应的时间戳

-- 将时间戳转为时间表
local dt = os.date("*t", os.time())
print(dt.year) --> 2026
print(dt.month) --> 6
print(dt.day) --> 1
print(dt.hour) --> 8
print(dt.min) --> 0
print(dt.sec) --> 0
print(dt.wday) --> 1(星期几,1=星期日)
print(dt.yday) --> 152(一年中的第几天)

格式化日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 标准格式化
print(os.date("%Y-%m-%d %H:%M:%S")) --> 2026-06-01 08:00:00
print(os.date("%Y年%m月%d日 %H时%M分")) --> 2026年06月01日 08时00分

-- 格式化说明
-- %Y:四位数年份
-- %y:两位数年份
-- %m:月份(01-12)
-- %d:日(01-31)
-- %H:小时(00-23)
-- %M:分钟(00-59)
-- %S:秒(00-59)
-- %A:星期全名(Saturday)
-- %a:星期缩写(Sat)
-- %B:月份全名(June)
-- %b:月份缩写(Jun)
-- %%:百分号字面量

时间差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
local start = os.time()

-- 模拟耗时操作
for i = 1, 1000000 do
math.sqrt(i)
end

local elapsed = os.difftime(os.time(), start)
print("耗时:" .. elapsed .. " 秒")

-- 更精确的计时
local start = os.clock()
for i = 1, 1000000 do
math.sqrt(i)
end
local elapsed = os.clock() - start
print(string.format("耗时:%.3f 秒", elapsed))

执行系统命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 执行命令(返回状态码)
local exit_code = os.execute("echo Hello from Lua")
-- Windows 上用:
-- os.execute("dir")

-- 捕获命令输出(需要自己封装)
local function execute_command(cmd)
local handle = io.popen(cmd, "r")
if handle then
local result = handle:read("*a")
handle:close()
return result
end
return nil
end

-- Windows
local output = execute_command("dir")
-- Linux/macOS
-- local output = execute_command("ls -la")

14.3 io 库——文件读写

文件模式

模式 含义
"r" 只读(文件必须存在)
"w" 只写(覆盖原有内容)
"a" 追加(写入末尾)
"r+" 读写(文件必须存在)
"w+" 读写(覆盖原有)
"a+" 读和追加
"b" 二进制模式(追加到模式后,如 "rb"

简单 I/O 模式

简单模式使用预定义的输入/输出流:

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
31
-- 写入文件
local file = io.open("test.txt", "w")
if file then
file:write("Hello Lua!\n")
file:write("这是第二行\n")
file:close()
end

-- 读取整个文件
file = io.open("test.txt", "r")
if file then
local content = file:read("*a") -- 读取全部
print(content)
file:close()
end

-- 按行读取
file = io.open("test.txt", "r")
if file then
for line in file:lines() do
print("行:", line)
end
file:close()
end

-- 追加内容
file = io.open("test.txt", "a")
if file then
file:write("追加的一行\n")
file:close()
end

io.read 的读取格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- "*a":读取整个文件(从当前位置到末尾)
-- "*l":读取一行(默认)
-- "*n":读取一个数字
-- number:读取指定字节数

local file = io.open("data.txt", "r")
if file then
-- 读取一个数字
local num = file:read("*n")
print("数字:", num)

-- 读取一行
local line = file:read()
print("行:", line)

-- 读取 10 个字节
local chunk = file:read(10)
print("10 字节:", chunk)

file:close()
end

完全 I/O 模式

完全模式使用文件句柄:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 打开文件
local f = io.open("test.txt", "w+")

-- 写入
f:write("Lua 文件操作\n")
f:write("第二行内容\n")

-- 定位到文件开头
f:seek("set", 0)

-- 读取
local content = f:read("*a")
print(content)

-- 查看当前位置
local pos = f:seek("cur", 0)
print("当前位置:", pos)

-- 定位到末尾
local size = f:seek("end", 0)
print("文件大小:", size)

-- 关闭
f:close()

file:seek 参数

1
2
3
4
5
6
7
8
-- seek(whence, offset)
-- "set":相对于文件开头
-- "cur":相对于当前位置
-- "end":相对于文件末尾

f:seek("set", 0) -- 回到开头
f:seek("end", -10) -- 定位到倒数第 10 个字节
f:seek("end", 0) -- 定位到末尾(可获取文件大小)

14.4 实用 I/O 示例

逐行处理大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local function process_large_file(filename)
local f = io.open(filename, "r")
if not f then
print("无法打开文件:", filename)
return
end

local line_count = 0
for line in f:lines() do
line_count = line_count + 1
-- 处理每一行
-- 这里简单打印行号
if line_count % 1000 == 0 then
print("已处理行数:", line_count)
end
end

f:close()
print("总行数:", line_count)
end

读取 CSV 文件

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
31
32
33
34
35
36
37
38
39
40
41
local function parse_csv_line(line)
local fields = {}
local current = ""
local in_quotes = false

for i = 1, #line do
local c = line:sub(i, i)

if c == '"' then
in_quotes = not in_quotes
elseif c == ',' and not in_quotes then
table.insert(fields, current)
current = ""
else
current = current .. c
end
end
table.insert(fields, current)

return fields
end

local function read_csv(filename)
local f = io.open(filename, "r")
if not f then return nil end

local data = {}
local headers = parse_csv_line(f:read())

for line in f:lines() do
local fields = parse_csv_line(line)
local record = {}
for i, header in ipairs(headers) do
record[header] = fields[i]
end
table.insert(data, record)
end

f:close()
return data
end

写入 CSV 文件

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
local function write_csv(filename, data, headers)
local f = io.open(filename, "w")
if not f then return false end

-- 写入表头
if headers then
f:write(table.concat(headers, ",") .. "\n")
end

-- 写入数据
for _, record in ipairs(data) do
local fields = {}
for _, header in ipairs(headers) do
local value = tostring(record[header] or "")
-- 如果包含逗号或引号,用引号包裹
if value:find('[,"]') then
value = '"' .. value:gsub('"', '""') .. '"'
end
table.insert(fields, value)
end
f:write(table.concat(fields, ",") .. "\n")
end

f:close()
return true
end

14.5 代码加载:dofile / loadfile / load

dofile

直接执行文件中的 Lua 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 创建一个脚本文件
local f = io.open("greeting.lua", "w")
f:write([[
local name = "Lua"
print("Hello, " .. name .. "!")
return 42
]])
f:close()

-- 执行文件
local result = dofile("greeting.lua")
--> Hello, Lua!
print(result) --> 42

loadfile

将文件编译为函数但不执行:

1
2
3
4
5
6
7
8
9
10
11
-- 加载但不执行
local func = loadfile("greeting.lua")
if func then
-- 执行
local result = func()
print(result) --> 42
end

-- 可以多次执行
func()
func()

load

从字符串加载 Lua 代码:

1
2
3
4
5
6
7
8
9
10
11
12
-- 编译字符串形式的 Lua 代码
local func = load("return 2 + 2")
if func then
print(func()) --> 4
end

-- 动态代码执行
local expression = "10 * (5 + 3)"
local f = load("return " .. expression)
if f then
print(f()) --> 80
end

14.6 错误处理

error()assert()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 主动抛出错误
function divide(a, b)
if b == 0 then
error("除数不能为 0") -- 抛出错误字符串
end
return a / b
end

-- error 的第二个参数指定错误级别
function inner()
error("出错了", 2) -- 错误报告给调用者
end

function outer()
inner()
end

-- assert 简化检查
local function open_file(filename)
local f = assert(io.open(filename, "r"), "无法打开文件: " .. filename)
return f
end

pcall(保护调用)

pcall 以保护模式调用函数,捕获错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
local function risky_function(x)
if x < 0 then
error("不能为负数")
end
return math.sqrt(x)
end

-- 正常调用
local ok, result = pcall(risky_function, 9)
if ok then
print("结果:", result) --> 结果: 3
end

-- 错误调用
local ok, err = pcall(risky_function, -1)
if not ok then
print("出错:", err) --> 出错: 不能为负数
end

-- pcall 的返回值:
-- 成功:true, 函数返回值...
-- 失败:false, 错误信息

xpcall(带错误处理函数)

xpcall 在出错时调用自定义的错误处理函数,通常用于获取堆栈信息:

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
-- 错误处理函数
local function error_handler(err)
print("发生了错误:")
print(" 消息:", err)
print(" 堆栈:", debug.traceback())
end

-- 引发错误的函数
local function level3()
error("深层错误")
end

local function level2()
level3()
end

local function level1()
level2()
end

-- 使用 xpcall
local ok, err = xpcall(level1, error_handler)
if not ok then
print("调用失败,已捕获错误")
end

错误处理的最佳实践

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
-- 方式一:返回错误(类似 Go 风格)
local function read_file_safe(filename)
local f = io.open(filename, "r")
if not f then
return nil, "无法打开文件"
end

local content = f:read("*a")
f:close()
return content, nil
end

local content, err = read_file_safe("nonexistent.txt")
if not content then
print("读取失败:", err)
end

-- 方式二:使用 pcall
local function safe_call(func, ...)
local results = {pcall(func, ...)}
if not results[1] then
print("调用失败:", results[2])
return nil
end
table.remove(results, 1)
return table.unpack(results)
end

local result = safe_call(math.sqrt, -1)

14.7 本章小结

重要函数
math floorceilrandomsqrtsinpi
os timedatedifftimeclockexecute
io openreadwritelinesseekclose
加载 dofileloadfileload
错误 errorassertpcallxpcall

练习题

  1. 写一个函数 copy_file(src, dst) 实现文件复制
  2. 读取文件内容,统计单词出现频率,输出 TOP 10
  3. 实现一个简单的 INI 文件解析器(读取和写入 .ini 格式)
  4. math.random 模拟掷骰子(1~6),统计 10000 次的结果分布
  5. 写一个安全的 eval(str) 函数,使用 pcall 捕获解析和运行时的错误
ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: