From d5355ad2ec6f732242ca4ed20149f4fcacd7c7d9 Mon Sep 17 00:00:00 2001 From: Will Date: Sun, 3 Mar 2019 00:40:37 +0800 Subject: [PATCH] Add more term fixes and markdown format fixes --- zh-tw/01.1.md | 23 +++++++++----- zh-tw/01.2.md | 6 ++++ zh-tw/01.4.md | 10 ++++-- zh-tw/02.1.md | 2 +- zh-tw/02.2.md | 79 ++++++++++++++++++++++++------------------------ zh-tw/02.3.md | 62 ++++++++++++++++++------------------- zh-tw/02.4.md | 14 ++++----- zh-tw/02.5.md | 27 +++++++++++------ zh-tw/02.6.md | 16 ++++++++++ zh-tw/02.7.md | 13 +++++++- zh-tw/02.8.md | 2 +- zh-tw/03.2.md | 4 +-- zh-tw/03.3.md | 3 +- zh-tw/03.4.md | 15 ++++++--- zh-tw/04.1.md | 8 ++--- zh-tw/04.2.md | 30 +++++++++--------- zh-tw/04.3.md | 8 ++--- zh-tw/04.4.md | 2 +- zh-tw/04.5.md | 6 ++-- zh-tw/05.1.md | 32 ++++++++++---------- zh-tw/05.2.md | 2 +- zh-tw/05.3.md | 2 +- zh-tw/05.4.md | 3 +- zh-tw/05.5.md | 51 +++++++++++++++---------------- zh-tw/05.6.md | 10 +++--- zh-tw/06.1.md | 14 ++++----- zh-tw/06.2.md | 27 ++++++++--------- zh-tw/06.3.md | 8 ++--- zh-tw/06.4.md | 14 ++++----- zh-tw/07.1.md | 24 +++++++-------- zh-tw/07.2.md | 34 ++++++++++----------- zh-tw/07.3.md | 34 +++++++++++---------- zh-tw/07.4.md | 46 ++++++++++++++-------------- zh-tw/07.5.md | 21 ++++++++----- zh-tw/07.6.md | 1 + zh-tw/08.1.md | 33 ++++++++++++++------ zh-tw/08.2.md | 5 ++- zh-tw/08.3.md | 3 +- zh-tw/08.4.md | 26 +++++++--------- zh-tw/09.0.md | 2 +- zh-tw/09.1.md | 11 +++---- zh-tw/09.2.md | 12 ++++---- zh-tw/09.3.md | 2 +- zh-tw/09.4.md | 11 +++++-- zh-tw/09.5.md | 8 +++-- zh-tw/09.6.md | 5 +-- zh-tw/10.1.md | 4 +++ zh-tw/10.2.md | 14 ++++++--- zh-tw/10.3.md | 21 ++++++++++--- zh-tw/11.1.md | 20 +++++++++++- zh-tw/11.2.md | 1 + zh-tw/12.0.md | 2 +- zh-tw/12.1.md | 10 ++++-- zh-tw/12.2.md | 5 +-- zh-tw/12.3.md | 5 +-- zh-tw/12.4.md | 22 ++++++++------ zh-tw/12.5.md | 4 +-- zh-tw/13.2.md | 29 ++++++++++++------ zh-tw/13.3.md | 3 ++ zh-tw/13.4.md | 9 +++++- zh-tw/13.5.md | 7 ++++- zh-tw/14.1.md | 3 ++ zh-tw/14.2.md | 6 ++++ zh-tw/14.3.md | 3 ++ zh-tw/14.4.md | 12 ++++++-- zh-tw/14.5.md | 6 ++++ zh-tw/14.6.md | 5 ++- zh-tw/SUMMARY.md | 2 +- zh-tw/preface.md | 2 +- 69 files changed, 573 insertions(+), 393 deletions(-) diff --git a/zh-tw/01.1.md b/zh-tw/01.1.md index 0570995e..3e01de9e 100644 --- a/zh-tw/01.1.md +++ b/zh-tw/01.1.md @@ -118,16 +118,18 @@ Linux 系統使用者可透過在 Terminal 中執行命令`arch`(即`uname -m`) ### GVM gvm 是第三方開發的 Go 多版本管理工具,類似 ruby 裡面的 rvm 工具。使用起來相當的方便,安裝 gvm 使用如下命令: -```sh +```sh bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) ``` -安裝完成後我們就可以安裝 go 了: -```sh +安裝完成後我們就可以安裝 go 了: + +```sh gvm install go1.8.3 gvm use go1.8.3 ``` + 也可以使用下面的命令,省去每次呼叫 gvm use 的麻煩: gvm use go1.8.3 --default @@ -135,33 +137,38 @@ gvm use go1.8.3 ### apt-get Ubuntu 是目前使用最多的 Linux 桌面系統,使用`apt-get`命令來管理軟體套件,我們可以透過下面的命令來安裝 Go,為了以後方便,應該把 `git` `mercurial` 也安裝上: -```sh +```sh sudo apt-get install python-software-properties sudo add-apt-repository ppa:gophers/go sudo apt-get update sudo apt-get install golang-stable git-core mercurial ``` -### wget -```sh +### wget + +```sh wget https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz sudo tar -xzf go1.8.3.linux-amd64.tar.gz -C /usr/local ``` 配置環境變數: -```sh +```sh export GOROOT=/usr/local/go export GOBIN=$GOROOT/bin export PATH=$PATH:$GOBIN export GOPATH=$HOME/gopath (可選設定) ``` + 或者使用: + ```sh sudo vim /etc/profile ``` + 並新增下面的內容: + ```sh export GOROOT=/usr/local/go export GOBIN=$GOROOT/bin @@ -170,9 +177,11 @@ export GOPATH=$HOME/gopath (可選設定) ``` 重新載入 profile 檔案 + ```sh source /etc/profile ``` + ### homebrew homebrew 是 Mac 系統下面目前使用最多的管理軟體的工具,目前已支援 Go,可以透過命令直接安裝 Go,為了以後方便,應該把 `git` `mercurial` 也安裝上: diff --git a/zh-tw/01.2.md b/zh-tw/01.2.md index c20220b3..dd8d774f 100644 --- a/zh-tw/01.2.md +++ b/zh-tw/01.2.md @@ -11,12 +11,14 @@ *(注:這個不是 Go 安裝目錄。下面以筆者的工作目錄為範例,如果你想不一樣請把 GOPATH 替換成你的工作目錄。)* 在類別 Unix 環境下大概這樣設定: + ```sh export GOPATH=/home/apple/mygo ``` 為了方便,應該建立以上資料夾,並且上一行加入到 `.bashrc` 或者 `.zshrc` 或者自己的 `sh` 的配置檔案中。 Windows 設定如下,建立一個環境變數名稱叫做 GOPATH: + ```sh GOPATH=c:\mygo ``` @@ -40,6 +42,7 @@ GOPATH 下的 src 目錄就是接下來開發程式的主要目錄,所有的 下面我就以 mymath 為例來講述如何編寫套件,執行如下程式碼 + ```sh cd $GOPATH/src mkdir mymath @@ -68,6 +71,7 @@ func Sqrt(x float64) float64 { 2、在任意的目錄執行如下程式碼`go install mymath` 安裝完之後,我們可以進入如下目錄 + ```sh cd $GOPATH/pkg/${GOOS}_${GOARCH} //可以看到如下檔案 @@ -103,11 +107,13 @@ func main() { 可以看到這個的 package 是`main`,import 裡面呼叫的套件是`mymath`,這個就是相對於`$GOPATH/src`的路徑,如果是多階層目錄,就在 import 裡面引入多階層目錄,如果你有多個 GOPATH,也是一樣,Go 會自動在多個`$GOPATH/src`中尋找。 如何編譯程式呢?進入該應用目錄,然後執行`go build`,那麼在該目錄下面會產生一個 mathapp 的可執行檔案 + ```sh ./mathapp ``` 輸出如下內容 + ```sh Hello, world. Sqrt(2) = 1.414213562373095 ``` diff --git a/zh-tw/01.4.md b/zh-tw/01.4.md index 6eb1f6be..8b819fbf 100644 --- a/zh-tw/01.4.md +++ b/zh-tw/01.4.md @@ -222,6 +222,7 @@ VSCode 程式碼設定可用於 Go 擴充套件。這些都可以在使用者的 ``` 接著安裝相依套件支援(網路不穩定,請直接到 Github [Golang](https://github.com/golang) 下載再移動到相關目錄): + ```Go go get -u -v github.com/nsf/gocode go get -u -v github.com/rogpeppe/godef @@ -243,9 +244,10 @@ VSCode 還有一項很強大的功能就是斷點除錯,結合 [delve](https:/ go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv brew install go-delve/delve/delve(mac 可選) - ``` + 如果有問題再來一遍: + ```Go go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv ``` @@ -285,6 +287,7 @@ Atom 是 Github 基於 Electron 和 web 技術建構的開源編輯器, 是一 go-plus 是 Atom 上面的一款開源的 go 語言開發環境的的外掛 它需要依賴下面的 go 語言工具: + ```Go 1.autocomplete-go :gocode 的程式碼自動提示 2.gofmt :使用 goftm,goimports,goturns @@ -293,8 +296,8 @@ Atom 是 Github 基於 Electron 和 web 技術建構的開源編輯器, 是一 5.navigator-godef:godef 6.tester-goo :go test 7.gorename :rename - ``` + 在 Atom 中的 Preference 中可以找到 install 選單,輸入 go-plus,然後點選安裝(install) 就會開始安裝 go-plus , go-plus 外掛會自動安裝對應的依賴外掛,如果沒有安裝對應的 go 的類別函式庫會自動執行: go get 安裝。 @@ -342,8 +345,8 @@ Plugin 'gmarik/Vundle.vim' " All of your Plugins must be added before the following line call vundle#end() " required filetype plugin indent on " required - ``` + 2.安裝 Vim-go 修改~/.vimrc,在 vundle#begin 和 vundle#end 間增加一行: @@ -357,6 +360,7 @@ Plugin 'fatih/vim-go' 3.安裝 YCM(Your Complete Me)進行自動自動完成 在~/.vimrc 中新增一行: + ```sh Plugin 'Valloric/YouCompleteMe' diff --git a/zh-tw/02.1.md b/zh-tw/02.1.md index c1a80c65..65b868fb 100644 --- a/zh-tw/02.1.md +++ b/zh-tw/02.1.md @@ -7,8 +7,8 @@ 這就像一個傳統,在學習大部分語言之前,你先學會如何編寫一個可以輸出`hello world`的程式。 準備好了嗎?Let's Go! -```Go +```Go package main import "fmt" diff --git a/zh-tw/02.2.md b/zh-tw/02.2.md index 87e777d5..ccbb5be3 100644 --- a/zh-tw/02.2.md +++ b/zh-tw/02.2.md @@ -7,26 +7,26 @@ Go 語言裡面定義變數有多種方式。 使用 `var` 關鍵字是 Go 最基本的定義變數方式,與 C 語言不同的是 Go 把變數型別放在變數名後面: -```Go +```Go //定義一個名稱為“variableName”,型別為"type"的變數 var variableName type ``` 定義多個變數 -```Go +```Go //定義三個型別都是“type”的變數 var vname1, vname2, vname3 type ``` 定義變數並初始化值 -```Go +```Go //初始化“variableName”的變數為“value”值,型別是“type” var variableName type = value ``` 同時初始化多個變數 -```Go +```Go /* 定義三個型別都是"type"的變數,並且分別初始化為相應的值 vname1 為 v1,vname2 為 v2,vname3 為 v3 @@ -34,8 +34,8 @@ var variableName type = value var vname1, vname2, vname3 type= v1, v2, v3 ``` 你是不是覺得上面這樣的定義有點繁瑣?沒關係,因為 Go 語言的設計者也發現了,有一種寫法可以讓它變得簡單一點。我們可以直接忽略型別宣告,那麼上面的程式碼變成這樣了: -```Go +```Go /* 定義三個變數,它們分別初始化為相應的值 vname1 為 v1,vname2 為 v2,vname3 為 v3 @@ -44,8 +44,8 @@ var vname1, vname2, vname3 type= v1, v2, v3 var vname1, vname2, vname3 = v1, v2, v3 ``` 你覺得上面的還是有些繁瑣?好吧,我也覺得。讓我們繼續簡化: -```Go +```Go /* 定義三個變數,它們分別初始化為相應的值 vname1 為 v1,vname2 為 v2,vname3 為 v3 @@ -60,8 +60,8 @@ vname1, vname2, vname3 := v1, v2, v3 _, b := 34, 35 Go 對於已宣告但未使用的變數會在編譯階段報錯,比如下面的程式碼就會產生一個錯誤:宣告了 `i` 但未使用。 -```Go +```Go package main func main() { @@ -73,15 +73,15 @@ func main() { 所謂常數,也就是在程式編譯階段就確定下來的值,而程式在執行時無法改變該值。在 Go 程式中,常數可定義為數值、布林值或字串等型別。 它的語法如下: -```Go +```Go const constantName = value //如果需要,也可以明確指定常數的型別: const Pi float32 = 3.1415926 ``` 下面是一些常數宣告的例子: -```Go +```Go const Pi = 3.1415926 const i = 10000 const MaxThread = 10 @@ -95,8 +95,8 @@ Go 常數和一般程式語言不同的是,可以指定相當多的小數位 ### Boolean 在 Go 中,布林值的型別為`bool`,值是 `true` 或`false`,預設為`false`。 -```Go +```Go //範例程式碼 var isActive bool // 全域性變數宣告 var enabled, disabled = true, false // 忽略型別的宣告 @@ -126,8 +126,8 @@ func test() { 浮點數的型別有 `float32` 和`float64`兩種(沒有 `float` 型別),預設是`float64`。 這就是全部嗎?No!Go 還支援複數。它的預設型別是`complex128`(64 位實數+64 位虛數)。如果需要小一些的,也有`complex64`(32 位實數+32 位虛數)。複數的形式為`RE + IMi`,其中 `RE` 是實數部分,`IM`是虛數部分,而最後的 `i` 是虛數單位。下面是一個使用複數的例子: -```Go +```Go var c complex64 = 5+5i //output: (5+5i) fmt.Printf("Value is: %v", c) @@ -136,8 +136,8 @@ fmt.Printf("Value is: %v", c) ### 字串 我們在上一節中講過,Go 中的字串都是採用`UTF-8`字符集編碼。字串是用一對雙引號(`""`)或反引號(`` ` `` `` ` ``)括起來定義,它的型別是`string`。 -```Go +```Go //範例程式碼 var frenchHello string // 宣告變數為字串的一般方法 var emptyString string = "" // 宣告了一個字串變數,初始化為空字串 @@ -148,15 +148,15 @@ func test() { } ``` 在 Go 中字串是不可變的,例如下面的程式碼編譯時會報錯:cannot assign to s[0] -```Go +```Go var s string = "hello" s[0] = 'c' - ``` -但如果真的想要修改怎麼辦呢?下面的程式碼可以實現: -```Go +但如果真的想要修改怎麼辦呢?下面的程式碼可以實現: + +```Go s := "hello" c := []byte(s) // 將字串 s 轉換為 []byte 型別 c[0] = 'c' @@ -165,16 +165,16 @@ fmt.Printf("%s\n", s2) ``` Go 中可以使用`+`運算子來連線兩個字串: -```Go +```Go s := "hello," m := " world" a := s + m fmt.Printf("%s\n", a) ``` 修改字串也可寫為: -```Go +```Go s := "hello" s = "c" + s[1:] // 字串雖不能更改,但可進行切片(slice)操作 fmt.Printf("%s\n", s) @@ -191,8 +191,8 @@ fmt.Printf("%s\n", s) ### 錯誤型別 Go 內建有一個 `error` 型別,專門用來處理錯誤資訊,Go 的 `package` 裡面還專門有一個套件 `errors` 來處理錯誤: -```Go +```Go err := errors.New("emit macho dwarf: elf header corrupted") if err != nil { fmt.Print(err) @@ -213,8 +213,8 @@ if err != nil { 在 Go 語言中,同時宣告多個常數、變數,或者匯入多個套件時,可採用分組的方式進行宣告。 例如下面的程式碼: -```Go +```Go import "fmt" import "os" @@ -227,8 +227,8 @@ var pi float32 var prefix string ``` 可以分組寫成如下形式: -```Go +```Go import( "fmt" "os" @@ -249,8 +249,8 @@ var( ### iota 列舉 Go 裡面有一個關鍵字 `iota`,這個關鍵字用來宣告 `enum` 的時候採用,它預設開始值是 0,const 中每增加一行加 1: -```Go +```Go package main import ( @@ -293,13 +293,13 @@ Go 之所以會那麼簡潔,是因為它有一些預設的行為: ### array `array`就是陣列,它的定義方式如下: -```Go +```Go var arr [n]type ``` 在 `[n]type` 中,`n`表示陣列的長度,`type`表示儲存元素的型別。對陣列的操作和其它語言類似,都是透過 `[]` 來進行讀取或賦值: -```Go +```Go var arr [10]int // 宣告了一個 int 型別的陣列 arr[0] = 42 // 陣列下標是從 0 開始的 arr[1] = 13 // 賦值操作 @@ -309,8 +309,8 @@ fmt.Printf("The last element is %d\n", arr[9]) //回傳未賦值的最後一個 由於長度也是陣列型別的一部分,因此 `[3]int` 與`[4]int`是不同的型別,陣列也就不能改變長度。陣列之間的賦值是值的賦值,即當把一個陣列作為參數傳入函式的時候,傳入的其實是該陣列的副本,而不是它的指標。如果要使用指標,那麼就需要用到後面介紹的 `slice` 型別了。 陣列可以使用另一種 `:=` 來宣告 -```Go +```Go a := [3]int{1, 2, 3} // 宣告了一個長度為 3 的 int 陣列 b := [10]int{1, 2, 3} // 宣告了一個長度為 10 的 int 陣列,其中前三個元素初始化為 1、2、3,其它預設為 0 @@ -318,8 +318,8 @@ b := [10]int{1, 2, 3} // 宣告了一個長度為 10 的 int 陣列,其中前 c := [...]int{4, 5, 6} // 可以省略長度而採用`...`的方式,Go 會自動根據元素個數來計算長度 ``` 也許你會說,我想陣列裡面的值還是陣列,能實現嗎?當然囉,Go 支援巢狀陣列,即多維陣列。比如下面的程式碼就宣告了一個二維陣列: -```Go +```Go // 宣告了一個二維陣列,該陣列以兩個陣列作為元素,其中每個陣列中又有 4 個 int 型別的元素 doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} @@ -338,19 +338,19 @@ easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} 在很多應用場景中,陣列並不能滿足我們的需求。在初始定義陣列時,我們並不知道需要多大的陣列,因此我們就需要“動態陣列”。在 Go 裡面這種資料結構叫`slice` `slice`並不是真正意義上的動態陣列,而是一個參考型別。`slice`總是指向一個底層`array`,`slice`的宣告也可以像 `array` 一樣,只是不需要長度。 -```Go +```Go // 和宣告 array 一樣,只是少了長度 var fslice []int ``` 接下來我們可以宣告一個`slice`,並初始化資料,如下所示: -```Go +```Go slice := []byte {'a', 'b', 'c', 'd'} ``` `slice`可以從一個陣列或一個已經存在的 `slice` 中再次宣告。`slice`透過 `array[i:j]` 來取得,其中 `i` 是陣列的開始位置,`j`是結束位置,但不包含`array[j]`,它的長度是`j-i`。 -```Go +```Go // 宣告一個含有 10 個元素元素型別為 byte 的陣列 var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} @@ -380,8 +380,8 @@ slice 有一些簡便的操作 - 如果從一個陣列裡面直接取得`slice`,可以這樣`ar[:]`,因為預設第一個序列是 0,第二個是陣列的長度,即等價於`ar[0:len(ar)]` 下面這個例子展示了更多關於 `slice` 的操作: -```Go +```Go // 宣告一個陣列 var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} // 宣告兩個 slice @@ -405,8 +405,8 @@ bSlice = aSlice[:] // bSlice 包含所有 aSlice 的元素: d,e,f,g - 一個指標,指向陣列中 `slice` 指定的開始位置 - 長度,即 `slice` 的長度 - 最大長度,也就是 `slice` 開始位置到陣列的最後位置的長度 -```Go +```Go Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} Slice_a := Array_a[2:5] ``` @@ -427,8 +427,8 @@ bSlice = aSlice[:] // bSlice 包含所有 aSlice 的元素: d,e,f,g 但當 `slice` 中沒有剩餘空間(即`(cap-len) == 0`)時,此時將動態分配新的陣列空間。回傳的 `slice` 陣列指標將指向這個空間,而原陣列的內容將保持不變;其它參考此陣列的 `slice` 則不受影響。 從 Go1.2 開始 slice 支援了三個參數的 slice,之前我們一直採用這種方式在 slice 或者 array 基礎上來取得一個 slice -```Go +```Go var array [10]int slice := array[2:4] ``` @@ -445,8 +445,8 @@ slice := array[2:4] `map`也就是 Python 中字典的概念,它的格式為`map[keyType]valueType` 我們看下面的程式碼,`map`的讀取和設定也類似 `slice` 一樣,透過 `key` 來操作,只是 `slice` 的`index`只能是`int`型別,而 `map` 多了很多型別,可以是`int`,可以是 `string` 及所有完全定義了 `==` 與`!=`操作的型別。 -```Go +```Go // 宣告一個 key 是字串,值為 int 的字典,這種方式的宣告需要在使用之前使用 make 初始化 var numbers map[string]int // 另一種 map 的宣告方式 @@ -473,7 +473,6 @@ fmt.Println("第三個數字是: ", numbers["three"]) // 讀取資料 透過 `delete` 刪除 `map` 的元素: ```Go - // 初始化一個字典 rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } // map 有兩個回傳值,第二個回傳值,如果不存在 key,那麼 ok 為 false,如果存在 ok 為 true @@ -485,17 +484,17 @@ if ok { } delete(rating, "C") // 刪除 key 為 C 的元素 - ``` -上面說過了,`map`也是一種參考型別,如果兩個 `map` 同時指向一個底層,那麼一個改變,另一個也相應的改變: -```Go +上面說過了,`map`也是一種參考型別,如果兩個 `map` 同時指向一個底層,那麼一個改變,另一個也相應的改變: + +```Go m := make(map[string]string) m["Hello"] = "Bonjour" m1 := m m1["Hello"] = "Salut" // 現在 m["hello"]的值已經是 Salut 了 - ``` + ### make、new 操作 `make`用於內建型別(`map`、`slice` 和`channel`)的記憶體分配。`new`用於各種型別的記憶體分配。 @@ -518,8 +517,8 @@ m1["Hello"] = "Salut" // 現在 m["hello"]的值已經是 Salut 了 ## 零值 關於“零值”,所指並非是空值,而是一種“變數未填充前”的預設值,通常為 0。 此處羅列 部分類型 的 “零值” -```Go +```Go int 0 int8 0 int32 0 @@ -531,8 +530,8 @@ float32 0 //長度為 4 byte float64 0 //長度為 8 byte bool false string "" - ``` + ## links * [目錄]() * 上一章: [你好,Go](<02.1.md>) diff --git a/zh-tw/02.3.md b/zh-tw/02.3.md index cb7a2959..47d6a00e 100644 --- a/zh-tw/02.3.md +++ b/zh-tw/02.3.md @@ -6,8 +6,8 @@ `if`也許是各種程式語言中最常見的了,它的語法概括起來就是:如果滿足條件就做某事,否則做另一件事。 Go 裡面 `if` 條件判斷語句中不需要括號,如下程式碼所示 -```Go +```Go if x > 10 { fmt.Println("x is greater than 10") } else { @@ -15,8 +15,8 @@ if x > 10 { } ``` Go 的 `if` 還有一個強大的地方就是條件判斷語句裡面允許宣告一個變數,這個變數的作用域只能在該條件邏輯區塊內,其他地方就無法使用,如下所示 -```Go +```Go // 計算取得值 x,然後根據 x 回傳的大小,判斷是否大於 10。 if x := computedValue(); x > 10 { fmt.Println("x is greater than 10") @@ -28,8 +28,8 @@ if x := computedValue(); x > 10 { fmt.Println(x) ``` 多個條件的時候如下所示: -```Go +```Go if integer == 3 { fmt.Println("The integer is equal to 3") } else if integer < 3 { @@ -41,8 +41,8 @@ if integer == 3 { ### goto Go 有 `goto` 語句——請明智地使用它。用 `goto` 跳轉到必須在當前函式內定義的標籤。例如假設這樣一個迴圈: -```Go +```Go func myFunc() { i := 0 Here: //這行的第一個詞,以冒號結束作為標籤 @@ -55,8 +55,8 @@ Here: //這行的第一個詞,以冒號結束作為標籤 ### for Go 裡面最強大的一個控制邏輯就是`for`,它既可以用來迴圈讀取資料,又可以當作 `while` 來控制邏輯,還能迭代操作。它的語法如下: -```Go +```Go for expression1; expression2; expression3 { //... } @@ -64,8 +64,8 @@ for expression1; expression2; expression3 { `expression1`、`expression2`和 `expression3` 都是表示式,其中 `expression1` 和`expression3`是變數宣告或者函式呼叫回傳值之類別的,`expression2`是用來條件判斷,`expression1`在迴圈開始之前呼叫,`expression3`在每輪迴圈結束之時呼叫。 一個例子比上面講那麼多更有用,那麼我們看看下面的例子吧: -```Go +```Go package main import "fmt" @@ -83,24 +83,24 @@ func main(){ 有些時候如果我們忽略 `expression1` 和`expression3`: -```Go +```Go sum := 1 for ; sum < 1000; { sum += sum } ``` 其中 `;` 也可以省略,那麼就變成如下的程式碼了,是不是似曾相識?對,這就是 `while` 的功能。 -```Go +```Go sum := 1 for sum < 1000 { sum += sum } ``` 在迴圈裡面有兩個關鍵操作 `break` 和`continue` ,`break`操作是跳出當前迴圈,`continue`是跳過本次迴圈。當巢狀過深的時候,`break`可以配合標籤使用,即跳轉至標籤所指定的位置,詳細參考如下例子: -```Go +```Go for index := 10; index>0; index-- { if index == 5{ break // 或者 continue @@ -113,8 +113,8 @@ for index := 10; index>0; index-- { `break`和 `continue` 還可以跟著標號,用來跳到多重迴圈中的外層迴圈 `for`配合 `range` 可以用於讀取 `slice` 和`map`的資料: -```Go +```Go for k,v:=range map { fmt.Println("map's key:",k) fmt.Println("map's val:",v) @@ -122,17 +122,17 @@ for k,v:=range map { ``` 由於 Go 支援 “多值回傳”, 而對於“宣告而未被呼叫”的變數, 編譯器會報錯, 在這種情況下, 可以使用 `_` 來丟棄不需要的回傳值 例如 -```Go +```Go for _, v := range map{ fmt.Println("map's val:", v) } - ``` + ### switch 有些時候你需要寫很多的`if-else`來實現一些邏輯處理,這個時候程式碼看上去就很醜很冗長,而且也不易於以後的維護,這個時候 `switch` 就能很好的解決這個問題。它的語法如下 -```Go +```Go switch sExpr { case expr1: some instructions @@ -145,8 +145,8 @@ default: } ``` `sExpr`和`expr1`、`expr2`、`expr3`的型別必須一致。Go 的 `switch` 非常靈活,表示式不必是常數或整數,執行的過程從上至下,直到找到匹配項;而如果 `switch` 沒有表示式,它會匹配`true`。 -```Go +```Go i := 10 switch i { case 1: @@ -160,8 +160,8 @@ default: } ``` 在第 5 行中,我們把很多值聚合在了一個 `case` 裡面,同時,Go 裡面 `switch` 預設相當於每個 `case` 最後帶有`break`,匹配成功後不會自動向下執行其他 case,而是跳出整個`switch`, 但是可以使用 `fallthrough` 強制執行後面的 case 程式碼。 -```Go +```Go integer := 6 switch integer { case 4: @@ -184,18 +184,18 @@ default: } ``` 上面的程式將輸出 -```Go +```Go The integer was <= 6 The integer was <= 7 The integer was <= 8 default case - ``` + ## 函式 函式是 Go 裡面的核心設計,它透過關鍵字 `func` 來宣告,它的格式如下: -```Go +```Go func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { //這裡是處理邏輯程式碼 //回傳多個值 @@ -213,8 +213,8 @@ func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { - 如果有回傳值, 那麼必須在函式的外層新增 return 語句 下面我們來看一個實際應用函式的例子(用來計算 Max 值) -```Go +```Go package main import "fmt" @@ -246,8 +246,8 @@ func main() { Go 語言比 C 更先進的特性,其中一點就是函式能夠回傳多個值。 我們直接上程式碼看例子 -```Go +```Go package main import "fmt" @@ -268,8 +268,8 @@ func main() { } ``` 上面的例子我們可以看到直接回傳了兩個參數,當然我們也可以命名回傳參數的變數,這個例子裡面只是用了兩個型別,我們也可以改成如下這樣的定義,然後回傳的時候不用帶上變數名,因為直接在函式裡面初始化了。但如果你的函式是匯出的(首字母大寫),官方建議:最好命名回傳值,因為不命名回傳值,雖然使得程式碼更加簡潔了,但是會造成產生的文件可讀性差。 -```Go +```Go func SumAndProduct(A, B int) (add int, Multiplied int) { add = A+B Multiplied = A*B @@ -328,8 +328,8 @@ func main() { 那你也許會問了,如果真的需要傳這個 `x` 本身,該怎麼辦呢? 這就牽扯到了所謂的指標。我們知道,變數在記憶體中是存放於一定地址上的,修改變數實際是修改變數地址處的記憶體。只有 `add1` 函式知道 `x` 變數所在的地址,才能修改 `x` 變數的值。所以我們需要將 `x` 所在地址`&x`傳入函式,並將函式的參數的型別由 `int` 改為`*int`,即改為指標型別,才能在函式中修改 `x` 變數的值。此時參數仍然是按 copy 傳遞的,只是 copy 的是一個指標。請看下面的例子 -```Go +```Go package main import "fmt" @@ -359,8 +359,8 @@ func main() { ### defer Go 語言中有種不錯的設計,即延遲(defer)語句,你可以在函式中新增多個 defer 語句。當函式執行到最後時,這些 defer 語句會按照逆序執行,最後該函式回傳。特別是當你在進行一些開啟資源的操作時,遇到錯誤需要提前回傳,在回傳前你需要關閉相應的資源,不然很容易造成資源洩露等問題。如下程式碼所示,我們一般寫開啟一個資源是這樣操作的: -```Go +```Go func ReadWrite() bool { file.Open("file") // 做一些工作 @@ -379,8 +379,8 @@ func ReadWrite() bool { } ``` 我們看到上面有很多重複的程式碼,Go 的 `defer` 有效解決了這個問題。使用它後,不但程式碼量減少了很多,而且程式變得更優雅。在 `defer` 後指定的函式會在函式退出前呼叫。 -```Go +```Go func ReadWrite() bool { file.Open("file") defer file.Close() @@ -394,8 +394,8 @@ func ReadWrite() bool { } ``` 如果有很多呼叫`defer`,那麼 `defer` 是採用後進先出模式,所以如下程式碼會輸出`4 3 2 1 0` -```Go +```Go for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } @@ -407,8 +407,8 @@ for i := 0; i < 5; i++ { type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) 函式作為型別到底有什麼好處呢?那就是可以把這個型別的函式當做值來傳遞,請看下面的例子 -```Go +```Go package main import "fmt" @@ -463,8 +463,8 @@ Recover >是一個內建的函式,可以讓進入 `panic` 狀態的 `goroutine` 恢復過來。`recover`僅在延遲函式中有效。在正常的執行過程中,呼叫 `recover` 會回傳`nil`,並且沒有其它任何效果。如果當前的 `goroutine` 陷入 `panic` 狀態,呼叫 `recover` 可以捕獲到 `panic` 的輸入值,並且恢復正常的執行。 下面這個函式示範了如何在過程中使用`panic` -```Go +```Go var user = os.Getenv("USER") func init() { @@ -474,8 +474,8 @@ func init() { } ``` 下面這個函式檢查作為其參數的函式在執行時是否會產生`panic`: -```Go +```Go func throwsPanic(f func()) (b bool) { defer func() { if x := recover(); x != nil { @@ -500,15 +500,15 @@ Go 程式會自動呼叫`init()`和`main()`,所以你不需要在任何地方 ### import 我們在寫 Go 程式碼的時候經常用到 import 這個命令用來匯入套件檔案,而我們經常看到的方式參考如下: -```Go +```Go import( "fmt" ) ``` 然後我們程式碼裡面可以透過如下的方式呼叫 -```Go +```Go fmt.Println("hello world") ``` 上面這個 fmt 是 Go 語言的標準函式庫,其實是去 `GOROOT` 環境變數指定目錄下去載入該模組,當然 Go 的 import 還支援如下兩種方式來載入自己寫的模組: @@ -548,8 +548,8 @@ fmt.Println("hello world") 3. _操作 這個操作經常是讓很多人難以理解的一個運算子,請看下面這個 import -```Go +```Go import ( "database/sql" _ "github.com/ziutek/mymysql/godrv" diff --git a/zh-tw/02.4.md b/zh-tw/02.4.md index e70e62ac..cc24d88a 100644 --- a/zh-tw/02.4.md +++ b/zh-tw/02.4.md @@ -1,8 +1,8 @@ # 2.4 struct 型別 ## struct Go 語言中,也和 C 或者其他語言一樣,我們可以宣告新的型別,作為其它型別的屬性或欄位的容器。例如,我們可以建立一個自訂型別 `person` 代表一個人的實體。這個實體擁有屬性:姓名和年齡。這樣的型別我們稱之`struct`。如下程式碼所示: -```Go +```Go type person struct { name string age int @@ -13,8 +13,8 @@ type person struct { - 一個 int 型別的欄位 age,用來儲存使用者年齡這個屬性 如何使用 struct 呢?請看下面的程式碼 -```Go +```Go type person struct { name string age int @@ -41,8 +41,8 @@ fmt.Printf("The person's name is %s", P.name) // 訪問 P 的 name 屬性. P := new(person) 下面我們看一個完整的使用 struct 的例子 -```Go +```Go package main import "fmt" @@ -94,8 +94,8 @@ func main() { 當匿名欄位是一個 struct 的時候,那麼這個 struct 所擁有的全部欄位都被隱含的引入了當前定義的這個 struct。 讓我們來看一個例子,讓上面說的這些更具體化 -```Go +```Go package main import "fmt" @@ -141,14 +141,14 @@ func main() { 圖 2.7 struct 組合,Student 組合了 Human struct 和 string 基本型別 我們看到 Student 訪問屬性 age 和 name 的時候,就像訪問自己所有用的欄位一樣,對,匿名欄位就是這樣,能夠實現欄位的繼承。是不是很酷啊?還有比這個更酷的呢,那就是 student 還能訪問 Human 這個欄位作為欄位名。請看下面的程式碼,是不是更酷了。 -```Go +```Go mark.Human = Human{"Marcus", 55, 220} mark.Human.age -= 1 ``` 透過匿名訪問和修改欄位相當的有用,但是不僅僅是 struct 欄位哦,所有的內建型別和自訂型別都是可以作為匿名欄位的。請看下面的例子 -```Go +```Go package main import "fmt" @@ -194,8 +194,8 @@ func main() { Go 裡面很簡單的解決了這個問題,最外層的優先訪問,也就是當你透過`student.phone`訪問的時候,是訪問 student 裡面的欄位,而不是 human 裡面的欄位。 這樣就允許我們去過載透過匿名欄位繼承的一些欄位,當然如果我們想訪問過載後對應匿名型別裡面的欄位,可以透過匿名欄位名來訪問。請看下面的例子 -```Go +```Go package main import "fmt" diff --git a/zh-tw/02.5.md b/zh-tw/02.5.md index b77bff25..bd8dea9b 100644 --- a/zh-tw/02.5.md +++ b/zh-tw/02.5.md @@ -1,10 +1,12 @@ # 2.5 物件導向 + 前面兩章我們介紹了函式和 struct,那你是否想過函式當作 struct 的欄位一樣來處理呢?今天我們就講解一下函式的另一種形態,帶有接收者的函式,我們稱為`method` ## method -現在假設有這麼一個場景,你定義了一個 struct 叫做長方形,你現在想要計算他的面積,那麼按照我們一般的思路應該會用下面的方式來實現 -```Go +現在假設有這麼一個場景,你定義了一個 struct 叫做長方形,你現在想要計算他的面積,那麼按照我們一般的思路應該會用下面的方式來實現 + +```Go package main import "fmt" @@ -51,8 +53,8 @@ method 的語法如下: func (r ReceiverType) funcName(parameters) (results) 下面我們用最開始的例子用 method 來實現: -```Go +```Go package main import ( @@ -108,13 +110,13 @@ func main() { >值得說明的一點是,圖示中 method 用虛線標出,意思是此處方法的 Receiver 是以值傳遞,而非參考傳遞,是的,Receiver 還可以是指標, 兩者的差別在於, 指標作為 Receiver 會對實體物件的內容發生操作,而普通型別作為 Receiver 僅僅是以副本作為操作物件,並不對原實體物件發生操作。後文對此會有詳細論述。 那是不是 method 只能作用在 struct 上面呢?當然不是囉,他可以定義在任何你自訂的型別、內建型別、struct 等各種型別上面。這裡你是不是有點迷糊了,什麼叫自訂型別,自訂型別不就是 struct 嘛,不是這樣的哦,struct 只是自訂型別裡面一種比較特殊的型別而已,還有其他自訂型別宣告,可以透過如下這樣的宣告來實現。 -```Go +```Go type typeName typeLiteral ``` 請看下面這個宣告自訂型別的程式碼 -```Go +```Go type ages int type money float32 @@ -133,8 +135,8 @@ m := months { 好了,讓我們回到`method` 你可以在任何的自訂型別中定義任意多的`method`,接下來讓我們看一個複雜一點的例子 -```Go +```Go package main import "fmt" @@ -226,6 +228,7 @@ func main() { 上面的程式碼透過文字描述出來之後是不是很簡單?我們一般解決問題都是透過問題的描述,去寫相應的程式碼實現。 ### 指標作為 receiver + 現在讓我們回過頭來看看 SetColor 這個 method,它的 receiver 是一個指向 Box 的指標,是的,你可以使用*Box。想想為啥要使用指標而不是 Box 本身呢? 我們定義 SetColor 的真正目的是想改變這個 Box 的顏色,如果不傳 Box 的指標,那麼 SetColor 接受的其實是 Box 的一個 copy,也就是說 method 內對於顏色值的修改,其實只作用於 Box 的 copy,而不是真正的 Box。所以我們需要傳入指標。 @@ -251,8 +254,8 @@ func main() { ### method 繼承 前面一章我們學習了欄位的繼承,那麼你也會發現 Go 的一個神奇之處,method 也是可以繼承的。如果匿名欄位實現了一個 method,那麼包含這個匿名欄位的 struct 也能呼叫該 method。讓我們來看下面這個例子 -```Go +```Go package main import "fmt" @@ -286,10 +289,12 @@ func main() { sam.SayHi() } ``` -### method 重寫 -上面的例子中,如果 Employee 想要實現自己的 SayHi,怎麼辦?簡單,和匿名欄位衝突一樣的道理,我們可以在 Employee 上面定義一個 method,重寫了匿名欄位的方法。請看下面的例子 -```Go +### method 重寫 + +上面的例子中,如果 Employee 想要實現自己的 SayHi,怎麼辦?簡單,和匿名欄位衝突一樣的道理,我們可以在 Employee 上面定義一個 method,重寫了匿名欄位的方法。請看下面的例子 + +```Go package main import "fmt" @@ -332,7 +337,9 @@ func main() { 上面的程式碼設計的是如此的美妙,讓人不自覺的為 Go 的設計驚歎! 透過這些內容,我們可以設計出基本的物件導向的程式了,但是 Go 裡面的物件導向是如此的簡單,沒有任何的私有、公有關鍵字,透過大小寫來實現(大寫開頭的為公有,小寫開頭的為私有),方法也同樣適用這個原則。 + ## links + * [目錄]() * 上一章: [struct 型別](<02.4.md>) * 下一節: [interface](<02.6.md>) diff --git a/zh-tw/02.6.md b/zh-tw/02.6.md index 4dd4d7a5..dec10ffc 100644 --- a/zh-tw/02.6.md +++ b/zh-tw/02.6.md @@ -15,6 +15,7 @@ Go 語言裡面設計最精妙的應該算 interface,它讓物件導向,內 上面這些方法的組合稱為 interface(被物件 Student 和 Employee 實現)。例如 Student 和 Employee 都實現了 interface:SayHi 和 Sing,也就是這兩個物件是該 interface 型別。而 Employee 沒有實現這個 interface:SayHi、Sing 和 BorrowMoney,因為 Employee 沒有實現 BorrowMoney 這個方法。 ### interface 型別 interface 型別定義了一組方法,如果某個物件實現了某個介面的所有方法,則此物件就實現了此介面。詳細的語法參考下面這個例子 + ```Go type Human struct { @@ -98,6 +99,7 @@ type ElderlyGent interface { 因為 m 能夠持有這三種類型的物件,所以我們可以定義一個包含 Men 型別元素的 slice,這個 slice 可以被賦予實現了 Men 介面的任意結構的物件,這個和我們傳統意義上面的 slice 有所不同。 讓我們來看一下下面這個例子: + ```Go package main @@ -185,6 +187,7 @@ func main() { ### 空 interface 空 interface(interface{})不包含任何的 method,正因為如此,所有的型別都實現了空 interface。空 interface 對於描述起不到任何的作用(因為它不包含任何的 method),但是空 interface 在我們需要儲存任意型別的數值的時候相當有用,因為它可以儲存任意型別的數值。它有點類似於 C 語言的 void*型別。 + ```Go // 定義 a 為空介面 @@ -200,6 +203,7 @@ a = s interface 的變數可以持有任意實現該 interface 型別的物件,這給我們編寫函式(包括 method)提供了一些額外的思考,我們是不是可以透過定義 interface 參數,讓函式接受各種型別的參數。 舉個例子:fmt.Println 是我們常用的一個函式,但是你是否注意到它可以接受任意型別的資料。開啟 fmt 的原始碼檔案,你會看到這樣一個定義: + ```Go type Stringer interface { @@ -207,6 +211,7 @@ type Stringer interface { } ``` 也就是說,任何實現了 String 方法的型別都能作為參數被 fmt.Println 呼叫,讓我們來試一試 + ```Go package main @@ -232,6 +237,7 @@ func main() { } ``` 現在我們再回顧一下前面的 Box 範例,你會發現 Color 結構也定義了一個 method:String。其實這也是實現了 fmt.Stringer 這個 interface,即如果需要某個型別能被 fmt 套件以特殊的格式輸出,你就必須實現 Stringer 這個介面。如果沒有實現這個介面,fmt 將以預設的方式輸出。 + ```Go //實現同樣的功能 @@ -250,6 +256,7 @@ fmt.Println("The biggest one is", boxes.BiggestsColor()) 如果 element 裡面確實儲存了 T 型別的數值,那麼 ok 回傳 true,否則回傳 false。 讓我們透過一個例子來更加深入的理解。 + ```Go package main @@ -297,6 +304,7 @@ fmt.Println("The biggest one is", boxes.BiggestsColor()) - switch 測試 最好的講解就是程式碼例子,現在讓我們重寫上面的這個實現 + ```Go package main @@ -346,6 +354,7 @@ fmt.Println("The biggest one is", boxes.BiggestsColor()) Go 裡面真正吸引人的是它內建的邏輯語法,就像我們在學習 Struct 時學習的匿名欄位,多麼的優雅啊,那麼相同的邏輯引入到 interface 裡面,那不是更加完美了。如果一個 interface1 作為 interface2 的一個嵌入欄位,那麼 interface2 隱式的包含了 interface1 裡面的 method。 我們可以看到原始碼套件 container/heap 裡面有這樣的一個定義 + ```Go type Interface interface { @@ -355,6 +364,7 @@ type Interface interface { } ``` 我們看到 sort.Interface 其實就是嵌入欄位,把 sort.Interface 的所有 method 給隱式的包含進來了。也就是下面三個方法: + ```Go type Interface interface { @@ -368,6 +378,7 @@ type Interface interface { } ``` 另一個例子就是 io 套件下面的 io.ReadWriter ,它包含了 io 套件下面的 Reader 和 Writer 兩個 interface: + ```Go // io.ReadWriter @@ -380,18 +391,21 @@ type ReadWriter interface { Go 語言實現了反射,所謂反射就是能檢查程式在執行時的狀態。我們一般用到的套件是 reflect 套件。如何運用 reflect 套件,官方的這篇文章詳細的講解了 reflect 套件的實現原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html) 使用 reflect 一般分成三步,下面簡要的講解一下:要去反射是一個型別的值(這些值都實現了空 interface),首先需要把它轉化成 reflect 物件(reflect.Type 或者 reflect.Value,根據不同的情況呼叫不同的函式)。這兩種取得方式如下: + ```Go t := reflect.TypeOf(i) //得到型別的 Meta 資料,透過 t 我們能取得型別定義裡面的所有元素 v := reflect.ValueOf(i) //得到實際的值,透過 v 我們取得儲存在裡面的值,還可以去改變值 ``` 轉化為 reflect 物件之後我們就可以進行一些操作了,也就是將 reflect 物件轉化成相應的值,例如 + ```Go tag := t.Elem().Field(0).Tag //取得定義在 struct 裡面的標籤 name := v.Elem().Field(0).String() //取得儲存在第一個欄位裡面的值 ``` 取得反射值能回傳相應的型別和數值 + ```Go var x float64 = 3.4 @@ -401,6 +415,7 @@ fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float()) ``` 最後,反射的話,那麼反射的欄位必須是可修改的,我們前面學習過傳值和傳參考,這個裡面也是一樣的道理。反射的欄位必須是可讀寫的意思是,如果下面這樣寫,那麼會發生錯誤 + ```Go var x float64 = 3.4 @@ -408,6 +423,7 @@ v := reflect.ValueOf(x) v.SetFloat(7.1) ``` 如果要修改相應的值,必須這樣寫 + ```Go var x float64 = 3.4 diff --git a/zh-tw/02.7.md b/zh-tw/02.7.md index 679f5883..285f78ee 100644 --- a/zh-tw/02.7.md +++ b/zh-tw/02.7.md @@ -7,11 +7,13 @@ goroutine 是 Go 並行設計的核心。goroutine 說到底其實就是[協程](https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B) (Coroutine),但是它比執行緒更小,十幾個 goroutine 可能體現在底層就是五六個執行緒,Go 語言內部幫你實現了這些 goroutine 之間的記憶體共享。執行 goroutine 只需極少的棧記憶體(大概是 4~5KB),當然會根據相應的資料伸縮。也正因為如此,可同時執行成千上萬個併發任務。goroutine 比 thread 更易用、更高效、更輕便。 goroutine 是透過 Go 的 runtime 管理的一個執行緒管理器。goroutine 透過 `go` 關鍵字實現了,其實就是一個普通的函式。 + ```Go go hello(a, b, c) ``` 透過關鍵字 go 就啟動了一個 goroutine。我們來看一個例子 + ```Go package main @@ -55,6 +57,7 @@ func main() { ## channels goroutine 執行在相同的地址空間,因此訪問共享記憶體必須做好同步。那麼 goroutine 之間如何進行資料的通訊呢,Go 提供了一個很好的通訊機制 channel。channel 可以與 Unix shell 中的雙向管道做類別比:可以透過它傳送或者接收值。這些值只能是特定的型別:channel 型別。定義一個 channel 時,也需要定義傳送到 channel 的值的型別。注意,必須使用 make 建立 channel: + ```Go ci := make(chan int) @@ -62,13 +65,15 @@ cs := make(chan string) cf := make(chan interface{}) ``` channel 透過運算子`<-`來接收和傳送資料 + ```Go ch <- v // 傳送 v 到 channel ch. v := <-ch // 從 ch 中接收資料,並賦值給 v - ``` + 我們把這些應用到我們的例子中來: + ```Go package main @@ -98,6 +103,7 @@ func main() { ## Buffered Channels 上面我們介紹了預設的非快取型別的 channel,不過 Go 也允許指定 channel 的緩衝大小,很簡單,就是 channel 可以儲存多少元素。ch:= make(chan bool, 4),建立了可以儲存 4 個元素的 bool 型 channel。在這個 channel 中,前 4 個元素可以無阻塞的寫入。當寫入第 5 個元素時,程式碼將會阻塞,直到其他 goroutine 從 channel 中讀取一些元素,騰出空間。 + ```Go ch := make(chan type, value) @@ -105,6 +111,7 @@ ch := make(chan type, value) 當 value = 0 時,channel 是無緩衝阻塞讀寫的,當 value > 0 時,channel 有緩衝、是非阻塞的,直到寫滿 value 個元素才阻塞寫入。 我們看一下下面這個例子,你可以在自己本機測試一下,修改相應的 value 值 + ```Go package main @@ -124,6 +131,7 @@ func main() { ## Range 和 Close 上面這個例子中,我們需要讀取兩次 c,這樣不是很方便,Go 考慮到了這一點,所以也可以透過 range,像操作 slice 或者 map 一樣操作快取型別的 channel,請看下面的例子 + ```Go package main @@ -160,6 +168,7 @@ func main() { 我們上面介紹的都是隻有一個 channel 的情況,那麼如果存在多個 channel 的時候,我們該如何操作呢,Go 裡面提供了一個關鍵字`select`,透過 `select` 可以監聽 channel 上的資料流動。 `select` 預設是阻塞的,只有當監聽的 channel 中有傳送或接收可以進行時才會執行,當多個 channel 都準備好的時候,select 會隨機選擇其中一個執行。 + ```Go package main @@ -192,6 +201,7 @@ func main() { } ``` 在 `select` 裡面還有 default 語法,`select`其實就是類似 switch 的功能,default 就是當監聽的 channel 都沒有準備好的時候,預設執行的(select 不再阻塞等待 channel)。 + ```Go select { @@ -203,6 +213,7 @@ default: ``` ## 超時 有時候會出現 goroutine 阻塞的情況,那麼我們如何避免整個程式進入阻塞的情況呢?我們可以利用 select 來設定超時,透過如下的方式實現: + ```Go func main() { diff --git a/zh-tw/02.8.md b/zh-tw/02.8.md index b012d79f..e236ca2b 100644 --- a/zh-tw/02.8.md +++ b/zh-tw/02.8.md @@ -1,8 +1,8 @@ # 2.8 總結 這一章我們主要介紹了 Go 語言的一些語法,透過語法我們可以發現 Go 是多麼的簡單,只有二十五個關鍵字。讓我們再來回顧一下這些關鍵字都是用來幹什麼的。 -```Go +```Go break default func interface select case defer go map struct chan else goto package switch diff --git a/zh-tw/03.2.md b/zh-tw/03.2.md index 1908a73e..941db050 100644 --- a/zh-tw/03.2.md +++ b/zh-tw/03.2.md @@ -3,8 +3,8 @@ 前面小節已經介紹了 Web 是基於 http 協議的一個服務,Go 語言裡面提供了一個完善的 net/http 套件,透過 http 套件可以很方便的建立起來一個可以執行的 Web 服務。同時使用這個套件能很簡單地對 Web 的路由,靜態檔案,模版,cookie 等資料進行設定和操作。 ## http 套件建立 Web 伺服器 -```Go +```Go package main import ( @@ -34,8 +34,8 @@ func main() { log.Fatal("ListenAndServe: ", err) } } - ``` + 上面這個程式碼,我們 build 之後,然後執行 web.exe,這個時候其實已經在 9090 埠監聽 http 連結請求了。 在瀏覽器輸入`http://localhost:9090` diff --git a/zh-tw/03.3.md b/zh-tw/03.3.md index acc289d6..6f59cf96 100644 --- a/zh-tw/03.3.md +++ b/zh-tw/03.3.md @@ -36,6 +36,7 @@ Handler:處理請求和產生回傳資訊的處理邏輯 前面小節的程式碼裡面我們可以看到,Go 是透過一個函式 `ListenAndServe` 來處理這些事情的,這個底層其實這樣處理的:初始化一個 server 物件,然後呼叫了`net.Listen("tcp", addr)`,也就是底層用 TCP 協議建立了一個服務,然後監聽我們設定的埠。 下面程式碼來自 Go 的 http 套件的原始碼,透過下面的程式碼我們可以看到整個的 http 處理過程: + ```Go func (srv *Server) Serve(l net.Listener) error { @@ -67,8 +68,8 @@ func (srv *Server) Serve(l net.Listener) error { go c.serve() } } - ``` + 監聽之後如何接收客戶端的請求呢?上面程式碼執行監聽埠之後,呼叫了`srv.Serve(net.Listener)`函式,這個函式就是處理接收客戶端的請求資訊。這個函式裡面起了一個`for{}`,首先透過 Listener 接收請求,其次建立一個 Conn,最後單獨開了一個 goroutine,把這個請求的資料當做參數扔給這個 conn 去服務:`go c.serve()`。這個就是高併發體現了,使用者的每一次請求都是在一個新的 goroutine 去服務,相互不影響。 那麼如何具體分配到相應的函式來處理請求呢?conn 首先會解析 request:`c.readRequest()`,然後取得相應的 handler:`handler := c.server.Handler`,也就是我們剛才在呼叫函式 `ListenAndServe` 時候的第二個參數,我們前面例子傳遞的是 nil,也就是為空,那麼預設取得`handler = DefaultServeMux`,那麼這個變數用來做什麼的呢?對,這個變數就是一個路由器,它用來匹配 url 跳轉到其相應的 handle 函式,那麼這個我們有設定過嗎 ? 有,我們呼叫的程式碼裡面第一句不是呼叫了`http.HandleFunc("/", sayhelloName)`嘛。這個作用就是註冊了請求`/`的路由規則,當請求 uri 為"/",路由就會轉到函式 sayhelloName,DefaultServeMux 會呼叫 ServeHTTP 方法,這個方法內部其實就是呼叫 sayhelloName 本身,最後透過寫入 response 的資訊反饋到客戶端。 diff --git a/zh-tw/03.4.md b/zh-tw/03.4.md index 20a13037..fa853127 100644 --- a/zh-tw/03.4.md +++ b/zh-tw/03.4.md @@ -8,6 +8,7 @@ Go 的 http 有兩個核心功能:Conn、ServeMux 與我們一般編寫的 http 伺服器不同, Go 為了實現高併發和高效能, 使用了 goroutines 來處理 Conn 的讀寫事件, 這樣每個請求都能保持獨立,相互不會阻塞,可以高效的回應網路事件。這是 Go 高效的保證。 Go 在等待客戶端請求裡面是這樣寫的: + ```Go c, err := srv.newConn(rw) @@ -15,14 +16,15 @@ if err != nil { continue } go c.serve() - ``` + 這裡我們可以看到客戶端的每次請求都會建立一個 Conn,這個 Conn 裡面儲存了該次請求的資訊,然後再傳遞到對應的 handler,該 handler 中便可以讀取到相應的 header 資訊,這樣保證了每個請求的獨立性。 ## ServeMux 的自訂 我們前面小節講述 conn.server 的時候,其實內部是呼叫了 http 套件預設的路由器,透過路由器把本次請求的資訊傳遞到了後端的處理函式。那麼這個路由器是怎麼實現的呢? 它的結構如下: + ```Go type ServeMux struct { @@ -30,8 +32,8 @@ type ServeMux struct { m map[string]muxEntry // 路由規則,一個 string 對應一個 mux 實體,這裡的 string 就是註冊的路由表示式 hosts bool // 是否在任意的規則中帶有 host 資訊 } - ``` + 下面看一下 muxEntry ```Go @@ -42,17 +44,19 @@ type muxEntry struct { pattern string //匹配字串 } - ``` + 接著看一下 Handler 的定義 + ```Go type Handler interface { ServeHTTP(ResponseWriter, *Request) // 路由實現器 } - ``` + Handler 是一個介面,但是前一小節中的 `sayhelloName` 函式並沒有實現 ServeHTTP 這個介面,為什麼能新增呢?原來在 http 套件裡面還定義了一個型別`HandlerFunc`,我們定義的函式 `sayhelloName` 就是這個 HandlerFunc 呼叫之後的結果,這個型別預設就實現了 ServeHTTP 這個介面,即我們呼叫了 HandlerFunc(f),強制型別轉換 f 成為 HandlerFunc 型別,這樣 f 就擁有了 ServeHTTP 方法。 + ```Go type HandlerFunc func(ResponseWriter, *Request) @@ -63,6 +67,7 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { } ``` 路由器裡面儲存好了相應的路由規則之後,那麼具體的請求又是怎麼分發的呢?請看下面的程式碼,預設的路由器實現了`ServeHTTP`: + ```Go func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { @@ -78,6 +83,7 @@ func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { 如上所示路由器接收到請求之後,如果是`*`那麼關閉連結,不然呼叫`mux.Handler(r)`回傳對應設定路由的處理 Handler,然後執行`h.ServeHTTP(w, r)` 也就是呼叫對應路由的 handler 的 ServerHTTP 介面,那麼 mux.Handler(r)怎麼處理的呢? + ```Go func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { @@ -112,6 +118,7 @@ func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { 透過上面這個介紹,我們了解了整個路由過程,Go 其實支援外部實現的路由器 `ListenAndServe`的第二個參數就是用以配置外部路由器的,它是一個 Handler 介面,即外部路由器只要實現了 Handler 介面就可以,我們可以在自己實現的路由器的 ServeHTTP 裡面實現自訂路由功能。 如下程式碼所示,我們自己實現了一個簡易的路由器 + ```Go package main diff --git a/zh-tw/04.1.md b/zh-tw/04.1.md index 90192390..c09a896e 100644 --- a/zh-tw/04.1.md +++ b/zh-tw/04.1.md @@ -19,8 +19,8 @@ 上面提交表單到伺服器的`/login`,當用戶輸入資訊點選登入之後,會跳轉到伺服器的路由 `login` 裡面,我們首先要判斷這個是什麼方式傳遞過來,POST 還是 GET 呢? http 套件裡面有一個很簡單的方式就可以取得,我們在前面 web 的例子的基礎上來看看怎麼處理 login 頁面的 form 資料 -```Go +```Go package main import ( @@ -65,8 +65,8 @@ func main() { log.Fatal("ListenAndServe: ", err) } } - ``` + 透過上面的程式碼我們可以看出取得請求方法是透過`r.Method`來完成的,這是個字串型別的變數,回傳 GET, POST, PUT 等 method 資訊。 login 函式中我們根據`r.Method`來判斷是顯示登入介面還是處理登入邏輯。當 GET 方式請求時顯示登入介面,其他方式請求時則處理登入邏輯,如查詢資料庫、驗證登入資訊等。 @@ -90,8 +90,8 @@ login 函式中我們根據`r.Method`來判斷是顯示登入介面還是處理 圖 4.2 伺服器端列印接收到的資訊 `request.Form`是一個 url.Values 型別,裡面儲存的是對應的類似 `key=value` 的資訊,下面展示了可以對 form 資料進行的一些操作: -```Go +```Go v := url.Values{} v.Set("name", "Ava") v.Add("friend", "Jess") @@ -101,8 +101,8 @@ v.Add("friend", "Zoe") fmt.Println(v.Get("name")) fmt.Println(v.Get("friend")) fmt.Println(v["friend"]) - ``` + >**Tips**: >Request 本身也提供了 FormValue()函式來取得使用者提交的參數。如 r.Form["username"]也可寫成 r.FormValue("username")。呼叫 r.FormValue 時會自動呼叫 r.ParseForm,所以不必提前呼叫。r.FormValue 只會回傳同名參數中的第一個,若參數不存在則回傳空字串。 diff --git a/zh-tw/04.2.md b/zh-tw/04.2.md index 31aec992..6d04e526 100644 --- a/zh-tw/04.2.md +++ b/zh-tw/04.2.md @@ -6,8 +6,8 @@ ## 必填欄位 你想要確保從一個表單元素中得到一個值,例如前面小節裡面的使用者名稱,我們如何處理呢?Go 有一個內建函式 `len` 可以取得字串的長度,這樣我們就可以透過 len 來取得資料的長度,例如: -```Go +```Go if len(r.Form["username"][0])==0{ //為空的處理 } @@ -18,8 +18,8 @@ if len(r.Form["username"][0])==0{ 你想要確保一個表單輸入框中取得的只能是數字,例如,你想透過表單取得某個人的具體年齡是 50 歲還是 10 歲,而不是像“一把年紀了”或“年輕著呢”這種描述 如果我們是判斷正整數,那麼我們先轉化成 int 型別,然後進行處理 -```Go +```Go getint,err:=strconv.Atoi(r.Form.Get("age")) if err!=nil{ //數字轉化出錯了,那麼可能就不是數字 @@ -31,8 +31,8 @@ if getint >100 { } ``` 還有一種方式就是正則匹配的方式 -```Go +```Go if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m { return false } @@ -43,8 +43,8 @@ if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m { ## 中文 有時候我們想透過表單元素取得一個使用者的中文名字,但是又為了保證取得的是正確的中文,我們需要進行驗證,而不是使用者隨便的一些輸入。對於中文我們目前有兩種方式來驗證,可以使用 `unicode` 套件提供的 `func Is(rangeTab *RangeTable, r rune) bool` 來驗證,也可以使用正則方式來驗證,這裡使用最簡單的正則方式,如下程式碼所示 -```Go +```Go if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m { return false } @@ -53,28 +53,28 @@ if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m { 我們期望透過表單元素取得一個英文值,例如我們想知道一個使用者的英文名,應該是 astaxie,而不是 asta 謝。 我們可以很簡單的透過正則驗證資料: -```Go +```Go if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m { return false } - ``` + ## 電子郵件地址 你想知道使用者輸入的一個 Email 地址是否正確,透過如下這個方式可以驗證: -```Go +```Go if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m { fmt.Println("no") }else{ fmt.Println("yes") } - ``` + ## 手機號碼 你想要判斷使用者輸入的手機號碼是否正確,透過正則也可以驗證: -```Go +```Go if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m { return false } @@ -92,8 +92,8 @@ if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile") ``` 那麼我們可以這樣來驗證 -```Go +```Go slice:=[]string{"apple","pear","banana"} v := r.Form.Get("fruit") @@ -113,8 +113,8 @@ return false 女 ``` 那我們也可以類似下拉選單的做法一樣 -```Go +```Go slice:=[]string{"1","2"} for _, v := range slice { @@ -133,8 +133,8 @@ return false 網球 ``` 對於複選框我們的驗證和單選有點不一樣,因為接收到的資料是一個 slice -```Go +```Go slice:=[]string{"football","basketball","tennis"} a:=Slice_diff(r.Form["interest"],slice) if a == nil{ @@ -150,8 +150,8 @@ return false ,使用者在日程表中安排 8 月份的第 45 天開會,或者提供未來的某個時間作為生日。 Go 裡面提供了一個 time 的處理套件,我們可以把使用者的輸入年月日轉化成相應的時間,然後進行邏輯判斷 -```Go +```Go t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) fmt.Printf("Go launched at %s\n", t.Local()) ``` @@ -159,8 +159,8 @@ fmt.Printf("Go launched at %s\n", t.Local()) ## 身份證號碼 如果我們想驗證表單輸入的是否是身份證,透過正則也可以方便的驗證,但是身份證有 15 位和 18 位,我們兩個都需要驗證 -```Go +```Go //驗證 15 位身份證,15 位的是全部數字 if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m { return false @@ -170,8 +170,8 @@ if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m { if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m { return false } - ``` + 上面列出了我們一些常用的伺服器端的表單元素驗證,希望透過這個引匯入門,能夠讓你對 Go 的資料驗證有所了解,特別是 Go 裡面的正則處理。 ## links diff --git a/zh-tw/04.3.md b/zh-tw/04.3.md index 6961e7df..d1ff7172 100644 --- a/zh-tw/04.3.md +++ b/zh-tw/04.3.md @@ -14,8 +14,8 @@ 我們看 4.1 小節的例子 -```Go +```Go fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //輸出到伺服器端 fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) template.HTMLEscape(w, []byte(r.Form.Get("username"))) //輸出到客戶端 @@ -27,8 +27,8 @@ template.HTMLEscape(w, []byte(r.Form.Get("username"))) //輸出到客戶端 圖 4.3 Javascript 過濾之後的輸出 Go 的 html/template 套件預設幫你過濾了 html 標籤,但是有時候你只想要輸出這個``看起來正常的資訊,該怎麼處理?請使用 text/template。請看下面的例子: -```Go +```Go import "text/template" ... t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) @@ -39,8 +39,8 @@ err = t.ExecuteTemplate(out, "T", " Hello, ! 或者使用 template.HTML 型別 -```Go +```Go import "html/template" ... t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) @@ -53,8 +53,8 @@ err = t.ExecuteTemplate(out, "T", template.HTML("