查看源代码 记录

记录和元组

使用记录而不是元组的主要优点是,记录中的字段通过名称访问,而元组中的字段通过位置访问。为了说明这些差异,假设你想用元组 {Name, Address, Phone} 表示一个人。

要编写操作此数据的函数,请记住以下几点:

  • Name 字段是元组的第一个元素。
  • Address 字段是第二个元素。
  • Phone 字段是第三个元素。

例如,要从包含此类元组的变量 P 中提取数据,可以编写以下代码,然后使用模式匹配来提取相关字段:

Name = element(1, P),
Address = element(2, P),
...

这样的代码难以阅读和理解,并且如果元组中元素的编号错误,则会发生错误。如果字段的数据表示通过重新排序、添加或删除字段而更改,则必须检查并可能修改对 person 元组的所有引用。

记录允许通过名称而不是位置引用字段。在以下示例中,使用记录而不是元组来存储数据:

-record(person, {name, phone, address}).

这使得可以通过名称引用记录的字段。例如,如果 P 是一个变量,其值是 person 记录,则以下代码将访问记录的名称和地址字段:

Name = P#person.name,
Address = P#person.address,
...

在内部,记录使用带标签的元组表示:

{person, Name, Phone, Address}

定义记录

本节中的多个示例中使用了以下 person 的定义。其中包含三个字段:namephoneaddressnamephone 的默认值分别为 "" 和 []。address 的默认值是原子 undefined,因为没有为此字段提供默认值:

-record(person, {name = "", phone = [], address}).

必须在 shell 中定义记录,才能在示例中使用记录语法:

> rd(person, {name = "", phone = [], address}).
person

这是因为记录定义仅在编译时可用,而不在运行时可用。有关 shell 中记录的详细信息,请参阅 STDLIB 中的 shell 手册页。

创建记录

创建新的 person 记录如下:

> #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

由于省略了 address 字段,因此使用其默认值。

从 Erlang 5.1/OTP R8B 开始,可以使用特殊字段 _ 为记录中的所有字段设置值。_ 表示“所有未明确指定的字段”。

示例:

> #person{name = "Jakob", _ = '_'}.
#person{name = "Jakob",phone = '_',address = '_'}

它主要用于 ets:match/2mnesia:match_object/3,将记录字段设置为原子 '_'。(这是 ets:match/2 中的通配符。)

访问记录字段

以下示例显示如何访问记录字段:

> P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
> P#person.name.
"Joe"

更新记录

以下示例显示如何更新记录:

> P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
#person{name = "Joe",phone = [1,2,3],address = "A street"}
> P2 = P1#person{name="Robert"}.
#person{name = "Robert",phone = [1,2,3],address = "A street"}

类型测试

以下示例显示,如果 Pperson 类型的记录,则 guard 成功:

foo(P) when is_record(P, person) -> a_person;
foo(_) -> not_a_person.

模式匹配

匹配可以与记录结合使用,如以下示例所示:

> P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
#person{name = "Joe",phone = [0,0,7],address = "A street"}
> #person{name = Name} = P3, Name.
"Joe"

以下函数接受 person 记录列表,并搜索具有特定名称的人的电话号码:

find_phone([#person{name=Name, phone=Phone} | _], Name) ->
    {found,  Phone};
find_phone([_| T], Name) ->
    find_phone(T, Name);
find_phone([], Name) ->
    not_found.

可以在模式中以任何顺序给出引用的字段。

嵌套记录

记录中字段的值可以是记录的实例。嵌套数据的检索可以逐步完成,也可以一步完成,如下例所示:

-record(name, {first = "Robert", last = "Ericsson"}).
-record(person, {name = #name{}, phone}).

demo() ->
  P = #person{name= #name{first="Robert",last="Virding"}, phone=123},
  First = (P#person.name)#name.first.

在这里,demo() 的计算结果为 "Robert"

更长的示例

注释嵌入在以下示例中:

%% File: person.hrl

%%-----------------------------------------------------------
%% Data Type: person
%% where:
%%    name:  A string (default is undefined).
%%    age:   An integer (default is undefined).
%%    phone: A list of integers (default is []).
%%    dict:  A dictionary containing various information
%%           about the person.
%%           A {Key, Value} list (default is the empty list).
%%------------------------------------------------------------
-record(person, {name, age, phone = [], dict = []}).
-module(person).
-include("person.hrl").
-compile(export_all). % For test purposes only.

%% This creates an instance of a person.
%%   Note: The phone number is not supplied so the
%%         default value [] will be used.

make_hacker_without_phone(Name, Age) ->
   #person{name = Name, age = Age,
           dict = [{computer_knowledge, excellent},
                   {drinks, coke}]}.

%% This demonstrates matching in arguments

print(#person{name = Name, age = Age,
              phone = Phone, dict = Dict}) ->
  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).

%% Demonstrates type testing, selector, updating.

birthday(P) when is_record(P, person) ->
   P#person{age = P#person.age + 1}.

register_two_hackers() ->
   Hacker1 = make_hacker_without_phone("Joe", 29),
   OldHacker = birthday(Hacker1),
   % The central_register_server should have
   % an interface function for this.
   central_register_server ! {register_person, Hacker1},
   central_register_server ! {register_person,
             OldHacker#person{name = "Robert",
                              phone = [0,8,3,2,4,5,3,1]}}.