1. 程式人生 > >Go語言1

Go語言1

入門 網頁 efault print hub channel 自然 邏輯 主程

開發環境搭建

隨便學學,平時用的都是WIndows10的操作系統,就在這下面搞了。

下載安裝

下載go安裝程序,下載地址:https://golang.org/dl/ (被墻了,打不開)
墻內下載地址http://www.golangtc.com/download
我下的是這個:go1.9.2.windows-amd64.msi
安裝就下一步就好了,裝完之後驗證一下。默認已經把Go的路徑加到環境變量中去了,所以可以直接在cmd中輸入go命令執行:
技術分享圖片

環境變量

裝完之後,我裝到了D:\Go\,已經幫我們添加好了2個環境變量:

  • GOROOT: D:\Go\
  • Path: D:\Go\bin
  • GOPATH: 這個環境變量也要,但是需要手動設置

GOPATH也是要的,設置工作區路徑,不過如果沒有設置的話,有一個默認位置(差不多就是系統的家目錄)。執行幫助命令 go help gopath ,我這截了一段說明:

If the environment variable is unset, GOPATH defaults
to a subdirectory named "go" in the user‘s home directory
($HOME/go on Unix, %USERPROFILE%\go on Windows),
unless that directory holds a Go distribution.

Run "go env GOPATH" to see the current GOPATH.

暫時不設置也沒影響,反正也有默認的,後面寫 Hellow World 的時候再看設置在哪個目錄

開發工具

開發工具先沒有裝,不過這裏先記一下工具的名稱:

Visual Studio Code (簡稱 VS Code / VSC) 是一款免費開源的現代化輕量級代碼編輯器,支持幾乎所有主流的開發語言的語法高亮、智能代碼補全、自定義熱鍵、括號匹配、代碼片段、代碼對比 Diff、GIT 等特性,支持插件擴展,並針對網頁開發和雲端應用開發做了優化。軟件跨平臺支持 Win、Mac 以及 Linux。

這是一個通用的代碼編輯器,裝完之後要再裝一下go的擴展:

  1. 點開,菜單-查看>>擴展
  2. 搜索“go”,找到“Rich Go language support for Visual Studio Code”,作者“lukehoban”,安裝
  3. 裝好之後用的時候

調試工具

Delve是一個非常棒的golang調試工具,支持多種調試方式,直接運行調試,或者attach到一個正在運行中的golang程序,進行調試。
安裝Delve非常簡單,直接運行 go get 即可:

go get -u github.com/derekparker/delve/cmd/dlv

先記一下,我並沒有裝

寫代碼

在第一次寫代碼前,還要先把go的工作區設置好。

設置工作區

工作區是存放go源碼文件的目錄。一般情況,Go源碼文件都需要存放到工作區中,不過命令源碼文檔不是必須的。
每個工作區的目錄結構都類似,有3個文件夾:

  • src/: 用於存放源碼以代碼包為組織形式
  • pkg/: 用於存放歸檔文件(名稱以.a為後綴的文件)
  • bin/: 用於存放當前工作區中的GO程序的可執行文件

找一個合適的位置,作為工作區。比如:E:\Go。這時就可以去把GOPATH環境變量設置為 “E:\Go” 。
按上面說的,還要工作區中還要創建3個文件夾,暫時只需要src,另外兩個暫時不用。

Hello World

到這裏需要一些儀式感,就是寫一個 Hellow World 。在src目錄下創建文件 “hellow.go” 。如果用了VS Code,會提示你裝一些擴展,點下Install All,這樣會自動把一些go的插件給裝好。
下面是程序的源碼:

package main

import(
    "fmt"
)

func main() {
    fmt.Println("Hello World")
}

第一部分,聲明這是一個包。可執行文件的包名必須是“main”。
第二部分,導入包。所有的庫,包括自己的庫,用之前都需要導入。fmt是包名,裏面有一個方法可以向終端打印信息。
第三部分,定義了一個函數。必須要定義一個main函數,這是個入口函數。
最後在cmd命令行裏運行一下:

go run hello.go

做加法

這次寫一個有參數有返回值的函數:

package main

import(
    "fmt"
)

func add(a int, b int) int {
    var sum int
    sum = a + b
    return sum
}

func main(){
    var c int
    c = add(11, 22)
    fmt.Println("11 + 22 =", c)
}

