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}
```
下一章我們將介紹結構體是如何同協議進行交互的。