美文网首页自动化程序员
Go 语法速览与实践清单(上-V0.5)

Go 语法速览与实践清单(上-V0.5)

作者: 51reboot | 来源:发表于2018-04-02 11:12 被阅读124次

    Go 语法速览与实践清单(V0.5)

    Go CheatSheet(http://t.cn/RnmIs48) 是对于 Go 学习/实践过程中的语法与技巧进行盘点,其属于 Awesome CheatSheet(http://t.cn/RnmMVnn) 系列,致力于提升学习速度与研发效能,即可以将其当做速查手册,也可以作为轻量级的入门学习资料。 本文参考了许多优秀的文章与代码示范,统一声明在了 Go Links(http://t.cn/RnmMiwq);如果希望深入了解某方面的内容,可以继续阅读 Go 开发:语法基础与工程实践(http://t.cn/RnmMoWb),或者前往 coding-snippets/go(http://t.cn/RnmMp4O) 查看使用 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 mainimport "fmt"func main() {   fmt.Println("hello world")}
    

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

    package mainimport "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 := 42switch {   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 - loopfor ; i < 10;  {}// 单条件情况下可以忽略分号for i < 10  {}// ~ while (true)for {}
    

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

    // loop over an array/a slicefor 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) // 6adder(9, 9) // 18nums := []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 []inta := IntSlice{1, 2}
    

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

    t := obj.(T) // if obj is not T, errort, ok := obj.(T) // if obj is not T, ok = false// 类型转换与判断str, ok := val.(string);
    

    基本数据类型

    interface {} // ~ java Objectbool // true/falsestringint8  int16  int32  int64int // =int32 on 32-bit, =int64 if 64-bit OSuint8 uint16 uint32 uint64 uintptruintbyte // alias for uint8rune // alias for int32, represents a Unicode code pointfloat32 float64
    

    字符串

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

    格式化字符串:

    fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // basic print, plus newlinep := struct { X, Y int }{ 17, 2 }fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etcs := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print to string variablefmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish formats2 := 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) // 3cap(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 的 Slices := make([]byte, 5)// 对 Slice 进行二次切片,此时 len 为 2,cap 为 3s = 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]intm = make(map[string]int)m["key"] = 42// 删除某个键delete(m, "key")// 测试该键对应的值是否存在elem, has_value := m["key"]// map literalvar 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 methodv.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 结构体对象的指针类型为 *Vertexvar 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 animalstype Animal interface {   Speaks() string}// implementation of Animaltype Dog struct{}func (a Dog) Speaks() string { return "woof" }/** 在需要的地方直接引用 **/package circusimport "animals"func Perform(a animal.Animal) { return a.Speaks() }
    

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

    func funcName(a INTERFACETYPE) CONCRETETYPE
    

    定义接口:

    package animalstype Dog struct{}func (a Dog) Speaks() string { return "woof" }/** 在需要使用实现的地方定义接口 **/package circustype Speaker interface {   Speaks() string}func Perform(a Speaker) { return a.Speaks() }
    

    未完待续......

    转载|Segmentfault
    感谢作者:王下邀月熊_Chevalier
    原文链接:http://t.cn/Rnm6kkn

    报名咨询方式


    扫码添加小助手微信,备注"公开课,来源简书",进入分享群

    相关文章

      网友评论

        本文标题:Go 语法速览与实践清单(上-V0.5)

        本文链接:https://www.haomeiwen.com/subject/pdmthftx.html