6-二進制-字符串-字符列表
========================
[UTF-8和Unicode](#61-utf-8%E5%92%8Cunicode)
[二進制(和bitstring)](#62-%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%92%8Cbitstring)
[字符列表](#63-%E5%AD%97%E7%AC%A6%E5%88%97%E8%A1%A8)
在“基本類型”一章中,介紹了字符串,以及使用```is_binary/1```函數檢查它:
```elixir
iex> string = "hello"
"hello"
iex> is_binary string
true
```
本章將學習理解,二進制(binaries)是個啥,它怎么和字符串扯上關系的。
以及用單引號包裹的值,```'like this'```是啥意思。
## 6.1-UTF-8和Unicode
字符串是UTF-8編碼的二進制。
為了弄清這句話啥意思,我們要先理解兩個概念:bytes和code point的區別。
字母```a```的code point是97,而字母```?```的code point是322。
當把字符串```"he??o"```寫到硬盤上的時候,需要將其code point轉化為bytes。
如果一個byte對應一個code point,那是寫不了```"he??o"```的,
因為字母```?```的code point是322,超過了一個byte所能存儲的最大數值(255)。
但是如你所見,該字母能夠顯示到屏幕上,說明還是有一定的解決方法的。于是 _編碼_ 便出現了。
要用byte表示code point,我們需要在一定程度上對其進行編碼。
Elixir使用UTF-8為默認編碼格式。
當我們說某個字符串是UTF-8編碼的二進制數據,意思是該字符串是一串byte,
以一定方法組織來表示特定的code points,即UTF-8編碼。
因此當我們存儲字母```?```的時候,實際上是用兩個bytes來表示它。
這就是為什么有時候對同一字符串,調用函數```byte_size/1```和```String.length/1```
結果不一樣:
```elixir
iex> string = "he??o"
"he??o"
iex> byte_size string
7
iex> String.length string
5
```
UTF-8需要1個byte來表示code points:h,e和o,用2個bytes表示?。
在Elixir中可以使用```?```運算符獲取code point值:
```elixir
iex> ?a
97
iex> ??
322
```
你還可以使用
[String模塊](http://elixir-lang.org/docs/stable/elixir/String.html)里的函數
將字符串切成單獨的code points:
```elixir
iex> String.codepoints("he??o")
["h", "e", "?", "?", "o"]
```
Elixir為字符串操作提供了強大的支持。實際上,Elixir通過了文章
[“字符串類型破了”](http://mortoray.com/2013/11/27/the-string-type-is-broken/)
記錄的所有測試。
不僅如此,因為字符串是二進制,Elixir還提供了更強大的底層類型的操作。
下面就來介紹該底層類型---二進制。
## 6.2-二進制(和bitstring)
在Elixir中可以用```<<>>```定義一個二進制:
```elixir
iex> <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> byte_size <<0, 1, 2, 3>>
4
```
一個二進制只是一連串bytes。這些bytes可以以任何方法組織,即使湊不成一個合法的字符串:
```elixir
iex> String.valid?(<<239, 191, 191>>)
false
```
字符串的拼接操作實際上是二進制的拼接操作:
```elixir
iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>
```
一個常見技巧是,通過給某字符串尾部拼接一個null byte```<<0>>```,
來看看該字符串內部二進制的樣子:
```elixir
iex> "he??o" <> <<0>>
<<104, 101, 197, 130, 197, 130, 111, 0>>
```
二進制中的每個數值都表示一個byte,因此其最大是255。
如果超出了255,二進制允許你再提供一個修改器(標識一下那個位置的存儲空間大小)使其可以存儲;
或者將其轉換為utf8編碼后的形式(變成多個byte的二進制):
```elixir
iex> <<255>>
<<255>>
iex> <<256>> # truncated
<<0>>
iex> <<256 :: size(16)>> # use 16 bits (2 bytes) to store the number
<<1, 0>>
iex> <<256 :: utf8>> # the number is a code point
"ā"
iex> <<256 :: utf8, 0>>
<<196, 128, 0>>
```
如果一個byte是8 bits,那如果我們給一個size是1 bit的修改器會怎樣?:
```elixir
iex> <<1 :: size(1)>>
<<1::size(1)>>
iex> <<2 :: size(1)>> # truncated
<<0::size(1)>>
iex> is_binary(<< 1 :: size(1)>>)
false
iex> is_bitstring(<< 1 :: size(1)>>)
true
iex> bit_size(<< 1 :: size(1)>>)
1
```
這樣(每個元素是1 bit)就不再是二進制(人家每個元素是byte,至少8 bits)了,
而是bitstring,就是一串比特!
所以實際上二進制就是一串比特,只是比特數是8的倍數。
也可以對二進制或bitstring做模式匹配:
```elixir
iex> <<0, 1, x>> = <<0, 1, 2>>
<<0, 1, 2>>
iex> x
2
iex> <<0, 1, x>> = <<0, 1, 2, 3>>
** (MatchError) no match of right hand side value: <<0, 1, 2, 3>>
```
注意(沒有修改器標識的情況下)二進制中的每個元素都應該匹配8 bits。
因此上面最后的例子,匹配的左右兩端不具有相同容量,因此出現錯誤。
下面是使用了修改器標識的匹配例子:
```elixir
iex> <<0, 1, x :: binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> x
<<2, 3>>
```
上面的模式僅在二進制 **尾部** 元素被修改器標識為又一個二進制時才正確。
字符串的連接操作也是一個意思:
```elixir
iex> "he" <> rest = "hello"
"hello"
iex> rest
"llo"
```
總之,記住字符串是UTF-8編碼的二進制,而二進制是特殊的、數量是8的倍數的bitstring。
這種機制增加了Elixir在處理bits或bytes時的靈活性。
而現實中99%的時候你會用```is_binary/1```和```byte_size/1```函數跟二進制打交道。
## 6.3-字符列表
字符列表就是字符的列表。
雙引號包裹字符串,單引號包裹字符列表。
```elixir
iex> 'he??o'
[104, 101, 322, 322, 111]
iex> is_list 'he??o'
true
iex> 'hello'
'hello'
```
字符列表存儲的不是bytes,而是字符的code points(實際上就是這些code points的普通列表)。
如果某字符不屬于ASCII返回,iex就打印它的code point。
實際應用中,字符列表常被用來做參數,同一些老的庫,或者同Erlang平臺交互。
因為這些老庫不接受二進制作為參數。
將字符列表和字符串之間轉換,使用函數```to_string/1```和```to_char_list/1```:
```elixir
iex> to_char_list "he??o"
[104, 101, 322, 322, 111]
iex> to_string 'he??o'
"he??o"
iex> to_string :hello
"hello"
iex> to_string 1
"1"
```
注意這些函數是多態的。它們不但轉化字符列表和字符串,還能轉化字符串和整數,等等。