博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
go语言错题集(坑)【一】
阅读量:4099 次
发布时间:2019-05-25

本文共 8557 字,大约阅读时间需要 28 分钟。

系列相关:

目录

 


文件名字不要轻易以__test.go为结尾

Golang的source文件的命名和其他语言本无差别,但是Golang自带Unit test,它的unit test有个小规范:所有unit test文件都要以__test.go为结尾!

所以,当你命名一个非unit test文件为XXX_test.go,而且执意要编译时,就会报错:no buildable Go source files in XXXXXX(你的文件路径)
所以,切记,以__test.go为结尾的都是unit test的文件,且切记不要把unit test文件和普通Go文件放到一起,一定要把unit test文件集体放到一个目录中,否则会编译不过的。

语句fmt.Println("这里是汉字:" + 字符串变量) 字符串变量的值打印不出来的问题

现有如下程序:

12345678910111213
package mainimport "fmt"func main()  {    m1 := getString()    fmt.Println("现在是:" + m1)}func getString()string{    return "abd"}

运行指令go run test.go

但是单独打印变量m1却可以正常显示

12345678910111213
import "fmt"func main()  {    m1 := getString()    fmt.Println(m1)    fmt.Println("现在是:" + m1)}func getString()string{    return "abd"}

这是为什么呢?很奇怪啊!

其实这要怪IDE,我的IDE是phpstorm + Golang插件包,IDE自带的console对中文的支持很不友好,带中文的字符串打印出来后,容易显示不全,其实通过terminal打印出来,是正确的!

多个defer出现的时候,多个defer之间按照LIFO(后进先出)的顺序执行

12345678910111213141516171819
package mainimport "fmt"func main(){    defer func(){        fmt.Println("1")    }()    defer func(){        fmt.Println("2")    }()    defer func(){        fmt.Println("3")    }()}

对应的输出是:

123
321

panic中可以传任何值,不仅仅可以传string

1234567891011121314
package mainimport "fmt"func main(){    defer func(){        if r := recover();r != nil{            fmt.Println(r)        }    }()    panic([]int{12312})}

输出:

1
[12312]

for range来遍历数组或者map的时候,被遍历的指针是不变的,每次遍历仅执行struct值的拷贝

