3  数据类型

3  数据类型

Erlang 提供了许多数据类型,在本节中列出。

请注意,Erlang 没有用户定义的类型,只有由 Erlang 项组成的复合类型(数据结构)。这意味着任何测试复合类型的函数,通常命名为 is_type/1,可能会对与所选表示形式一致的项返回 true。内置类型的对应函数不会出现这种情况。

任何数据类型的任何数据块都称为 **项**。

有两种类型的数字文字,**整数** 和 **浮点数**。除了传统表示法之外,还有两种 Erlang 特定的表示法

  • $char
    字符 **char** 的 ASCII 值或 Unicode 代码点。
  • base#value
    以 **base** 为基的整数,必须是 2..36 范围内的整数。

前导零将被忽略。单个下划线 _ 可以插入数字之间作为视觉分隔符。

示例

1> 42.
42
2> -1_234_567_890.
-1234567890
3> $A.
65
4> $\n.
10
5> 2#101.
5
6> 16#1f.
31
7> 16#4865_316F_774F_6C64.
5216630098191412324
8> 2.3.
2.3
9> 2.3e3.
2.3e3
10> 2.3e-3.
0.0023
11> 1_234.333_333
1234.333333

在使用浮点数时,您可能无法在打印或执行算术运算时看到您期望的结果。这是因为浮点数在以 2 为基的系统中用固定数量的位表示,而打印的浮点数则在以 10 为基的系统中表示。Erlang 使用 64 位浮点数。以下是这种现象的示例

> 0.1+0.2.
0.30000000000000004

实数 0.10.2 无法精确地表示为浮点数。

> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
{3.602879701896397e16, true,
 3.602879701896397e16, false}.

36028797018963968 可以精确地表示为浮点值,但 Erlang 的漂亮打印机将 36028797018963968.0 舍入为 3.602879701896397e16 (=36028797018963970.0),因为范围 [36028797018963966.0, 36028797018963972.0] 中的所有值都由 36028797018963968.0 表示。

有关浮点数及其问题的更多信息,请参见

如果您需要使用小数,例如需要表示货币,那么您应该使用处理小数的库,或者以美分而不是欧元进行操作,这样您就不需要小数。

另外请注意,Erlang 的浮点数与 IEEE 754 浮点数并不完全匹配,因为 Erlang 不支持 Inf 或 NaN。任何会导致 NaN、+Inf 或 -Inf 的操作将改为引发 badarith 异常。

