Go语言的那些坑

kdkiii 5年前
   <p>Golang是我最喜欢的一门语言,它简洁、高效、易学习、开发效率高、还可以编译成机器码…<br> 虽然它一出世,就饱受关注,而且现在在市面上逐渐流行开来,但是,它毕竟是一门新兴语言,还有很多让人不太习惯的地方(即坑,(<em>^__^</em>)),我作为新手,一边学习,一边踩坑,也希望对其他人有借鉴作用。</p>    <p>文件名字不要轻易以<code>__test.go</code>为结尾</p>    <p>Golang的source文件的命名和其他语言本无差别,但是Golang自带<code>Unit test</code>,它的<code>unit test</code>有个小规范:所有<code>unit test</code>文件都要以<code>__test.go</code>为结尾!<br> 所以,当你命名一个非<code>unit test</code>文件为XXX_test.go,而且执意要编译时,就会报错:<code>no buildable Go source files in XXXXXX(你的文件路径)</code>。<br> 所以,切记,以<code>__test.go</code>为结尾的都是<code>unit test</code>的文件,且切记不要把<code>unit test</code>文件和普通Go文件放到一起,一定要把<code>unit test</code>文件集体放到一个目录中,否则会编译不过的。</p>    <p>语句<code>fmt.Println("这里是汉字:" + 字符串变量)</code> 字符串变量的值打印不出来的问题</p>    <p>现有如下程序:</p>    <pre>  <code class="language-go">package main  import "fmt"    func main() {     m1 := getString()     fmt.Println("现在是:" + m1)  }    func getString()string{     return "abd"  }</code></pre>    <p>运行指令<code>go run test.go</code></p>    <p><img alt="Go语言的那些坑" src="https://simg.open-open.com/show/bf67984dda6881186875ea600d9c7e5e.jpg"></p>    <p>但是单独打印变量<code>m1</code>却可以正常显示</p>    <pre>  <code class="language-go">import "fmt"    func main() {     m1 := getString()     fmt.Println(m1)     fmt.Println("现在是:" + m1)  }    func getString()string{     return "abd"  }  </code></pre>    <p>这是为什么呢?很奇怪啊!</p>    <p><img alt="Go语言的那些坑" src="https://simg.open-open.com/show/80fd86c8eb3ecbefcec0ca278b304f34.jpg"></p>    <p>其实这要怪IDE,我的IDE是phpstorm + Golang插件包,IDE自带的console对中文的支持很不友好,带中文的字符串打印出来后,容易显示不全,其实通过<code>terminal</code>打印出来,是正确的!</p>    <p><img alt="Go语言的那些坑" src="https://simg.open-open.com/show/a49ff87ec7b776d6d6164bd3ebcc92f0.jpg"></p>    <p>多个<code>defer</code>出现的时候,多个<code>defer</code>之间按照LIFO(后进先出)的顺序执行</p>    <pre>  <code class="language-go">package main  import "fmt"    func main(){     defer func(){        fmt.Println("1")     }()       defer func(){        fmt.Println("2")     }()       defer func(){        fmt.Println("3")     }()  } </code></pre>    <p>对应的输出是:</p>    <p>3</p>    <p>2</p>    <p>1</p>    <p><code>panic</code>中可以传任何值,不仅仅可以传string</p>    <pre>  <code class="language-go">package main  import "fmt"    func main(){     defer func(){        if r := recover();r != nil{           fmt.Println(r)        }     }()     panic([]int{12312})  } </code></pre>    <p>输出:[12312]</p>    <p>用<code>for range</code>来遍历数组或者map的时候,被遍历的指针是不变的,每次遍历仅执行struct值的拷贝</p>    <pre>  <code class="language-go">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)     }  } </code></pre>    <p> </p>    <p>所以,结果输出为:</p>    <p> </p>    <p>key=0, value=&{two 19}</p>    <p>key=1, value=&{two 19}</p>    <p>Go中没有继承!没有继承!Go中是叫组合!是组合!</p>    <pre>  <code class="language-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()  } </code></pre>    <p>输出:</p>    <p>like first<br> love<br>  </p>    <p>不管运行顺序如何,当参数为函数的时候,要先计算参数的值</p>    <pre>  <code class="language-go">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)  }</code></pre>    <p><br> 输出:</p>    <p>2</p>    <p>1</p>    <p> </p>    <p>注意是<code>struct</code>的函数,还是<code>* struct</code>的函数</p>    <pre>  <code class="language-go">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()  }  </code></pre>    <p>输出:</p>    <p>cannot use student literal (type student) as type people in assignment:</p>    <p>student does not implement people (speak method has pointer receiver)</p>    <p> </p>    <p><code>make(chan int)</code> 和 <code>make(chan int, 1)</code>是不一样的</p>    <p><code>chan</code>一旦被写入数据后,当前<code>goruntine</code>就会被阻塞,知道有人接收才可以(即 “ <- ch”),如果没人接收,它就会一直阻塞着。而如果chan带一个缓冲,就会把数据放到缓冲区中,直到缓冲区满了,才会阻塞</p>    <pre>  <code class="language-go">import "fmt"    func main(){        ch := make(chan int) //改为 ch := make(chan int, 1) 就好了     ch <- 1     fmt.Println("success")  }</code></pre>    <p><br> 输出:fatal error: all goroutines are asleep - deadlock!</p>    <p>golang 的 select 的功能和 select, poll, epoll 相似, 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。</p>    <p>select 的代码形式和 switch 非常相似, 不过 select 的 case 里的操作语句只能是”IO操作”(不仅仅是取值<code><-channel</code>,赋值<code>channel<-</code>也可以), select 会一直等待等到某个 case 语句完成,也就是等到成功从channel中读到数据。 则 select 语句结束</p>    <pre>  <code class="language-go">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")  }  </code></pre>    <p>输出:</p>    <p><code>1<br> success</code></p>    <p><br> <code>default</code>可以判断chan是否已经满了</p>    <pre>  <code class="language-go">import "fmt"    func main(){     ch := make(chan int, 1)     select {     case msg :=<-ch:        fmt.Println(msg)     default:        fmt.Println("default")     }     fmt.Println("success")  }  </code></pre>    <p>输出:</p>    <p>default<br> success<br>  </p>    <p>此时因为<code>ch</code>中没有写入数据,为空,所以 case不会读取成功。 则 select 执行 default 语句。</p>    <p>golang中没有“对象”</p>    <pre>  <code class="language-go">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()  }  </code></pre>    <p>能正常输出吗?会报错吗?</p>    <p>输出为:</p>    <p>hello world</p>    <p>可以正常输出。Go本质上不是面向对象的语言,Go中是不存在<code>object</code>的含义的,Go语言书籍中的<code>对象</code>也和Java、PHP中的对象有区别,不是真正的”对象”,是Go中struct的实体。</p>    <p>调用<code>getName</code>方法,在Go中还可以转换,转换为:<code>Type.method(t Type, arguments)</code><br> 所以,以上代码main函数中还可以写成:</p>    <pre>  <code class="language-go">func main() {     (*test).getName(nil)  }  </code></pre>    <p>惊不惊喜?函数中没有用到(*test)类型的实体,所以传<code>nil</code>也可以</p>    <p>Go中的指针,<code>*</code>符号的含义</p>    <p><code>&</code>的意思大家都明白的,取地址,假如你想获得一个变量的地址,只需在变量前加上<code>&</code>即可。</p>    <p>例如:</p>    <pre>  <code class="language-go">a := 1  b := &a  </code></pre>    <p>现在,我拿到<code>a</code>的地址了,但是我想取得<code>a</code>指针指向的值,该如何操作呢?用<code>*</code>号,<code>*b</code>即可。</p>    <p><code>*</code>的意思是对指针取值。</p>    <p>下面对<code>a</code>的值加一</p>    <pre>  <code class="language-go">a := 1  b := &a  *b++  </code></pre>    <p><code>*</code>和<code>&</code>可以相互抵消,同时注意,<code>*&</code>可以抵消,但是<code>&*</code>不可以;所以<code>a</code>和<code>*&a</code>是一样的,和<code>*&*&*&a</code>也是一样的。</p>    <p><code>os.Args</code>获取命令行指令参数,应该从数组的1坐标开始</p>    <p><code>os.Args</code>的第一个元素,os.Args[0], 是命令本身的名字</p>    <pre>  <code class="language-go">package main  import (  "fmt"  "os"  )    func main() {     fmt.Println(os.Args[0])  }  </code></pre>    <p>以上代码,经过<code>go build</code>之后,打包成一个可执行文件<code>main</code>,然后运行指令<code>./main 123</code></p>    <p>输出:<code>./main</code></p>    <p>数组切片<code>slice</code>的容量问题带来的bug</p>    <p>请看下列代码</p>    <pre>  <code class="language-go">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)  }  </code></pre>    <p>请问输出什么?</p>    <p>答案是:[10 21]</p>    <p>如果稍作修改,将以上<code>newSlice</code>改为扩容三次,</p>    <p><code>newSlice := append(append(append(slice, 50), 100), 150)</code>如下:</p>    <pre>  <code class="language-go">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)  }  </code></pre>    <p>输出为:[10 20]</p>    <p>这特么是什么鬼?</p>    <p>这就要从Golang切片的扩容说起了;切片的扩容,就是当切片添加元素时,切片容量不够了,就会扩容,扩容的大小遵循下面的原则:(如果切片的容量小于<strong>1024</strong>个元素,那么扩容的时候slice的cap就翻番,乘以2;一旦元素个数超过1024个元素,增长因子就变成<code>1.25</code>,即每次增加原来容量的四分之一。)如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组(这就是产生bug的原因);如果扩容之后,超过了原数组的容量,那么,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。<br> 建议尽量避免bug的产生。</p>    <p>map引用不存在的key,不报错</p>    <p>请问下面的例子输出什么,会报错吗?</p>    <pre>  <code class="language-go">import (  "fmt"  )    func main(){     newMap := make(map[string]int)     fmt.Println(newMap["a"])  }  </code></pre>    <p>答案是:0</p>    <p>不报错。不同于PHP,Golang的map和Java的HashMap类似,Java引用不存在的会返回null,而Golang会返回初始值</p>    <p>map使用range遍历顺序问题,并不是录入的顺序,而是随机顺序</p>    <p>请看下面的例子:</p>    <pre>  <code class="language-go">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)     }  }  </code></pre>    <p>输出:</p>    <p>key is 1, value is 1<br> key is 3, value is 3<br> key is 5, value is 5<br> key is 7, value is 7<br> key is 9, value is 9<br> key is 0, value is 0<br> key is 2, value is 2<br> key is 4, value is 4<br> key is 6, value is 6<br> key is 8, value is 8</p>    <p>是杂乱无章的顺序。map的遍历顺序不固定,这种设计是有意为之的,能为能防止程序依赖特定遍历顺序。</p>    <p>channel作为函数参数传递,可以声明为只取(<code><- chan</code>)或者只发送(<code>chan <-</code>)</p>    <p>一个函数在将channel作为一个类型的参数来声明的时候,可以将channl声明为只可以取值(<code><- chan</code>)或者只可以发送值(<code>chan <-</code>),不特殊说明,则既可以取值,也可以发送值。</p>    <p>例如:只可以发送值</p>    <pre>  <code class="language-go">func setData(ch chan <- string){     //TODO  }  </code></pre>    <p>如果在以上函数中存在<code><-ch</code>则会编译不通过。</p>    <p>如下是只可以取值:</p>    <pre>  <code class="language-go">func setData(ch <- chan string){     //TODO  }  </code></pre>    <p>如果以上函数中存在<code>ch<-</code>则在编译期会报错</p>    <p>使用channel时,注意goroutine之间的执行流程问题</p>    <pre>  <code class="language-go">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"  }  </code></pre>    <p>以上代码的执行流程是怎样的呢?<br> 一个基于无缓存channel的发送或者取值操作,会导致当前goroutine阻塞,一直等待到另外的一个goroutine做相反的取值或者发送操作以后,才会正常跑。<br> 以上例子中的流程是这样的:</p>    <p>主goroutine等待接收,另外的那一个goroutine发送了“test”并等待处理;完成通信后,打印出”test”;两个goroutine各自继续跑自己的。<br> 主goroutine等待接收,另外的那一个goroutine发送了“hello world”并等待处理;完成通信后,打印出”hello world”;两个goroutine各自继续跑自己的。<br> 主goroutine等待接收,另外的那一个goroutine发送了“123”并等待处理;完成通信后,打印出”123”;两个goroutine各自继续跑自己的。<br> 主goroutine等待接收,另外的那一个goroutine发送了“456”并等待处理;完成通信后,打印出”456”;两个goroutine各自继续跑自己的。<br> 主goroutine等待接收,另外的那一个goroutine发送了“789”并等待处理;完成通信后,打印出”789”;两个goroutine各自继续跑自己的。</p>    <p>记住:Golang的channel是用来goroutine之间通信的,且通信过程中会阻塞。</p>    <p> </p>    <p>来自: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/</p>