go是一個強類型語言,所有變量都要設置類型。
add函數,寫參數的時候,參數後面要用類型。並且由於add函數有返回值,返回值的類型也要在函數名後面定義好。
聲明變量用var,後面是變量名,在後面是變量的類型。
最後如果你的變量定義之後,沒有被應用,也是會報錯的。這樣強制你去掉冗余的代碼,這個變量既然沒有用到,自然就不需要出現,比如把最後一句fmt.Println(c)去掉或者換成打印一個固定的字符串就會報錯。

golang語言特性

  1. 垃圾回收
    • 內存自動回收,不需要開發人員管理內存
    • 開發人員專註業務實現,減輕負擔
    • 只需要new分配內存,不需要釋放
  2. 天然並發
    • 從語言層面支持並發,非常簡單
    • goroute,輕量級線程,創建成千上萬個goroute成為可能
    • 基於CSP(Communicating Sequential Process)模型實現
  3. channel
    • 管道,類似Linux中的pipe
    • 多個goroute之間通過channel進行通信
    • 支持任何類型
  4. 多返回值
    • 一個函數返回多個值

並發代碼演示

先寫一個程序goroute_print.go,很簡單,就是傳入參數稍微計算一下,然後打印結果:

package main

import (
    "fmt"
)

func goroute_print(a int) {
    b := a + 10000
    fmt.Println(b)
}

這裏用了 “:=” ,是聲明並賦值,並且系統自動推斷類型,不需要var關鍵字。
然後再寫一個程序goroute_for.go,裏面是一個for循環,起若幹個線程執行:

package main

import(
    "time"
)

func main(){
    for i := 0; i < 1000; i++ {
        go goroute_print(i)
    }

    // sleep1秒,保證上面的子線程運行結束
    time.Sleep(time.Second * 5)
}

在這個主程序要裏調用另外一個文件裏的程序。不過這2個文件都是同一個包,main包,開頭package聲明的。這裏在前面加上了go調用,就是起了一個goroute。(不加go調用,就是在主線程裏調用函數,如果調用前加上go,就是起了一個子線程調用這個函數)
主線程必須要等待子線程運行結束才能退出,否則主線程一旦結束,其他子線程也就沒有了。這裏簡單粗暴的先用sleep來進行等待。導入了一個time包。參數 time.Second 就是1秒,如果不是等1秒而是幾秒的話,就乘以幾。
這裏還有註釋,註釋的用法js一樣。單行註釋以 // 開頭,多行註釋以 /* 開始,以 */ 結尾。

執行的話在cmd命令行執行下面的命令:

go run goroute_for.go goroute_print.go

這個例子分在兩個文件裏了,所以run後面把兩個文件都要寫上。順序是無所謂的。

管道代碼示例

這裏用了make()函數,是用來分配內存的。第一個參數 chan int ,表示要創建的是chan(管道),類型是int:

package main

import(
    "fmt"
)

func main(){
    pipe := make(chan int, 3)  // 創建一個管道,存的數據類型是int,容量是3
    pipe <- 11  // 往管道裏存一個數
    fmt.Println(11, len(pipe))  // 打印長度
    pipe <- 22
    fmt.Println(22, len(pipe))

    var t1 int
    t1 =<- pipe  // 從管道裏取出一個數,賦值給t1
    fmt.Println(t1, len(pipe))
    t1 =<- pipe
    fmt.Println(t1, len(pipe))
}

似乎就是個隊列,先進先出,默認滿了也會阻塞。之所以叫管道不叫隊列,可能是它能支持多個goroute之間通過channel(管道)進行通信。暫時不需要這麽深入。

通過管道在goroute之間傳遞參數

有兩種方式可以實現。把管道定義為全局變量,或者把管道作為函數的參數傳遞。代碼中使用全局變量不是好的做法,所以推薦用傳參的方法。不過兩種實現方法都看一下。
全局變量的代碼示例:

package main

import(
    "fmt"
)

var pipe chan int  // 在全局聲明pipe變量,下面兩個方法裏都會用到

func add(a int, b int){
    sum := a + b
    pipe <- sum
}

func main(){
    pipe = make(chan int, 1)
    go add(1, 2)
    sum :=<- pipe
    fmt.Println("sum =", sum)
}

傳遞參數的代碼示例:

package main

import(
    "fmt"
)

func add(a int, b int, c chan int){
    sum := a + b
    c <- sum
}

