数据结构-记录

记录与元组

记录相对于元组的主要优势在于,它可以通过记录中的字段名称提取字段的值,而在元组中只能通过元素位置进行访问。为说明两者之间的差异,现在假设你想通过元组 {Name, Address, Phone} 来表示关于人的一些属性。

要在函数中操作这个数据结构时,请记住:

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

比如,想要从变量P(元组)中提取某位置的数据,可以通过下面代码中的方式提取相关字段的值:

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

看得出,上面的代码比较难以阅读和理解。并且如果元素位置输入错误,将导致所提取到的值也是错误的。如果元组中的数据的位置发生改变、元组中添加新的数据或者删除数据,都将必须重新检查所有用到该元组的代码,并可能需要对使用到该元组的代码进行修改。

记录允许通过字段名称来提取其中的数据。在下面的示例中,我们使用记录替代元组来存储数据:

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

这样就可以通过记录中的字段名称来提取数据啦。比如,如果变量 P 是一个 person 记录。在下面的示例中,演示了如何提取 nameaddress 字段中保存的数据:

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

事实上,记录是以带标记的元组表示的:

{person, Name, Phone, Address}

定义记录

下面定义的 person 记录将在本文中的示例中多次使用。该记录中包含 namephoneaddress 字段。其中 name 的默认值是 ""phone 的默认值是 []address 的默认值是原子 undefined ,因为 address 字段没有默认值。

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

在 Shell 中使用记录时,必须像下面这样在SHELL中定义记录:

Erlang/OTP 25 [erts-13.1.5] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Eshell V13.1.5  (abort with ^G)
1> rd(person, {name = "", phone = [], address}).
person

这是因为记录的定义只允许在编译时进行,而不是运行时。在SHELL中使用记录的详细信息,参见 STDLIB 手册中的 shell(3)

创建记录

一个新的 person 记录实例像下面这样创建:

Erlang/OTP 25 [erts-13.1.5] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Eshell V13.1.5  (abort with ^G)
1> rd(person, {name = "", phone = [], address}).
person
2> #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}
3>

其中忽略了 address 字段的赋值,所以它的值将是记录定义时的默认值。

From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the special field _. _ means "all fields not explicitly specified".

示例:

Erlang/OTP 25 [erts-13.1.5] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Eshell V13.1.5  (abort with ^G)
1> rd(person, {name = "", phone = [], address}).
person
2> #person{name = "Jakob", _ = '_'}.
#person{name = "Jakob",phone = '_',address = '_'}

It is primarily intended to be used in ets:match/2 and mnesia:match_object/3, to set record fields to the atom '_'. (This is a wildcard in ets:match/2.)

访问记录中的字段

下面的示例展示了如何访问记录中字段的值:

Erlang/OTP 25 [erts-13.1.5] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Eshell V13.1.5  (abort with ^G)
1> rd(person, {name = "", phone = [], address}).
person
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}
3> P#person.name.
"Joe"

更新记录

下面的示例展示了如何更新记录中字段的值:

Erlang/OTP 25 [erts-13.1.5] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Eshell V13.1.5  (abort with ^G)
1> rd(person, {name = "", phone = [], address}).
person
2> P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
#person{name = "Joe",phone = [1,2,3],address = "A street"}
3> P2 = P1#person{name="Robert"}.
#person{name = "Robert",
        phone = [1,2,3],
        address = "A street"}

类型测试

下面的示例展示了在关卡中使用记录(如果 P 是 person 记录类型):

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

模式匹配

下面的示例展示了如何对记录进行模式匹配:

Erlang/OTP 25 [erts-13.1.5] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Eshell V13.1.5  (abort with ^G)
1> rd(person, {name = "", phone = [], address}).
person
2> P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
#person{name = "Joe",phone = [0,0,7],address = "A street"}
3> #person{name = Name} = P3, Name.
"Joe"

下面的函数会在 person 记录集合中搜索可以匹配给定的 Name 的记录,并返回该记录中的 phone 字段的值。

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 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]}}.

Date: 2018-08-01 Wed 15:25

Author: xueshumeng

Email: xue.shumeng@yahoo.com

Created: 2023-08-28 Mon 14:16