6. Golang 入門初體驗
當前有兩個Go語言的編譯器的分支。官方編譯器gc和gccgo。官方編譯器在初期使用C寫成,後用go
重寫從而實作自舉。Gccgo是一個使用標準GCC作為後端的Go編譯器。
官方編譯器支援跨平台編譯(但不支援CGO),允許將源碼編譯為可在目標系統、架構上執行的二進
位檔案。
Go 在2007年9月開始為 google 內部員工的 20% 自由時間的實驗項目, 目的為改善公司內部的開發速
度. 隔了一年受到 google 公司的重視和支持, 2009年11 月對外發佈第一版本. 過去 google 常用的語
言有 c , java , python來開發他們的服務. 但是這些語言是在創造的時候的硬體環境, 和現在有很大的
不同. 而 Golang 在設計的時候就考慮了這些架構, 所以在開發上和過去的語言比較起來有很大的優勢.
Go的關鍵字不多(目前約25個 c 37個,c++ 84個而且還在增加中), 學習上很容易上手, 再加上有不少和
c 的關鍵字重複, 所以如果你原本就是寫c/c++的人, 在轉換上會相對其他語言容易.
平均下來效能是輸c, 可是贏其他語言如 ruby(ror), php, java, python等語言. 主要原因的先天優勢是,
他語言架構設計非常單純, 並不像是如物件導向語言這麼的大. 再來是他不是直譯語言. 光是這兩點優
勢, 讓他有更好的體質去有較好的執行效率. 已經有很多案例, 在開發伺服器的軟體時, 因為效能問題而
改換 go 去寫, 結果讓他們減少了伺服器的數量就可以達到原本的效能, 增加了服務的效率也省下了硬
體成本.
7. Golang 入門初體驗
goroutine
這是Go的最大特色之一, thread 的使用是非常消耗系統資源的, 而且當你使用越來越多時管理上也越
困難. 而 goroutine 有輕巧低消耗資源的特性, 而這優點在於系統資源的消耗也會比較少.
可以使用 c library
他可以使用 c library, 解決新語言在 library 上的不足. 不過基本上 go 本身的 lib 已經很豐富了.
Unicode slice map 都有支援.
目前越來越多公司用 go 來寫他們網站後端的系統, 過去 google 在寫他的服務大多使用 c + python,
python 是個很簡潔的語言在建構服務是非常快的, c 是用來解決效能上的問題, 現在內部也都慢慢轉成
go 來寫. 在2014年TIOBE四月程式語言排名已經到第30名, 2013基本上都是在50名以外, 而且我相信
這名次會持續在進步.
第一個 go 程式
package main
import "fmt“
func main () {
fmt.Println("Hello , World!")
}
8. Golang 入門初體驗
Go 安裝
http://golang.org/dl/
Windows:
安裝好 GO 後, 還需要 GCC 編譯環境, 因為 Mac 的 Xcode 本身就有 GCC, windows 沒有, 所以
選擇了 mingw 這個輕量型的 GCC
http://blog.jex.tw/blog/2013/12/17/windows-install-gcc-compiler-mingw/
設定 GOPATH, 這是放一些你自己或 go get from github 存放的地方
1. 在 c:Go 下建立 mygo 資料夾, 並且在 mygo 下建立三個資料夾 src, pkg, bin
2. 設定環境變數
GOROOT : C:Go GOPATH : C:Gomygo PATH :
在最後面加上 ;%GOROOT%bin;%GOPATH%bin;C:Program Files (x86)Gitbin
除了加上 GOROOTbin、GOPATHbin, 也可視需求加上 git bin 的路徑,重新啟動 cmd 使變數
生效
9. Golang 入門初體驗
Go 執行
https://golang.org/cmd/go/
go run
要撰寫第一個 Hello, World 程式,你可以建立一個 main.go,在當中撰寫以下的內容:
package main
import "fmt"
func main() {
fmt.Println("Hello, World")
fmt.Println("哈囉!世界!")
}
10. Golang 入門初體驗
Go 執行
每個 .go 原始碼,都必須從 package 定義開始,而對於包括程式進入點 main 函式的 .go 原始碼,
必須是在 package main 之中,為了要能輸出訊息,這邊使用了 fmt 套件(package)之中的
Println 函式,開頭的大寫 P 表示這是個公開的函式,可以在套件之外進行呼叫。
Go 的創建者之一也是 UTF-8 的創建者,因此,Go 可以直接處理多國語言,只要你確定編輯器
編碼為 UTF-8 就可以了,如果你使用 vim,可以在 vim 的命令模式下輸入 :set encoding=utf-8,
或者是在 .vimrc 之中增加一行 set encoding=utf-8。
Go 可以用直譯的方式來執行程式,第一個 Hello, World 程式就是這麼做的,執行 go run 指定
你的原始碼檔名就可以了:
$ go run main.go
Hello, World
哈囉!世界!
package 與 GOPATH
15. Golang 入門初體驗
Go 執行
go build
go build 會忽略目錄下以"_"或"."開頭的go文件
如果原始程式碼針對不同的作業系統需要不同的處理,那麼可以根據不同的作業系統尾碼來命名檔。
例如,readfile_linux.go,readfile_drawin.go,readfile_windows.go.go build 會選擇性編譯
檔,Linux系統只編譯readfile_linux.go,其他文件則被忽略。
16. Golang 入門初體驗
Go 執行
go build
如果想編譯原始碼為可執行檔,那麼可以使用 go build,例如,直接 go build main.go,就會在
執行指令的目錄下,產生一個名稱為 main 的可執行檔,可執行檔的名稱是來自己指定的原始碼
檔案主檔名,執行產生出來的可執行檔就會顯示 Hello, World。
你也可以建立一個 bin 目錄,然後執行 go build -o bin/main main.go,這樣產生出來的可執行
檔,就會被放在 bin 底下,如果想將原始碼全部放在 src 底下管理,那麼就將 main.go 放到
src/main 底下,然後執行 go build -o bin/main src/main/main.go。
go install
每次使用 go build,都是從原始碼編譯為可執行檔,這比較沒有效率,如果想要編譯時更有效率
一些,可以使用 go install,例如,在目前既有的目錄與原始碼架構之下,執行 go install main
17. Golang 入門初體驗
Go 執行
go build
~go
|~bin/
| |-main
|~pkg/
| `~linux_arm/
| `-hello.a
|~src/
| |~hello/
| | `-hello.go
| |~main/
| | `-main.go
go install packageName 表示要安裝指定名稱的套件,如果是 main 套件,那麼會在 bin 中產生
可執行檔,如果是公用套件,那麼會在 pkg 目錄的 $GOOS_$GOARCH 目錄中產生 .a 檔案
18. Golang 入門初體驗
Go 執行
go build
可以使用 go env 來查看 Go 使用到的環境變數,例如:
$ go env
GOARCH="arm"
GOBIN=""
GOEXE=""
GOHOSTARCH="arm"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/root/workspace/go"
GORACE=""
GOROOT="/opt/go"
GOTOOLDIR="/opt/go/pkg/tool/linux_arm"
GO15VENDOREXPERIMENT=""
CC="gcc"
GOGCCFLAGS="-fPIC -marm -pthread -fmessage-length=0"
CXX="g++"
CGO_ENABLED="1"
19. Golang 入門初體驗
Go 執行
go build
.a 檔案是編譯過後的套件,因此,你看到的 hello.a,就是 hello.go 編譯之後的結果,如果編譯
時需要某個套件,而對應的 .a 檔案存在,且原始碼自上次編譯後未曾經過修改,那麼就會直接使
用 .a 檔案,而不是從原始碼開始編譯起。
go clean
用來移除當前源碼包裡編譯生成的檔,這些檔包括,_obj(舊的objects目錄,Makefiles遺留),
_test(舊的test目錄),_testmain.go(舊的gotest文件),test.out、build.out(舊的test記錄)
*.[568ao] object文件,DIR(.exe)(由go build產生),DIR.test(.ext)(由go test -c產生),
MAINFILE(.exe) (由go build MAINFILE.go產生)
該命令最大的作用,清除編譯文件後,上傳git,保持源碼清潔。
20. Golang 入門初體驗
Go 執行
os.Args
那麼,如果想在執行 Go 程式時使用命令列引數呢?可以使用 os 套件的 Args,例如,寫一個
main.go:
package main
import "os"
import "fmt"
func main() {
fmt.Printf("Command: %sn", os.Args[0])
fmt.Printf("Hello, %sn", os.Args[1])
}
21. Golang 入門初體驗
Go 執行
os.Args 是個陣列,索引從 0 開始,索引 0 會是編譯後的可執行檔名稱,索引 1 開始會是你提供
的引數,例如,在執行過 go build 或 go install 之後,如下直接執行編譯出來的執行檔,會產生
的訊息是…
$ ./bin/main Justin
Command: ./bin/main
Hello, Justin
go doc
22. Golang 入門初體驗
Go 執行
fmt 的 Printf,就像是 C 的 printf,可用的格式控制字元可參考 Package fmt 的說明。實際上,
Go 本身附帶了說明文件,可以執行 go doc <pkg> <sym>[.<method>] 來查詢說明。例如:
$ go doc fmt.Printf
func Printf(format string, a ...interface{}) (n int, err error)
Printf formats according to a format specifier and writes to standard
output. It returns the number of bytes written and any write error
encountered.
23. Golang 入門初體驗
Go 執行
go fmt
幫助格式化寫好的代碼檔,讓寫代碼時不關心格式,寫完後,輕鬆執行go fmt 檔案名.go就好
提高效率。更多的時候可以採用gofmt,同時增加-w的參數,否則格式化結果不會寫入檔,例如:
gofmt -w src 來格式整個項目。
go get
動態獲取遠端代碼。這個代碼內部分為兩部分,一是下載源碼包,另一步是執行go install
為了讓go get正常使用,需保證安裝了合適的源碼管理工具,並將這些命令加入到PATH中。
可通過go help remote 瞭解更多。
go test
執行這個命令會自動讀取源碼目錄下名為*_test.go檔,生成並運行測試用的可執行檔。
預設情況下不需要任何參數,也可帶上參數,具體可參見go help testflag
24. Golang 入門初體驗
Go 執行
go doc
如何查看相應的package文檔呢?
如果是builtin包,可以執行go doc builtin;如果是http包,執行go doc net/http;
查看某個包裡面的函數,類似執行 godoc fmt Println,還可以查看相應代碼
godoc -src fmt Println
很棒的一點是,可以在終端執行godoc -http=:埠號,例如godoc -http=:8080 ,就可以在
流覽器中敲入127.0.0.1:8080進行文檔內容的查看。
其他命令
go fix 用來修復以前老版本的代碼到新版本
go version 查看go當前的版本
go env 查看當前go的環境變數
go list 列出當前全部安裝的package
go run 編譯並運行go語言程式
25. Golang 入門初體驗
Go IDEs
IDEs 有以下幾種,使用文字編輯器亦可完成程式編輯,再至Command Line執行編譯
Lite IDE
GoSublime
Visual Studio Code
Goclipse
VIM / VIM-go
26. Golang 入門初體驗
Go 語法 (參考 https://polor10101.gitbooks.io/golang_note/content/about_golang.html)
variable 變數
雖然號稱和 c 類似, 不過 go 在變數的命名上有很大的不同, 他是以 type 放後面為原則
以下表示宣告一個變數 x 且他是整數型態.
var x int
如果要給他初始值, 有以下幾種方式
給予初始值
var x int = 10
var y = 20
var z int
z = 30
k := 40
常數
const PI = 3.1415
const NAME = "Nelson"
常數不需要給定 type, 只要直接給予值即可.
而且不能用 ':=' 的方式給予初始值.
你可以發現每行的結尾少了分號 ';', 是的 go 省略分號, 只要斷行來分就好.
27. Golang 入門初體驗
Go 語法
函式
函式的宣告方式也和 c 不同
func add( a int , b int){
c := a + b
}
如果要回傳東西
func add( a int , b int) int{
c := a + b
return c
}
但是還是維持 type 在後面的原則
在此在大括號的位置也有規定, 如例子所示
28. Golang 入門初體驗
Go 語法
函式
你也可以
func add( a int , b int) int{ }
但是不可以採用對齊的方式
func add( a int , b int) int
{
}
從以上例子來看, 會發現 go 在程式的風格上有強制規定, 不符合規定會有 error
30. Golang 入門初體驗
Go 語法
關於語法
go 在語法中做了改變, 官方的說法是, 改這樣的原因是因為這樣比較好閱讀. 接著來比較 c 和 go 的語法
以宣告 variable 來看
int add; //c
var add int //go
go 還多一個 var, 似乎也沒有比較方便
以 function 來看
int add(int a, intb); //c
func add(a int, b int)int //go
也還好, 不過比較起來 go 比較容易分辨出是 variable 還是 function, 但是差距也沒有很大, 而且 go 還需要多打
字.
31. Golang 入門初體驗
Go 語法
關於語法
如果宣告一個 function pointer 然後輸入一個 function pointer 變數
int (*fp)(int (*ff)(int x, int y), int b) //c
f func(func(int,int) int, int) int //go
c 已經有點讓人混亂了, 且不好閱讀.
go 這樣是不是有比較清楚?
個人是覺得有, 還很明顯.
再加個回傳 function pointer 的宣告比較.
int (*(*fp)(int (*)(int, int), int))(int, int) //c
f func(func(int,int) int, int) func(int, int) int //go
結論 : 真的有差.
f := add
f(10,20)
32. Golang 入門初體驗
Go 語法
loop 迴圈
以 c 來說迴圈有 for , while , do while 三個關鍵字. 在 go 裡面只有一個 for 來達到全部
以 for 迴圈來說 c 為
int i = 0;
int sum = 0;
for( i = 0 ; i < 10 ; ++i )
{
sum += i;
}
以下為 go 的 for 迴圈範例
sum := 0
for i := 0 ; i < 10 ; ++i {
sum += i
}
從範例中可以看到 "( )" 省略了, 再來兩個中夸號並不是對齊的
33. Golang 入門初體驗
Go 語法
loop 迴圈
c 的 do while 迴圈
int i = 0;
do{
i += 1
}while( i < 10 );
go 中為
i := 0
for i < 100 {
i += 1
}
c 的無限 while 迴圈
while(1)
{
}
go 的
for {
}
以上為 go 和 c 的迴圈使用比較. 從中可以了解一些語法上
的差別
34. Golang 入門初體驗
Go 語法
if switch
和 c 不同的是, if 不需要小括號, 另外大括號必須要有, 並且強制是範例的排版方式, 否則會有 error
if a > 0 {
fmt.Println(a)
}
個人是習慣對齊的方法, 不過這樣的強制規定可以讓多人開發專案的程式碼有一致性, 這也是個好處.
switch 也是同樣的規定, 並且不需要加上 'break'
如以下所示
switch i {
case 1 : fmt.Println(1)
case 2 : fmt.Println(2)
default : fmt.Println("default")
}
35. Golang 入門初體驗
Go 語法
Array
array 的宣告
var a [5]int //宣告一個大小5的 int array
var b []int //宣告空的 int array
var c = []int{1,2,3,4,5}//宣告一個大小5的 int array, 並給初始值
如果要列印 array 裡面的值, 你可以使用 range 知道 array 目前的 index
import "fmt"
func main() {
var a = [4]int {1,2,3,4}
for i := range a {
fmt.Println(a[i])
}
}
38. Golang 入門初體驗
Go 語法
Slice
len 為實際資料長度, cap 表示容量
宣告的方式為
a := []int
此時的 len , cap 都為 0
你可以用 make 來宣告變數並指定 len 和 cap 的值
a := make([]int,5,10)
其中 5 為len, 10 為 cap
39. Golang 入門初體驗
Go 語法
Slice
也可以不指定 cap 寫成
a := make([]int,5)
這時 len 為 5 , cap 為 5
另外給定初始值的宣告
var a = []int {1,2,3,4,5}
如果要一個變數指向 a
b := a[2:4]
c := a[:]
40. Golang 入門初體驗
Go 語法
Slice
其中
b 為 3 , 4.
a[2:4] 的 2 表示指向 a index = 2 的位置, 4 表示為結尾在 index = 4(不包含), 所以則 length 為 2, 另外由於
array 是連續的, 所以 cap = 3
c 為 1,2,3,4,5
[:] 表示 [0:len(a)], 第一個值如果沒寫則為 0, 第二個沒寫的為 len(a) = 5
41. Golang 入門初體驗
Go 語法
Slice
所以可以得知
a[:3] 和 a[1:] 個別為
1,2,3 //a[:3]
2,3,4,5 //a[1:]
另外要特別注意的是,
b := a[1:3]
並不是 a copy 值給 b, 而是將 b 的指標指向 a ( index 2 ) 的位置
所以這表示如果你改了 b 的值, a 的也會一起改變, 因為他們共用同一個 array.
如果你要兩者是獨立的, 可以使用 copy 的方法
copy(b,c)
42. Golang 入門初體驗
Go 語法
Slice
這是把 c 的前兩個的值, copy 到 b 中. 為什麼只 copy c 的前兩個?因為 b 的大小只有二, 以小的為主
copy(c,b)
則表示將 b 的值, copy 覆蓋 c 的前兩個值.
另外可以用 append 來增加他的大小
如下所示
import "fmt"
func main(){
var a[]int
a = append(a,1)
a = append(a,2)
for i := range a {
fmt.Println(a[i])
}
}
43. Golang 入門初體驗
Go 語法
Map
建立一個 map 可以用以下兩種方式
m := make(map[string]int)
m := map[string]int{}
定義 age = 16
m["age"] = 16
回傳值, 和 map 是否存在
value , ok := m["age"]
如果是不存在的 key , value = 0 , ok = false
value , ok := m["height"]
44. Golang 入門初體驗
Go 語法
Map
利用 rage 取得 map 中所有的 key 和 value, 並列印
for key, value := range m {
fmt.Println("Key:", key, "Value:", value)
}
如果要一次宣告多個值, 可以用以下方式
person := map[string]int{
"age" : 16,
"height" : 180,
"weight" : 6,
}
https://play.golang.org/p/rPpLGhc9lE
45. Golang 入門初體驗
Go 語法
Goroutine
在講goroutine之前, 必須先了解 concurrency 和 parllelism的不同
concurrency (併發) vs parallelism (並行)
併發 concurrency 是將 process 切割成多個可執行單位(coroutine), 如果有多個 process 時則在 coroutine 中
交互執行, 這樣不會因為某一個 process 執行太久造成其他 process 無法處理, 會讓你有多工的感覺. 但這並不
會增加執行效率. 總執行時間是一樣的, 如圖一所示. 另外和 thread 不同的是, thread 是屬於 os 層面上的, 且
thread 非常消耗系統資源.
50. Golang 入門初體驗
Go 語法
goroutine - channels
Don't communicate by sharing memory; share memory by communicating.
不同於過去的 muliti thread 的程式開發, 常使用共用變數去做資訊傳遞 (communicate by sharing memory).
goroutine 彼此的溝通方式是使用 channel (share memory by communicating). 這樣的方式會讓整個流程在
表現上變得清楚.
channels
channel 的通訊, 需要一個 sender 和一個 receiver, 當 sender 和 receiver 都 ready 時候, 訊息才會成功的傳遞.
import "fmt"
func main(){
s := mack(chan string) //宣告一個 channel 變數
s <- "hello" //寫入 channel (sender)
val <- s //讀取 channel (receiver)
fmt.Println(val)
}
上例說明了如何宣告 channel 變數, 寫入 channel, 讀取 channel.
51. Golang 入門初體驗
Go 語法
goroutine - channels
但是 code 無法跑, compiler 會顯示 dead lock.
因為執行到 s <- "hello" 這步的時候, sender 就會進入 ready 狀態.
然後就停住了, 也就是他不會執行到 val := <- s .
這時我們建立一個 goroutine 去跑 s <- "hello"
import "fmt"
func main(){
s := make(chan string) //宣告一個 channel 變數
go func(){
s <- "hello" //寫入 channel (sender)
}()
val := <- s //讀取 channel (receiver)
fmt.Println(val)
}
52. Golang 入門初體驗
Go 語法
goroutine - channels
執行順序就是
1. 建立一個 goroutine //此時 s <- "hello" 還沒執行
2. 執行 val := <- s //s 空的, receiver ready (停住)
3. 執行 s <- "hello" //sender 將訊息寫入 s, sender ready
4. val := <- s //成功讀取 s, (因為 receiver, sender 都 ready)
5. fmt.Println(val)
goroutine 不見得會比較慢執行. 不過重點不在這, 就不多描述了.
所以要看執行的順序就要注意這原則 "當 sender 和 receiver 都 ready 時候, 訊息才會成功的傳遞. "
再舉一個例子, 請問順序式如何?
53. Golang 入門初體驗
Go 語法
goroutine – channels
import "fmt"
func main(){
s := make(chan string)
go func() {
for i := 0; i < 3; i++ {
fmt.Println("sender hello",i)
s <- fmt.Sprintf("receiver hello %d", i)
}
}()
for i := 0; i < 3; i++ {
val := <-s
fmt.Println(val)
}
}
54. Golang 入門初體驗
Go 語法
goroutine – channels
結果
sender hello 0
sender hello 1
receiver hello 0
receiver hello 1
sender hello 2
receiver hello 2
有對嗎?程式碼順序是這樣
1. 建立一個 goroutine
2. 執行 val := <- s //s 空的, receiver ready (停住)
[sender hello 0]
3. 執行 s <- fmt.Sprintf.. //sender 將訊息寫入 s, 傳訊成功
[sender hello 1]
4. 執行 s <- fmt.Sprintf.. //s 有值, sender ready(停住)
5. val := <-s //讀到 s 的值
[receiver hello 0]
6. val := <-s //s 空的, receiver ready, 傳訊成功
[receiver hello 1]
7. val := <-s //s 空的, receiver ready (停住)
[sender hello 2]
8. 執行 s <- fmt.Sprintf.. //sender 將訊息寫入 s, 傳訊成功
9. val := <-s //讀到 s 的值
[sender hello 2]
http://guzalexander.com/2013/12/06/golang-channels-tutorial.html
55. Golang 入門初體驗
Go 語法
goroutine – channels
另外我們可以修改 channel 的大小為 2
import "fmt"
func main(){
s := make(chan string,2)
go func() {
for i := 0; i < 3; i++ {
fmt.Println("sender hello",i)
s <- fmt.Sprintf("receiver hello %d", i)
}
}()
for i := 0; i < 3; i++ {
val := <-s
fmt.Println(val)
}
}
結果
sender hello 0
sender hello 1
sender hello 2
receiver hello 0
receiver hello 1
receiver hello 2
http://guzalexander.com/2013/12/06/golang-channels-tutorial.html
56. Golang 入門初體驗
Go 語法
defer panic recover
defer
先看以下範例
func f(){
for i := 0 ; i < 5 ; i++{
fmt.Println(i)
}
fmt.Println("f finish")
}
此時的結果是
0
1
2
3
4
f finish
57. Golang 入門初體驗
Go 語法
defer panic recover
如果再 fmt.Println 前面加上 defer
func f(){
for i := 0 ; i < 5 ; i++{
defer fmt.Println(i)
}
fmt.Println("f finish")
}
結果就變成
f finish
4
3
2
1
0