Go語言的那些坑三

熱烈歡迎你,相識是一種緣分,Echa 哥為了你的到來特意準備了一份驚喜,go學習資料《 》

目錄:


Go語言的那些坑三


不要對Go併發函數的執行時機做任何假設

請看下列的例子:

<code>import (
\t"fmt"
\t"runtime"
\t"time"
)

func main(){
\tnames := []string{"lily", "yoyo", "cersei", "rose", "annei"}
\tfor _, name := range names{
\t\tgo func(){
\t\t\tfmt.Println(name)
\t\t}()
\t}
\truntime.GOMAXPROCS(1)
\truntime.Gosched()
}/<code>

請問輸出什麼?

答案:

<code>annei
annei
annei
annei
annei/<code>

為什麼呢?是不是有點詫異?輸出的都是“annei”,而“annei”又是“names”的最後一個元素,那麼也就是說程序打印出了最後一個元素的值,而name對於匿名函數來講又是一個外部的值。因此,我們可以做一個推斷:雖然每次循環都啟用了一個協程,但是這些協程都是引用了外部的變量,當協程創建完畢,再執行打印動作的時候,name的值已經不知道變為啥了,因為主函數協程也在跑,大家並行,但是在此由於names數組長度太小,當協程創建完畢後,主函數循環早已結束,所以,打印出來的都是遍歷的names最後的那一個元素“annei”。如何證實以上的推斷呢?其實很簡單,每次循環結束後,停頓一段時間,等待協程打印當前的name便可。

<code>import (
\t"fmt"
\t"runtime"
\t"time"
)

func main(){
\tnames := []string{"lily", "yoyo", "cersei", "rose", "annei"}
\tfor _, name := range names{
\t\tgo func(){
\t\t\tfmt.Println(name)
\t\t}()
\t\ttime.Sleep(time.Second)
\t}
\truntime.GOMAXPROCS(1)
\truntime.Gosched()
}/<code>

打印結果:

<code>lily
yoyo
cersei
rose
annei/<code>

以上我們得出一個結論,不要對“go函數”的執行時機做任何的假設,除非你確實能做出讓這種假設成為絕對事實的保證。

假設T類型的方法上接收器既有T類型的,又有*T指針類型的,那麼就不可以在不能尋址的T值上調用*T接收器的方法

請看代碼,試問能正常編譯通過嗎?

<code>import (
\t"fmt"
)
type Lili struct{
\tName string

}

func (Lili *Lili) fmtPointer(){
\tfmt.Println("poniter")
}

func (Lili Lili) fmtReference(){
\tfmt.Println("reference")
}


func main(){
\tli := Lili{}
\tli.fmtPointer()
}/<code>

答案:

<code>能正常編譯通過,並輸出"poniter"/<code>

感覺有點詫異,請接著看以下的代碼,試問能編譯通過?

<code>import (
\t"fmt"
)
type Lili struct{
\tName string
}

func (Lili *Lili) fmtPointer(){
\tfmt.Println("poniter")
}

func (Lili Lili) fmtReference(){
\tfmt.Println("reference")
}


func main(){
\tLili{}.fmtPointer()
}/<code>

答案:

<code>不能編譯通過。
“cannot call pointer method on Lili literal”

“cannot take the address of Lili literal”/<code>

是不是有點奇怪?這是為什麼呢?其實在第一個代碼示例中,main主函數中的“li”是一個變量,li的雖然是類型Lili,但是li是可以尋址的,&li的類型是*Lili,因此可以調用*Lili的方法。

一個包含nil指針的接口不是nil接口

請看下列代碼,試問返回什麼

<code>import (
\t"bytes"
\t"fmt"
\t"io"
)

const debug = true

func main(){
\tvar buf *bytes.Buffer
\tif debug{
\t\tbuf = new(bytes.Buffer)
\t}
\tf(buf)
}
func f(out io.Writer){

\tif out != nil{
\t\tfmt.Println("surprise!")
\t}
}/<code>

答案是輸出:surprise。ok,讓我們吧debug開關關掉,及debug的值變為false。那麼輸出什麼呢?是不是什麼都不輸出?

<code>import (
\t"bytes"
\t"fmt"
\t"io"

)

const debug = false

func main(){
\tvar buf *bytes.Buffer
\tif debug{
\t\tbuf = new(bytes.Buffer)
\t}
\tf(buf)
}
func f(out io.Writer){

\tif out != nil{
\t\tfmt.Println("surprise!")
\t}
}/<code>

答案是:依然輸出surprise。

這是為什麼呢?這就牽扯到一個概念了,是關於接口值的。概念上講一個接口的值分為兩部分:一部分是類型,一部分是類型對應的值,他們分別叫:動態類型和動態值。類型系統是針對編譯型語言的,類型是編譯期的概念,因此類型不是一個值。在上述代碼中,給f函數的out參數賦了一個*bytes.Buffer的空指針,所以out的動態值是nil。然而它的動態類型是bytes.Buffer,意思是:“A non-nil interface containing a nil pointer”,所以“out!=nil”的結果依然是true。
但是,對於直接的``bytes.Buffer``類型的判空不會出現此問題。

<code>import (
\t"bytes"
\t"fmt"
)

func main(){
\tvar buf *bytes.Buffer
\tif buf == nil{

\t\tfmt.Println("right")
\t}
}/<code>