func main(){
    var pipe chan int  // 在main函數裏聲明pipe
    pipe = make(chan int, 1)
    go add(3, 4, pipe)
    sum :=<- pipe
    fmt.Println("sum =", sum)
}

多返回值的代碼示例

package main

import(
    "fmt"
)

func calc(a int, b int)(int, int){
    c1 := a + b
    c2 := (a + b)/2
    return c1, c2
}

func main(){
    sum , avg := calc(100, 200)
    fmt.Println("sum =", sum, "avg =", avg)
}

如果多返回值裏只想取其中部分的值,可以把剩下的值傳給占位符(“_”):

package main

import(
    "fmt"
)

func calc(a int, b int)(int, int){
    c1 := a + b
    c2 := (a + b)/2
    return c1, c2
}

func main(){
    sum , _ := calc(100, 200)
    fmt.Println("sum =", sum)
    fmt.Println(_)  // 打印下劃線會報錯
}

嘗試打印下劃線的報錯信息如下:

>go run calc.go
# command-line-arguments
.\calc.go:17:16: cannot use _ as value

代碼格式化(gofmt)

go默認提供一個代碼格式化命令 gofmt ,會按照Go語言代碼規範格式化文件中的代碼。把之前的hello.go的代碼搞亂一點,簡單一點就改下縮進好了,然後運行下面的命令:

gofmt hello.go

最終還是能再命令行裏看到整潔的代碼
技術分享圖片
還截圖的效果,縮進是8個字符的位置。
不過原文件裏還是亂糟糟的,可以加上-w參數,改寫原文件:

gofmt -w hello.go

實際寫到文件裏,縮進用的不是空格是Tab,具體效果是幾個字符就看文件的設置了。
另外換行是 \n ,這個windows的記事本下會用問題,記事本的換行是 \r\n

編譯Go源文件

創建Go工作目錄:H:\Go\src\go_dev\day1\hello\。把之前寫的示例hello.go放到hello目錄下。再建個文件夾,其他示例的源文件放到examples目錄下。這裏src文件夾的名字是不能變的,其他文件夾的名字包括上級的都隨意。
上面的演示都沒有對go的原文件進行編譯,編譯要用 go build 命令:

H:\Go>go build go_dev\day1\hello
can‘t load package: package go_dev/day1/hello: cannot find package "go_dev/day1/hello" in any of:
        D:\Go\src\go_dev\day1\hello (from $GOROOT)
        C:\Users\Steed\go\src\go_dev\day1\hello (from $GOPATH)

H:\Go>

這裏並沒有編譯成功,原因是找不到我們的源文件。之前因為沒設置環境變量GOPATH,所以默認沒有設置GOPATH就是把用戶家目錄作為GOPATH。這個GOPATH就是工作目錄。
先去把GOPATH的環境變量設置好,我這裏 “H:\Go\“ 就是我的工作目錄,設置成:“H:\Go\“ 。建議設置用戶變量,當然系統變量也沒問題。
完成之後要新開一個cmd,也有可能有要註銷當前用戶,下次登錄才能完全生效。然後可以驗證一下:

C:\Users\Steed>go env GOPATH
H:\Go

然後再編譯一下,這次隨便找個目錄執行命令:

J:\tmp>go build go_dev\day1\hello

J:\tmp>hello.exe
Hello World

命令執行後會在當前目錄下生成hello.exe的可執行文件,上面也直接執行了一下。文件名以及生成文件的路徑都是可以用參數指定的。
前面只所以不把所有的示例都放一起,是因為其他示例也有main入口函數,存在多個入口函數編譯會報錯。
其實可以編譯單個文件,但是路徑要寫全(絕對路徑或相對路徑),單文件編譯似乎不受環境變量影響:

J:\tmp>go build H:\Go\src\go_dev\day1\hello\hello.go

復雜一點的程序一般都不是單個文件的,所以一個文件夾裏就是一個小項目,只有一個入口函數。這樣編譯不會有問題。另外如果使用開發工具的話,這種情況開發工具直接也會提示錯誤的。
最後,下面這麽搞比較方便:

H:\Go\src>go build go_dev\day1\hello

H:\Go\src>go build go_dev\day1\hello\hello.go

選好cmd的啟動路徑,之後源文件的路徑用Tab就能自動補全出來了。

包的概念