12345678910111213141516171819202122232425
import "fmt"type student struct{    Name string    Age  int}func main(){    var stus []student    stus = []student{        {Name:"one", Age: 18},        {Name:"two", Age: 19},    }    data := make(map[int]*student)    for i, v := range stus{        data[i] = &v   //应该改为:data[i] = &stus[i]    }    for i, v := range data{        fmt.Printf("key=%d, value=%v \n", i,v)    }}

所以,结果输出为:

12
key=0, value=&{two 19} key=1, value=&{two 19}

Go中没有继承!没有继承!Go中是叫组合!是组合!

12345678910111213141516171819202122232425262728293031
import "fmt"type student struct{    Name string    Age  int}func (p *student) love(){    fmt.Println("love")}func (p *student) like(){    fmt.Println("like first")    p.love()}type boy struct {    student}func (b * boy) love(){    fmt.Println("hate")}func main(){    b := boy{}    b.like()}

输出:

12
like firstlove

不管运行顺序如何,当参数为函数的时候,要先计算参数的值

123456789101112
func main(){    a := 1    defer print(function(a))    a = 2;}func function(num int) int{    return num}func print(num int){    fmt.Println(num)}

输出:

1
1

注意是struct的函数,还是* struct的函数

1234567891011121314151617181920
import "fmt"type people interface {    speak()}type student struct{    name string    age int}func (stu *student) speak(){    fmt.Println("I am a student, I am ", stu.age)}func main(){    var p people    p = student{name:"RyuGou", age:12} //应该改为 p = &student{name:"RyuGou", age:12}    p.speak()}

输出:

12
cannot use student literal (type student) as type people in assignment:student does not implement people (speak method has pointer receiver)

make(chan int) 和 make(chan int, 1)是不一样的

chan一旦被写入数据后,当前goruntine就会被阻塞,知道有人接收才可以(即 “ <- ch”),如果没人接收,它就会一直阻塞着。而如果chan带一个缓冲,就会把数据放到缓冲区中,直到缓冲区满了,才会阻塞

12345678910
import "fmt"func main(){    ch := make(chan int) //改为 ch := make(chan int, 1) 就好了    ch <- 1    fmt.Println("success")}

输出:

1
fatal error: all goroutines are asleep - deadlock!

golang 的 select 的功能和 select, poll, epoll 相似, 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。

select 的代码形式和 switch 非常相似, 不过 select 的 case 里的操作语句只能是”IO操作”(不仅仅是取值<-channel,赋值channel<-也可以), select 会一直等待等到某个 case 语句完成,也就是等到成功从channel中读到数据。 则 select 语句结束

1234567891011121314151617
import "fmt"func main(){    ch := make(chan int, 1)    ch <- 1    select {    case msg :=<-ch:        fmt.Println(msg)    default:        fmt.Println("default")    }    fmt.Println("success")}

输出:

12
1success

default可以判断chan是否已经满了

123456789101112131415
import "fmt"func main(){    ch := make(chan int, 1)    select {    case msg :=<-ch:        fmt.Println(msg)    default:        fmt.Println("default")    }    fmt.Println("success")}

输出:

12
defaultsuccess

此时因为ch中没有写入数据,为空,所以 case不会读取成功。 则 select 执行 default 语句。

Go语言中不存在未初始化的变量

变量定义基本方式为:

1
var 发量名字 类型 = 表达式

其中类型和表达式均可省略,如果初始化表达式被省略,将用零值初始化该变量。

  • 数值变量对应的是0值
  • 布尔变量对应的是false
  • 字符串对应的零值是空字符串
  • 接口或者引用类型(包括slice,map,chan)变量对应的是nil
  • 数组或者结构体等聚合类型对应的零值是每个元素或字段对应该类型的零值。
12
var s string fmt.Println(s) // ""

:=注意的问题

  • 使用:=定义的变量,仅能使用在函数内部。
  • 在定义多个变量的时候:=周围不一定是全部都是刚刚声明的,有些可能只是赋值,例如下面的err变量
    123
    in, err := os.Open(infile)// TODOout, err := os.Create(outfile)

new在Go语言中只是一个预定义的函数,它并不是一个关键字,我们可以将new作为变量或者其他

例如:

123
func delta(old, new int) int {     return new - old }

以上是正确的。

并不是使用new就一定会在堆上分配内存

编译器会自动选择在栈上还是在堆上分配存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。

请看例子:

123456789101112
var global *int func f() {    var x int x=1     global = &x}func g() {    y := new(int)    *y = 1 }

f()函数中的x就是在堆上分配内存,而g()函数中的y就是分配在栈上。

init函数在同一个文件中可以包含多个

在同一个包文件中,可以包含有多个init函数,多个init函数的执行顺序和定义顺序一致。

Golang中没有“对象”

12345678910111213141516
package mainimport (    "fmt")type test struct {    name string}func (t *test) getName(){    fmt.Println("hello world")}func main() {    var t *test    t = nil    t.getName()}

能正常输出吗?会报错吗?

输出为:

1
hello world

可以正常输出。Go本质上不是面向对象的语言,Go中是不存在object的含义的,Go语言书籍中的对象也和Java、PHP中的对象有区别,不是真正的”对象”,是Go中struct的实体。

调用getName方法,在Go中还可以转换,转换为:Type.method(t Type, arguments)

所以,以上代码main函数中还可以写成:

123
func main() {    (*test).getName(nil)}

Go中的指针*符号的含义

&的意思大家都明白的,取地址,假如你想获得一个变量的地址,只需在变量前加上&即可。

例如:

12
a := 1b := &a

现在,我拿到a的地址了,但是我想取得a指针指向的值,该如何操作呢?用*号,*b即可。

*的意思是对指针取值。

下面对a的值加一

123
a := 1b := &a*b++

*&可以相互抵消,同时注意,*&可以抵消,但是&*不可以;所以a*&a是一样的,和*&*&*&a也是一样的。

os.Args获取命令行指令参数,应该从数组的1坐标开始

os.Args的第一个元素,os.Args[0], 是命令本身的名字

12345678
package mainimport (    "fmt"    "os")func main() {    fmt.Println(os.Args[0])}

以上代码,经过go build之后,打包成一个可执行文件main,然后运行指令./main 123

输出:./main

数组切片slice的容量问题带来的bug

请看下列代码:

12345678910
import (    "fmt")func main(){    array := [4]int{10, 20, 30, 40}    slice := array[0:2]    newSlice := append(slice, 50)    newSlice[1] += 1    fmt.Println(slice)}

请问输出什么?

答案是:

1
[10 21]

如果稍作修改,将以上newSlice改为扩容三次,newSlice := append(append(append(slice, 50), 100), 150)如下:

12345678910
import (    "fmt")func main(){    array := [4]int{10, 20, 30, 40}    slice := array[0:2]    newSlice := append(append(append(slice, 50), 100), 150)    newSlice[1] += 1    fmt.Println(slice)}

输出为:

1
[10 20]

这特么是什么鬼?

这就要从Golang切片的扩容说起了;切片的扩容,就是当切片添加元素时,切片容量不够了,就会扩容,扩容的大小遵循下面的原则:(如果切片的容量小于1024个元素,那么扩容的时候slice的cap就翻番,乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加原来容量的四分之一。)如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组(这就是产生bug的原因);如果扩容之后,超过了原数组的容量,那么,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。
建议尽量避免bug的产生。

map引用不存在的key,不报错

请问下面的例子输出什么,会报错吗?

12345678
import (    "fmt")func main(){    newMap := make(map[string]int)    fmt.Println(newMap["a"])}

答案是:

1
0

不报错。不同于PHP,Golang的map和Java的HashMap类似,Java引用不存在的会返回null,而Golang会返回初始值

map使用range遍历顺序问题,并不是录入的顺序,而是随机顺序

请看下面的例子:

1234567891011121314
import (    "fmt")func main(){    newMap := make(map[int]int)    for i := 0; i < 10; i++{        newMap[i] = i    }    for key, value := range newMap{        fmt.Printf("key is %d, value is %d\n", key, value)    }}

输出:

12345678910
key is 1, value is 1key is 3, value is 3key is 5, value is 5key is 7, value is 7key is 9, value is 9key is 0, value is 0key is 2, value is 2key is 4, value is 4key is 6, value is 6key is 8, value is 8

是杂乱无章的顺序。map的遍历顺序不固定,这种设计是有意为之的,能为能防止程序依赖特定遍历顺序。

channel作为函数参数传递,可以声明为只取(<- chan)或者只发送(chan <-)

一个函数在将channel作为一个类型的参数来声明的时候,可以将channl声明为只可以取值(<- chan)或者只可以发送值(chan <-),不特殊说明,则既可以取值,也可以发送值。

例如:只可以发送值

123
func setData(ch chan <- string){    //TODO}

如果在以上函数中存在<-ch则会编译不通过。

如下是只可以取值:

123
func setData(ch <- chan  string){    //TODO}

如果以上函数中存在ch<-则在编译期会报错

使用channel时,注意goroutine之间的执行流程问题

1234567891011121314151617181920
package mainimport (    "fmt")func main(){    ch := make(chan string)    go setData(ch)    fmt.Println(<-ch)    fmt.Println(<-ch)    fmt.Println(<-ch)    fmt.Println(<-ch)    fmt.Println(<-ch)}func setData(ch  chan  string){    ch <- "test"    ch <- "hello wolrd"    ch <- "123"    ch <- "456"    ch <- "789"}

以上代码的执行流程是怎样的呢?

一个基于无缓存channel的发送或者取值操作,会导致当前goroutine阻塞,一直等待到另外的一个goroutine做相反的取值或者发送操作以后,才会正常跑。
以上例子中的流程是这样的:

主goroutine等待接收,另外的那一个goroutine发送了“test”并等待处理;完成通信后,打印出”test”;两个goroutine各自继续跑自己的。

主goroutine等待接收,另外的那一个goroutine发送了“hello world”并等待处理;完成通信后,打印出”hello world”;两个goroutine各自继续跑自己的。
主goroutine等待接收,另外的那一个goroutine发送了“123”并等待处理;完成通信后,打印出”123”;两个goroutine各自继续跑自己的。
主goroutine等待接收,另外的那一个goroutine发送了“456”并等待处理;完成通信后,打印出”456”;两个goroutine各自继续跑自己的。
主goroutine等待接收,另外的那一个goroutine发送了“789”并等待处理;完成通信后,打印出”789”;两个goroutine各自继续跑自己的。

记住:Golang的channel是用来goroutine之间通信的,且通信过程中会阻塞。

转载地址:http://hzwsi.baihongyu.com/

你可能感兴趣的文章
MODULE_DEVICE_TABLE的理解
查看>>
No devices detected. Fatal server error: no screens found
查看>>
db db2_monitorTool IBM Rational Performace Tester
查看>>
postgresql监控工具pgstatspack的安装及使用
查看>>
【JAVA数据结构】双向链表
查看>>
【JAVA数据结构】先进先出队列
查看>>
谈谈加密和混淆吧[转]
查看>>
乘法逆元
查看>>
Objective-C 基础入门(一)
查看>>
通过mavlink实现自主航线的过程笔记
查看>>
Flutter Boost的router管理
查看>>
Vue2.0全家桶仿腾讯课堂(移动端)
查看>>
React+Redux系列教程
查看>>
19 个 JavaScript 常用的简写技术
查看>>
iOS开发支付集成之微信支付
查看>>
一个很棒的Flutter学习资源列表
查看>>
为什么你应该放弃React老的Context API用新的Context API
查看>>
Koa2初体验
查看>>
Koa 2 初体验(二)
查看>>
Koa2框架原理解析和实现
查看>>