還是輸出: right只有 接口指針 傳入函數的接口參數時,才會出現以上的坑。修改起來也很方便,把*bytes.Buffer改為io.Writer就好了。

<code>import (
\t"bytes"
\t"fmt"
\t"io"
)
const debug = false
func main(){
\tvar buf io.Writer //原來是var buf *bytes.Buffer
\tif debug{
\t\tbuf = new(bytes.Buffer)
\t}
\tf(buf)
}
func f(out io.Writer){
\tif out != nil{
\t\tfmt.Println("surprise!")
\t}
}/<code>

將map轉化為json字符串的時候,json字符串中的順序和map賦值順序無關

請看下列代碼,請問輸出什麼?若為json字符串,則json字符串中key的順序是什麼?

<code>func main() {
\tparams := make(map[string]string)

\tparams["id"] = "1"
\tparams["id1"] = "3"
\tparams["controller"] = "sections"

\tdata, _ := json.Marshal(params)
\tfmt.Println(string(data))
}/<code>

答案:輸出{"controller":"sections","id":"1","id1":"3"}利用Golang自帶的json轉換包轉換,會將map中key的順序改為字母順序,而不是map的賦值順序。map這個結構哪怕利用for range遍歷的時候,其中的key也是無序的,可以理解為map就是個無序的結構,和php中的array要區分開來

Json反序列化數字到interface{}類型的值中,默認解析為float64類型

請看以下程序,程序想要輸出json數據中整型id加上3的值,請問程序會報錯嗎?

<code>
func main(){
\tjsonStr := `{"id":1058,"name":"RyuGou"}`
\tvar jsonData map[string]interface{}
\tjson.Unmarshal([]byte(jsonStr), &jsonData)

\tsum := jsonData["id"].(int) + 3
\tfmt.Println(sum)
}

```
答案是會報錯,輸出結果為:/<code>

panic: interface conversion: interface {} is float64, not int

<code>使用 Golang 解析 JSON  格式數據時,若以 interface{} 接收數據,則會按照下列規則進行解析:/<code>

bool, for JSON booleans

float64, for JSON numbers

string, for JSON strings

[]interface{}, for JSON arrays

map[string]interface{}, for JSON objects

nil for JSON null

<code>
應該改為:
```go
func main(){
\tjsonStr := `{"id":1058,"name":"RyuGou"}`
\tvar jsonData map[string]interface{}
\tjson.Unmarshal([]byte(jsonStr), &jsonData)

\tsum := int(jsonData["id"].(float64)) + 3
\tfmt.Println(sum)
}/<code>

即使在有多個變量、且有的變量存在有的變量不存在、且這些變量共同賦值的情況下,也不可以使用:=來給全局變量賦值

:=往往是用來聲明局部變量的,在多個變量賦值且有的值存在的情況下,:=也可以用來賦值使用,例如:

<code>msgStr := "hello wolrd"
msgStr, err := "hello", errors.New("xxx")//err並不存在/<code>

但是,假如全局變量也使用類似的方式賦值,就會出現問題,請看下列代碼,試問能編譯通過嗎?

<code>var varTest string

func test(){
\tvarTest, err := function()
\tfmt.Println(err.Error())
}

func function()(string, error){
\treturn "hello world", errors.New("error")
}


func main(){

\ttest()
}/<code>

答案是:通不過。輸出:

<code>varTest declared and not used/<code>

但是如果改成如下代碼,就可以通過:

<code>var varTest string

func test(){
\terr := errors.New("error")
\tvarTest, err = function()
\tfmt.Println(err.Error())
}

func function()(string, error){
\treturn "hello world", errors.New("error")
}


func main(){
\ttest()
}/<code>

輸出:

<code>error/<code>

這是什麼原因呢?答案其實很簡單,在test方法中,如果使用varTest, err := function()這種方式的話,相當於在函數中又定義了一個和全局變量varTest名字相同的局部變量,而這個局部變量又沒有使用,所以會編譯不通過。

*interface 是一個指向interface的指針類型,而不是interface類型

請問以下代碼,能編譯通過嗎?

<code>import (
\t"fmt"
)

type Father interface {
\tHello()
}


type Child struct {
\tName string
}

func (s Child)Hello() {

}

func main(){
\tvar buf Child
\tbuf = Child{}
\tf(&buf)
}
func f(out *Father){
\tif out != nil{
\t\tfmt.Println("surprise!")
\t}
}/<code>

答案是:不能編譯通過。輸出:

<code>*Father is pointer to interface, not interface/<code>

注意了:接口類型的變量可以被賦值為實現接口的結構體的實例,但是並不能代表接口的指針可以被賦值為實現接口的結構體的指針實例。即:

<code>var buf Father = Child{}/<code>

是對的,但是

<code>var buf *Father = new(Child)/<code>

卻是不對的。應該改為:

<code>var buf Father = Child{}
var pointer *Father = &buf/<code>

要想讓問題最開始的代碼編譯通過要將以上代碼修改為:

<code>import (
\t"fmt"
)

type Father interface {
\tHello()
}


type Child struct {
\tName string
}

func (s Child)Hello() {

}

func main(){
\tvar buf Father
\tbuf = Child{}
\tf(&buf)
}
func f(out *Father){
\tif out != nil{
\t\tfmt.Println("surprise!")
\t}
}/<code>


分享到:


相關文章: