Go语言的那些坑

Golang   2017-09-06 10:12:53 发布
您的评价:
     
0.0
收藏     0收藏
文件夹
标签
(多个标签用逗号分隔)

Golang是我最喜欢的一门语言,它简洁、高效、易学习、开发效率高、还可以编译成机器码…
虽然它一出世,就饱受关注,而且现在在市面上逐渐流行开来,但是,它毕竟是一门新兴语言,还有很多让人不太习惯的地方(即坑,(^__^)),我作为新手,一边学习,一边踩坑,也希望对其他人有借鉴作用。

文件名字不要轻易以__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("这里是汉字:" + 字符串变量) 字符串变量的值打印不出来的问题

现有如下程序:

package main
import "fmt"

func main() {
   m1 := getString()
   fmt.Println("现在是:" + m1)
}

func getString()string{
   return "abd"
}

运行指令go run test.go

Go语言的那些坑

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

import "fmt"

func main() {
   m1 := getString()
   fmt.Println(m1)
   fmt.Println("现在是:" + m1)
}

func getString()string{
   return "abd"
}

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

Go语言的那些坑

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

Go语言的那些坑

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

package main
import "fmt"

func main(){
   defer func(){
      fmt.Println("1")
   }()

   defer func(){
      fmt.Println("2")
   }()

   defer func(){
      fmt.Println("3")
   }()
} 

对应的输出是:

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

package main
import "fmt"

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

输出:[12312]

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

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)
   }
} 

 

所以,结果输出为:

 

key=0, value=&{two 19}

key=1, value=&{two 19}

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

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()
} 

输出:

like first
love
 

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

func main(){
   a := 1
   defer print(function(a))

   a = 2
   fmt.Println(a)
}

func function(num int) int{
   return num
}

func print(num int){
   fmt.Println(num)
}


输出:

2

1

 

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

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()
}

输出:

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带一个缓冲,就会把数据放到缓冲区中,直到缓冲区满了,才会阻塞

import "fmt"

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


输出: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 语句结束

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")
}

输出:

1
success


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

import "fmt"

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

输出:

default
success
 

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

golang中没有“对象”

package main
import (
"fmt"
)

type test struct {
   name string
}

func (t *test) getName(){
   fmt.Println("hello world")
}

func main() {
   var t *test
   t = nil
   t.getName()
}

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

输出为:

hello world

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

调用getName方法,在Go中还可以转换,转换为:Type.method(t Type, arguments)
所以,以上代码main函数中还可以写成:

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

惊不惊喜?函数中没有用到(*test)类型的实体,所以传nil也可以

Go中的指针,*符号的含义

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

例如:

a := 1
b := &a

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

*的意思是对指针取值。

下面对a的值加一

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

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

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

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

package main
import (
"fmt"
"os"
)

func main() {
   fmt.Println(os.Args[0])
}

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

输出:./main

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

请看下列代码

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)
}

请问输出什么?

答案是:[10 21]

如果稍作修改,将以上newSlice改为扩容三次,

newSlice := append(append(append(slice, 50), 100), 150)如下:

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)
}

输出为:[10 20]

这特么是什么鬼?

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

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

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

import (
"fmt"
)

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

答案是:0

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

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

请看下面的例子:

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)
   }
}

输出:

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

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

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

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

例如:只可以发送值

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

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

如下是只可以取值:

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

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

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

package main
import (
   "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之间通信的,且通信过程中会阻塞。

 

来自:https://i6448038.github.io/2017/07/28/Go%E8%AF%AD%E8%A8%80%E7%9A%84%E9%82%A3%E4%BA%9B%E5%9D%91/

扩展阅读

Java中的纤程库 - Quasar
Golang资料集
Go语言(golang)开源项目大全
一个Go(Golang)语言轻量级MVC框架:utron
优秀的Go语言项目推荐

为您推荐

JSP知识点总结
Go语言错误处理
机器学习开源项目、类库、软件集合
机器学习框架、库和软件集合:awesome-machine-learning
awesome-machine-learning - 非常棒的机器学习框架,库和软件集合

更多

Golang
Google Go/Golang开发