代碼不能單獨存在,任何代碼都屬於一個包。所以第一行代碼,一定寫的是package。

  1. 把相同功能的代碼放到一個目錄,稱之為包。
  2. 包可以被其他包調用。
  3. main包是用來生成可執行文件的,沒個程序只有一個main包
  4. 包的主要用途是提高代碼的可復用性。

使用多個包的代碼示例

新建個文件夾package_exampl,寫一個由多個包多個文件構成的代碼,具體如下:

// go_dev/day1/package_example/calc/add.go
package calc

func Add(x int, y int) int {
    return x + y
}

// go_dev/day1/package_example/calc/sub.go
package calc

func Sub(x int, y int) int {
    return x - y
}

// go_dev/day1/package_example/main/main.go
package main

import(
    // "calc"
    "go_dev/day1/package_example/calc"
    "fmt"
)

func main(){
    sum := calc.Add(100, 200)
    sub := calc.Sub(10, 5)
    fmt.Println(sum, sub)
}

calc包裏的Add和Sub函數都是首字母大寫的。在Go語言裏首字母大寫有特殊的意義,就是全局變量。如果都小寫的話,在main函數裏這兩個函數,由於作用域的問題,都是不可見的。
結論:就是說一個變量(包括函數),如果要被外部的包調用,必須要首字母大寫。不過變量不推薦這麽做,因為這樣這個變量就是全局變量,而全局變量是不推薦使用的方法(別的語言裏也同樣是不推薦使用全局變量的)。
main函數裏導入包的時候,calc包的路徑要寫全,不能只寫 “calc” ,下面會具體分析。
編譯過程
先把import改成這樣,一步一步排錯:

import(
    "calc"
    // "go_dev/day1/package_example/calc"
    "fmt"
)

多個包的編譯

H:\Go\src>go build go_dev\day1\package_example
can‘t load package: package go_dev/day1/package_example: no Go files in H:\Go\src\go_dev\day1\package_example

這裏報錯,因為文件夾下確實沒有包,包的源文件在下一級的目錄裏:

H:\Go\src>go build go_dev\day1\package_example\main
go_dev\day1\package_example\main\main.go:4:5: cannot find package "calc" in any of:
        D:\Go\src\calc (from $GOROOT)
        H:\Go\src\calc (from $GOPATH)

繼續報錯,這是找不到calc包,並且這裏也看到了默認查找包的路徑。現在包的路徑應該怎麽寫全就清除了,把import的內容改回來,之後就能順利的編譯執行了:

H:\Go\src>go build go_dev\day1\package_example\main

H:\Go\src>main.exe
300 5

指定路徑和文件名
一般編譯後生成的可執行文件是放在工作目錄下的bin目錄下的。還是用go build命令,加上-o參數,windows系統下得加上擴展名exe之後才能執行:

H:\Go\src>go build -o ../bin/test.exe go_dev\day1\package_example\main

H:\Go\src>cd ../bin

H:\Go\bin>test
300 5

H:\Go\bin>

線程和管道的代碼示例

程序分成2部分。一部分是業務邏輯,放在goroute目錄下。還有一部分是函數的入口,放在main目錄下。文件結構:

H:\GO\SRC\GO_DEV\DAY1\GOROUTE_EXAMPLE
├─goroute
└─main

業務邏輯就是,獲取3個變量,2個數和1個管道,把2個數的和計算後,存到管道裏。
主函數裏則是,單獨起一個線程goroute進行計算,之後從管道裏獲取到計算結果,打印出來。
這個例子裏用到了線程goroute,以及管道用於goroute之間的通信。之前已經演示過了,這裏就是把代碼分成2個包再寫一遍:

// goroute/add.go
package goroute

func Add(a int, b int, c chan int){
    sum := a + b
    c <- sum
}

// goroute/main.go
package main

import(
    "go_dev/day1/goroute_example/goroute"
    "fmt"
)

func main(){
    var pipe chan int
    pipe = make(chan int, 1)

    go goroute.Add(100, 200, pipe)
    res := <- pipe
    fmt.Println(res)    
}

官網 Go 指南

都是中文版的。
Go 語言之旅:https://tour.go-zh.org/welcome/1
Go 編程語言:https://go-zh.org/
Go 語言之旅可以作為入門教程,並且可以Web在線寫代碼,提交執行在Web上顯示結果。

課後作業

使用fmt分別打印字符串、二進制、十進制、十六進制、浮點數。

Go語言1