Go語言的那些坑

Golang是我最喜歡的一門語言,它簡潔、高效、易學習、開發效率高、還能夠編譯成機器碼... 雖然它一出世,就飽受關注,並且如今在市面上逐漸流行開來,可是,它畢竟是一門新興語言,還有不少讓人不太習慣的地方(即坑,(^__^)),我做爲新手,一邊學習,一邊踩坑,但願對其餘人有借鑑做用。php

文件名字不要輕易以__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文件集體放到一個目錄中,不然會編譯不過的。git

語句fmt.Println("這裏是漢字:" + 字符串變量) 字符串變量的值打印不出來的問題

現有以下程序:github

package main

import "fmt"

func main()  {
    m1 := getString()

    fmt.Println("如今是:" + m1)
}

func getString()string{
    return "abd"
}
複製代碼

運行指令go run test.gogolang

可是單獨打印變量m1卻能夠正常顯示數組

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(後進先出)的順序執行

package main

import "fmt"

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

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

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


}
複製代碼

對應的輸出是:bash

3
2
1
複製代碼

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)
    }
}
複製代碼

因此,結果輸出爲:app

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()
}
複製代碼

輸出:phpstorm

like first
love
複製代碼

無論運行順序如何,當參數爲函數的時候,要先計算參數的值

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
複製代碼

注意是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 語句。

Go語言中不存在未初始化的變量

變量定義基本方式爲:

var 髮量名字 類型 = 表達式
複製代碼

其中類型和表達式都可省略,若是初始化表達式被省略,將用零值初始化該變量。

  • 數值變量對應的是0值
  • 布爾變量對應的是false
  • 字符串對應的零值是空字符串
  • 接口或者引用類型(包括slice,map,chan)變量對應的是nil
  • 數組或者結構體等聚合類型對應的零值是每一個元素或字段對應該類型的零值。
var s string 
 fmt.Println(s) // ""
複製代碼

:=注意的問題

  • 使用:=定義的變量,僅能使用在函數內部。
  • 在定義多個變量的時候:=周圍不必定是所有都是剛剛聲明的,有些可能只是賦值,例以下面的err變量
    in, err := os.Open(infile)
    // TODO
    out, err := os.Create(outfile)
    複製代碼

new在Go語言中只是一個預約義的函數,它並非一個關鍵字,咱們能夠將new做爲變量或者其餘

例如:

func delta(old, new int) int { 
    return new - old 
}
複製代碼

以上是正確的。

並非使用new就必定會在堆上分配內存

編譯器會自動選擇在棧上仍是在堆上分配存儲空間,但可能使人驚訝的是,這個選擇並非由用var仍是new聲明變量的方式決定的。

請看例子:

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中沒有「對象」

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)
}
複製代碼

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之間通訊的,且通訊過程當中會阻塞。

Go語言的那些坑二

Go語言的那些坑三

更多精彩內容,請關注個人微信公衆號 互聯網技術窩 或者加微信共同探討交流:

相關文章
相關標籤/搜索