在线文档教程
Elixir 1.5

结构 | Structs

结构

在第7章中,我们了解了地图:

iex> map = %{a: 1, b: 2} %{a: 1, b: 2} iex> map[:a] 1 iex> %{map | a: 3} %{a: 3, b: 2}

结构是构建在提供编译时检查和默认值的映射之上的扩展。

定义结构

要定义一个结构,使用defstruct结构:

iex> defmodule User do ...> defstruct name: "John", age: 27 ...> end

用于defstruct定义结构将具有哪些字段以及其默认值的关键字列表。

结构体采用它们定义的模块的名称。在上面的例子中,我们定义了一个名为 struct 的结构体User

我们现在可以通过使用类似于用于创建地图的语法来创建User结构:

iex> %User{} %User{age: 27, name: "John"} iex> %User{name: "Meg"} %User{age: 27, name: "Meg"}

结构提供了编译时保证,只有通过定义的字段(以及所有字段)defstruct才会被允许存在于结构中:

iex> %User{oops: :field} ** (KeyError) key :oops not found in: %User{age: 27, name: "John"}

访问和更新结构

当我们讨论地图时,我们展示了我们如何访问和更新地图的字段。相同的技术(以及相同的语法)也适用于结构:

iex> john = %User{} %User{age: 27, name: "John"} iex> john.name "John" iex> meg = %{john | name: "Meg"} %User{age: 27, name: "Meg"} iex> %{meg | oops: :field} ** (KeyError) key :oops not found in: %User{age: 27, name: "Meg"}

当使用更新语法(|)时,虚拟机知道没有新的键将被添加到结构中,允许下面的地图在内存中共享它们的结构。在上面的例子中,两个johnmeg共享存储器中的相同的密钥的结构。

结构体也可用于模式匹配,既用于匹配特定键的值,又用于确保匹配值是与匹配值相同类型的结构体。

iex> %User{name: name} = john %User{age: 27, name: "John"} iex> name "John" iex> %User{} = %{} ** (MatchError) no match of right hand side value: %{}

结构是底下裸露的地图

在上面的例子中,模式匹配是有效的,因为在结构下面的是裸露的地图和一组固定的字段。作为地图,结构存储一个名为“special”的字段__struct__,其中包含结构体的名称:

iex> is_map(john) true iex> john.__struct__ User

请注意,我们将结构称为地图,因为没有为地图实现的协议可用于结构。例如,你既不能枚举也不能访问一个结构体:

iex> john = %User{} %User{age: 27, name: "John"} iex> john[:name] ** (UndefinedFunctionError) function User.fetch/2 is undefined (User does not implement the Access behaviour) User.fetch(%User{age: 27, name: "John"}, :name) iex> Enum.each john, fn{field, value}) -> IO.puts(value) end ** (Protocol.UndefinedError) protocol Enumerable not implemented for %User{age: 27, name: "John"}

但是,由于结构只是地图,因此它们可以与Map模块中的功能一起使用:

iex> kurt = Map.put(%User{}, :name, "Kurt") %User{age: 27, name: "Kurt"} iex> Map.merge(kurt, %User{name: "Takashi"}) %User{age: 27, name: "Takashi"} iex> Map.keys(john) [:__struct__, :age, :name]

与协议结合提供了 Elixir 开发人员最重要的功能之一:数据多态性。这就是我们将在下一章探讨的内容。

默认值和必需的键

如果您在定义结构时未指定默认键值,nil则将假定:

iex> defmodule Product do ...> defstruct [:name] ...> end iex> %Product{} %Product{name: nil}

您还可以强制在创建结构时必须指定某些键:

iex> defmodule Car do ...> @enforce_keys [:make] ...> defstruct [:model, :make] ...> end iex> %Car{} ** (ArgumentError) the following keys must also be given when building struct Car: [:make] expanding struct: Car.__struct__/1