结构体

  1. 定义结构体
  2. 访问和更新结构体
  3. 结构体的底层是映射

在第七章,我们学习过了映射:

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

结构体是建立在映射的基础之上,提供了编译时检查和默认值设置.

定义结构体

可以使用defstruct结构定义结构体:

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

defstruct中使用的关键词列表定义了结构体领域中的默认值.

结构体会以其定义所处于的模块来命名.比如上面的例子中,我们定义了一个名为User的结构体.

我们现在可以用与创建映射类似的语法来创建结构体User:

  1. iex> %User{}
  2. %User{age: 27, name: "John"}
  3. iex> %User{name: "Meg"}
  4. %User{age: 27, name: "Meg"}

结构体提供了编译时确认,也就是只有在defsturct中定义了的领域能够存在于一个结构体中:

  1. iex> %User{oops: :field}
  2. ** (CompileError) iex:3: unknown key :oops for struct User

获取与更新结构体

当我们讨论映射时,我们展示了如何获取和更新映射中的领域.对与结构体,还是同样的技术(同样的语法):

  1. iex> john = %User{}
  2. %User{age: 27, name: "John"}
  3. iex> john.name
  4. "John"
  5. iex> meg = %{john | name: "Meg"}
  6. %User{age: 27, name: "Meg"}
  7. iex> %{meg | oops: :field}
  8. ** (KeyError) key :oops not found in: %User{age: 27, name: "Meg"}

当使用更新符号(|)时,VM会意识到没有新的键添加到结构体中,同时会允许底层的映射分享它们的结构.上述例子中,johnmeg在内存中分享着相同的键结构.

结构体也可用于模式匹配,需要同时匹配特定的键值和结构体的类型:

  1. iex> %User{name: name} = john
  2. %User{age: 27, name: "John"}
  3. iex> name
  4. "John"
  5. iex> %User{} = %{}
  6. ** (MatchError) no match of right hand side value: %{}

结构体的底层是映射

在上述例子中,模式匹配之所以能成功,是因为结构体的底层是被锁定了领域的映射.作为映射,结构体拥有一个”特殊”的领域叫做__struct__,里面是结构体的名字:

  1. iex> is_map(john)
  2. true
  3. iex> john.__struct__
  4. User

注意我们称结构体是裸露的映射,因为所有对映射生效的协议,结构体都不可用.例如,你既不能枚举也不能进入一个结构体:

  1. iex> john = %User{}
  2. %User{age: 27, name: "John"}
  3. iex> john[:name]
  4. ** (UndefinedFunctionError) undefined function: User.fetch/2
  5. iex> Enum.each john, fn({field, value}) -> IO.puts(value) end
  6. ** (Protocol.UndefinedError) protocol Enumerable not implemented for %User{age: 27, name: "John"}

然而,结构体也是映射,能够使用Map模块中的函数:

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

结构体协议为Elixir开发者提供了一个重要特性:数据多态性.这是我们将在下一章探索的.