|
|
51CTO旗下网站
|
|
移动端
创建专栏

Go 语法速览与实践清单

本文参考了许多优秀的文章与代码示范,统一声明在了 Go Links;如果希望深入了解某方面的内容,可以继续阅读 Go 开发:语法基础与工程实践,或者前往 coding-snippets/go 查看使用 Go 解决常见的数据结构与算法、设计模式、业务功能方面的代码实现。

作者:张梓雄|2018-04-09 14:26

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 文件中添加如下锁:

  1. [[constraint]] 
  2.   name = "github.com/pkg/errors" 
  3.   version = "0.8.0" 

简单的 Go 中 Hello World 代码如下:

  1. package main 
  2. import "fmt" 
  3. func main() { 
  4.     fmt.Println("hello world"

也可以使用 Beego 实现简单的 HTTP 服务器:

  1. package main 
  2. import "github.com/astaxie/beego" 
  3. func main() { 
  4.     beego.Run() 

Go 并没有相对路径引入,而是以文件夹为单位定义模块,譬如我们新建名为 math 的文件夹,然后使用 package math 来声明该文件中函数所属的模块。

  1. import ( 
  2.         mongo "mywebapp/libs/mongodb/db" // 对引入的模块重命名 
  3.         _ "mywebapp/libs/mysql/db" // 使用空白下划线表示仅调用其初始化函数 
  4.  

外部引用该模块是需要使用工作区间或者 vendor 相对目录,其目录索引情况如下:

  1. cannot find package "sub/math" in any of
  2.     ${PROJECTROOT}/vendor/sub/math (vendor tree) 
  3.     /usr/local/Cellar/go/1.10/libexec/src/sub/math (from $GOROOT) 
  4.     ${GOPATH}/src/sub/math (from $GOPATH) 

Go 规定每个源文件的首部需要进行包声明,可执行文件默认放在 main 包中;而各个包中默认首字母大写的函数作为其他包可见的导出函数,而小写函数则默认外部不可见的私有函数。

表达式与控制流

变量声明与赋值

作为强类型静态语言,Go 允许我们在变量之后标识数据类型,也为我们提供了自动类型推导的功能。

  1. // 声明三个变量,皆为 bool 类型 
  2. var c, python, java bool 
  3.  
  4. // 声明不同类型的变量,并且赋值 
  5. var i bool, j int = true, 2 
  6.  
  7. // 复杂变量声明 
  8. var ( 
  9.     ToBe   bool       = false 
  10.     MaxInt uint64     = 1<<64 - 1 
  11.     z      complex128 = cmplx.Sqrt(-5 + 12i) 
  12.  
  13. // 短声明变量 
  14. c, python, java := truefalse"no!" 
  15.  
  16. // 声明常量 
  17. const constant = "This is a constant" 

在 Go 中,如果我们需要比较两个复杂对象的相似性,可以使用 reflect.DeepEqual 方法:

  1. m1 := map[string]int
  2.     "a":1, 
  3.     "b":2, 
  4. m2 := map[string]int
  5.     "a":1, 
  6.     "b":2, 
  7. fmt.Println(reflect.DeepEqual(m1, m2)) 

条件判断

Go 提供了增强型的 if 语句进行条件判断:

  1. // 基础形式 
  2. if x > 0 { 
  3.     return x 
  4. else { 
  5.     return -x 
  6.  
  7. // 条件判断之前添加自定义语句 
  8. if a := b + c; a < 42 { 
  9.     return a 
  10. else { 
  11.     return a - 42 
  12.  
  13. // 常用的类型判断 
  14. var val interface{} 
  15. val = "foo" 
  16. if str, ok := val.(string); ok { 
  17.     fmt.Println(str) 

Go 也支持使用 Switch 语句:

  1. // 基础格式 
  2. switch operatingSystem { 
  3. case "darwin"
  4.     fmt.Println("Mac OS Hipster"
  5.     // 默认 break,不需要显式声明 
  6. case "linux"
  7.     fmt.Println("Linux Geek"
  8. default
  9.     // Windows, BSD, ... 
  10.     fmt.Println("Other"
  11.  
  12. // 类似于 if,可以在条件之前添加自定义语句 
  13. switch os := runtime.GOOS; os { 
  14. case "darwin": ... 
  15.  
  16. // 使用 switch 语句进行类型判断: 
  17. switch v := anything.(type) { 
  18.   case string: 
  19.     fmt.Println(v) 
  20.   case int32, int64: 
  21.     ... 
  22.   default
  23.     fmt.Println("unknown"

Switch 中也支持进行比较:

  1. number := 42 
  2. switch { 
  3.     case number < 42: 
  4.         fmt.Println("Smaller"
  5.     case number == 42: 
  6.         fmt.Println("Equal"
  7.     case number > 42: 
  8.         fmt.Println("Greater"

或者进行多条件匹配:

  1. var char byte = '?' 
  2. switch char { 
  3.     case ' ''?''&''=''#''+''%'
  4.         fmt.Println("Should escape"

循环

Go 支持使用 for 语句进行循环,不存在 while 或者 until:

  1. for i := 1; i < 10; i++ { 
  2.  
  3. // while - loop 
  4. for ; i < 10;  { 
  5.  
  6. // 单条件情况下可以忽略分号 
  7. for i < 10  { 
  8.  
  9. // ~ while (true
  10. for { 

我们也可以使用 range 函数,对于 Arrays 与 Slices 进行遍历:

  1. // loop over an array/a slice 
  2. for i, e := range a { 
  3.     // i 表示下标,e 表示元素 
  4.  
  5. // 仅需要元素 
  6. for _, e := range a { 
  7.     // e is the element 
  8.  
  9. // 或者仅需要下标 
  10. for i := range a { 
  11.  
  12. // 定时执行 
  13. for range time.Tick(time.Second) { 
  14.     // do it once a sec 

Function: 函数

定义,参数与返回值

  1. // 简单函数定义 
  2. func functionName() {} 
  3.  
  4. // 含参函数定义 
  5. func functionName(param1 string, param2 int) {} 
  6.  
  7. // 多个相同类型参数的函数定义 
  8. func functionName(param1, param2 int) {} 
  9.  
  10. // 函数表达式定义 
  11. add := func(a, b intint { 
  12.     return a + b 

Go 支持函数的最后一个参数使用 ... 设置为不定参数,即可以传入一个或多个参数值:

  1. func adder(args ...intint { 
  2.     total := 0 
  3.     for _, v := range args { // Iterates over the arguments whatever the number. 
  4.         total += v 
  5.     } 
  6.     return total 
  7.  
  8. adder(1, 2, 3) // 6 
  9. adder(9, 9) // 18 
  10.  
  11. nums := []int{10, 20, 30} 
  12. adder(nums...) // 60 

我们也可以使用 Function Stub 作为函数参数传入,以实现回调函数的功能:

  1. func Filter(s []int, fn func(int) bool) []int { 
  2.     var p []int // == nil 
  3.     for _, v := range s { 
  4.         if fn(v) { 
  5.             p = append(p, v) 
  6.         } 
  7.     } 
  8.     return p 

虽然 Go 不是函数式语言,但是也可以用其实现柯里函数(Currying Function):

  1. func add(x, y intint { 
  2.     return x+ y 
  3.  
  4. func adder(x int) (func(intint) { 
  5.     return func(y intint { 
  6.         return add(x, y) 
  7.     } 
  8.  
  9. func main() { 
  10.     add3 := adder(3) 
  11.     fmt.Println(add3(4))    // 7 

Go 支持多个返回值:

  1. // 返回单个值 
  2. func functionName() int { 
  3.     return 42 
  4.  
  5. // 返回多个值 
  6. func returnMulti() (int, string) { 
  7.     return 42, "foobar" 
  8. var x, str = returnMulti() 
  9.  
  10. // 命名返回多个值 
  11. func returnMulti2() (n int, s string) { 
  12.     n = 42 
  13.     s = "foobar" 
  14.     // n and s will be returned 
  15.     return 
  16. var x, str = returnMulti2() 

闭包: Closure

Go 同样支持词法作用域与变量保留,因此我们可以使用闭包来访问函数定义处外层的变量:

  1. func scope() func() int
  2.     outer_var := 2 
  3.     foo := func() int { return outer_var} 
  4.     return foo 

闭包中并不能够直接修改外层变量,而是会自动重定义新的变量值:

  1. func outer() (func() intint) { 
  2.     outer_var := 2 
  3.     inner := func() int { 
  4.         outer_var += 99 
  5.         return outer_var // => 101 (but outer_var is a newly redefined 
  6.     } 
  7.     return inner, outer_var // => 101, 2 (outer_var is still 2, not mutated by inner!) 

函数执行

Go 中提供了 defer 关键字,允许将某个语句的执行推迟到函数返回语句之前:

  1. func read(...) (...) { 
  2.   f, err := os.Open(file) 
  3.   ... 
  4.   defer f.Close() 
  5.   ... 
  6.   return .. // f will be closed 

异常处理

Go 语言中并不存在 try-catch 等异常处理的关键字,对于那些可能返回异常的函数,只需要在函数返回值中添加额外的 Error 类型的返回值:

  1. type error interface { 
  2.     Error() string 

某个可能返回异常的函数调用方式如下:

  1. import ( 
  2.     "fmt" 
  3.     "errors" 
  4.  
  5. func main() { 
  6.     result, err:= Divide(2,0) 
  7.  
  8.     if err != nil { 
  9.             fmt.Println(err) 
  10.     }else { 
  11.             fmt.Println(result) 
  12.     } 
  13.  
  14. func Divide(value1 int,value2 int)(int, error) { 
  15.     if(value2 == 0){ 
  16.         return 0, errors.New("value2 mustn't be zero"
  17.     } 
  18.     return value1/value2  , nil 

Go 还为我们提供了 panic 函数,所谓 panic,即是未获得预期结果,常用于抛出异常结果。譬如当我们获得了某个函数返回的异常,却不知道如何处理或者不需要处理时,可以直接通过 panic 函数中断当前运行,打印出错误信息、Goroutine 追踪信息,并且返回非零的状态码:

  1. _, err := os.Create("/tmp/file"
  2. if err != nil { 
  3.     panic(err) 

数据类型与结构

类型绑定与初始化

Go 中的 type 关键字能够对某个类型进行重命名:

  1. // IntSlice 并不等价于 []int,但是可以利用类型转换进行转换 
  2. type IntSlice []int 
  3. a := IntSlice{1, 2} 

可以使用 T(v) 或者 obj.(T) 进行类型转换,obj.(T) 仅针对 interface{} 类型起作用:

  1. t := obj.(T) // if obj is not T, error 
  2. t, ok := obj.(T) // if obj is not T, ok = false 
  3.  
  4. // 类型转换与判断 
  5. str, ok := val.(string); 

基本数据类型

  1. interface {} // ~ java Object 
  2. bool // true/false 
  3. string 
  4. int8  int16  int32  int64 
  5. int // =int32 on 32-bit, =int64 if 64-bit OS 
  6. uint8 uint16 uint32 uint64 uintptr 
  7. uint 
  8. byte // alias for uint8 
  9. rune // alias for int32, represents a Unicode code point 
  10. float32 float64 

字符串

// 多行字符串声明

  1. // 多行字符串声明 
  2. hellomsg := ` 
  3.  "Hello" in Chinese is 你好 ('Ni Hao'
  4.  "Hello" in Hindi is नमस्ते ('Namaste'

格式化字符串:

  1. fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // basic print, plus newline 
  2. p := struct { X, Y int }{ 17, 2 } 
  3. fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etc 
  4. s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print to string variable 
  5.  
  6. fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish format 
  7. s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // formatted print to string variable 

序列类型

Array 与 Slice 都可以用来表示序列数据,二者也有着一定的关联。

Array

其中 Array 用于表示固定长度的,相同类型的序列对象,可以使用如下形式创建:

  1. [N]Type 
  2. [N]Type{value1, value2, ..., valueN} 
  3.  
  4. // 由编译器自动计算数目 
  5. [...]Type{value1, value2, ..., valueN} 

其具体使用方式为:

  1. // 数组声明 
  2. var a [10]int 
  3.  
  4. // 赋值 
  5. a[3] = 42 
  6.  
  7. // 读取 
  8. i := a[3] 
  9.  
  10. // 声明与初始化 
  11. var a = [2]int{1, 2} 
  12. a := [2]int{1, 2} 
  13. a := [...]int{1, 2} 
  14. Go 内置了 len 与 cap 函数,用于获取数组的尺寸与容量: 
  15.  
  16. var arr = [3]int{1, 2, 3} 
  17. arr := [...]int{1, 2, 3} 
  18.  
  19. len(arr) // 3 
  20. cap(arr) // 3 

不同于 C/C++ 中的指针(Pointer)或者 Java 中的对象引用(Object Reference),Go 中的 Array 只是值(Value)。这也就意味着,当进行数组拷贝,或者函数调用中的参数传值时,会复制所有的元素副本,而非仅仅传递指针或者引用。显而易见,这种复制的代价会较为昂贵。

Slice

Slice 为我们提供了更为灵活且轻量级地序列类型操作,可以使用如下方式创建 Slice:

  1. // 使用内置函数创建 
  2. make([]Type, length, capacity) 
  3. make([]Type, length) 
  4.  
  5. // 声明为不定长度数组 
  6. []Type{} 
  7. []Type{value1, value2, ..., valueN} 
  8.  
  9. // 对现有数组进行切片转换 
  10. array[:] 
  11. array[:2] 
  12. array[2:] 
  13. array[2:3] 

不同于 Array,Slice 可以看做更为灵活的引用类型(Reference Type),它并不真实地存放数组值,而是包含数组指针(ptr),len,cap 三个属性的结构体。换言之,Slice 可以看做对于数组中某个段的描述,包含了指向数组的指针,段长度,以及段的最大潜在长度,其结构如下图所示:

  1. // 创建 len 为 5,cap 为 5 的 Slice 
  2. s := make([]byte, 5) 
  3.  
  4. // 对 Slice 进行二次切片,此时 len 为 2,cap 为 3 
  5. s = s[2:4] 
  6.  
  7. // 恢复 Slice 的长度 
  8. s = s[:cap(s)] 

需要注意的是, 切片操作并不会真实地复制 Slice 中值,只是会创建新的指向原数组的指针,这就保证了切片操作和操作数组下标有着相同的高效率。不过如果我们修改 Slice 中的值,那么其会 真实修改底层数组中的值,也就会体现到原有的数组中:

  1. d := []byte{'r''o''a''d'
  2. e := d[2:] 
  3. // e == []byte{'a''d'
  4. e[1] = 'm' 
  5. // e == []byte{'a''m'
  6. // d == []byte{'r''o''a''m'

Go 提供了内置的 append 函数,来动态为 Slice 添加数据,该函数会返回新的切片对象,包含了原始的 Slice 中值以及新增的值。如果原有的 Slice 的容量不足以存放新增的序列,那么会自动分配新的内存:

  1. // len=0 cap=0 [] 
  2. var s []int 
  3.  
  4. // len=1 cap=2 [0] 
  5. s = append(s, 0) 
  6.  
  7. // len=2 cap=2 [0 1] 
  8. s = append(s, 1) 
  9.  
  10. // len=5 cap=8 [0 1 2 3 4] 
  11. s = append(s, 2, 3, 4) 
  12.  
  13. // 使用 ... 来自动展开数组 
  14. a := []string{"John""Paul"
  15. b := []string{"George""Ringo""Pete"
  16. a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])" 
  17. // a == []string{"John""Paul""George""Ringo""Pete"

我们也可以使用内置的 copy 函数,进行 Slice 的复制,该函数支持对于不同长度的 Slice 进行复制,其会自动使用最小的元素数目。同时,copy 函数还能够自动处理使用了相同的底层数组之间的 Slice 复制,以避免额外的空间浪费。

  1. func copy(dst, src []T) int 
  2.  
  3. // 申请较大的空间容量 
  4. t := make([]byte, len(s), (cap(s)+1)*2) 
  5. copy(t, s) 
  6. s = t 

映射类型

  1. var m map[string]int 
  2. m = make(map[string]int
  3. m["key"] = 42 
  4.  
  5. // 删除某个键 
  6. delete(m, "key"
  7.  
  8. // 测试该键对应的值是否存在 
  9. elem, has_value := m["key"
  10.  
  11. // map literal 
  12. var m = map[string]Vertex{ 
  13.     "Bell Labs": {40.68433, -74.39967}, 
  14.     "Google":    {37.42202, -122.08408}, 

Struct & Interface: 结构体与接口

Struct: 结构体

Go 语言中并不存在类的概念,只有结构体,结构体可以看做属性的集合,同时可以为其定义方法。

  1. // 声明结构体 
  2. type Vertex struct { 
  3.     // 结构体的属性,同样遵循大写导出,小写私有的原则 
  4.     X, Y int 
  5.     z bool 
  6.  
  7. // 也可以声明隐式结构体 
  8. point := struct { 
  9.     X, Y int 
  10. }{1, 2} 
  11.  
  12. // 创建结构体实例 
  13. var v = Vertex{1, 2} 
  14.  
  15. // 读取或者设置属性 
  16. v.X = 4; 
  17.  
  18. // 显示声明键 
  19. var v = Vertex{X: 1, Y: 2} 
  20.  
  21. // 声明数组 
  22. var v = []Vertex{{1,2},{5,2},{5,5}} 

方法的声明也非常简洁,只需要在 func 关键字与函数名之间声明结构体指针即可,该结构体会在不同的方法间进行复制:

  1. func (v Vertex) Abs() float64 { 
  2.  
  3.     return math.Sqrt(v.X*v.X + v.Y*v.Y) 
  4.  
  5.  
  6. // Call method 
  7.  
  8. v.Abs() 

对于那些需要修改当前结构体对象的方法,则需要传入指针:

  1. func (v *Vertex) add(n float64) { 
  2.     v.X += n 
  3.     v.Y += n 
  4. var p *Person = new(Person) // pointer of type Person 

Pointer: 指针

  1. // p 是 Vertex 类型 
  2. p := Vertex{1, 2}   
  3.  
  4. // q 是指向 Vertex 的指针 
  5. q := &p 
  6.  
  7. // r 同样是指向 Vertex 对象的指针 
  8. r := &Vertex{1, 2} 
  9.  
  10. // 指向 Vertex 结构体对象的指针类型为 *Vertex 
  11. var s *Vertex = new(Vertex) 

Interface: 接口

Go 允许我们通过定义接口的方式来实现多态性:

  1. // 接口声明 
  2. type Awesomizer interface { 
  3.     Awesomize() string 
  4.  
  5. // 结构体并不需要显式实现接口 
  6. type Foo struct {} 
  7.  
  8. // 而是通过实现所有接口规定的方法的方式,来实现接口 
  9. func (foo Foo) Awesomize() string { 
  10.     return "Awesome!" 
  11. type Shape interface { 
  12.    area() float64 
  13.  
  14. func getArea(shape Shape) float64 { 
  15.    return shape.area() 
  16.  
  17. type Circle struct { 
  18.    x,y,radius float64 
  19.  
  20. type Rectangle struct { 
  21.    width, height float64 
  22.  
  23. func(circle Circle) area() float64 { 
  24.    return math.Pi * circle.radius * circle.radius 
  25.  
  26. func(rect Rectangle) area() float64 { 
  27.    return rect.width * rect.height 
  28.  
  29. func main() { 
  30.    circle := Circle{x:0,y:0,radius:5} 
  31.    rectangle := Rectangle {width:10, height:5} 
  32.  
  33.    fmt.Printf("Circle area: %f\n",getArea(circle)) 
  34.    fmt.Printf("Rectangle area: %f\n",getArea(rectangle)) 
  35. //Circle area: 78.539816 
  36. //Rectangle area: 50.000000 

惯用的思路是先定义接口,再定义实现,最后定义使用的方法:

  1. package animals 
  2.  
  3. type Animal interface { 
  4.     Speaks() string 
  5.  
  6. // implementation of Animal 
  7. type Dog struct{} 
  8. func (a Dog) Speaks() string { return "woof" } 
  9.  
  10. /** 在需要的地方直接引用 **/ 
  11.  
  12. package circus 
  13.  
  14. import "animals" 
  15.  
  16. func Perform(a animal.Animal) { return a.Speaks() } 

Go 也为我们提供了另一种接口的实现方案,我们可以不在具体的实现处定义接口,而是在需要用到该接口的地方,该模式为:

  1. func funcName(a INTERFACETYPE) CONCRETETYPE 

定义接口:

  1. package animals 
  2.  
  3. type Dog struct{} 
  4. func (a Dog) Speaks() string { return "woof" } 
  5.  
  6. /** 在需要使用实现的地方定义接口 **/ 
  7. package circus 
  8.  
  9. type Speaker interface { 
  10.     Speaks() string 
  11.  
  12. func Perform(a Speaker) { return a.Speaks() } 

Embedding

Go 语言中并没有子类继承这样的概念,而是通过嵌入(Embedding)的方式来实现类或者接口的组合。

  1. // ReadWriter 的实现需要同时满足 Reader 与 Writer 
  2. type ReadWriter interface { 
  3.     Reader 
  4.     Writer 
  5.  
  6. // Server 暴露了所有 Logger 结构体的方法 
  7. type Server struct { 
  8.     Host string 
  9.     Port int 
  10.     *log.Logger 
  11.  
  12. // 初始化方式并未受影响 
  13. server := &Server{"localhost", 80, log.New(...)} 
  14.  
  15. // 却可以直接调用内嵌结构体的方法,等价于 server.Logger.Log(...) 
  16. server.Log(...) 
  17.  
  18. // 内嵌结构体的名词即是类型名 
  19. var logger *log.Logger = server.Logger 

并发编程

Goroutines

Goroutines 是轻量级的线程,可以参考并发编程导论一文中的进程、线程与协程的讨论;Go 为我们提供了非常便捷的 Goroutines 语法:

  1. // 普通函数 
  2. func doStuff(s string) { 
  3.  
  4. func main() { 
  5.     // 使用命名函数创建 Goroutine 
  6.     go doStuff("foobar"
  7.  
  8.     // 使用匿名内部函数创建 Goroutine 
  9.     go func (x int) { 
  10.         // function body goes here 
  11.     }(42) 

Channels

信道(Channel)是带有类型的管道,可以用于在不同的 Goroutine 之间传递消息,其基础操作如下:

  1. // 创建类型为 int 的信道 
  2. ch := make(chan int
  3.  
  4. // 向信道中发送值 
  5. ch <- 42 
  6.  
  7. // 从信道中获取值 
  8. v := <-ch 
  9.  
  10. // 读取,并且判断其是否关闭 
  11. v, ok := <-ch 
  12.  
  13. // 读取信道,直至其关闭 
  14. for i := range ch { 
  15.     fmt.Println(i) 

譬如我们可以在主线程中等待来自 Goroutine 的消息,并且输出:

  1. // 创建信道 
  2. messages := make(chan string) 
  3.  
  4. // 执行 Goroutine 
  5. go func() { messages <- "ping" }() 
  6.  
  7. // 阻塞,并且等待消息 
  8. msg := <-messages 
  9.  
  10. // 使用信道进行并发地计算,并且阻塞等待结果 
  11. c := make(chan int
  12. go sum(s[:len(s)/2], c) 
  13. go sum(s[len(s)/2:], c) 
  14. x, y := <-c, <-c // 从 c 中接收 

如上创建的是无缓冲型信道(Non-buffered Channels),其是阻塞型信道;当没有值时读取方会持续阻塞,而写入方则是在无读取时阻塞。我们可以创建缓冲型信道(Buffered Channel),其读取方在信道被写满前都不会被阻塞:

  1. ch := make(chan int, 100) 
  2.  
  3. // 发送方也可以主动关闭信道 
  4. close(ch) 

Channel 同样可以作为函数参数,并且我们可以显式声明其是用于发送信息还是接收信息,从而增加程序的类型安全度:

  1. // ping 函数用于发送信息 
  2. func ping(pings chan<- string, msg string) { 
  3.     pings <- msg 
  4.  
  5. // pong 函数用于从某个信道中接收信息,然后发送到另一个信道中 
  6. func pong(pings <-chan string, pongs chan<- string) { 
  7.     msg := <-pings 
  8.     pongs <- msg 
  9.  
  10. func main() { 
  11.     pings := make(chan string, 1) 
  12.     pongs := make(chan string, 1) 
  13.     ping(pings, "passed message"
  14.     pong(pings, pongs) 
  15.     fmt.Println(<-pongs) 

同步

同步,是并发编程中的常见需求,这里我们可以使用 Channel 的阻塞特性来实现 Goroutine 之间的同步:

  1. func worker(done chan bool) { 
  2.     time.Sleep(time.Second
  3.     done <- true 
  4.  
  5. func main() { 
  6.     done := make(chan bool, 1) 
  7.     go worker(done) 
  8.  
  9.     // 阻塞直到接收到消息 
  10.     <-done 

Go 还为我们提供了 select 关键字,用于等待多个信道的执行结果:

  1. // 创建两个信道 
  2. c1 := make(chan string) 
  3. c2 := make(chan string) 
  4.  
  5. // 每个信道会以不同时延输出不同值 
  6. go func() { 
  7.     time.Sleep(1 * time.Second
  8.     c1 <- "one" 
  9. }() 
  10. go func() { 
  11.     time.Sleep(2 * time.Second
  12.     c2 <- "two" 
  13. }() 
  14.  
  15. // 使用 select 来同时等待两个信道的执行结果 
  16. for i := 0; i < 2; i++ { 
  17.     select { 
  18.     case msg1 := <-c1: 
  19.         fmt.Println("received", msg1) 
  20.     case msg2 := <-c2: 
  21.         fmt.Println("received", msg2) 
  22.     } 

Web 编程

HTTP Server

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "net/http" 
  6.  
  7. // define a type for the response 
  8. type Hello struct{} 
  9.  
  10. // let that type implement the ServeHTTP method (defined in interface http.Handler) 
  11. func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) { 
  12.     fmt.Fprint(w, "Hello!"
  13.  
  14. func main() { 
  15.     var h Hello 
  16.     http.ListenAndServe("localhost:4000", h) 
  17.  
  18. // Here's the method signature of http.ServeHTTP: 
  19. // type Handler interface { 
  20. //     ServeHTTP(w http.ResponseWriter, r *http.Request) 
  21. // } 

Beego

利用 Beego 官方推荐的 bee 命令行工具,我们可以快速创建 Beego 项目,其目录组织方式如下:

  1. quickstart 
  2. ├── conf 
  3. │   └── app.conf 
  4. ├── controllers 
  5. │   └── default.go 
  6. ├── main.go 
  7. ├── models 
  8. ├── routers 
  9. │   └── router.go 
  10. ├── static 
  11. │   ├── css 
  12. │   ├── img 
  13. │   └── js 
  14. ├── tests 
  15. │   └── default_test.go 
  16. └── views 
  17.     └── index.tpl 

在 main.go 文件中,我们可以启动 Beego 实例,并且调用路由的初始化配置文件:

  1. package main 
  2.  
  3. import ( 
  4.         _ "quickstart/routers" 
  5.         "github.com/astaxie/beego" 
  6.  
  7. func main() { 
  8.         beego.Run() 

而在路由的初始化函数中,我们会声明各个路由与控制器之间的映射关系:

  1. package routers 
  2.  
  3. import ( 
  4.         "quickstart/controllers" 
  5.         "github.com/astaxie/beego" 
  6.  
  7. func init() { 
  8.         beego.Router("/", &controllers.MainController{}) 

也可以手动指定 Beego 项目中的静态资源映射:

  1. beego.SetStaticPath("/down1""download1"
  2. beego.SetStaticPath("/down2""download2"

在具体的控制器中,可以设置返回数据,或者关联的模板名:

  1. package controllers 
  2.  
  3. import ( 
  4.         "github.com/astaxie/beego" 
  5.  
  6. type MainController struct { 
  7.         beego.Controller 
  8.  
  9. func (this *MainController) Get() { 
  10.         this.Data["Website"] = "beego.me" 
  11.         this.Data["Email"] = "astaxie@gmail.com" 
  12.         this.TplNames = "index.tpl" // version 1.6 use this.TplName = "index.tpl" 

DevPractics: 开发实践

文件读写

  1. import ( 
  2.     "io/ioutil" 
  3. ... 
  4. datFile1, errFile1 := ioutil.ReadFile("file1"
  5. if errFile1 != nil { 
  6.     panic(errFile1) 
  7. ... 

测试

VSCode 可以为函数自动生成基础测试用例,并且提供了方便的用例执行与调试的功能。

  1. /** 交换函数 */ 
  2. func swap(x *int, y *int) { 
  3.     x, y = y, x 
  4.  
  5. /** 自动生成的测试函数 */ 
  6. func Test_swap(t *testing.T) { 
  7.     type args struct { 
  8.         x *int 
  9.         y *int 
  10.     } 
  11.     tests := []struct { 
  12.         name string 
  13.         args args 
  14.     }{ 
  15.         // TODO: Add test cases. 
  16.     } 
  17.     for _, tt := range tests { 
  18.         t.Run(tt.name, func(t *testing.T) { 
  19.             swap(tt.args.x, tt.args.y) 
  20.         }) 
  21.     } 

【本文是51CTO专栏作者“张梓雄 ”的原创文章,如需转载请通过51CTO与作者联系】

戳这里,看该作者更多好文

【编辑推荐】

  1. 第45期:大数据计算语法的SQL化
  2. 韩超:制造业“双创”平台建设实践与探索|V课堂第104期
  3. 袁卓伟:新时代下央企内部双创平台之探索与实践|V课堂第105期
  4. 如何利用MongoDB实现高性能,高可用的双活应用架构?
  5. 实时视频通话超低延迟架构的思考与实践
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

热门职位+更多