15-结构体
=========
在之前的几章中,我们谈到过图:
```elixir
iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> map[:a]
1
iex> %{map | a: 3}
%{a: 3, b: 2}
```
结构体是基于图的一个扩展。它引入了默认值、编译期验证和多态性。
定义一个结构体,你只需在模块中调用```defstruct/1```:
```elixir
iex> defmodule User do
...> defstruct name: "john", age: 27
...> end
```
现在可以用```%User()```语法创建这个结构体的“实例”了:
```elixir
iex> %User{}
%User{ name: "john", age: 27 }
iex> %User{ name: "meg" }
%User{ name: "meg", age: 27 }
iex> is_map(%User{})
true
```
结构体的编译期验证,指的是代码在编译时会检查结构体的字段存不存在:
```elixir
iex> %User{ oops: :field }
** (CompileError) iex:3: unknown key :oops for struct User
```
当讨论图的时候,我们演示了如何访问和修改图现有的字段。结构体也是一样的:
```elixir
iex> john = %User{}
%User{ name: "john", age: 27 }
iex> john.name
"john"
iex> meg = %{ john | name: "meg" }
%User{ name: "meg", age: 27 }
iex> %{ meg | oops: :field }
** (ArgumentError) argument error
```
使用这种修改的语法,虚拟机可以知道没有新的键增加到图/结构体中,
使得图可以在内存中共享它们的结构。在上面例子中,john和meg共享了相同的键结构。
结构体也能用在模式匹配中,它们保证结构体有相同的类型:
```elixir
iex> %User{name: name} = john
%User{name: "john", age: 27}
iex> name
"john"
iex> %User{} = %{}
** (MatchError) no match of right hand side value: %{}
```
这里可以用模式匹配,是因为在结构体底层的图中有个叫```__struct__```的字段:
```elixir
iex> john.__struct__
User
```
简单说,结构体就是个光秃秃的图外加一个默认字段。
但是,为图实现的协议都不能用于结构体。
例如,你不能枚举也不能用[]访问一个结构体:
```elixir
iex> user = %User{}
%User{name: "john", age: 27}
iex> user[:name]
** (Protocol.UndefinedError) protocol Access not implemented for %User{age: 27, name: "john"}
```
结构体也不是字典,因而也不能使用字典模块的函数:
```elixir
iex> Dict.get(%User{}, :name)
** (ArgumentError) unsupported dict: %User{name: "john", age: 27}
```
下一章我们将介绍结构体是如何同协议进行交互的。