原子是文字,是带名称的常量。如果原子不以小写字母开头,或者包含除字母数字字符、下划线 (_) 或 @ 之外的其他字符,则应将其用单引号 (') 括起来。

示例

hello
phone_number
'Monday'
'phone number'

比特串用于存储未类型化的内存区域。

比特串使用 比特语法 表示。

包含可被 8 整除的位数的比特串称为 **二进制**

示例

1> <<10,20>>.
<<10,20>>
2> <<"ABC">>.
<<"ABC">>
1> <<1:1,0:1>>.
<<2:2>>

有关更多示例,请参见 编程示例

连接节点之间唯一的项。引用可以通过调用 make_ref/0 BIF 来创建。可以使用 is_reference/1 BIF 来测试项是否为引用。

函数是函数对象。函数使创建匿名函数并传递函数本身(而不是其名称)作为其他函数的参数成为可能。

示例

1> Fun1 = fun (X) -> X+1 end.
#Fun<erl_eval.6.39074546>
2> Fun1(2).
3

函数表达式 中阅读有关函数的更多信息。有关更多示例,请参见 编程示例

端口标识符标识 Erlang 端口。

open_port/2 用于创建端口,它返回这种数据类型的值。

端口和端口驱动 中阅读有关端口的更多信息。

PID 是进程标识符的缩写。每个进程都有一个 PID,用于标识该进程。PID 在连接节点上的活动进程中是唯一的。但是,终止进程的 PID 在一段时间后可以被用作新进程的 PID。

BIF self/0 返回调用进程的 PID。当 创建新进程 时,父进程可以通过返回值(例如调用 spawn/3 BIF 时)或通过消息(例如调用 spawn_request/5 BIF 时)获取子进程的 PID。PID 通常用于向进程发送 信号。可以使用 is_pid/1 BIF 来测试项是否为 PID。

示例

-module(m).
-export([loop/0]).

loop() ->
    receive
        who_are_you ->
            io:format("I am ~p~n", [self()]),
            loop()
    end.

1> P = spawn(m, loop, []).
<0.58.0>
2> P ! who_are_you.
I am <0.58.0>
who_are_you

进程 中阅读有关进程的更多信息。

元组是一种包含固定数量项的复合数据类型

{Term1,...,TermN}

元组中的每个项 Term 都称为 **元素**。元素的数量称为元组的 **大小**。

存在许多 BIF 来操作元组。

示例

1> P = {adam,24,{july,29}}.
{adam,24,{july,29}}
2> element(1,P).
adam
3> element(3,P).
{july,29}
4> P2 = setelement(2,P,25).
{adam,25,{july,29}}
5> tuple_size(P).
3
6> tuple_size({}).
0

映射是一种包含可变数量的键值关联的复合数据类型

#{Key1=>Value1,...,KeyN=>ValueN}

映射中的每个键值关联都称为 **关联对**。对的键和值部分称为 **元素**。关联对的数量称为映射的 **大小**。

存在许多 BIF 来操作映射。

示例

1> M1 = #{name=>adam,age=>24,date=>{july,29}}.
#{age => 24,date => {july,29},name => adam}
2> maps:get(name,M1).
adam
3> maps:get(date,M1).
{july,29}
4> M2 = maps:update(age,25,M1).
#{age => 25,date => {july,29},name => adam}
5> map_size(M).
3
6> map_size(#{}).
0

在 STDLIB 的 maps 手册页中可以找到映射处理函数的集合。

映射表达式 中阅读有关映射的更多信息。

变更

映射在 Erlang/OTP R17 中作为一项实验性功能引入。它们的功能在 Erlang/OTP 18 中得到了扩展并得到完全支持。

列表是一种包含可变数量项的复合数据类型。

[Term1,...,TermN]

列表中的每个项 Term 都称为 **元素**。元素的数量称为列表的 **长度**。

正式地,列表要么是空列表 [],要么包含一个 **头部**(第一个元素)和一个 **尾部**(列表的剩余部分)。**尾部** 也是一个列表。后者可以表示为 [H|T]。上面的表示法 [Term1,...,TermN] 等效于列表 [Term1|[...|[TermN|[]]]]

示例

[] 是一个列表,因此
[c|[]] 是一个列表,因此
[b|[c|[]]] 是一个列表,因此
[a|[b|[c|[]]]] 是一个列表,或简写为 [a,b,c]

尾部为列表的列表有时称为 **正确列表**。允许列表的尾部不是列表,例如 [a|b]。但是,这种类型的列表在实际应用中很少使用。

示例

1> L1 = [a,2,{c,4}].
[a,2,{c,4}]
2> [H|T] = L1.
[a,2,{c,4}]
3> H.
a
4> T.
[2,{c,4}]
5> L2 = [d|T].
[d,2,{c,4}]
6> length(L1).
3
7> length([]).
0

在 STDLIB 的 lists 手册页中可以找到列表处理函数的集合。

字符串用双引号 (") 括起来,但在 Erlang 中不是数据类型。相反,字符串 "hello" 是列表 [$h,$e,$l,$l,$o] 的简写,即 [104,101,108,108,111]

两个相邻的字符串文字将连接成一个。这是在编译时完成的,因此不会产生任何运行时开销。

示例

"string" "42"

等效于

"string42"

记录是一种用于存储固定数量元素的数据结构。它具有命名的字段,类似于 C 语言中的结构体。但是,记录不是真正的數據類型。相反,记录表达式在编译期间会被转换为元组表达式。因此,除非采取特殊措施,否则 shell 无法理解记录表达式。有关详细信息,请参阅 STDLIB 中的 shell(3) 手册页。

示例

-module(person).
-export([new/2]).

-record(person, {name, age}).

new(Name, Age) ->
    #person{name=Name, age=Age}.

1> person:new(ernie, 44).
{person,ernie,44}

Records 中了解更多关于记录的信息。更多示例可以在 Programming Examples 中找到。

Erlang 中没有布尔值数据类型。而是使用原子 truefalse 来表示布尔值。

示例

1> 2 =< 3.
true
2> true or false.
true

在字符串和带引号的原子中,识别以下转义序列

序列 描述
\b 退格键 (ASCII 码 8)
\d 删除键 (ASCII 码 127)
\e 转义键 (ASCII 码 27)
\f 换页 (ASCII 码 12)
\n 换行符 (ASCII 码 10)
\r 回车键 (ASCII 码 13)
\s 空格 (ASCII 码 32)
\t (水平) 制表符 (ASCII 码 9)
\v 垂直制表符 (ASCII 码 11)
\XYZ, \YZ, \Z 八进制表示为 XYZ、YZ 或 Z 的字符
\xXY 十六进制表示为 XY 的字符
\x{X...} 十六进制表示的字符;X... 是一个或多个十六进制字符
\^a...\^z
\^A...\^Z
控制键 A 到控制键 Z
\^@ 空字符 (ASCII 码 0)
\^[ 转义键 (ASCII 码 27)
\^\ 文件分隔符 (ASCII 码 28)
\^] 组分隔符 (ASCII 码 29)
\^^ 记录分隔符 (ASCII 码 30)
\^_ 单元分隔符 (ASCII 码 31)
\^? 删除键 (ASCII 码 127)
\' 单引号
\" 双引号
\\ 反斜杠

表 3.1:   识别的转义序列

变更

从 Erlang/OTP 26 开始,$\^? 的值已更改为 127 (删除键),而不是 31。之前的版本允许 $\^ 后面的任何字符;从 Erlang/OTP 26 开始,只允许使用已记录的字符。

有许多用于类型转换的 BIF。

示例

1> atom_to_list(hello).
"hello"
2> list_to_atom("hello").
hello
3> binary_to_list(<<"hello">>).
"hello"
4> binary_to_list(<<104,101,108,108,111>>).
"hello"
5> list_to_binary("hello").
<<104,101,108,108,111>>
6> float_to_list(7.0).
"7.00000000000000000000e+00"
7> list_to_float("7.000e+00").
7.0
8> integer_to_list(77).
"77"
9> list_to_integer("77").
77
10> tuple_to_list({a,b,c}).
[a,b,c]
11> list_to_tuple([a,b,c]).
{a,b,c}
12> term_to_binary({a,b,c}).
<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
{a,b,c}
14> binary_to_integer(<<"77">>).
77
15> integer_to_binary(77).
<<"77">>
16> float_to_binary(7.0).
<<"7.00000000000000000000e+00">>
17> binary_to_float(<<"7.000e+00">>).
7.0