## 7.10. 類型斷言
類型斷言是一個使用在接口值上的操作。語法上它看起來像x.(T)被稱為斷言類型,這里x表示一個接口的類型和T表示一個類型。一個類型斷言檢查它操作對象的動態類型是否和斷言的類型匹配。
這里有兩種可能。第一種,如果斷言的類型T是一個具體類型,然后類型斷言檢查x的動態類型是否和T相同。如果這個檢查成功了,類型斷言的結果是x的動態值,當然它的類型是T。換句話說,具體類型的類型斷言從它的操作對象中獲得具體的值。如果檢查失敗,接下來這個操作會拋出panic。例如:
```go
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
```
第二種,如果相反地斷言的類型T是一個接口類型,然后類型斷言檢查是否x的動態類型滿足T。如果這個檢查成功了,動態值沒有獲取到;這個結果仍然是一個有相同動態類型和值部分的接口值,但是結果為類型T。換句話說,對一個接口類型的類型斷言改變了類型的表述方式,改變了可以獲取的方法集合(通常更大),但是它保留了接口值內部的動態類型和值的部分。
在下面的第一個類型斷言后,w和rw都持有os.Stdout,因此它們都有一個動態類型`*os.File`,但是變量w是一個io.Writer類型,只對外公開了文件的Write方法,而rw變量還公開了它的Read方法。
```go
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
```
如果斷言操作的對象是一個nil接口值,那么不論被斷言的類型是什么這個類型斷言都會失敗。我們幾乎不需要對一個更少限制性的接口類型(更少的方法集合)做斷言,因為它表現的就像是賦值操作一樣,除了對于nil接口值的情況。
```go
w = rw // io.ReadWriter is assignable to io.Writer
w = rw.(io.Writer) // fails only if rw == nil
```
經常地,對一個接口值的動態類型我們是不確定的,并且我們更愿意去檢驗它是否是一些特定的類型。如果類型斷言出現在一個預期有兩個結果的賦值操作中,例如如下的定義,這個操作不會在失敗的時候發生panic,但是替代地返回一個額外的第二個結果,這個結果是一個標識成功與否的布爾值:
```go
var w io.Writer = os.Stdout
f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
```
第二個結果通常賦值給一個命名為ok的變量。如果這個操作失敗了,那么ok就是false值,第一個結果等于被斷言類型的零值,在這個例子中就是一個nil的`*bytes.Buffer`類型。
這個ok結果經常立即用于決定程序下面做什么。if語句的擴展格式讓這個變的很簡潔:
```go
if f, ok := w.(*os.File); ok {
// ...use f...
}
```
當類型斷言的操作對象是一個變量,你有時會看見原來的變量名重用而不是聲明一個新的本地變量名,這個重用的變量原來的值會被覆蓋(理解:其實是聲明了一個同名的新的本地變量,外層原來的w不會被改變),如下面這樣:
```go
if w, ok := w.(*os.File); ok {
// ...use w...
}
```
- 前言
- Go語言起源
- Go語言項目
- 本書的組織
- 更多的信息
- 致謝
- 入門
- Hello, World
- 命令行參數
- 查找重復的行
- GIF動畫
- 獲取URL
- 并發獲取多個URL
- Web服務
- 本章要點
- 程序結構
- 命名
- 聲明
- 變量
- 賦值
- 類型
- 包和文件
- 作用域
- 基礎數據類型
- 整型
- 浮點數
- 復數
- 布爾型
- 字符串
- 常量
- 復合數據類型
- 數組
- Slice
- Map
- 結構體
- JSON
- 文本和HTML模板
- 函數
- 函數聲明
- 遞歸
- 多返回值
- 錯誤
- 函數值
- 匿名函數
- 可變參數
- Deferred函數
- Panic異常
- Recover捕獲異常
- 方法
- 方法聲明
- 基于指針對象的方法
- 通過嵌入結構體來擴展類型
- 方法值和方法表達式
- 示例: Bit數組
- 封裝
- 接口
- 接口是合約
- 接口類型
- 實現接口的條件
- flag.Value接口
- 接口值
- sort.Interface接口
- http.Handler接口
- error接口
- 示例: 表達式求值
- 類型斷言
- 基于類型斷言識別錯誤類型
- 通過類型斷言查詢接口
- 類型分支
- 示例: 基于標記的XML解碼
- 補充幾點
- Goroutines和Channels
- Goroutines
- 示例: 并發的Clock服務
- 示例: 并發的Echo服務
- Channels
- 并發的循環
- 示例: 并發的Web爬蟲
- 基于select的多路復用
- 并發的退出
- 示例: 聊天服務
- 基于共享變量的并發
- 競爭條件
- sync.Mutex互斥鎖
- sync.RWMutex讀寫鎖
- 內存同步
- 競爭條件檢測
- 示例: 并發的非阻塞緩存
- Goroutines和線程
- 包和工具
- 包簡介
- 導入路徑
- 包聲明
- 導入聲明
- 包的匿名導入
- 包和命名
- 工具
- 測試
- go test
- 測試函數
- 測試覆蓋率
- 基準測試
- 剖析
- 示例函數
- 反射
- 為何需要反射?
- reflect.Type和reflect.Value
- Display遞歸打印
- 示例: 編碼S表達式
- 通過reflect.Value修改值
- 示例: 解碼S表達式
- 顯示一個類型的方法集
- 幾點忠告
- 底層編程
- unsafe.Sizeof, Alignof 和 Offsetof
- unsafe.Pointer
- 示例: 深度相等判斷
- 通過cgo調用C代碼
- 幾點忠告
- 附錄
- 附錄A:原文勘誤
- 附錄B:作者譯者
- 附錄C:譯文授權
- 附錄D:其它語言