轉載:https://www.luozhiyun.com/archives/206java
GOPATH簡單理解成Go語言的工做目錄,它的值是一個目錄的路徑,也能夠是多個目錄路徑,每一個目錄都表明Go語言的一個工做區(workspace)。linux
在GOPATH放置Go語言的源碼文件(source file),以及安裝(install)後的歸檔文件(archive file,也就是以「.a」爲擴展名的文件)和可執行文件(executable file)。git
好比,一個已存在的代碼包的導入路徑是github
github.com/labstack/echo,
那麼執行命令進行源碼的安裝算法
go install github.com/labstack/echo
在安裝後若是產生了歸檔文件(以「.a」爲擴展名的文件),就會放進該工做區的pkg子目錄;若是產生了可執行文件,就可能會放進該工做區的bin子目錄。數組
上面該命令在安裝後生成的歸檔文件的相對目錄就是 github.com/labstack, 文件名爲echo.a。安全
除此以外,歸檔文件的相對目錄與pkg目錄之間還有一級目錄,叫作平臺相關目錄。平臺相關目錄的名稱是由build(也稱「構建」)的目標操做系統、下劃線和目標計算架構的代號組成的。閉包
好比,構建某個代碼包時的目標操做系統是Linux,目標計算架構是64位的,那麼對應的平臺相關目錄就是linux_amd64。架構
咱們來看一下下面的代碼:併發
var block = "package" func main() { block := "function" { block := "inner" fmt.Printf("The block is %s.\n", block) } fmt.Printf("The block is %s.\n", block) blockFun() }
這個命令源碼⽂件中有四個代碼塊,它們是:全域代碼塊、main包表明的代碼塊、main函數表明的代碼塊,以及在main函 數中的⼀個⽤花括號包起來的代碼塊。
若是運行該代碼,那麼會獲得以下結果:
The block is inner. The block is function.
在go中,首先,代碼引⽤變量的時候總會最優先查找當前代碼塊中的那個變量。
其次,若是當前代碼塊中沒有聲明以此爲名的變量,那麼程序會沿着代碼塊的嵌套關係,從直接包含當前代碼塊的那個代 碼塊開始,⼀層⼀層地查找。
⼀般狀況下,程序會⼀直查到當前代碼包表明的代碼塊。若是仍然找不到,那麼Go語⾔的編譯器就會報錯了。
因此上面的例子中,main代碼塊首先沒法引用到最內層代碼塊中的變量,最內層的代碼塊也會優先去找本身代碼塊的變量。
須要注意一點的是,在不一樣的代碼塊中,變量的名字能夠相同可是類型能夠不一樣的。
其實若是使用過java,就會發現這些都和java的變量申明是同樣的。
在java中,咱們能夠用instanceof來判斷類型,在go中要稍微麻煩一點,具體的以下:
func main() { container := map[int]string{0: "zero", 1: "one", 2: "two"} fmt.Printf("The element is %q.\n", container[1]) value2, ok2 := interface{}(container).(map[int]string) value1, ok1 := interface{}(container).([]string) fmt.Println(value1) fmt.Println(value2) if !(ok1 || ok2) { fmt.Printf("Error: unsupported container type: %T\n", container) return } }
也就是說須要經過interface{}(container).(map[int]string)
這樣的一句表達式來實現判斷類型。
它包括了⽤來把container變量的值轉換爲空接⼝值的interface{}(container)。 以及⼀個⽤於判斷前者的類型是否爲map類型 map[int]string 的 .(map[int]string)。
這個表達式返回兩個變量,ok表明是否判斷成功,若是爲true,那麼被判斷的值將會被自動轉換爲map[int]string,不然value將被賦 予nil(即「空」)。
咱們通常能夠經過以下的方式實現類型轉換:
var srcInt = int16(-255) dstInt := int8(srcInt) fmt.Println(dstInt)
在上面的類型轉換中須要注意的是,這裏是範圍大的類型轉換成範圍小的類型,Go語⾔會把在較⾼ 位置(或者說最左邊位置)上的8位⼆進制數直接截掉,因此dstInt的值就是1。
相似的快⼑斬亂麻規則還有:當把⼀個浮點數類型的值轉換爲整數類型值時,前者的⼩數部分會被所有截掉。
因此在類型轉換的時候要時刻提防類型範圍的問題。
別名類型與其源類型的區別恐怕只是在名稱上,它們 是徹底相同的。
type MyString = string
定義新的類型,這個類型會不一樣於其餘任何類型。
type MyString2 string // 注意,這⾥沒有等號。
若是兩個值潛在類型相同,卻屬於不一樣類型,它們之間是能夠進⾏類型轉換的。以下:
type MyString string str := "BCD" myStr1 := MyString(str) myStr2 := MyString("A" + str)
可是兩個類型的潛在類型相同,它們的值之間也不能進⾏判等或⽐較,它們的變量之間也不能賦值。以下:
type MyString2 string str := "BCD" myStr2 := MyString2(str) //myStr2 = str // 這裏的賦值不合法,會引起編譯錯誤。 //fmt.Printf("%T(%q) == %T(%q): %v\n", // str, str, myStr2, myStr2, str == myStr2) // 這裏的判等不合法,會引起編譯錯誤。
對於集合類的類型[]MyString2與[]string來講是不能夠進⾏類型轉換和比較的,由於[]MyString2與[]string的潛在類型不 同,分別是MyString2和string。以下:
type MyString string strs := []string{"E", "F", "G"} var myStrs []MyString //myStrs := []MyString(strs) // 這裏的類型轉換不合法,會引起編譯錯誤。
通道類型的值自己就是併發安全的,這也是Go語⾔⾃帶的、惟⼀⼀個能夠滿⾜併發安全性的類型。
當容量爲0時,咱們能夠稱通道爲⾮緩衝通道,也就是不帶緩衝的通道。⽽當容量⼤於0時,咱們能夠稱爲緩衝通道,也就是 帶有緩衝的通道。
⼀個通道至關於⼀個先進先出(FIFO)的隊列。也就是說,通道中的各個元素值都是嚴格地按照發送的順序排列的,先被髮 送通道的元素值⼀定會先被接收。元素值的發送和接收都須要⽤到操做符<-。咱們也能夠叫它接送操做符。⼀個左尖括號緊 接着⼀個減號形象地表明瞭元素值的傳輸⽅向。
func main() { ch1 := make(chan int, 3) //往channel中放入元素 ch1 <- 2 ch1 <- 1 ch1 <- 3 //往channel中獲取元素 elem1 := <-ch1 fmt.Printf("The first element received from channel ch1: %v\n", elem1) }
在同⼀時刻,Go語⾔的運⾏時系統(如下簡稱運⾏時系統)只會執⾏對同⼀個通道的任意個發 送操做中的某⼀個。直到這個元素值被徹底複製進該通道以後,其餘針對該通道的發送操做纔可能被執⾏。
相似的,在同⼀時刻,運⾏時系統也只會執⾏,對同⼀個通道的任意個接收操做中的某⼀個。
另外,對於通道中的同⼀個元素值來講,發送操做和接收操做之間也是互斥的。例如,雖然會出現,正在被複制進通道但還未 複製完成的元素值,可是這時它毫不會被想接收它的⼀⽅看到和取⾛。
須要注意的是:進⼊通道的並非在接收操做符右邊的那個元素 值,⽽是它的副本。
發送操做和接收操做中對元素值的處理都是不可分割的。
如發送操做要麼還沒複製元素值,要麼已經複製完畢,毫不會出現只複製了⼀部分的狀況。
發送操做在徹底完成以前會被阻塞。接收操做也是如此。
發送操做包括了「複製元素值」和「放置副本到通道內部」這兩個步驟。
在這兩個步驟徹底完成以前,發起這個發送操做的那句代碼會⼀直阻塞在那⾥。也就是說,在它以後的代碼不會有執⾏的機 會,直到這句代碼的阻塞解除。
因爲發送操做在這種狀況下被阻塞後,它們所在的goroutine會順序地進⼊通道內部的發送等待隊列,因此通知的順序老是公平的。
// 示例1。 ch1 := make(chan int, 1) ch1 <- 1 //ch1 <- 2 // 通道已滿,所以這裏會形成阻塞。 // 示例2。 ch2 := make(chan int, 1) //elem, ok := <-ch2 // 通道已空,所以這裏會形成阻塞。 //_, _ = elem, ok ch2 <- 1
⽆論是發送操做仍是接收操做,⼀開始執⾏就會被阻塞,直到配對的操做也開始執⾏,才 會繼續傳遞。由此可⻅,⾮緩衝通道是在⽤同步的⽅式傳遞數據。也就是說,只有收發雙⽅對接上了,數據纔會被傳遞。
ch1 := make(chan int ) ch1 <- 10 fmt.Println("End." )//這裏會形成阻塞。
對於⼀個已初始化的通道來講,若是通道一旦關閉,再對它進⾏發送操做,就會 引起panic。
若是試圖關閉⼀個已經關閉了的通道,也會引起panic。
因此咱們在關閉通道的時候應當讓發送方作這件事,接收操做是能夠感知到通道的關閉的,並可以安全退出。
若是通道關閉時,⾥⾯還有元素值未被取出,那麼接收表達式的第⼀個結果,仍會是通道中的某⼀個元素值,⽽第⼆個 結果值⼀定會是true。
func main() { ch1 := make(chan int, 2) // 發送方。 go func() { for i := 0; i < 10; i++ { fmt.Printf("Sender: sending element %v...\n", i) ch1 <- i } fmt.Println("Sender: close the channel...") close(ch1) }() // 接收方。 for { elem, ok := <-ch1 if !ok { fmt.Println("Receiver: closed channel") break } fmt.Printf("Receiver: received an element: %v\n", elem) } fmt.Println("End.") }
以下,這表示了這個通道是單向的,而且只能發⽽不能收。
var uselessChan = make(chan<- int, 1)
單向通道最主要的⽤途就是約束其餘代碼的⾏爲。
例如:
func main() { // 初始化一個容量爲3的通道 intChan1 := make(chan int, 3) //將通道傳入到函數中 SendInt(intChan1) } //使用單向通道限制這個函數只能放入元素到通道中 func SendInt(ch chan<- int) { ch <- rand.Intn(1000) }
在SendInt函數中的代碼只能 向參數ch發送元素值,⽽不能從它那⾥接收元素值。這就起到了約束函數⾏爲的做⽤。
一樣單通道也能夠做爲函數的返回值:
func main() { intChan2 := getIntChan() for elem := range intChan2 { fmt.Printf("The element in intChan2: %v\n", elem) } } func getIntChan() <-chan int { num := 5 ch := make(chan int, num) for i := 0; i < num; i++ { ch <- i } close(ch) return ch }
函數getIntChan會返回⼀個<-chan int類型的通道,這就意味着獲得該通道的程序,只能從通道中接收元素值。
select語句只能與通道聯⽤,它⼀般由若⼲個分⽀組成。每次執⾏這種語句的時候,⼀般只有⼀個分⽀中的代碼會被運⾏。
咱們經過下面的例子來展現:
func example1() { // 準備好幾個通道。 intChannels := [3]chan int{ make(chan int, 1), make(chan int, 1), make(chan int, 1), } // 隨機選擇一個通道,並向它發送元素值。 index := rand.Intn(3) fmt.Printf("The index: %d\n", index) intChannels[index] <- index // 哪個通道中有可取的元素值,哪一個對應的分支就會被執行。 select { case <-intChannels[0]: fmt.Println("The first candidate case is selected.") case <-intChannels[1]: fmt.Println("The second candidate case is selected.") case elem := <-intChannels[2]: fmt.Printf("The third candidate case is selected, the element is %d.\n", elem) default: fmt.Println("No candidate case is selected!") } }
在使用select語句中,須要注意:
select 裏面會根據兩個case的返回時間來選擇運行,哪一個先返回哪一個就先執行,因此利用這個功能,能夠實現超時返回。
func TestSelect(t *testing.T) { //select 裏面會根據兩個case的返回時間來選擇運行 //哪一個先返回哪一個就先執行 //因此利用這個功能,能夠實現超時返回 select { case ret:=<-AsyncService(): t.Log(ret) case <-time.After(time.Microsecond*100): t.Error("time out") } } func AsyncService() chan string { retCh := make(chan string,1) go func() { ret := service() fmt.Println("return result.") retCh <- ret fmt.Println("service exited.") }() return retCh }
咱們能夠先申明一個函數類型:
type operate func(x, y int) int
而後將這個函數當作參數傳入到函數內
func calculate(x int, y int, op operate) (int, error) { if op == nil { return 0, errors.New("invalid operation") } return op(x, y), nil }
能夠借閉包在程序運⾏的過程當中,根據須要⽣成功能不一樣的函數,繼⽽影響後續的程序⾏爲。
例如:
type calculateFunc func(x int, y int) (int, error) func genCalculator(op operate) calculateFunc { return func(x int, y int) (int, error) { if op == nil { return 0, errors.New("invalid operation") } return op(x, y), nil } } func main() { x, y = 56, 78 add := genCalculator(op) result, err = add(x, y) fmt.Printf("The result: %d (error: %v)\n", result, err) }
分爲兩種類型來處理,值類型和引用類型
以下:
func main() { // 示例1。 array1 := [3]string{"a", "b", "c"} fmt.Printf("The array: %v\n", array1) array2 := modifyArray(array1) fmt.Printf("The modified array: %v\n", array2) fmt.Printf("The original array: %v\n", array1) fmt.Println() } // 示例1。 func modifyArray(a [3]string) [3]string { a[1] = "x" return a }
返回的是:
The array: [a b c] The modified array: [a x c] The original array: [a b c]
因爲數組是值類型,因此每⼀次複製都會拷⻉它,以及它的全部元素值。我在modify函數中修改的只是原數組的副本⽽已, 並不會對原數組形成任何影響。
以切⽚值爲例,如此複製的時候,只是拷⻉了它指向底層數組中某⼀個元素的指針,以及它的⻓度值和容量值,⽽它的底層數 組並不會被拷⻉。
以下:
func main() { slice1 := []string{"x", "y", "z"} fmt.Printf("The slice: %v\n", slice1) slice2 := modifySlice(slice1) fmt.Printf("The modified slice: %v\n", slice2) fmt.Printf("The original slice: %v\n", slice1) fmt.Println() } func modifySlice(a []string) []string { a[1] = "i" return a }
返回:
The slice: [x y z] The modified slice: [x i z] The original slice: [x i z]
因爲類modifySlice傳入的是一個指針的引用,因此當指針所指向的底層數組發生變化,那麼原值就會發生變化。
以下:
func main() { complexArray1 := [3][]string{ []string{"d", "e", "f"}, []string{"g", "h", "i"}, []string{"j", "k", "l"}, } fmt.Printf("The complex array: %v\n", complexArray1) complexArray2 := modifyComplexArray(complexArray1) fmt.Printf("The modified complex array: %v\n", complexArray2) fmt.Printf("The original complex array: %v\n", complexArray1) } func modifyComplexArray(a [3][]string) [3][]string { a[1][1] = "s" a[2] = []string{"o", "p", "q"} return a }
返回:
The complex array: [[d e f] [g h i] [j k l]] The modified complex array: [[d e f] [g s i] [o p q]] The original complex array: [[d e f] [g s i] [j k l]]
實際上仍是和上面的同樣的理論,傳入modifyComplexArray方法的數組是複製的,可是數組裏面的元素傳的是引用,因此直接修改引用的切片值會影響到原來的值,可是直接以這樣的方式a[2] = []string{"o", "p", "q"}
新建了一個數組則不會改變。