15-结构体

    结构体是基于图的一个扩展。它引入了默认值、编译期验证。

    定义一个结构体,只需调用:

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

    defstruct/1的参数(一个键值列表)定义了结构体的字段和默认值。结构体使用了定义它的模块的名字。
    像上面这个例子,我们定义的结构体叫做User

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

    结构体提供编译期验证,在代码在编译时会检查结构体内仅有之前定义的字段(而且一个字段也不少):

    当讨论图的时候,我们演示了如何访问和修改图的字段。
    访问和修改结构体的技术(以及语法)也是一样的:

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

    当使用语法标记(|)的时候,虚拟机知道并没有增加新的字段,这使得底层的图可以共享内存中得结构。
    像上文的例子,johnmeg在内存中使用相同的键值结构。

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

    结构体和底层的图

    在上面的例子里,之所以可以用模式匹配,是因为结构体底层不过是拥有固定字段的图。
    而作为图,结构体还存储了一个名叫__struct__的特殊字段,来存储结构体的名字:

    简单说,结构体就是个被扒光的图外加一个默认字段。
    为啥说是被扒光的图?因为,所有为图实现的协议(protocols)都不能用于结构体。
    例如,你不能像对图那样枚举或直接访问一个结构体:

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

    尽管如此,因为结构体说到底还是图,对图有效的函数也可以作用于结构体:

    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. [:__struct__, :age, :name]

    下一章我们将介绍结构体是如何同协议进行交互的。