golang錯題集

本文即Go語言的那些坑三。php

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

請看下列的列子:git

import (
	"fmt"
	"runtime"
	"time"
)

func main(){
	names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
	for _, name := range names{
		go func(){
			fmt.Println(name)
		}()
	}
	runtime.GOMAXPROCS(1)
	runtime.Gosched()
}
複製代碼

請問輸出什麼?github

答案:json

annei
annei
annei
annei
annei
複製代碼

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

import (
	"fmt"
	"runtime"
	"time"
)

func main(){
	names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
	for _, name := range names{
		go func(){
			fmt.Println(name)
		}()
		time.Sleep(time.Second)
	}
	runtime.GOMAXPROCS(1)
	runtime.Gosched()
}

複製代碼

打印結果:bash

lily
yoyo
cersei
rose
annei
複製代碼

以上咱們得出一個結論,不要對「go函數」的執行時機作任何的假設,除非你確實能作出讓這種假設成爲絕對事實的保證。併發

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

請看代碼,試問能正常編譯經過嗎?函數

import (
	"fmt"
)
type Lili struct{
	Name string
}

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

func (Lili Lili) fmtReference(){
	fmt.Println("reference")
}


func main(){
	li := Lili{}
	li.fmtPointer()
}
複製代碼

答案:ui

能正常編譯經過,並輸出"poniter"
複製代碼

感受有點詫異,請接着看如下的代碼,試問能編譯經過?spa

import (
	"fmt"
)
type Lili struct{
	Name string
}

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

func (Lili Lili) fmtReference(){
	fmt.Println("reference")
}


func main(){
	Lili{}.fmtPointer()
}

複製代碼

答案:

不能編譯經過。
「cannot call pointer method on Lili literal」
「cannot take the address of Lili literal」

複製代碼

是否是有點奇怪?這是爲何呢?其實在第一個代碼示例中,main主函數中的「li」是一個變量,li的雖然是類型Lili,可是li是能夠尋址的,&li的類型是*Lili,所以能夠調用*Lili的方法。

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

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

import (
	"bytes"
	"fmt"
	"io"
)

const debug = true

func main(){
	var buf *bytes.Buffer
	if debug{
		buf = new(bytes.Buffer)
	}
	f(buf)
}
func f(out io.Writer){

	if out != nil{
		fmt.Println("surprise!")
	}
}
複製代碼

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

import (
	"bytes"
	"fmt"
	"io"
)

const debug = false

func main(){
	var buf *bytes.Buffer
	if debug{
		buf = new(bytes.Buffer)
	}
	f(buf)
}
func f(out io.Writer){

	if out != nil{
		fmt.Println("surprise!")
	}
}

複製代碼

答案是:依然輸出surprise。

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

import (
	"bytes"
	"fmt"
)

func main(){
	var buf *bytes.Buffer
	if buf == nil{
		fmt.Println("right")
	}
}
複製代碼

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

import (
	"bytes"
	"fmt"
	"io"
)
const debug = false
func main(){
	var buf  io.Writer //原來是var buf *bytes.Buffer
	if debug{
		buf = new(bytes.Buffer)
	}
	f(buf)
}
func f(out io.Writer){
	if out != nil{
		fmt.Println("surprise!")
	}
}
複製代碼

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

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

func main() {
	params := make(map[string]string)

	params["id"] = "1"
	params["id1"] = "3"
	params["controller"] = "sections"

	data, _ := json.Marshal(params)
	fmt.Println(string(data))
}
複製代碼

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

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

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

func main(){
	jsonStr := `{"id":1058,"name":"RyuGou"}`
	var jsonData map[string]interface{}
	json.Unmarshal([]byte(jsonStr), &jsonData)

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

複製代碼

答案是會報錯,輸出結果爲:

panic: interface conversion: interface {} is float64, not int
複製代碼

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

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

複製代碼

應該改成:

func main(){
	jsonStr := `{"id":1058,"name":"RyuGou"}`
	var jsonData map[string]interface{}
	json.Unmarshal([]byte(jsonStr), &jsonData)

	sum :=  int(jsonData["id"].(float64)) + 3
	fmt.Println(sum)
}
複製代碼

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

:=每每是用來聲明局部變量的,在多個變量賦值且有的值存在的狀況下,:=也能夠用來賦值使用,例如:

msgStr := "hello wolrd"
msgStr, err := "hello", errors.New("xxx")//err並不存在
複製代碼

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

var varTest string

func test(){
	varTest, err := function()
	fmt.Println(err.Error())
}

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


func main(){
	test()
}

複製代碼

答案是:通不過。輸出:

varTest declared and not used
複製代碼

可是若是改爲以下代碼,就能夠經過:

var varTest string

func test(){
	err := errors.New("error")
	varTest, err = function()
	fmt.Println(err.Error())
}

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


func main(){
	test()
}
複製代碼

輸出:

error
複製代碼

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

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

請問如下代碼,能編譯經過嗎?

import (
	"fmt"
)

type Father interface {
	Hello()
}


type Child struct {
	Name string
}

func (s Child)Hello()  {

}

func main(){
	var buf  Child
	buf = Child{}
	f(&buf)
}
func f(out *Father){
	if out != nil{
		fmt.Println("surprise!")
	}
}
複製代碼

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

*Father is pointer to interface, not interface
複製代碼

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

var buf Father = Child{}
複製代碼

是對的,可是

var buf *Father = new(Child)
複製代碼

倒是不對的。應該改成:

var buf Father = Child{}
 var pointer *Father = &buf
複製代碼

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

import (
	"fmt"
)

type Father interface {
	Hello()
}


type Child struct {
	Name string
}

func (s Child)Hello()  {

}

func main(){
	var buf  Father
	buf = Child{}
	f(&buf)
}
func f(out *Father){
	if out != nil{
		fmt.Println("surprise!")
	}
}
複製代碼
相關文章
相關標籤/搜索