golang错题集

原文作者:RyuGou

本文即Go语言的那些坑三。

不要对Go并发函数的执行时机做任何假设

请看下列的列子:

 1import (
 2    "fmt"
 3    "runtime"
 4    "time"
 5)
 6func main(){
 7    names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
 8    for _, name := range names{
 9        go func(){
10            fmt.Println(name)
11        }()
12    }
13    runtime.GOMAXPROCS(1)
14    runtime.Gosched()
15}

请问输出什么?

答案:

1annei
2annei
3annei
4annei
5annei

为什么呢?是不是有点诧异? 输出的都是“annei”,而“annei”又是“names”的最后一个元素,那么也就是说程序打印出了最后一个元素的值,而name对于匿名函数来讲又是一个外部的值。因此,我们可以做一个推断:虽然每次循环都启用了一个协程,但是这些协程都是引用了外部的变量,当协程创建完毕,再执行打印动作的时候,name的值已经不知道变为啥了,因为主函数协程也在跑,大家并行,但是在此由于names数组长度太小,当协程创建完毕后,主函数循环早已结束,所以,打印出来的都是遍历的names最后的那一个元素“annei”。 如何证实以上的推断呢? 其实很简单,每次循环结束后,停顿一段时间,等待协程打印当前的name便可。

 1import (
 2    "fmt"
 3    "runtime"
 4    "time"
 5)
 6func main(){
 7    names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
 8    for _, name := range names{
 9        go func(){
10            fmt.Println(name)
11        }()
12        time.Sleep(time.Second)
13    }
14    runtime.GOMAXPROCS(1)
15    runtime.Gosched()
16}

打印结果:

1lily
2yoyo
3cersei
4rose
5annei

以上我们得出一个结论,不要对“go函数”的执行时机做任何的假设,除非你确实能做出让这种假设成为绝对事实的保证。

假设T类型的方法上接收器既有T类型的,又有*T指针类型的,那么就不可以在不能寻址的T值上调用*T接收器的方法

请看代码,试问能正常编译通过吗?

 1import (
 2    "fmt"
 3)
 4type Lili struct{
 5    Name string
 6}
 7func (Lili *Lili) fmtPointer(){
 8    fmt.Println("poniter")
 9}
10func (Lili Lili) fmtReference(){
11    fmt.Println("reference")
12}
13func main(){
14    li := Lili{}
15    li.fmtPointer()
16}

答案:

1能正常编译通过,并输出"poniter"

感觉有点诧异,请接着看以下的代码,试问能编译通过?

 1import (
 2    "fmt"
 3)
 4type Lili struct{
 5    Name string
 6}
 7func (Lili *Lili) fmtPointer(){
 8    fmt.Println("poniter")
 9}
10func (Lili Lili) fmtReference(){
11    fmt.Println("reference")
12}
13func main(){
14    Lili{}.fmtPointer()
15}

答案:

1不能编译通过。
2“cannot call pointer method on Lili literal”
3“cannot take the address of Lili literal”

是不是有点奇怪?这是为什么呢?其实在第一个代码示例中,main主函数中的“li”是一个变量,li的虽然是类型Lili,但是li是可以寻址的,&li的类型是*Lili,因此可以调用*Lili的方法。

一个包含nil指针的接口不是nil接口

请看下列代码,试问返回什么

 1import (
 2    "bytes"
 3    "fmt"
 4    "io"
 5)
 6const debug = true
 7func main(){
 8    var buf *bytes.Buffer
 9    if debug{
10        buf = new(bytes.Buffer)
11    }
12    f(buf)
13}
14func f(out io.Writer){
15    if out != nil{
16        fmt.Println("surprise!")
17    }
18}

答案是输出:surprise。 ok,让我们吧debug开关关掉,及debug的值变为false。那么输出什么呢?是不是什么都不输出?

 1import (
 2    "bytes"
 3    "fmt"
 4    "io"
 5)
 6const debug = false
 7func main(){
 8    var buf *bytes.Buffer
 9    if debug{
10        buf = new(bytes.Buffer)
11    }
12    f(buf)
13}
14func f(out io.Writer){
15    if out != nil{
16        fmt.Println("surprise!")
17    }
18}

答案是:依然输出surprise。

这是为什么呢? 这就牵扯到一个概念了,是关于接口值的。概念上讲一个接口的值分为两部分:一部分是类型,一部分是类型对应的值,他们分别叫:动态类型和动态值。类型系统是针对编译型语言的,类型是编译期的概念,因此类型不是一个值。 在上述代码中,给f函数的out参数赋了一个*bytes.Buffer的空指针,所以out的动态值是nil。然而它的动态类型是bytes.Buffer,意思是:“A non-nil interface containing a nil pointer”,所以“out!=nil”的结果依然是true。 但是,对于直接的``bytes.Buffer``类型的判空不会出现此问题。

 1import (
 2    "bytes"
 3    "fmt"
 4)
 5func main(){
 6    var buf *bytes.Buffer
 7    if buf == nil{
 8        fmt.Println("right")
 9    }
10}

还是输出: right 只有 接口指针 传入函数的接口参数时,才会出现以上的坑。 修改起来也很方便,把*bytes.Buffer改为io.Writer就好了。

 1import (
 2    "bytes"
 3    "fmt"
 4    "io"
 5)
 6const debug = false
 7func main(){
 8    var buf  io.Writer //原来是var buf *bytes.Buffer
 9    if debug{
10        buf = new(bytes.Buffer)
11    }
12    f(buf)
13}
14func f(out io.Writer){
15    if out != nil{
16        fmt.Println("surprise!")
17    }
18}

