数据结构-记录
记录与元组
记录相对于元组的主要优势在于,它可以通过记录中的字段名称提取字段的值,而在元组中只能通过元素位置进行访问。为说明两者之间的差异,现在假设你想通过元组 {Name, Address, Phone}
来表示关于人的一些属性。
要在函数中操作这个数据结构时,请记住:
- 字段
Name
是元组中的第一个元素 - 字段
Address
是元组中的第二个元素 - 字段
Phone
是元组中的第三个元素
比如,想要从变量P(元组)中提取某位置的数据,可以通过下面代码中的方式提取相关字段的值:
Name = element(1, P),
Address = element(2, P),
看得出,上面的代码比较难以阅读和理解。并且如果元素位置输入错误,将导致所提取到的值也是错误的。如果元组中的数据的位置发生改变、元组中添加新的数据或者删除数据,都将必须重新检查所有用到该元组的代码,并可能需要对使用到该元组的代码进行修改。
记录允许通过字段名称来提取其中的数据。在下面的示例中,我们使用记录替代元组来存储数据:
-record(person, {name, phone, address}).
这样就可以通过记录中的字段名称来提取数据啦。比如,如果变量 P 是一个 person
记录。在下面的示例中,演示了如何提取 name
和 address
字段中保存的数据:
Name = P#person.name,
Address = P#person.address,
事实上,记录是以带标记的元组表示的:
{person, Name, Phone, Address}
定义记录
下面定义的 person
记录将在本文中的示例中多次使用。该记录中包含 name
、 phone
、 address
字段。其中 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]}}.