我在網上看到了一篇《十條有用的 GO 技術》其實是很多篇,該死的爬蟲。
下面的內容都是誤導別人的錯誤邏輯,可能是原創作者當時入坑不深,我發表此文章的本意僅僅是為了討論技術,可能會使某人不開心,但一個正確的技術答案,是所有人想要的。
其中第六條這樣解釋:
6. 讓 iota 從 a +1 開始增量
在前面的例子中同時也產生了一個我已經遇到過許多次的 bug。假設你有一個新的結構體,有一個 State 欄位:type T struct { Name string Port int State State }
現在如果基於 T 建立一個新的變數,然後輸出,你會得到奇怪的結果 (http://play.golang.org/p/LPG2RF3y39) :func main() { t := T{Name: "example", Port: 6666} // prints: "t {Name:example Port:6666 State:Running}" fmt.Printf("t %+vn", t) }
看到 bug 了嗎?State 欄位沒有被初始化,Go 預設使用對應型別的零值進行填充。由於 State 是一個整數,零值也就是 0,但在我們的例子中它表示 Running。
如何知道 State 被初始化了?如何得知是在 Running 模式?事實上,沒有很好的辦法區分它們,並且它們還會產生更多未知的、不可預測的 Bug。不過,修復這個很容易,只要讓 iota 從 +1 開始 (http://play.golang.org/p/VyAq-3OItv):const ( Running State = iota + 1 Stopped Rebooting Terminated )
現在 t 變數將預設輸出 Unknown,是不是?func main() { t := T{Name: "example", Port: 6666} // 輸出: "t {Name:example Port:6666 State:Unknown}" fmt.Printf("t %+vn", t) }
不過讓 iota 從零值開始也是一種解決辦法。例如,你可以引入一個新的狀態叫做 Unknown,將其修改為:const ( Unknown State = iota Running Stopped Rebooting Terminated )
OK,上面的內容都是誤導別人的錯誤邏輯,可能是原創作者當時入坑不深,我發表此文章的本意僅僅是為了討論技術,可能會使某人不開心,但一個正確的技術答案,是所有人想要的。
先放一段上面的程式碼
type T struct {
Name string
Port int
State State
}
type State int
const (
Running State = iota //iota將const常量依次繫結了數值0,1,2,3
Stopped
Rebooting
Terminated
)
//我們這裡重寫了string官方函式,
//使得State型別的列印結果不是01234... 而是指定的字串。
func (s State) String() string {
switch s {
case Running:
return "Running"
case Stopped:
return "Stopped"
case Rebooting:
return "Rebooting"
case Terminated:
return "Terminated"
default:
return "Unknown"
}
}
func main() {
t := T{Name: "example", Port: 6666}
fmt.Printf("t %+v\n", t)
}
//t {Name:example Port:6666 State:Running}
我們看一下有疑問的地方 main
裡的 fmt.Printf("t %+v\n", t)
t 結構體的第三個欄位沒有賦值,會預設賦予零值。
但是為什麼T.State列印出來是Running,這是正確的,請看我的解釋:
❌有人會這樣想:結果是出乎意料的,T.State的列印結果應該是0。因為State 是一個整數,零值也就是 0,不應該是Running。。。。這樣的邏輯思想是錯誤的。
✅我的結論:(nil != nil 也是這樣的邏輯)
這是因為我們重寫了string函式,在switch判斷中,Running和T.State兩者的型別和值都一樣,所以判斷條件成立,相當於 nameA int 1 == nameB int 1。 在string函式中,我們判斷的是State型別,它與常量和結構體無關。
解決:
1️⃣可以使用標準庫的string列印:fmt.Printf("t %#v\n", t)
2️⃣或者我們去掉自定義的string方法即可。
3️⃣或者修改string方法的接收器
4️⃣或者修改T.State型別
✅總之,在string判斷中,只要型別和值匹配成功,則一定會呼叫重構的string。
✅stackoverflow論壇的回答:(我提問這個問題之後,被很多人鄙視我。。。)
T.State的型別為State。這與欄位T無關。State不是T(struct)的一部分,僅僅是成員。
T.State型別的零值為0,也等於Running。
因此,當您分配給T的例項且T.State未初始化時,T.State會得到零值,即
Running
✅github,GO官方人員回答:(有幸得到了官方的回覆)
就像@DisposaBoy所說的,這是因為State的任何整數值在列印時都會被字串化%+v,在這種情況下,設定了State的零值,所以Running將其列印出來。參見https://yourbasic.org/golang/iota/