将map转化为json字符串的时候,json字符串中的顺序和map赋值顺序无关

请看下列代码,请问输出什么?若为json字符串,则json字符串中key的顺序是什么?

1func main() {
2    params := make(map[string]string)
3    params["id"] = "1"
4    params["id1"] = "3"
5    params["controller"] = "sections"
6    data, _ := json.Marshal(params)
7    fmt.Println(string(data))
8}

答案:输出{"controller":"sections","id":"1","id1":"3"} 利用Golang自带的json转换包转换,会将map中key的顺序改为字母顺序,而不是map的赋值顺序。map这个结构哪怕利用for range遍历的时候,其中的key也是无序的,可以理解为map就是个无序的结构,和php中的array要区分开来

Json反序列化数字到interface{}类型的值中,默认解析为float64类型

请看以下程序,程序想要输出json数据中整型id加上3的值,请问程序会报错吗?

1func main(){
2    jsonStr := `{"id":1058,"name":"RyuGou"}`
3    var jsonData map[string]interface{}
4    json.Unmarshal([]byte(jsonStr), &jsonData)
5    sum :=  jsonData["id"].(int) + 3
6    fmt.Println(sum)
7}

答案是会报错,输出结果为: ```

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

1使用 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

1应该改为:
2go
3func main(){
4    jsonStr := `{"id":1058,"name":"RyuGou"}`
5    var jsonData map[string]interface{}
6    json.Unmarshal([]byte(jsonStr), &jsonData)
7    sum :=  int(jsonData["id"].(float64)) + 3
8    fmt.Println(sum)
9}

即使在有多个变量、且有的变量存在有的变量不存在、且这些变量共同赋值的情况下,也不可以使用:=来给全局变量赋值

:=往往是用来声明局部变量的,在多个变量赋值且有的值存在的情况下,:=也可以用来赋值使用,例如:

1msgStr := "hello wolrd"
2msgStr, err := "hello", errors.New("xxx")//err并不存在

但是,假如全局变量也使用类似的方式赋值,就会出现问题,请看下列代码,试问能编译通过吗?

 1var varTest string
 2func test(){
 3    varTest, err := function()
 4    fmt.Println(err.Error())
 5}
 6func function()(string, error){
 7    return "hello world", errors.New("error")
 8}
 9func main(){
10    test()
11}

答案是:通不过。输出:

1varTest declared and not used

但是如果改成如下代码,就可以通过:

 1var varTest string
 2func test(){
 3    err := errors.New("error")
 4    varTest, err = function()
 5    fmt.Println(err.Error())
 6}
 7func function()(string, error){
 8    return "hello world", errors.New("error")
 9}
10func main(){
11    test()
12}

输出:

1error

这是什么原因呢? 答案其实很简单,在test方法中,如果使用varTest, err := function()这种方式的话,相当于在函数中又定义了一个和全局变量varTest名字相同的局部变量,而这个局部变量又没有使用,所以会编译不通过。


版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2018-07-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阮一峰的网络日志

async 函数的含义和用法

本文是《深入掌握 ECMAScript 6 异步编程》系列文章的最后一篇。 Generator函数的含义与用法 Thunk函数的含义与用法 co函数库的含义...

28660
来自专栏debugeeker的专栏

《coredump问题原理探究》Linux x86版3.4节栈布局之函数参数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/detai...

7910
来自专栏小灰灰

SPI框架实现之旅二:整体设计

SPI框架实现之旅二:整体设计 上一篇简单的说了一下spi相关的东西, 接下来我们准备开动,本篇博文主要集中在一些术语,使用规范的约定和使用方式 设计思路 下...

36080
来自专栏landv

Golang 新手可能会踩的 50 个坑【转】

译文:https://github.com/wuYin/blog/blob/master/50-shades-of-golang-traps-gotchas-m...

38120
来自专栏C/C++基础

web前端开发初学者十问集锦(4)

利用JS来控制页面控件的显示和隐藏有两种方法,两种方法分别利用HTML的style中的两个属性,两种方法的不同之处在于控件隐藏后是否还在页面上占空位。

17920
来自专栏王磊的博客

vuejs深入浅出—基础篇

一、从HelloWorld说起 任何语言的都是从Hello World开始的,VueJs也不例外,直接上代码: <script src="https://unp...

33260
来自专栏一个会写诗的程序员的博客

ES6 极简教程(ES6 Tutorial) 文 / 东海陈光剑ES6 极简教程(ES6 Tutorial)Kotlin 开发者社区

JavaScript是ECMAScript的实现和扩展,由ECMA(一个类似W3C的标准组织)参与进行标准化。ECMAScript定义了:

10330
来自专栏Pythonista

Python3编程技巧

Microsoft Excel是Microsoft为使用Windows和Apple Macintosh操作系统的计算机编写的一款电子表格软件。直观的界面、出色的...

11220
来自专栏大前端_Web

SASS相关

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

19910
来自专栏TungHsu

这或许是对小白最友好的python入门了吧——6,删除列表元素

这个时候我们CET考完了,怎么才能把它删除呢。这时候我们可以用del这个函数,用法如下: del exam[0] print(exam) #print的作用...

37380

扫码关注云+社区

领取腾讯云代金券