題注:阿里巴巴南京研發中心成立一年來業務與團隊迅速擴充套件,誠求前端/後端等各路英才。歡迎關注 [阿里南京技術專刊] (
https://
zhuanlan。zhihu。com/ali-
nanjing)
,也歡迎投遞簡歷,傳送簡歷到 zixiong。zzx@alibaba-inc。com, 誠邀各路大佬前來指教。
Go CheatSheet 是對於 Go 學習/實踐過程中的語法與技巧進行盤點,其屬於 Awesome CheatSheet 系列,致力於提升學習速度與研發效能,即可以將其當做速查手冊,也可以作為輕量級的入門學習資料。 本文參考了許多優秀的文章與程式碼示範,統一宣告在了 Go Links;如果希望深入瞭解某方面的內容,可以繼續閱讀 Go 開發:語法基礎與工程實踐,或者前往 coding-snippets/go 檢視使用 Go 解決常見的資料結構與演算法、設計模式、業務功能方面的程式碼實現。
環境配置與語法基礎
可以前往這裡下載 Go SDK 安裝包,或者使用 brew 等包管理器安裝。go 命令依賴於 $GOPATH 環境變數進行程式碼組織,多專案情況下也可以使用 ln 進行目錄對映以方便進行專案管理。GOPATH 允許設定多個目錄,每個目錄都會包含三個子目錄:src 用於存放原始碼,pkg 用於存放編譯後生成的檔案,bin 用於存放編譯後生成的可執行檔案。
環境配置完畢後,可以使用 go get 獲取依賴,go run 執行程式,go build 來編譯專案生成與包名(資料夾名)一致的可執行檔案。Golang 1。8 之後支援 dep 依賴管理工具,對於空的專案使用 dep init 初始化依賴配置,其會生成
Gopkg。toml Gopkg。lock vendor/
這三個檔案(夾)。
我們可以使用
dep ensure -add github。com/pkg/errors
新增依賴,執行之後,其會在 toml 檔案中新增如下鎖:
[[constraint]]
name = “github。com/pkg/errors”
version = “0。8。0”
簡單的 Go 中 Hello World 程式碼如下:
package
main
import
“fmt”
func
main
()
{
fmt
。
Println
(
“hello world”
)
}
也可以使用 Beego 實現簡單的 HTTP 伺服器:
package
main
import
“github。com/astaxie/beego”
func
main
()
{
beego
。
Run
()
}
Go 並沒有相對路徑引入,而是以資料夾為單位定義模組,譬如我們新建名為 math 的資料夾,然後使用
package math
來宣告該檔案中函式所屬的模組。
import
(
mongo
“mywebapp/libs/mongodb/db”
//
對引入的模組重新命名
_
“mywebapp/libs/mysql/db”
//
使用空白下劃線表示僅呼叫其初始化函式
)
外部引用該模組是需要使用工作區間或者 vendor 相對目錄,其目錄索引情況如下:
cannot find package “sub/math” in any of:
${PROJECTROOT}/vendor/sub/math (vendor tree)
/usr/local/Cellar/go/1。10/libexec/src/sub/math (from $GOROOT)
${GOPATH}/src/sub/math (from $GOPATH)
Go 規定每個原始檔的首部需要進行包宣告,可執行檔案預設放在 main 包中;而各個包中預設首字母大寫的函式作為其他包可見的匯出函式,而小寫函式則預設外部不可見的私有函式。
表示式與控制流
變數宣告與賦值
作為強型別靜態語言,Go 允許我們在變數之後標識資料型別,也為我們提供了自動型別推導的功能。
// 宣告三個變數,皆為 bool 型別
var
c
,
python
,
java
bool
// 宣告不同型別的變數,並且賦值
var
i
bool
,
j
int
=
true
,
2
// 複雜變數宣告
var
(
ToBe
bool
=
false
MaxInt
uint64
=
1
<<
64
-
1
z
complex128
=
cmplx
。
Sqrt
(
-
5
+
12i
)
)
// 短宣告變數
c
,
python
,
java
:=
true
,
false
,
“no!”
// 宣告常量
const
constant
=
“This is a constant”
在 Go 中,如果我們需要比較兩個複雜物件的相似性,可以使用 reflect。DeepEqual 方法:
m1
:=
map
[
string
]
int
{
“a”
:
1
,
“b”
:
2
,
}
m2
:=
map
[
string
]
int
{
“a”
:
1
,
“b”
:
2
,
}
fmt
。
Println
(
reflect
。
DeepEqual
(
m1
,
m2
))
條件判斷
Go 提供了增強型的 if 語句進行條件判斷:
// 基礎形式
if
x
>
0
{
return
x
}
else
{
return
-
x
}
// 條件判斷之前新增自定義語句
if
a
:=
b
+
c
;
a
<
42
{
return
a
}
else
{
return
a
-
42
}
// 常用的型別判斷
var
val
interface
{}
val
=
“foo”
if
str
,
ok
:=
val
。(
string
);
ok
{
fmt
。
Println
(
str
)
}
Go 也支援使用 Switch 語句:
// 基礎格式
switch
operatingSystem
{
case
“darwin”
:
fmt
。
Println
(
“Mac OS Hipster”
)
// 預設 break,不需要顯式宣告
case
“linux”
:
fmt
。
Println
(
“Linux Geek”
)
default
:
// Windows, BSD, 。。。
fmt
。
Println
(
“Other”
)
}
// 類似於 if,可以在條件之前新增自定義語句
switch
os
:=
runtime
。
GOOS
;
os
{
case
“darwin”
:
。。。
}
// 使用 switch 語句進行型別判斷:
switch
v
:=
anything
。(
type
)
{
case
string
:
fmt
。
Println
(
v
)
case
int32
,
int64
:
。。。
default
:
fmt
。
Println
(
“unknown”
)
}
Switch 中也支援進行比較:
number
:=
42
switch
{
case
number
<
42
:
fmt
。
Println
(
“Smaller”
)
case
number
==
42
:
fmt
。
Println
(
“Equal”
)
case
number
>
42
:
fmt
。
Println
(
“Greater”
)
}
或者進行多條件匹配:
var
char
byte
=
‘?’
switch
char
{
case
‘ ’
,
‘?’
,
‘&’
,
‘=’
,
‘#’
,
‘+’
,
‘%’
:
fmt
。
Println
(
“Should escape”
)
}
迴圈
Go 支援使用 for 語句進行迴圈,不存在 while 或者 until:
for
i
:=
1
;
i
<
10
;
i
++
{
}
// while - loop
for
;
i
<
10
;
{
}
// 單條件情況下可以忽略分號
for
i
<
10
{
}
// ~ while (true)
for
{
}
我們也可以使用 range 函式,對於 Arrays 與 Slices 進行遍歷:
// loop over an array/a slice
for
i
,
e
:=
range
a
{
// i 表示下標,e 表示元素
}
// 僅需要元素
for
_
,
e
:=
range
a
{
// e is the element
}
// 或者僅需要下標
for
i
:=
range
a
{
}
// 定時執行
for
range
time
。
Tick
(
time
。
Second
)
{
// do it once a sec
}
Function: 函式
定義,引數與返回值
// 簡單函式定義
func
functionName
()
{}
// 含參函式定義
func
functionName
(
param1
string
,
param2
int
)
{}
// 多個相同型別引數的函式定義
func
functionName
(
param1
,
param2
int
)
{}
// 函式表示式定義
add
:=
func
(
a
,
b
int
)
int
{
return
a
+
b
}
Go 支援函式的最後一個引數使用 。。。 設定為不定引數,即可以傳入一個或多個引數值:
func
adder
(
args
。。。
int
)
int
{
total
:=
0
for
_
,
v
:=
range
args
{
// Iterates over the arguments whatever the number。
total
+=
v
}
return
total
}
adder
(
1
,
2
,
3
)
// 6
adder
(
9
,
9
)
// 18
nums
:=
[]
int
{
10
,
20
,
30
}
adder
(
nums
。。。
)
// 60
我們也可以使用 Function Stub 作為函式引數傳入,以實現回撥函式的功能:
func
Filter
(
s
[]
int
,
fn
func
(
int
)
bool
)
[]
int
{
var
p
[]
int
// == nil
for
_
,
v
:=
range
s
{
if
fn
(
v
)
{
p
=
append
(
p
,
v
)
}
}
return
p
}
雖然 Go 不是函式式語言,但是也可以用其實現柯里函式(Currying Function):
func
add
(
x
,
y
int
)
int
{
return
x
+
y
}
func
adder
(
x
int
)
(
func
(
int
)
int
)
{
return
func
(
y
int
)
int
{
return
add
(
x
,
y
)
}
}
func
main
()
{
add3
:=
adder
(
3
)
fmt
。
Println
(
add3
(
4
))
// 7
}
Go 支援多個返回值:
// 返回單個值
func
functionName
()
int
{
return
42
}
// 返回多個值
func
returnMulti
()
(
int
,
string
)
{
return
42
,
“foobar”
}
var
x
,
str
=
returnMulti
()
// 命名返回多個值
func
returnMulti2
()
(
n
int
,
s
string
)
{
n
=
42
s
=
“foobar”
// n and s will be returned
return
}
var
x
,
str
=
returnMulti2
()
閉包: Closure
Go 同樣支援詞法作用域與變數保留,因此我們可以使用閉包來訪問函式定義處外層的變數:
func
scope
()
func
()
int
{
outer_var
:=
2
foo
:=
func
()
int
{
return
outer_var
}
return
foo
}
閉包中並不能夠直接修改外層變數,而是會自動重定義新的變數值:
func
outer
()
(
func
()
int
,
int
)
{
outer_var
:=
2
inner
:=
func
()
int
{
outer_var
+=
99
return
outer_var
// => 101 (but outer_var is a newly redefined
}
return
inner
,
outer_var
// => 101, 2 (outer_var is still 2, not mutated by inner!)
}
函式執行
Go 中提供了 defer 關鍵字,允許將某個語句的執行推遲到函式返回語句之前:
func
read
(
。。。
)
(
。。。
)
{
f
,
err
:=
os
。
Open
(
file
)
。。。
defer
f
。
Close
()
。。。
return
。。
// f will be closed
異常處理
Go 語言中並不存在 try-catch 等異常處理的關鍵字,對於那些可能返回異常的函式,只需要在函式返回值中新增額外的 Error 型別的返回值:
type
error
interface
{
Error
()
string
}
某個可能返回異常的函式呼叫方式如下:
import
(
“fmt”
“errors”
)
func
main
()
{
result
,
err
:=
Divide
(
2
,
0
)
if
err
!=
nil
{
fmt
。
Println
(
err
)
}
else
{
fmt
。
Println
(
result
)
}
}
func
Divide
(
value1
int
,
value2
int
)(
int
,
error
)
{
if
(
value2
==
0
){
return
0
,
errors
。
New
(
“value2 mustn‘t be zero”
)
}
return
value1
/
value2
,
nil
}
Go 還為我們提供了 panic 函式,所謂 panic,即是未獲得預期結果,常用於丟擲異常結果。譬如當我們獲得了某個函式返回的異常,卻不知道如何處理或者不需要處理時,可以直接透過 panic 函式中斷當前執行,打印出錯誤資訊、Goroutine 追蹤資訊,並且返回非零的狀態碼:
_
,
err
:=
os
。
Create
(
“/tmp/file”
)
if
err
!=
nil
{
panic
(
err
)
}
資料型別與結構
型別繫結與初始化
Go 中的 type 關鍵字能夠對某個型別進行重新命名:
// IntSlice 並不等價於 []int,但是可以利用型別轉換進行轉換
type
IntSlice
[]
int
a
:=
IntSlice
{
1
,
2
}
可以使用 T(v) 或者 obj。(T) 進行型別轉換,obj。(T) 僅針對 interface{} 型別起作用:
t
:=
obj
。(
T
)
// if obj is not T, error
t
,
ok
:=
obj
。(
T
)
// if obj is not T, ok = false
// 型別轉換與判斷
str
,
ok
:=
val
。(
string
);
基本資料型別
interface
{}
// ~ java Object
bool
// true/false
string
int8
int16
int32
int64
int
// =int32 on 32-bit, =int64 if 64-bit OS
uint8
uint16
uint32
uint64
uintptr
uint
byte
// alias for uint8
rune
// alias for int32, represents a Unicode code point
float32
float64
字串
// 多行字串宣告
hellomsg
:=
`
“Hello” in Chinese is 你好 (’Ni Hao‘)
“Hello” in Hindi is नमस्ते (’Namaste‘)
`
格式化字串:
fmt
。
Println
(
“Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ”
)
// basic print, plus newline
p
:=
struct
{
X
,
Y
int
}{
17
,
2
}
fmt
。
Println
(
“My point:”
,
p
,
“x coord=”
,
p
。
X
)
// print structs, ints, etc
s
:=
fmt
。
Sprintln
(
“My point:”
,
p
,
“x coord=”
,
p
。
X
)
// print to string variable
fmt
。
Printf
(
“%d hex:%x bin:%b fp:%f sci:%e”
,
17
,
17
,
17
,
17。0
,
17。0
)
// c-ish format
s2
:=
fmt
。
Sprintf
(
“%d %f”
,
17
,
17。0
)
// formatted print to string variable
序列型別
Array 與 Slice 都可以用來表示序列資料,二者也有著一定的關聯。
Array
其中 Array 用於表示固定長度的,相同型別的序列物件,可以使用如下形式建立:
[
N
]
Type
[
N
]
Type
{
value1
,
value2
,
。。。
,
valueN
}
// 由編譯器自動計算數目
[
。。。
]
Type
{
value1
,
value2
,
。。。
,
valueN
}
其具體使用方式為:
// 陣列宣告
var
a
[
10
]
int
// 賦值
a
[
3
]
=
42
// 讀取
i
:=
a
[
3
]
// 宣告與初始化
var
a
=
[
2
]
int
{
1
,
2
}
a
:=
[
2
]
int
{
1
,
2
}
a
:=
[
。。。
]
int
{
1
,
2
}
Go 內建了 len 與 cap 函式,用於獲取陣列的尺寸與容量:
var
arr
=
[
3
]
int
{
1
,
2
,
3
}
arr
:=
[
。。。
]
int
{
1
,
2
,
3
}
len
(
arr
)
// 3
cap
(
arr
)
// 3
不同於 C/C++ 中的指標(Pointer)或者 Java 中的物件引用(Object Reference),Go 中的 Array 只是值(Value)。這也就意味著,當進行陣列複製,或者函式呼叫中的引數傳值時,會複製所有的元素副本,而非僅僅傳遞指標或者引用。顯而易見,這種複製的代價會較為昂貴。
Slice
Slice 為我們提供了更為靈活且輕量級地序列型別操作,可以使用如下方式建立 Slice:
// 使用內建函式建立
make
([]
Type
,
length
,
capacity
)
make
([]
Type
,
length
)
// 宣告為不定長度陣列
[]
Type
{}
[]
Type
{
value1
,
value2
,
。。。
,
valueN
}
// 對現有陣列進行切片轉換
array
[:]
array
[:
2
]
array
[
2
:]
array
[
2
:
3
]
不同於 Array,Slice 可以看做更為靈活的引用型別(Reference Type),它並不真實地存放陣列值,而是包含陣列指標(ptr),len,cap 三個屬性的結構體。換言之,Slice 可以看做對於陣列中某個段的描述,包含了指向陣列的指標,段長度,以及段的最大潛在長度,其結構如下圖所示:
// 建立 len 為 5,cap 為 5 的 Slice
s
:=
make
([]
byte
,
5
)
// 對 Slice 進行二次切片,此時 len 為 2,cap 為 3
s
=
s
[
2
:
4
]
// 恢復 Slice 的長度
s
=
s
[:
cap
(
s
)]
需要注意的是, 切片操作並不會真實地複製 Slice 中值,只是會建立新的指向原陣列的指標,這就保證了切片操作和運算元組下標有著相同的高效率。不過如果我們修改 Slice 中的值,那麼其會 真實修改底層陣列中的值,也就會體現到原有的陣列中:
d
:=
[]
byte
{
’r‘
,
’o‘
,
’a‘
,
’d‘
}
e
:=
d
[
2
:]
// e == []byte{’a‘, ’d‘}
e
[
1
]
=
’m‘
// e == []byte{’a‘, ’m‘}
// d == []byte{’r‘, ’o‘, ’a‘, ’m‘}
Go 提供了內建的 append 函式,來動態為 Slice 新增資料,該函式會返回新的切片物件,包含了原始的 Slice 中值以及新增的值。如果原有的 Slice 的容量不足以存放新增的序列,那麼會自動分配新的記憶體:
// len=0 cap=0 []
var
s
[]
int
// len=1 cap=2 [0]
s
=
append
(
s
,
0
)
// len=2 cap=2 [0 1]
s
=
append
(
s
,
1
)
// len=5 cap=8 [0 1 2 3 4]
s
=
append
(
s
,
2
,
3
,
4
)
// 使用 。。。 來自動展開陣列
a
:=
[]
string
{
“John”
,
“Paul”
}
b
:=
[]
string
{
“George”
,
“Ringo”
,
“Pete”
}
a
=
append
(
a
,
b
。。。
)
// equivalent to “append(a, b[0], b[1], b[2])”
// a == []string{“John”, “Paul”, “George”, “Ringo”, “Pete”}
我們也可以使用內建的 copy 函式,進行 Slice 的複製,該函式支援對於不同長度的 Slice 進行復制,其會自動使用最小的元素數目。同時,copy 函式還能夠自動處理使用了相同的底層陣列之間的 Slice 複製,以避免額外的空間浪費。
func
copy
(
dst
,
src
[]
T
)
int
// 申請較大的空間容量
t
:=
make
([]
byte
,
len
(
s
),
(
cap
(
s
)
+
1
)
*
2
)
copy
(
t
,
s
)
s
=
t
對映型別
var
m
map
[
string
]
int
m
=
make
(
map
[
string
]
int
)
m
[
“key”
]
=
42
// 刪除某個鍵
delete
(
m
,
“key”
)
// 測試該鍵對應的值是否存在
elem
,
has_value
:=
m
[
“key”
]
// map literal
var
m
=
map
[
string
]
Vertex
{
“Bell Labs”
:
{
40。68433
,
-
74。39967
},
“Google”
:
{
37。42202
,
-
122。08408
},
}
Struct & Interface: 結構體與介面
Struct: 結構體
Go 語言中並不存在類的概念,只有結構體,結構體可以看做屬性的集合,同時可以為其定義方法。
// 宣告結構體
type
Vertex
struct
{
// 結構體的屬性,同樣遵循大寫匯出,小寫私有的原則
X
,
Y
int
z
bool
}
// 也可以宣告隱式結構體
point
:=
struct
{
X
,
Y
int
}{
1
,
2
}
// 建立結構體例項
var
v
=
Vertex
{
1
,
2
}
// 讀取或者設定屬性
v
。
X
=
4
;
// 顯示宣告鍵
var
v
=
Vertex
{
X
:
1
,
Y
:
2
}
// 宣告陣列
var
v
=
[]
Vertex
{{
1
,
2
},{
5
,
2
},{
5
,
5
}}
方法的宣告也非常簡潔,只需要在 func 關鍵字與函式名之間宣告結構體指標即可,該結構體會在不同的方法間進行復制:
func
(
v
Vertex
)
Abs
()
float64
{
return
math
。
Sqrt
(
v
。
X
*
v
。
X
+
v
。
Y
*
v
。
Y
)
}
// Call method
v
。
Abs
()
對於那些需要修改當前結構體物件的方法,則需要傳入指標:
func
(
v
*
Vertex
)
add
(
n
float64
)
{
v
。
X
+=
n
v
。
Y
+=
n
}
var
p
*
Person
=
new
(
Person
)
// pointer of type Person
Pointer: 指標
// p 是 Vertex 型別
p
:=
Vertex
{
1
,
2
}
// q 是指向 Vertex 的指標
q
:=
&
p
// r 同樣是指向 Vertex 物件的指標
r
:=
&
Vertex
{
1
,
2
}
// 指向 Vertex 結構體物件的指標型別為 *Vertex
var
s
*
Vertex
=
new
(
Vertex
)
Interface: 介面
Go 允許我們透過定義介面的方式來實現多型性:
// 介面宣告
type
Awesomizer
interface
{
Awesomize
()
string
}
// 結構體並不需要顯式實現介面
type
Foo
struct
{}
// 而是透過實現所有介面規定的方法的方式,來實現介面
func
(
foo
Foo
)
Awesomize
()
string
{
return
“Awesome!”
}
type
Shape
interface
{
area
()
float64
}
func
getArea
(
shape
Shape
)
float64
{
return
shape
。
area
()
}
type
Circle
struct
{
x
,
y
,
radius
float64
}
type
Rectangle
struct
{
width
,
height
float64
}
func
(
circle
Circle
)
area
()
float64
{
return
math
。
Pi
*
circle
。
radius
*
circle
。
radius
}
func
(
rect
Rectangle
)
area
()
float64
{
return
rect
。
width
*
rect
。
height
}
func
main
()
{
circle
:=
Circle
{
x
:
0
,
y
:
0
,
radius
:
5
}
rectangle
:=
Rectangle
{
width
:
10
,
height
:
5
}
fmt
。
Printf
(
“Circle area: %f\n”
,
getArea
(
circle
))
fmt
。
Printf
(
“Rectangle area: %f\n”
,
getArea
(
rectangle
))
}
//Circle area: 78。539816
//Rectangle area: 50。000000
慣用的思路是先定義介面,再定義實現,最後定義使用的方法:
package
animals
type
Animal
interface
{
Speaks
()
string
}
// implementation of Animal
type
Dog
struct
{}
func
(
a
Dog
)
Speaks
()
string
{
return
“woof”
}
/** 在需要的地方直接引用 **/
package
circus
import
“animals”
func
Perform
(
a
animal
。
Animal
)
{
return
a
。
Speaks
()
}
Go 也為我們提供了另一種介面的實現方案,我們可以不在具體的實現處定義介面,而是在需要用到該介面的地方,該模式為:
func
funcName
(
a
INTERFACETYPE
)
CONCRETETYPE
定義介面:
package
animals
type
Dog
struct
{}
func
(
a
Dog
)
Speaks
()
string
{
return
“woof”
}
/** 在需要使用實現的地方定義介面 **/
package
circus
type
Speaker
interface
{
Speaks
()
string
}
func
Perform
(
a
Speaker
)
{
return
a
。
Speaks
()
}
Embedding
Go 語言中並沒有子類繼承這樣的概念,而是透過嵌入(Embedding)的方式來實現類或者介面的組合。
// ReadWriter 的實現需要同時滿足 Reader 與 Writer
type
ReadWriter
interface
{
Reader
Writer
}
// Server 暴露了所有 Logger 結構體的方法
type
Server
struct
{
Host
string
Port
int
*
log
。
Logger
}
// 初始化方式並未受影響
server
:=
&
Server
{
“localhost”
,
80
,
log
。
New
(
。。。
)}
// 卻可以直接呼叫內嵌結構體的方法,等價於 server。Logger。Log(。。。)
server
。
Log
(
。。。
)
// 內嵌結構體的名詞即是型別名
var
logger
*
log
。
Logger
=
server
。
Logger
併發程式設計
Goroutines
Goroutines 是輕量級的執行緒,可以參考併發程式設計導論一文中的程序、執行緒與協程的討論;Go 為我們提供了非常便捷的 Goroutines 語法:
// 普通函式
func
doStuff
(
s
string
)
{
}
func
main
()
{
// 使用命名函式建立 Goroutine
go
doStuff
(
“foobar”
)
// 使用匿名內部函式建立 Goroutine
go
func
(
x
int
)
{
// function body goes here
}(
42
)
}
Channels
通道(Channel)是帶有型別的管道,可以用於在不同的 Goroutine 之間傳遞訊息,其基礎操作如下:
// 建立型別為 int 的通道
ch
:=
make
(
chan
int
)
// 向通道中傳送值
ch
<-
42
// 從通道中獲取值
v
:=
<-
ch
// 讀取,並且判斷其是否關閉
v
,
ok
:=
<-
ch
// 讀取通道,直至其關閉
for
i
:=
range
ch
{
fmt
。
Println
(
i
)
}
譬如我們可以在主執行緒中等待來自 Goroutine 的訊息,並且輸出:
// 建立通道
messages
:=
make
(
chan
string
)
// 執行 Goroutine
go
func
()
{
messages
<-
“ping”
}()
// 阻塞,並且等待訊息
msg
:=
<-
messages
// 使用通道進行併發地計算,並且阻塞等待結果
c
:=
make
(
chan
int
)
go
sum
(
s
[:
len
(
s
)
/
2
],
c
)
go
sum
(
s
[
len
(
s
)
/
2
:],
c
)
x
,
y
:=
<-
c
,
<-
c
// 從 c 中接收
如上建立的是無緩衝型通道(Non-buffered Channels),其是阻塞型通道;當沒有值時讀取方會持續阻塞,而寫入方則是在無讀取時阻塞。我們可以建立緩衝型通道(Buffered Channel),其讀取方在通道被寫滿前都不會被阻塞:
ch
:=
make
(
chan
int
,
100
)
// 傳送方也可以主動關閉通道
close
(
ch
)
Channel 同樣可以作為函式引數,並且我們可以顯式宣告其是用於傳送資訊還是接收資訊,從而增加程式的型別安全度:
// ping 函式用於傳送資訊
func
ping
(
pings
chan
<-
string
,
msg
string
)
{
pings
<-
msg
}
// pong 函式用於從某個通道中接收資訊,然後傳送到另一個通道中
func
pong
(
pings
<-
chan
string
,
pongs
chan
<-
string
)
{
msg
:=
<-
pings
pongs
<-
msg
}
func
main
()
{
pings
:=
make
(
chan
string
,
1
)
pongs
:=
make
(
chan
string
,
1
)
ping
(
pings
,
“passed message”
)
pong
(
pings
,
pongs
)
fmt
。
Println
(
<-
pongs
)
}
同步
同步,是併發程式設計中的常見需求,這裡我們可以使用 Channel 的阻塞特性來實現 Goroutine 之間的同步:
func
worker
(
done
chan
bool
)
{
time
。
Sleep
(
time
。
Second
)
done
<-
true
}
func
main
()
{
done
:=
make
(
chan
bool
,
1
)
go
worker
(
done
)
// 阻塞直到接收到訊息
<-
done
}
Go 還為我們提供了 select 關鍵字,用於等待多個通道的執行結果:
// 建立兩個通道
c1
:=
make
(
chan
string
)
c2
:=
make
(
chan
string
)
// 每個通道會以不同時延輸出不同值
go
func
()
{
time
。
Sleep
(
1
*
time
。
Second
)
c1
<-
“one”
}()
go
func
()
{
time
。
Sleep
(
2
*
time
。
Second
)
c2
<-
“two”
}()
// 使用 select 來同時等待兩個通道的執行結果
for
i
:=
0
;
i
<
2
;
i
++
{
select
{
case
msg1
:=
<-
c1
:
fmt
。
Println
(
“received”
,
msg1
)
case
msg2
:=
<-
c2
:
fmt
。
Println
(
“received”
,
msg2
)
}
}
Web 程式設計
HTTP Server
package
main
import
(
“fmt”
“net/http”
)
// define a type for the response
type
Hello
struct
{}
// let that type implement the ServeHTTP method (defined in interface http。Handler)
func
(
h
Hello
)
ServeHTTP
(
w
http
。
ResponseWriter
,
r
*
http
。
Request
)
{
fmt
。
Fprint
(
w
,
“Hello!”
)
}
func
main
()
{
var
h
Hello
http
。
ListenAndServe
(
“localhost:4000”
,
h
)
}
// Here’s the method signature of http。ServeHTTP:
// type Handler interface {
// ServeHTTP(w http。ResponseWriter, r *http。Request)
// }
Beego
利用 Beego 官方推薦的 bee 命令列工具,我們可以快速建立 Beego 專案,其目錄組織方式如下:
quickstart
├──
conf
│
└──
app
。
conf
├──
controllers
│
└──
default
。
go
├──
main
。
go
├──
models
├──
routers
│
└──
router
。
go
├──
static
│
├──
css
│
├──
img
│
└──
js
├──
tests
│
└──
default_test
。
go
└──
views
└──
index
。
tpl
在 main。go 檔案中,我們可以啟動 Beego 例項,並且呼叫路由的初始化配置檔案:
package
main
import
(
_
“quickstart/routers”
“github。com/astaxie/beego”
)
func
main
()
{
beego
。
Run
()
}
而在路由的初始化函式中,我們會宣告各個路由與控制器之間的對映關係:
package
routers
import
(
“quickstart/controllers”
“github。com/astaxie/beego”
)
func
init
()
{
beego
。
Router
(
“/”
,
&
controllers
。
MainController
{})
}
也可以手動指定 Beego 專案中的靜態資源對映:
beego
。
SetStaticPath
(
“/down1”
,
“download1”
)
beego
。
SetStaticPath
(
“/down2”
,
“download2”
)
在具體的控制器中,可以設定返回資料,或者關聯的模板名:
package
controllers
import
(
“github。com/astaxie/beego”
)
type
MainController
struct
{
beego
。
Controller
}
func
(
this
*
MainController
)
Get
()
{
this
。
Data
[
“Website”
]
=
“beego。me”
this
。
Data
[
“Email”
]
=
“astaxie@gmail。com”
this
。
TplNames
=
“index。tpl”
// version 1。6 use this。TplName = “index。tpl”
}
DevPractics: 開發實踐
檔案讀寫
import
(
“io/ioutil”
)
。。。
datFile1
,
errFile1
:=
ioutil
。
ReadFile
(
“file1”
)
if
errFile1
!=
nil
{
panic
(
errFile1
)
}
。。。
測試
VSCode 可以為函式自動生成基礎測試用例,並且提供了方便的用例執行與除錯的功能。
/** 交換函式 */
func
swap
(
x
*
int
,
y
*
int
)
{
x
,
y
=
y
,
x
}
/** 自動生成的測試函式 */
func
Test_swap
(
t
*
testing
。
T
)
{
type
args
struct
{
x
*
int
y
*
int
}
tests
:=
[]
struct
{
name
string
args
args
}{
// TODO: Add test cases。
}
for
_
,
tt
:=
range
tests
{
t
。
Run
(
tt
。
name
,
func
(
t
*
testing
。
T
)
{
swap
(
tt
。
args
。
x
,
tt
。
args
。
y
)
})
}
}