Go CheatSheet 是對于 Go 學(xué)習(xí)/實(shí)踐過程中的語法與技巧進(jìn)行盤點(diǎn),,其屬于 Awesome CheatSheet 系列,,致力于提升學(xué)習(xí)速度與研發(fā)效能,,即可以將其當(dāng)做速查手冊,,也可以作為輕量級的入門學(xué)習(xí)資料,。 本文參考了許多優(yōu)秀的文章與代碼示范,,統(tǒng)一聲明在了 Go Links,;如果希望深入了解某方面的內(nèi)容,可以繼續(xù)閱讀 Go 開發(fā):語法基礎(chǔ)與工程實(shí)踐,,或者前往 coding-snippets/go 查看使用 Go 解決常見的數(shù)據(jù)結(jié)構(gòu)與算法,、設(shè)計(jì)模式、業(yè)務(wù)功能方面的代碼實(shí)現(xiàn),。
環(huán)境配置與語法基礎(chǔ)
可以前往這里下載
Go SDK 安裝包,,或者使用 brew 等包管理器安裝。go 命令依賴于 $GOPATH 環(huán)境變量進(jìn)行代碼組織,,多項(xiàng)目情況下也可以使用 ln
進(jìn)行目錄映射以方便進(jìn)行項(xiàng)目管理,。GOPATH 允許設(shè)置多個(gè)目錄,每個(gè)目錄都會包含三個(gè)子目錄:src 用于存放源代碼,,pkg
用于存放編譯后生成的文件,,bin 用于存放編譯后生成的可執(zhí)行文件。
環(huán)境配置完畢后,,可以使用 go get 獲取依賴,,go run 運(yùn)行程序,go build
來編譯項(xiàng)目生成與包名(文件夾名)一致的可執(zhí)行文件。Golang 1.8 之后支持 dep 依賴管理工具,,對于空的項(xiàng)目使用 dep init
初始化依賴配置,,其會生成 Gopkg.toml Gopkg.lock vendor/
這三個(gè)文件(夾)。
我們可以使用 dep ensure -add github.com/pkg/errors
添加依賴,,運(yùn)行之后,,其會在 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 實(shí)現(xiàn)簡單的 HTTP 服務(wù)器:
package main
import "github.com/astaxie/beego"
func main() {
beego.Run()
}
Go 并沒有相對路徑引入,而是以文件夾為單位定義模塊,,譬如我們新建名為 math 的文件夾,,然后使用 package math
來聲明該文件中函數(shù)所屬的模塊。
import (
mongo "mywebapp/libs/mongodb/db" // 對引入的模塊重命名
_ "mywebapp/libs/mysql/db" // 使用空白下劃線表示僅調(diào)用其初始化函數(shù)
)
外部引用該模塊是需要使用工作區(qū)間或者 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 規(guī)定每個(gè)源文件的首部需要進(jìn)行包聲明,,可執(zhí)行文件默認(rèn)放在 main 包中;而各個(gè)包中默認(rèn)首字母大寫的函數(shù)作為其他包可見的導(dǎo)出函數(shù),,而小寫函數(shù)則默認(rèn)外部不可見的私有函數(shù),。
表達(dá)式與控制流
變量聲明與賦值
作為強(qiáng)類型靜態(tài)語言,Go 允許我們在變量之后標(biāo)識數(shù)據(jù)類型,,也為我們提供了自動類型推導(dǎo)的功能,。
// 聲明三個(gè)變量,皆為 bool 類型
var c, python, java bool
// 聲明不同類型的變量,,并且賦值
var i bool, j int = true, 2
// 復(fù)雜變量聲明
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 中,,如果我們需要比較兩個(gè)復(fù)雜對象的相似性,可以使用 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 提供了增強(qiáng)型的 if 語句進(jìn)行條件判斷:
// 基礎(chǔ)形式
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 語句:
// 基礎(chǔ)格式
switch operatingSystem {
case "darwin":
fmt.Println("Mac OS Hipster")
// 默認(rèn) break,,不需要顯式聲明
case "linux":
fmt.Println("Linux Geek")
default:
// Windows, BSD, ...
fmt.Println("Other")
}
// 類似于 if,,可以在條件之前添加自定義語句
switch os := runtime.GOOS; os {
case "darwin": ...
}
// 使用 switch 語句進(jìn)行類型判斷:
switch v := anything.(type) {
case string:
fmt.Println(v)
case int32, int64:
...
default:
fmt.Println("unknown")
}
Switch 中也支持進(jìn)行比較:
number := 42
switch {
case number < 42:
fmt.Println("Smaller")
case number == 42:
fmt.Println("Equal")
case number > 42:
fmt.Println("Greater")
}
或者進(jìn)行多條件匹配:
var char byte = '?'
switch char {
case ' ', '?', '&', '=', '#', '+', '%':
fmt.Println("Should escape")
}
循環(huán)
Go 支持使用 for 語句進(jìn)行循環(huán),不存在 while 或者 until:
for i := 1; i < 10; i++ {
}
// while - loop
for ; i < 10; {
}
// 單條件情況下可以忽略分號
for i < 10 {
}
// ~ while (true)
for {
}
我們也可以使用 range 函數(shù),,對于 Arrays 與 Slices 進(jìn)行遍歷:
// loop over an array/a slice
for i, e := range a {
// i 表示下標(biāo),,e 表示元素
}
// 僅需要元素
for _, e := range a {
// e is the element
}
// 或者僅需要下標(biāo)
for i := range a {
}
// 定時(shí)執(zhí)行
for range time.Tick(time.Second) {
// do it once a sec
}
Function: 函數(shù)
定義,參數(shù)與返回值
// 簡單函數(shù)定義
func functionName() {}
// 含參函數(shù)定義
func functionName(param1 string, param2 int) {}
// 多個(gè)相同類型參數(shù)的函數(shù)定義
func functionName(param1, param2 int) {}
// 函數(shù)表達(dá)式定義
add := func(a, b int) int {
return a + b
}
Go 支持函數(shù)的最后一個(gè)參數(shù)使用 ... 設(shè)置為不定參數(shù),,即可以傳入一個(gè)或多個(gè)參數(shù)值:
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 作為函數(shù)參數(shù)傳入,,以實(shí)現(xiàn)回調(diào)函數(shù)的功能:
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 不是函數(shù)式語言,但是也可以用其實(shí)現(xiàn)柯里函數(shù)(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 支持多個(gè)返回值:
// 返回單個(gè)值
func functionName() int {
return 42
}
// 返回多個(gè)值
func returnMulti() (int, string) {
return 42, "foobar"
}
var x, str = returnMulti()
// 命名返回多個(gè)值
func returnMulti2() (n int, s string) {
n = 42
s = "foobar"
// n and s will be returned
return
}
var x, str = returnMulti2()
閉包: Closure
Go 同樣支持詞法作用域與變量保留,,因此我們可以使用閉包來訪問函數(shù)定義處外層的變量:
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!)
}
函數(shù)執(zhí)行
Go 中提供了 defer 關(guān)鍵字,允許將某個(gè)語句的執(zhí)行推遲到函數(shù)返回語句之前:
func read(...) (...) {
f, err := os.Open(file)
...
defer f.Close()
...
return .. // f will be closed
異常處理
Go 語言中并不存在 try-catch 等異常處理的關(guān)鍵字,,對于那些可能返回異常的函數(shù),,只需要在函數(shù)返回值中添加額外的 Error 類型的返回值:
type error interface {
Error() string
}
某個(gè)可能返回異常的函數(shù)調(diào)用方式如下:
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 函數(shù),所謂
panic,,即是未獲得預(yù)期結(jié)果,,常用于拋出異常結(jié)果,。譬如當(dāng)我們獲得了某個(gè)函數(shù)返回的異常,卻不知道如何處理或者不需要處理時(shí),,可以直接通過
panic 函數(shù)中斷當(dāng)前運(yùn)行,,打印出錯(cuò)誤信息、Goroutine 追蹤信息,,并且返回非零的狀態(tài)碼:
_, err := os.Create("/tmp/file")
if err != nil {
panic(err)
}
數(shù)據(jù)類型與結(jié)構(gòu)
類型綁定與初始化
Go 中的 type 關(guān)鍵字能夠?qū)δ硞€(gè)類型進(jìn)行重命名:
// IntSlice 并不等價(jià)于 []int,,但是可以利用類型轉(zhuǎn)換進(jìn)行轉(zhuǎn)換
type IntSlice []int
a := IntSlice{1, 2}
可以使用 T(v) 或者 obj.(T) 進(jìn)行類型轉(zhuǎn)換,obj.(T) 僅針對 interface{} 類型起作用:
t := obj.(T) // if obj is not T, error
t, ok := obj.(T) // if obj is not T, ok = false
// 類型轉(zhuǎn)換與判斷
str, ok := val.(string);
基本數(shù)據(jù)類型
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 都可以用來表示序列數(shù)據(jù),,二者也有著一定的關(guān)聯(lián),。
Array
其中 Array 用于表示固定長度的,相同類型的序列對象,,可以使用如下形式創(chuàng)建:
[N]Type
[N]Type{value1, value2, ..., valueN}
// 由編譯器自動計(jì)算數(shù)目
[...]Type{value1, value2, ..., valueN}
其具體使用方式為:
// 數(shù)組聲明
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 內(nèi)置了 len 與 cap 函數(shù),,用于獲取數(shù)組的尺寸與容量:
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),。這也就意味著,,當(dāng)進(jìn)行數(shù)組拷貝,,或者函數(shù)調(diào)用中的參數(shù)傳值時(shí),,會復(fù)制所有的元素副本,而非僅僅傳遞指針或者引用,。顯而易見,,這種復(fù)制的代價(jià)會較為昂貴。
Slice
Slice 為我們提供了更為靈活且輕量級地序列類型操作,,可以使用如下方式創(chuàng)建 Slice:
// 使用內(nèi)置函數(shù)創(chuàng)建
make([]Type, length, capacity)
make([]Type, length)
// 聲明為不定長度數(shù)組
[]Type{}
[]Type{value1, value2, ..., valueN}
// 對現(xiàn)有數(shù)組進(jìn)行切片轉(zhuǎn)換
array[:]
array[:2]
array[2:]
array[2:3]
不同于 Array,,Slice 可以看做更為靈活的引用類型(Reference
Type),它并不真實(shí)地存放數(shù)組值,,而是包含數(shù)組指針(ptr),,len,cap 三個(gè)屬性的結(jié)構(gòu)體,。換言之,,Slice
可以看做對于數(shù)組中某個(gè)段的描述,包含了指向數(shù)組的指針,,段長度,,以及段的最大潛在長度,其結(jié)構(gòu)如下圖所示:
// 創(chuàng)建 len 為 5,,cap 為 5 的 Slice
s := make([]byte, 5)
// 對 Slice 進(jìn)行二次切片,,此時(shí) len 為 2,cap 為 3
s = s[2:4]
// 恢復(fù) Slice 的長度
s = s[:cap(s)]
需要注意的是,, 切片操作并不會真實(shí)地復(fù)制 Slice 中值,,只是會創(chuàng)建新的指向原數(shù)組的指針,,這就保證了切片操作和操作數(shù)組下標(biāo)有著相同的高效率。不過如果我們修改 Slice 中的值,,那么其會 真實(shí)修改底層數(shù)組中的值,,也就會體現(xiàn)到原有的數(shù)組中:
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 提供了內(nèi)置的 append 函數(shù),來動態(tài)為 Slice 添加數(shù)據(jù),,該函數(shù)會返回新的切片對象,,包含了原始的 Slice 中值以及新增的值。如果原有的 Slice 的容量不足以存放新增的序列,,那么會自動分配新的內(nèi)存:
// 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)
// 使用 ... 來自動展開數(shù)組
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"}
我們也可以使用內(nèi)置的 copy 函數(shù),,進(jìn)行 Slice 的復(fù)制,該函數(shù)支持對于不同長度的 Slice 進(jìn)行復(fù)制,,其會自動使用最小的元素?cái)?shù)目,。同時(shí),copy 函數(shù)還能夠自動處理使用了相同的底層數(shù)組之間的 Slice 復(fù)制,,以避免額外的空間浪費(fèi),。
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
// 刪除某個(gè)鍵
delete(m, "key")
// 測試該鍵對應(yīng)的值是否存在
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: 結(jié)構(gòu)體與接口
Struct: 結(jié)構(gòu)體
Go 語言中并不存在類的概念,只有結(jié)構(gòu)體,,結(jié)構(gòu)體可以看做屬性的集合,,同時(shí)可以為其定義方法。
// 聲明結(jié)構(gòu)體
type Vertex struct {
// 結(jié)構(gòu)體的屬性,,同樣遵循大寫導(dǎo)出,,小寫私有的原則
X, Y int
z bool
}
// 也可以聲明隱式結(jié)構(gòu)體
point := struct {
X, Y int
}{1, 2}
// 創(chuàng)建結(jié)構(gòu)體實(shí)例
var v = Vertex{1, 2}
// 讀取或者設(shè)置屬性
v.X = 4;
// 顯示聲明鍵
var v = Vertex{X: 1, Y: 2}
// 聲明數(shù)組
var v = []Vertex{{1,2},{5,2},{5,5}}
方法的聲明也非常簡潔,只需要在 func 關(guān)鍵字與函數(shù)名之間聲明結(jié)構(gòu)體指針即可,,該結(jié)構(gòu)體會在不同的方法間進(jìn)行復(fù)制:
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// Call method
v.Abs()
對于那些需要修改當(dāng)前結(jié)構(gòu)體對象的方法,,則需要傳入指針:
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 結(jié)構(gòu)體對象的指針類型為 *Vertex
var s *Vertex = new(Vertex)
Interface: 接口
Go 允許我們通過定義接口的方式來實(shí)現(xiàn)多態(tài)性:
// 接口聲明
type Awesomizer interface {
Awesomize() string
}
// 結(jié)構(gòu)體并不需要顯式實(shí)現(xiàn)接口
type Foo struct {}
// 而是通過實(shí)現(xiàn)所有接口規(guī)定的方法的方式,來實(shí)現(xiàn)接口
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
慣用的思路是先定義接口,,再定義實(shí)現(xiàn),,最后定義使用的方法:
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 也為我們提供了另一種接口的實(shí)現(xiàn)方案,我們可以不在具體的實(shí)現(xiàn)處定義接口,,而是在需要用到該接口的地方,,該模式為:
func funcName(a INTERFACETYPE) CONCRETETYPE
定義接口:
package animals
type Dog struct{}
func (a Dog) Speaks() string { return "woof" }
/** 在需要使用實(shí)現(xiàn)的地方定義接口 **/
package circus
type Speaker interface {
Speaks() string
}
func Perform(a Speaker) { return a.Speaks() }
Embedding
Go 語言中并沒有子類繼承這樣的概念,而是通過嵌入(Embedding)的方式來實(shí)現(xiàn)類或者接口的組合,。
// ReadWriter 的實(shí)現(xiàn)需要同時(shí)滿足 Reader 與 Writer
type ReadWriter interface {
Reader
Writer
}
// Server 暴露了所有 Logger 結(jié)構(gòu)體的方法
type Server struct {
Host string
Port int
*log.Logger
}
// 初始化方式并未受影響
server := &Server{"localhost", 80, log.New(...)}
// 卻可以直接調(diào)用內(nèi)嵌結(jié)構(gòu)體的方法,,等價(jià)于 server.Logger.Log(...)
server.Log(...)
// 內(nèi)嵌結(jié)構(gòu)體的名詞即是類型名
var logger *log.Logger = server.Logger
并發(fā)編程
Goroutines
Goroutines 是輕量級的線程,可以參考并發(fā)編程導(dǎo)論一文中的進(jìn)程,、線程與協(xié)程的討論,;Go 為我們提供了非常便捷的 Goroutines 語法:
// 普通函數(shù)
func doStuff(s string) {
}
func main() {
// 使用命名函數(shù)創(chuàng)建 Goroutine
go doStuff("foobar")
// 使用匿名內(nèi)部函數(shù)創(chuàng)建 Goroutine
go func (x int) {
// function body goes here
}(42)
}
Channels
信道(Channel)是帶有類型的管道,可以用于在不同的 Goroutine 之間傳遞消息,,其基礎(chǔ)操作如下:
// 創(chuàng)建類型為 int 的信道
ch := make(chan int)
// 向信道中發(fā)送值
ch <- 42
// 從信道中獲取值
v := <-ch
// 讀取,,并且判斷其是否關(guān)閉
v, ok := <-ch
// 讀取信道,,直至其關(guān)閉
for i := range ch {
fmt.Println(i)
}
譬如我們可以在主線程中等待來自 Goroutine 的消息,并且輸出:
// 創(chuàng)建信道
messages := make(chan string)
// 執(zhí)行 Goroutine
go func() { messages <- "ping" }()
// 阻塞,,并且等待消息
msg := <-messages
// 使用信道進(jìn)行并發(fā)地計(jì)算,,并且阻塞等待結(jié)果
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 從 c 中接收
如上創(chuàng)建的是無緩沖型信道(Non-buffered Channels),其是阻塞型信道,;當(dāng)沒有值時(shí)讀取方會持續(xù)阻塞,,而寫入方則是在無讀取時(shí)阻塞。我們可以創(chuàng)建緩沖型信道(Buffered Channel),,其讀取方在信道被寫滿前都不會被阻塞:
ch := make(chan int, 100)
// 發(fā)送方也可以主動關(guān)閉信道
close(ch)
Channel 同樣可以作為函數(shù)參數(shù),,并且我們可以顯式聲明其是用于發(fā)送信息還是接收信息,從而增加程序的類型安全度:
// ping 函數(shù)用于發(fā)送信息
func ping(pings chan<- string, msg string) {
pings <- msg
}
// pong 函數(shù)用于從某個(gè)信道中接收信息,,然后發(fā)送到另一個(gè)信道中
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)
}
同步
同步,,是并發(fā)編程中的常見需求,這里我們可以使用 Channel 的阻塞特性來實(shí)現(xiàn) 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 關(guān)鍵字,,用于等待多個(gè)信道的執(zhí)行結(jié)果:
// 創(chuàng)建兩個(gè)信道
c1 := make(chan string)
c2 := make(chan string)
// 每個(gè)信道會以不同時(shí)延輸出不同值
go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}()
// 使用 select 來同時(shí)等待兩個(gè)信道的執(zhí)行結(jié)果
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 命令行工具,,我們可以快速創(chuàng)建 Beego 項(xiàng)目,其目錄組織方式如下:
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 實(shí)例,,并且調(diào)用路由的初始化配置文件:
package main
import (
_ "quickstart/routers"
"github.com/astaxie/beego"
)
func main() {
beego.Run()
}
而在路由的初始化函數(shù)中,我們會聲明各個(gè)路由與控制器之間的映射關(guān)系:
package routers
import (
"quickstart/controllers"
"github.com/astaxie/beego"
)
func init() {
beego.Router("/", &controllers.MainController{})
}
也可以手動指定 Beego 項(xiàng)目中的靜態(tài)資源映射:
beego.SetStaticPath("/down1", "download1")
beego.SetStaticPath("/down2", "download2")
在具體的控制器中,,可以設(shè)置返回?cái)?shù)據(jù),,或者關(guān)聯(lián)的模板名:
package controllers
import (
"github.com/astaxie/beego"
)
type MainController struct {
beego.Controller
}
func (this *MainController) Get() {
this.Data["Website"] = ""
this.Data["Email"] = "[email protected]"
this.TplNames = "index.tpl" // version 1.6 use this.TplName = "index.tpl"
}
DevPractics: 開發(fā)實(shí)踐
文件讀寫
import (
"io/ioutil"
)
...
datFile1, errFile1 := ioutil.ReadFile("file1")
if errFile1 != nil {
panic(errFile1)
}
...
測試
VSCode 可以為函數(shù)自動生成基礎(chǔ)測試用例,并且提供了方便的用例執(zhí)行與調(diào)試的功能,。
/** 交換函數(shù) */
func swap(x *int, y *int) {
x, y = y, x
}
/** 自動生成的測試函數(shù) */
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)
})
}
}