Go語言1
開發環境搭建
隨便學學,平時用的都是WIndows10的作業系統,就在這下面搞了。
下載安裝
下載go安裝程式,下載地址: ofollow,noindex" target="_blank">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的擴充套件:
- 點開,選單-檢視>>擴充套件
- 搜尋“go”,找到“Rich Go language support for Visual Studio Code”,作者“lukehoban”,安裝
- 裝好之後用的時候
除錯工具
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語言特性
- 垃圾回收
- 記憶體自動回收,不需要開發人員管理記憶體
- 開發人員專注業務實現,減輕負擔
- 只需要new分配記憶體,不需要釋放
- 天然併發
- 從語言層面支援併發,非常簡單
- goroute,輕量級執行緒,建立成千上萬個goroute成為可能
- 基於CSP(Communicating Sequential Process)模型實現
- channel
- 管道,類似Linux中的pipe
- 多個goroute之間通過channel進行通訊
- 支援任何型別
- 多返回值
- 一個函式返回多個值
併發程式碼演示
先寫一個程式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。
- 把相同功能的程式碼放到一個目錄,稱之為包。
- 包可以被其他包呼叫。
- main包是用來生成可執行檔案的,沒個程式只有一個main包
- 包的主要用途是提高程式碼的可複用性。
使用多個包的程式碼示例
新建個資料夾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分別列印字串、二進位制、十進位制、十六進位制、浮點數。