Erlang 记录(Record)本质上是对元组的一种宏封装。它允许程序员通过字段名而不是索引来访问元组中的数据,从而显著提升代码的可读性和维护性。以下指南将详细介绍如何定义、实例化、操作以及在模块间共享记录。
定义记录
记录的定义通常位于 Erlang 源代码文件的头部,或者专门的头文件(.hrl)中。定义使用 -record 指令。
- 打开你的 Erlang 源代码文件(例如
person.erl)。 - 输入
-record指令来声明记录结构。 - 指定记录名称和字段列表。字段列表是一个元组,包含原子形式的字段名。你可以设置默认值,语法为
FieldName = DefaultValue。如果不设置默认值,初始值为undefined。
%% 定义一个名为 person 的记录
%% 包含 name, age 和 city 三个字段
%% age 的默认值为 0
-record(person, {name, age = 0, city}).
创建记录实例
定义好记录后,需要在模块中编译或包含它才能使用。创建实例即是构造一个元组,但在语法上使用 # 符号。
- 确认记录已在当前模块中定义或通过头文件包含。
- 使用
#记录名{}结构创建一个空实例。未赋值的字段将使用定义时的默认值。 - 赋值特定字段。在花括号内输入
字段名 = 值,字段之间用逗号分隔。
%% 创建一个空实例
P1 = #person{}.
%% 结果: {person, undefined, 0, undefined}
%% 创建一个指定 name 的实例
P2 = #person{name = "Alice"}.
%% 结果: {person, "Alice", 0, undefined}
%% 创建一个指定所有字段的实例
P3 = #person{name = "Bob", age = 25, city = "New York"}.
访问与读取字段
在 Erlang 中,读取记录字段最常用的方式是通过模式匹配。虽然可以通过“点语法”读取,但这种方式仅限于编译时已知记录名的情况,且依赖于宏扩展。
- 使用模式匹配从记录中提取数据。这是最推荐的方式。
- 利用
#记录名.字段名语法直接获取字段值(仅限字段值是字面量或已知变量时)。
%% 方式一:模式匹配(推荐)
%% 从 P3 中提取 name 和 age
#person{name = Name, age = Age} = P3.
%% Name 变量现在绑定为 "Bob"
%% Age 变量现在绑定为 25
%% 方式二:点语法(直接访问)
%% 获取 P3 的 city 字段
City = P3#person.city.
%% City 变量现在绑定为 "New York"
更新记录
Erlang 的变量是单向绑定的(不可变),因此“更新”记录实际上是创建一个包含新值的新记录副本。更新操作使用原记录作为模板。
- 引用原记录变量。
- 使用
#记录名{原记录变量 | 字段 = 新值}语法进行更新。 - 指定需要修改的字段。未指定的字段将保留原记录中的值。
%% 原始记录
OldPerson = #person{name = "Tom", age = 20, city = "London"}.
%% 更新 age 为 21,保留其他字段
NewPerson = OldPerson#person{age = 21}.
%% 结果:NewPerson 是 {person, "Tom", 21, "London"}
%% OldPerson 保持不变
在模块间共享记录
为了在多个 Erlang 模块中使用同一个记录定义,通常将记录定义放在单独的头文件中。
- 创建一个后缀为
.hrl的文件(例如records.hrl)。 - 剪切已有的
-record()定义并粘贴到该头文件中。 - 打开需要使用该记录的模块文件(
.erl)。 - 添加
-include("records.hrl").指令。注意文件名需用引号包裹。
records.hrl 内容:
-record(user, {id, username, role = guest}).
app.erl 内容:
-module(app).
-include("records.hrl"). %% 包含记录定义
-export([create_user/1]).
create_user(Name) ->
%% 现在可以使用 #user{} 了
#user{username = Name}.
当编译器处理 app.erl 时,它会将 -include 指令替换为 records.hrl 中的具体内容,使得 #user{} 语法生效。
记录与元组的映射
理解记录在底层的存储形式有助于调试。记录在运行时就是普通的元组,元组的第一个元素是记录名(原子)。
- 查看记录的内部表示。你可以使用
term_to_string或直接在 Shell 中打印。
下面的表格展示了记录语法与底层元组的对应关系。
| 记录语法 | 底层元组结构 | 说明 |
|---|---|---|
#person{name = "A", age = 1} |
{person, "A", 1, undefined} |
首元素是记录名,后续按定义顺序排列 |
#person{} |
{person, undefined, 0, undefined} |
使用默认值填充 |
is_record(T, person) |
检查 T 是否是以 person 开头的元组 | 运行时检查函数 |
在函数头中使用模式匹配
记录最强大的功能之一是直接在函数定义的头部进行解构和匹配。
- 编写函数子句,参数位置直接使用记录模式。
- 匹配特定字段值来路由逻辑。
%% 仅处理成年人(age >= 18)的打印
print_info(#person{age = Age} = P) when Age >= 18 ->
io:format("Adult: ~p~n", [P#person.name]);
%% 处理未成年人
print_info(#person{name = Name}) ->
io:format("Minor: ~p~n", [Name]).
这种写法避免了在函数体内部写大量的 case 或 if 语句,代码逻辑更加直观。
graph LR
A[输入记录] --> B{模式匹配 age}
B -- >= 18 --> C[执行 Adult 分支]
B -- < 18 --> D[执行 Minor 分支]
C --> E[输出 Adult: Name]
D --> F[输出 Minor: Name]

暂无评论,快来抢沙发吧!