第十章 FluffOS 现代特性
本章介绍 FluffOS 驱动支持的现代 LPC 特性,这些特性在传统 LPC 教程中较少涉及,但能显著提升代码的可读性与开发效率。建议配合
#pragma strict_types使用。
10.1 模板字面量(Template Literals)
使用反引号 ` 包裹字符串,支持 ${expression} 插值,类似 JavaScript 的模板字符串。
string name = "Alice";
int count = 3;
`Hello, ${name}!` // "Hello, Alice!"
`You have ${count} items.` // "You have 3 items."
`Sum: ${1 + 2}` // "Sum: 3"
转义:
`price is \$100` // 美元符号转义
`use \`backticks\`` // 反引号转义
换行处理: 模板字面量内的换行会被折叠(移除):
`line one
line two`
// 结果: "line oneline two"
相邻拼接: 模板字面量可与普通字符串和其它模板字面量相邻拼接:
`Hello, ${name}! ` `How are you?`
`Count: ${n}` " items"
"Hello, " `${name}!`
10.2 可选链(Optional Chaining)
在访问可能为 0(undefined)的 mapping 时,可选链可以安全地返回 0 而不会报错。传统的 m["key"] 在 m 为 0 时会抛出 "Indexing on illegal type" 错误。
mapping m = 0;
// 传统写法会报错:
// mixed val = m["key"]; // Error: Indexing on illegal type
// 可选链安全返回 0:
mixed val = m?.key; // 返回 0,不报错
mixed val2 = m?.["key"]; // 等价写法
mixed val3 = m.?[\"key"]; // 另一种写法
可选链支持三种语法形式,可根据代码风格选用:
| 语法 | 说明 |
|---|---|
m?.key |
点号形式,适合简单键名 |
m?.["key"] |
方括号形式,?. 在前 |
m.?[\"key"] |
方括号形式,.? 在前 |
可选链与空值合并搭配使用效果更佳:
mapping user = get_user_data();
string name = user?.name ?? "匿名用户";
10.3 空值合并运算符(Null Coalescing)
?? 运算符在左侧为 undefined(即 0)时返回右侧值。注意:0 和 "" 不是 undefined,只有未初始化/赋值为 0 的变量才触发。
mixed val = some_mapping["key"] ?? "default";
// 等价于
mixed val = some_mapping["key"];
if (undefinedp(val)) val = "default";
与三元运算符的区别:
int x = 0;
mixed a = x ?? 42; // a = 0,因为 x 不是 undefined(它是 0)
mixed b = undefinedp(x) ? 42 : x; // 同上
string s = "";
mixed c = s ?? "empty"; // c = "",因为空字符串不是 undefined
10.4 逻辑赋值运算符
FluffOS 支持三种短路逻辑赋值运算符,简化条件赋值模式:
| 运算符 | 等价形式 | 说明 |
|---|---|---|
\|\|= |
x = x \|\| val |
当 x 为 falsy 时赋值 |
&&= |
x = x && val |
当 x 为 truthy 时赋值 |
??= |
x = x ?? val |
当 x 为 undefined 时赋值 |
// ||=:设置默认值(如果当前值为 falsy)
mapping config = ([]);
config["debug"] ||= FALSE; // 如果 debug 为 0/falsy,设为 FALSE
// &&=:仅在已有值时更新
int hp = 50;
hp &&= hp - 10; // hp 为 truthy 时减 10,结果 40
// ??=:仅在 undefined 时赋值
mapping m = ([]);
m["key"] ??= "default"; // key 不存在时设为 "default"
10.5 展开运算符(Spread Operator)
展开运算符 ... 可以将数组展开为独立元素,用于数组字面量和函数调用。
数组字面量中展开:
int *a = ({ 1, 2 });
int *b = ({ 3, 4 });
int *c = ({ a..., b... }); // ({ 1, 2, 3, 4 })
// 可以在展开间穿插元素
int *d = ({ a..., 99, b... }); // ({ 1, 2, 99, 3, 4 })
函数调用中展开:
void show(string a, string b, string c) {
write(a + ", " + b + ", " + c + "\n");
}
string *args = ({ "Alice", "Bob", "Charlie" });
show(args...); // 等价于 show("Alice", "Bob", "Charlie")
// 展开后还可以追加参数
void log(string level, mixed *msgs...) { }
string *base_args = ({ "INFO" });
log(base_args..., "extra msg"); // level="INFO", msgs=({"extra msg"})
10.6 按引用传递(ref / & 参数)
函数参数默认按值传递(值类型)或按引用共享(引用类型)。使用 ref 或 & 可以显式声明按引用传递,允许函数修改调用者的变量。
声明引用参数:
void increment(int ref value) {
value++;
}
void append_item(mixed & arr, mixed item) {
arr += ({ item });
}
调用时使用 ref 或 &:
int x = 10;
increment(ref x); // x = 11
increment(& x); // x = 12
mixed *items = ({ "a" });
append_item(& items, "b"); // items = ({ "a", "b" })
限制:
ref必须在声明和调用处同时使用- 参数必须是左值(变量,不能是表达式)
- 不能引用数组/字符串的范围
10.7 foreach 与 ref 结合
foreach 支持 ref(或 &)遍历,可以直接修改原数组或 buffer 的元素。
// 按引用遍历数组——修改原数组
int *nums = ({ 1, 2, 3 });
foreach (int ref n in nums) {
n *= 2; // nums 变为 ({ 2, 4, 6 })
}
// & 是 ref 的语法糖
foreach (int & n in nums) {
n *= 2; // nums 变为 ({ 4, 8, 12 })
}
// 按引用遍历 buffer
buffer buf = allocate_buffer(3);
buf[0] = 10; buf[1] = 20; buf[2] = 30;
foreach (int & b in buf) {
b += 1; // 每个字节值加 1
}
注意: ref 只能修改数组和 buffer 的元素。对字符串使用 ref 遍历不会修改原字符串(字符串是值类型,传入 foreach 的是副本)。
10.8 Heredoc 文本块(@ 和 @@)
Heredoc 语法用于定义多行文本,避免手动拼接换行符。
@ 产生单个字符串:
string text = @END
这里可以写
多行文本内容
END
// text 包含末尾换行符
@@ 产生字符串数组:
string *lines = @@END
第一行
第二行
第三行
END
// lines = ({ "第一行", "第二行", "第三行" })
// 不包含换行符
实际应用:
void show_help() {
string help = @HELP
可用指令:
look - 查看周围
north - 向北移动
south - 向南移动
quit - 退出游戏
HELP
write(help);
}
// @@ 适合定义配置列表
string *banned_words = @@WORDS
badword1
badword2
badword3
WORDS
10.9 类型严格检查(#pragma strict_types)
强烈推荐在每个 LPC 文件开头启用严格类型检查。启用后编译器会在编译时捕获类型错误,避免运行时意外。
#pragma strict_types
启用后的效果:
- 函数必须声明返回类型和参数类型:
// 正确
int add(int a, int b) {
return a + b;
}
// 错误——缺少类型声明(编译报错)
// add(a, b) { return a + b; }
- 调用和赋值会进行类型检查:
int x = add(1, 2); // 正确
string s = add(1, 2); // 编译警告/错误:int 赋给 string
- 使用
mixed关闭特定位置的检查:
varargs mixed query(string key, mixed def) {
// def 可以是任意类型
return def;
}
相关 pragma:
| 指令 | 说明 |
|---|---|
#pragma strict_types |
强制类型检查 |
#pragma save_types |
保存类型信息供继承者检查 |
#pragma warnings |
启用编译警告 |
#pragma no_warnings |
禁用编译警告 |
#pragma optimize |
启用额外优化 |
10.10 简化函数指针语法
FluffOS 支持将函数名直接赋值给 function 类型变量,无需 (: :) 包裹。
传统语法:
function f = (: local_func :);
function f = (: write :);
int result = evaluate(f, "hello\n");
简化语法:
function f = local_func;
function f = write;
int result = f("hello\n"); // 直接调用,无需 evaluate()
在数据结构中使用:
// 函数数组
function *ops = ({ add, subtract, multiply });
int r = ops[0](10, 5); // 调用 add(10, 5)
// 函数 mapping——实现命令分发
mapping cmds = ([
"attack": do_attack,
"defend": do_defend,
"heal": do_heal,
]);
if (cmds[action]) cmds[action]();
10.11 匿名函数(Lambda)
匿名函数允许直接在表达式中定义函数体,适合回调和一次性使用的逻辑。
// 基本匿名函数
function f = function(int x) {
int y = x * 2;
return y + 1;
};
int result = f(5); // result = 11
// 作为回调使用
int *evens = filter_array(arr, function(int x) {
return x % 2 == 0;
});
// 排序比较器
sort_array(players, function(object a, object b) {
return (int)a->query_level() - (int)b->query_level();
});
匿名函数与闭包: 匿名函数可以捕获定义时的外部变量(闭包):
function make_adder(int base) {
return function(int x) {
return base + x; // 捕获了外部的 base
};
}
function add5 = make_adder(5);
int r = add5(10); // r = 15
10.12 new() 关键字
new 关键字用于克隆对象和创建 class 实例,是 clone_object() 的替代语法。
克隆对象:
// 基本克隆
object ob = new("/npc/goblin");
// 等价于
object ob = clone_object("/npc/goblin");
// 带参数克隆(参数传给 create())
object ob = new("/npc/goblin", "Goblin King");
创建 class 实例:
class Person {
string name;
int level;
float *scores;
}
// 创建空实例
class Person p = new(class Person);
// 带命名初始化
class Person p = new(class Person, name : "Alice", level : 10);
// 访问成员
p.name = "Bob";
p->name = "Bob"; // 箭头语法同样可用
class 的跨文件共享: 通过 inherit 共享 class 定义,不要用 #include:
// /std/person.h 中定义 class
// /std/npc.c 中使用
inherit "/std/person";
class Person npc = new(class Person, name : "Guard");
10.13 综合示例
以下示例综合运用了多种现代特性:
#pragma strict_types
inherit "/std/object";
// Heredoc 定义帮助文本
private string *help_lines = @@HELP
look - 查看周围
inv - 查看背包
quit - 退出游戏
HELP
// 使用匿名函数作为命令处理器
private mapping command_handlers = ([]);
void init_handlers() {
command_handlers["look"] = function(object player) {
string desc = environment(player)?.long ?? "一片虚无。";
tell_object(player, desc);
};
}
// 使用 ref 参数和展开运算符
varargs void broadcast(string *exclude..., string msg) {
object *users = users() - exclude;
foreach (object &u in users) {
tell_object(u, msg);
}
}
// 使用可选链和空值合并安全访问数据
string get_player_title(mapping data) {
return data?.title ?? data?.name ?? "无名者";
}
// 使用模板字面量格式化输出
string format_status(string name, int hp, int max_hp) {
return `[$[name] $[hp]/$[max_hp]]`;
}
// 使用 ??= 设置默认配置
void ensure_defaults(mapping config) {
config["max_hp"] ??= 100;
config["max_mp"] ??= 50;
config["debug"] ||= FALSE;
config["name"] ??= "default";
}
本章介绍的现代特性均基于 FluffOS 驱动源码
src/compiler/internal/grammar.y中的语法实现。不同版本的 FluffOS 对这些特性的支持程度可能有所差异,建议使用较新版本的 FluffOS 驱动。