基本种类 | Basic types
基本类型
在本章中,我们将学习更多关于 Elixir 基本类型的知识:整数、浮点数、布尔值、原子、字符串、列表和元组。一些基本类型是:
iex> 1 # integer
iex> 0x1F # integer
iex> 1.0 # float
iex> true # boolean
iex> :atom # atom / symbol
iex> "elixir" # string
iex> [1, 2, 3] # list
iex> {1, 2, 3} # tuple
基本算法
打开iex
并键入以下表达式:
iex> 1 + 2
3
iex> 5 * 5
25
iex> 10 / 2
5.0
注意10 / 2
返回一个 float 5.0
而不是一个整数5
。这是预料之中的。在 Elixir 中,操作员/
总是返回一个浮点数。如果你想做整数除法或得到除法余数,你可以调用div
和rem
函数:
iex> div(10, 2)
5
iex> div 10, 2
5
iex> rem 10, 3
1
请注意,Elixir 允许您在调用命名函数时删除括号。这个特性在编写声明和控制流结构时给出了一个更清晰的语法。
Elixir 还支持输入二进制,八进制和十六进制数字的快捷方式符号:
iex> 0b1010
10
iex> 0o777
511
iex> 0x1F
31
浮点数需要一个点,后面至少有一个数字,同时也支持e
科学记数法:
iex> 1.0
1.0
iex> 1.0e-10
1.0e-10
Elixir 中的浮点数是64位双精度。
您可以调用round
函数来获取给定浮点数的最接近的整数,或者trunc
获取浮点数整数部分的函数。
iex> round(3.58)
4
iex> trunc(3.58)
3
定义函数
Elixir 中的函数由他们的函数名称和携带参数数量来确定。函数的参数数量描述函数携带的参数的数量。从这一点开始,我们将使用函数名称和参数个数描述整个文档中的函数。round/1
定义了函数名字为round,并且携带了1个参数,round/2
定义了一个和round名字相同的函数,但是参数的个数却是2个
。
布尔
Elixir 支持true
和false
作为布尔值:
iex> true
true
iex> true == false
false
Elixir 提供了一些谓词函数来检查值类型。例如,该is_boolean/1
函数可用于检查值是否为布尔值:
iex> is_boolean(true)
true
iex> is_boolean(1)
false
你也可以使用is_integer/1
,is_float/1
或is_number/1
检查,分别,如果一个参数是一个整数,浮点数,或任一。
注意:在任何时候,您都可以在 s
h
ell 中输入h()
以打印有关如何使用 sh
ell 的信息。h
帮助程序也可用于访问任何功能的文档。例如,输入h is_integer/1
将打印该is_integer/1
功能的文档。它也适用于操作符和其他构造(尝试h ==/2
)。
原子
原子是一个常数,其名称是它自己的值。其他一些语言称这些符号为:
iex> :hello
:hello
iex> :hello == :world
false
布尔true
和false
实际上是原子:
iex> true == :true
true
iex> is_atom(false)
true
iex> is_boolean(:false)
true
最后,Elixir 有一个名为别名的构造,我们将在后面进行探讨。以大写字母开头的别名也是原子:
iex> is_atom(Hello)
true
字符串
Elixir 中的字符串用双引号分隔,并以 UTF-8 编码:
iex> "hellö"
"hellö"
注意:如果你在 Windows 上运行,你的终端有可能默认不使用 UTF-8。您可以
chcp 65001
在输入 IEx 之前运行,更改当前会话的编码。
Elixir 还支持字符串插值:
iex> "hellö #{:world}"
"hellö world"
字符串可以有换行符。你可以使用转义序列来介绍它们:
iex> "hello
...> world"
"hello\nworld"
iex> "hello\nworld"
"hello\nworld"
您可以使用IO
模块中的IO.puts/1
功能打印字符串:
iex> IO.puts "hello\nworld"
hello
world
:ok
请注意,IO.puts/1
函数:ok
在打印后返回原子。
Elixir 中的字符串由字节序列的二进制文件内部表示:
iex> is_binary("hellö")
true
我们还可以获取字符串中的字节数:
iex> byte_size("hellö")
6
请注意,该字符串中的字节数是6,即使它有5个字符。这是因为字符“ö”需要2个字节以 UTF-8 表示。我们可以通过使用下面的String.length/1
函数,根据字符的数量得到字符串的实际长度:
iex> String.length("hellö")
5
字符串模块
包含一组上作为 Unicode 标准定义的字符串操作的功能:
iex> String.upcase("hellö")
"HELLÖ"
匿名函数
匿名函数可以内联创建并由关键字fn
和end
分隔:
iex> add = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3
iex> is_function(add)
true
iex> is_function(add, 2) # check if add is a function that expects exactly 2 arguments
true
iex> is_function(add, 1) # check if add is a function that expects exactly 1 argument
false
函数是 Elixir 中的“头等公民”,意思是它们可以像整数和字符串一样作为参数传递给其他函数。在这个例子中,我们已经将变量add
中的is_function/1
函数传递给正确返回true
的函数。我们也可以通过调用来检查函数的参数is_function/2
。
请注意,.
变量和括号之间的点()需要调用匿名函数。点确保调用名为匿名函数add
和add/2
命名函数之间不存在歧义。从这个意义上说,Elixir 明确区分了匿名函数和命名函数。我们将在第8章探讨这些差异。
匿名函数是闭包,因此当函数被定义时,它们可以访问范围内的变量。让我们定义一个新的匿名函数,它使用add
我们之前定义的匿名函数:
iex> double = fn a -> add.(a, a) end
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> double.(2)
4
请记住,函数内部分配的变量不会影响其周围的环境:
iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42
(链接)列表
Elixir 使用方括号来指定一个值列表。值可以是任何类型:
iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3
两个列表可以使用++/2
和--/2
运算符连接或者减去:
iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]
列表操作符从不修改现有列表。连接到或从列表中删除元素将返回一个新列表。我们说 Elixir 的数据结构是不可变的
。不变性的一个优点是它导致更清晰的代码。您可以随意传递数据并保证没有人会改变它 - 只能对其进行转换。
在整个教程中,我们将讨论很多关于列表的首尾的内容。头是列表的第一个元素,尾部是列表的其余部分。它们可以通过函数hd/1
和tl/1
。让我们给列表分配一个变量并检索它的头部和尾部:
iex> list = [1, 2, 3]
iex> hd(list)
1
iex> tl(list)
[2, 3]
获取空列表的头或尾会引发一个错误:
iex> hd []
** (ArgumentError) argument error
有时你会创建一个列表,它将返回一个单引号的值。例如:
iex> [11, 12, 13]
'\v\f\r'
iex> [104, 101, 108, 108, 111]
'hello'
当Elixir看到可打印的 ASCII 码列表时,Elixir 将把它打印为charlist(字面上是一个字符列表)。Charlists 在与现有的 Erlang 代码交互时非常常见。无论何时您在 IEx 中看到一个值,并且您不确定它是什么,您都可以使用它i/1
来检索有关它的信息:
iex> i 'hello'
Term
'hello'
Data type
List
Description
...
Raw representation
[104, 101, 108, 108, 111]
Reference modules
List
请记住,Elixir 中的单引号和双引号表示不同,因为它们由不同类型表示:
iex> 'hello' == "hello"
false
单引号是 charlists,双引号是字符串。我们将在“二进制文件,字符串和 charlists”一章中详细讨论它们。
元组
Elixir 使用大括号来定义元组。像列表一样,元组可以保存任何值:
iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple_size {:ok, "hello"}
2
元组将元素连续存储在内存中。这意味着通过索引访问元组元素或获取元组大小是一种快速操作。索引从零开始:
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
iex> tuple_size(tuple)
2
也可以将元素放在元组中的特定索引处put_elem/3
:
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> put_elem(tuple, 1, "world")
{:ok, "world"}
iex> tuple
{:ok, "hello"}
注意put_elem/3
返回一个新的元组。存储在tuple
变量中的原始元组未被修改。像列表一样,元组也是不可变的。对元组的每一个操作都会返回一个新的元组,它永远不会改变给定的元组。
列表或元组?
列表和元组有什么区别?
列表作为链接列表存储在内存中,这意味着列表中的每个元素都保存其值并指向下一个元素,直到到达列表的末尾。这意味着访问列表的长度是一个线性操作:我们需要遍历整个列表以便找出它的大小。
同样,列表级联的性能取决于左侧列表的长度:
iex> list = [1, 2, 3]
# This is fast as we only need to traverse `[0]` to prepend to `list`
iex> [0] ++ list
[0, 1, 2, 3]
# This is slow as we need to traverse `list` to append 4
iex> list ++ [4]
[1, 2, 3, 4]
另一方面,元组连续存储在内存中。这意味着获取元组大小或按索引访问元素的速度很快。然而,为元组更新或添加元素是很昂贵的,因为它需要在内存中创建一个新的元组:
iex> tuple = {:a, :b, :c, :d}
iex> put_elem(tuple, 2, :e)
{:a, :b, :e, :d}
请注意,这仅适用于元组本身,而不适用于其内容。例如,当你更新一个元组时,除了已被替换的条目外,所有条目都在旧元组和新元组之间共享。换句话说,Elixir 中的元组和列表能够分享他们的内容。这减少了语言需要执行的内存分配量,并且只能归功于语言的不可变语义。
这些性能特征决定了这些数据结构的使用。元组的一个非常常见的用例是使用它们从函数返回额外的信息。例如,File.read/1
是一个可以用来读取文件内容的函数。它返回一个元组:
iex> File.read("path/to/existing/file")
{:ok, "... contents ..."}
iex> File.read("path/to/unknown/file")
{:error, :enoent}
如果给定的路径File.read/1
存在,它将返回一个:ok
元素,其中atom 为第一个元素,文件内容为第二个元素。否则,它返回一个元组:error
和错误描述。
大多数时候,Elixir 会引导你做正确的事情。例如,有一个elem/2
函数可以访问一个元组项,但是没有内建的列表等价物:
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
在对数据结构中的元素进行计数时,Elixir 还遵循一个简单的规则:size
如果操作的时间是恒定的(即,该值是预先计算的),或者length
操作是线性的(即计算长度变慢随着输入的增长)。作为助记符,“length
”和“linear”都以“l”开始。
例如,到目前为止,我们已经使用了4个计数函数:( byte_size/1
对于字符串中的字节数),tuple_size/1
(对于元组大小),length/1
(对于列表长度)和String.length/1
(对于字符串中的字符数)。我们byte_size
用来获取字符串中的字节数 - 一个便宜的操作。另一方面,检索 Unicode 字符的数量使用String.length
,并且可能是昂贵的,因为它依赖遍历整个字符串。
Elixir 还提供Port
,Reference
和PID
作为数据类型(通常用于过程交流),我们将在谈论过程时快速浏览它们。现在,我们来看看一些基本类型的基本操作符。