Go語言1
隨便學學,平時用的都是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的擴展:
- 點開,菜單-查看>>擴展
- 搜索“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分別打印字符串、二進制、十進制、十六進制、浮點數。
Go語言1