Go語言的那些坑

kdkiii 7年前發布 | 30K 次閱讀 Google Go/Golang開發 Golang

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/

 本文由用戶 kdkiii 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!