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!")
	}
}
复制代码
相关文章
相关标签/搜索