3
头图

Go was created out of a need to get work done. It's not the latest trend in programming language theory, but it's a way to solve real-world problems.

It draws concepts from imperative languages with static typing. It compiles fast, executes fast, it adds easy-to-understand concurrency since multi-core CPUs are common these days, and it's used successfully for large codebases (Google has about 100 million lines of Go code).

Minute 1: The Appointment

Download and install

GoLand

Project directory structure

  • pkg: make files after compilation
  • src : the source code of the project
  • bin: compiled executable file

Minute Two: Grammar

 // 单行注释
/* 多
行注释 */

 /* 构建标签是以 // +build 开头的行注释
  ,可以通过 go build -tags="foo bar" 命令执行。
  构建标记放置在靠近或文件顶部的 package 子句之前,
  后跟空行或其他行注释。*/ 
// +build prod, dev, test

// package 子句启动每个源文件。
// Main 是一个特殊的名称,它声明一个可执行文件而不是一个库。
package main

// 导入声明声明此文件中引用的库包。
import  ( 
    "fmt"        // Go 标准库中的一个包。
    "io/ioutil"  // 实现一些 I/O 实用函数。
    m  "math"    // 具有本地别名为 m 的数学库。
    "net/http"   //是的,一个网络服务器!
    “os”         //操作系统功能,如:使用文件系统
    “strconv”    //字符串转换。
)

// 一个函数定义。main是特别的。它是
// 可执行程序的入口点。爱也好恨也好,Go 使用大括号。
func  main ()  { 
    // Println 输出一行到标准输出。
    // 它来自包 fmt. 
    fmt.Println("Hello world!")

    // 在这个包中调用另一个函数。
    beyondHello()
}

// 函数有括号中的参数。
// 如果没有参数,仍然需要空括号。
func beyondHello() {
    var x int  // 变量声明。变量必须在使用前声明。
    x = 3      // 变量赋值。
    // “短”声明使用 := 来推断类型、声明和分配。
    y := 4 
    sum, prod := learnMultiple(x, y)         // 函数返回两个值。
    fmt.Println("sum:", sum, "prod:", prod)  // 简单输出。
    learnTypes()                             // < 15 分钟,了解更多!
}

/* <- 多行注释
函数可以有参数和(多个!)返回值。
这里 `x`、`y` 是参数,`sum`、`prod` 是签名(返回的内容)。
注意 `x` 和 `sum` 接收类型 `int`。
*/ 
func learnMultiple(x, y int) (sum, prod int) {
    return  x + y ,  x * y  // 返回两个值。
}

// 一些内置类型和文字。
func  learnTypes ()  { 
    // 简短的声明通常会给你你想要的。
    str  :=  "学习Go!"  // 字符串类型。

    s2  :=  `“原始”字符串文字
可以包含换行符。`  // 相同的字符串类型。

    // 非 ASCII 文字。Go 源代码是 UTF-8。
    g  :=  'Σ'  // rune (符文)类型,int32 的别名,包含一个 unicode 代码点。

    f  :=  3.14195  // float64,一个 IEEE-754 64 位浮点数。
    c  :=  3  +  4i   // complex128,内部用两个 float64 表示。

    // 带有初始化器的 var 语法。
    var u uint = 7  // 无符号,但与 int 一样取决于实现的大小。
    var pi float32 = 22. / 7

    // 带有简短声明的转换语法。
    n := byte('\n')  // byte 是 uint8 的别名。

    // 数组的大小在编译时是固定的。
    var a4 [4]int               // 4 个 int 的数组,初始化为全 0。
    a5 := [...]int{3, 1, 5, 10, 100} // 一个固定大小为 5 的数组初始化
    // 元素,值为 3、1、5、10 和 100。

    // 数组具有值语义。
    a4_cpy := a4             // a4_cpy 是 a4 的副本,两个独立的实例。
    a4_cpy[0] = 25          // 只有 a4_cpy 改变了,a4 保持不变。
    fmt.Println(a4_cpy[0] == a4[0])  // false

    // 切片具有动态大小。数组和切片各有优势
    // 但切片的用例更为常见。
    s3 := []int{4, 5, 9}     // 与 a5 比较。这里没有省略号。
    s4 := make([]int, 4)     // 分配 4 个 int 的切片,初始化为全 0。
    var d2 [][]float64       // 仅声明,此处不分配任何内容。
    bs := []byte("a slice")  // 类型转换语法。

    // 切片(以及地图和通道)具有引用语义。
    s3_cpy  :=  s3             // 两个变量都指向同一个实例。
    s3_cpy[0] = 0             // 这意味着两者都更新了。
    fmt.Println(s3_cpy[0] == s3[0])  // 真

    // 因为它们是动态的,切片可以按需追加。
    // 要将元素附加到切片,使用内置的 append() 函数。
    // 第一个参数是我们要附加的切片。通常,
    // 数组变量会就地更新,如下例所示。
    s := []int{1, 2, 3}     // 结果是一个长度为 3 的切片。
    s = append(s, 4, 5, 6)   // 添加了 3 个元素。切片现在的长度为 6. 
    fmt.Println(s) // 更新的切片现在是 [1 2 3 4 5 6]

    // 要附加另一个切片,而不是原子元素列表,我们可以
    // 传递对切片的引用或像这样的切片字面量,带有一个
    // 尾随省略号,意思是获取一个切片并解包其元素,
    // 附加它们切片 s。
    s = append(s, []int{7, 8, 9}...)   // 第二个参数是一个切片文字。
    fmt.Println(s)   // 更新后的切片现在是 [1 2 3 4 5 6 7 8 9]

    p, q := learnMemory()  // 声明 p, q 为指向 int 的类型指针。
    fmt.Println(*p, *q)    // * 跟随一个指针。这会打印两个整数。

    // Map 是一种动态可增长的关联数组类型,就像一些其他语言的
    // 哈希或字典类型。
    m := map[string]int{"three": 3, "four": 4}
    m["one"] = 1

    // 未使用的变量是 Go 中的错误。
    // 下划线让你“使用”一个变量但丢弃它的值。
   _, _, _, _, _, _, _, _, _, _ = str, s2, g, f, u, pi, n, a5, s4, bs 
    // 通常你用它来忽略其中一个函数的返回值
    // 例如,在一个又脏又快的脚本中,您可能会忽略
    // 从 os.Create 返回的错误值,并期望文件
    // 将始终被创建。
    file, _ := os.Create("output.txt")
    fmt.Fprint(file, "This is how you write to a file, by the way")
    file.Close()

    // 当然,输出算作使用变量。
    fmt.Println(s, c, a4, s3, d2, m)

    learnFlowControl()  // 回到流程中。
}

// 与许多其他语言不同,go 中的函数
// 有可能具有命名的返回值。
// 为函数声明行中返回的类型分配一个名称
// 允许我们轻松地从函数中的多个点返回,以及
// 仅使用 return 关键字,而无需进一步。
func learnNamedReturns(x, y int) (z int) {
    z = x * y
    return  // z 在这里是隐含的,因为我们之前命名了它。
}

// Go 是完全垃圾回收的。它有指针但没有指针算术。
// 你可以用 nil 空指针搞出错,但不能通过递增修改指针。
// 与 C/Cpp 不同,获取和返回局部变量的地址也是安全的。
func learnMemory() (p, q *int) {
    // 命名返回值 p 和 q 具有指向 int 的类型指针。
    p = new(int)  // 内置函数 new 分配内存。
    // 分配的 int slice 初始化为 0,p 不再为 nil。
    s := make([]int, 20)  // 分配 20 个整数作为单个内存块。
    s[3] = 7              // 分配其中之一。
    r := -2               // 声明另一个局部变量。
    return &s[3], &r      // & 获取对象的地址。
}


// 使用别名 数学库(参见上面的导入)
     
func expensiveComputation() float64 {
    return m.Exp(10)
}

func learnFlowControl() {
    // If 语句需要大括号,不需要括号。
    if true {
        fmt.Println("told ya")
    }
    // 格式由命令行命令“go fmt”标准化。
    if  false  { 
        // 噘嘴 - Pout 
    }  else  { 
        // 幸灾乐祸 - Gloat
    } 
    // 使用 switch 优先于链式 if 语句。
    x := 42.0
    switch x {
    case 0:
    case 1, 2: // 一个case可以有多个匹配
    case 42: 
        // case不会“失败”。
        /*
        但是有一个 `fallthrough` 关键字,请参阅:
          https ://github.com/golang/go/wiki/Switch#fall-through 
        */ 
    case  43 : 
        // Unreached。
    default : 
        // 默认情况是可选的。
    }

    // 类型开关允许打开某物的类型而不是值
    var data interface{}
    data = ""
    switch c := data.(type) {
    case string:
        fmt.Println(c, "is a string")
    case int64:
        fmt.Printf("%d is an int64\n", c)
    default:
        // 所有其他情况
    }

    // 就像 if, for 也不使用括号。
    // 在 for 和 if 中声明的变量在其范围内是本地的。
    for x := 0; x < 3; x++ {  // ++ 是一个语句。
        fmt.Println("iteration", x)
    } 
    // x == 42 这里。

    // For 是 Go 中唯一的循环语句,但它有其他形式。
    for  {  // 无限循环
        break     // 开个玩笑
        continue // 未到达
    }

    // 您可以使用 range 来迭代数组、切片、字符串、映射或通道。
    // range 返回一个(通道)或两个值(数组、切片、字符串和映射)。
    for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
        // 对于 map 中的每一对,打印 key 和 value 
        fmt.Printf("key=%s, value=%d\n", key, value)
    }
    // 如果只需要值,使用下划线作为 _ 的键
    for _, name := range []string{"Bob", "Bill", "Joe"} {
        fmt.Printf("Hello, %s\n", name)
    }

    // 与 for 一样,if 语句中的 := 表示先声明和赋值
    // y,然后测试 y > x。
    if y := expensiveComputation(); y > x {
        x = y
    }
    // 函数字面量(literals)是闭包(closures)。
    xBig := func() bool {
        return x > 10000 // 引用在 switch 语句上面声明的 x。
    } 
    x  =  99999 
    fmt.Println("xBig:", xBig())   // true 
    x  =  1.3e3                    // 这使得 x == 1300 
    fmt.Println("xBig:", xBig())   // 现在为假。

    // 更重要的是函数字面量可以被定义时立即调用,
    // 作为函数的参数,只要:
    // a) 函数字面量被立即调用 (), 
    // b) 结果类型匹配预期的参数类型. 
    fmt.Println("Add + double two numbers: ",
        func(a, b int) int {
            return (a + b) * 2
        }(10, 2)) // 使用 args 10 和 2 调用
    // => Add + double两个数字:24

    // 当你需要它时,你会爱上它。
    goto love
love:

    learnFunctionFactory() // func 返回 func is fun(3)(3) 
    learnDefer()       // 快速绕道一个重要的关键字。
    learnInterfaces()  // 好东西来了!
}

func learnFunctionFactory() {
    // 接下来两个是等价的,第二个更实用
    fmt.Println(sentenceFactory("summer")("A beautiful", "day!"))

    d := sentenceFactory("summer")
    fmt.Println(d("A beautiful", "day!"))
    fmt.Println(d("A lazy", "afternoon!"))
}

// 装饰器在其他语言中很常见。同样可以在 Go 
// 中使用接受参数的函数文字来完成。
func sentenceFactory(mystring string) func(before, after string) string {
    return func(before, after string) string {
        return fmt.Sprintf("%s %s %s", before, mystring, after) // 新字符串
    }
}

func learnDefer() (ok bool) {
    // defer 语句将函数调用推送到列表中。保存的
    // 调用列表在周围函数返回后执行
    defer fmt.Println("延迟语句以相反 (LIFO) 顺序执行.")
    defer fmt.Println("\n此行首先打印,因为")

    // Defer 通常用于关闭文件,因此关闭文件的函数
    // 与打开文件的函数保持接近。
    return true
}

// 将 Stringer 定义为具有一种方法 String 的接口类型。
type Stringer interface {
    String() string
}

// 将 pair 定义为具有两个字段的结构,int 名为 x 和 y。
type pair struct {
    x, y int
}


// 在类型对上定义一个方法。Pair 现在实现了 Stringer,因为 Pair 已经定义了接口中的所有方法。
func (p pair) String() string {  // p 被称为“接收者” 
    // Sprintf 是 fmt 包中的另一个公共函数。
    // 点语法引用 p 的字段。
    return fmt.Sprintf("(%d, %d)", p.x, p.y)
}

func learnInterfaces() {
    // 大括号语法是“结构字面量”。它评估为一个初始化的
    // 结构。:= 语法声明并初始化 p 到这个结构。
    p := pair{3, 4}
    fmt.Println(p.String())   // 调用 p 的 String 方法,类型为 pair。
    var  i  Stringer           // 声明 i 的接口类型为 Stringer。
    i  =  p                    // 有效,因为 pair 实现了 Stringer 
    // 调用 i 的 String 方法,Stringer 类型。输出同上。
    fmt.Println(i.String())

    // fmt 包中的函数调用 String 方法来请求对象
    // 获取其自身的可打印表示。
    fmt.Println(p)   // 输出同上。Println 调用 String 方法。
    fmt.Println(i)  // 输出同上。

    learnVariadicParams("great", "learning", "here!") 
}

// 函数可以有可变参数。
func learnVariadicParams(myStrings ...interface{}) {
    // 迭代可变参数的每个值。
    // 这里的下划线忽略了数组的索引参数。
    for _, param := range myStrings {
        fmt.Println("param:", param)
    }

    // 将可变参数值作为可变参数传递。
    fmt.Println("params:", fmt.Sprintln(myStrings...))

    learnErrorHandling()
}

func  learnErrorHandling ()  { 
    // ", ok" 模式用来判断某事是否有效。
    m := map[int]string{3: "three", 4: "four"}
    if x, ok := m[1]; !ok {   // ok 将是假的,因为 1 不在map中。
         fmt.Println("no one there")
    } else {
        fmt.Print(x)  // x 将是值,如果它在map中。
    } 
    // 错误值不仅传达“ok”,还传达更多关于问题的信息。
    if _, err := strconv.Atoi("non-int"); err != nil {  // _ 丢弃值
        // 打印 'strconv.ParseInt: parsing "non-int": invalid syntax' 
        fmt.Println(err)
    } 
    // 我们稍后再讨论接口。并发,
    learnConcurrency()
}

// c 是一个通道,一个并发安全的通信对象。
func inc(i int, c chan int) {
    c <- i + 1  // <- 是当频道出现在左侧时的“发送”运算符。
}

// 我们将使用 inc 来同时增加一些数字。
func learnConcurrency() {
    // 之前使用相同的 make 函数来制作切片。Make 分配和
    // 初始化切片、映射和通道。
    c := make(chan int) 
    // 启动三个并发的 goroutine。
    // 如果机器有能力并且//正确配置,数字将同时递增,可能是并行递增。
    这三个都发送到同一个频道。
    go inc(0, c)  // go 是一个启动新 goroutine 的语句。
    go inc(10, c)
    go inc(-805, c) 
    // 从通道中读取三个结果并打印出来。
    // 不知道结果将以什么顺序到达!
    fmt.Println(<-c, <-c, <-c)  // 右边的通道,<- 是“接收”操作符。

    cs := make(chan string)       // 另一个通道,这个通道处理字符串。
    ccs := make(chan chan string)  // 字符串通道的通道。
    go func() { c <- 84 }()        // 启动一个新的 goroutine 只是为了发送一个值。
    go func() { cs <- "wordy" }()  // 同样,这次是 cs。
    // Select 的语法类似于 switch 语句,但每种情况都涉及
    // 一个通道操作。它从案例中随机选择一个案例
    // 准备好进行通信。
    select  { 
    case i := <-c:   // 接收到的值可以分配给变量
        fmt.Printf("it's a %T", i) 
    case <-cs:  // 或者接收到的值可以被丢弃。
        fmt.Println("it's a string")
    case <-ccs:   // 空通道,未准备好进行通信。
        fmt.Println("didn't happen.")
    }
    // 此时,从 c 或 cs 中获取了一个值。上面启动的两个
    // goroutine 之一已经完成,另一个将保持阻塞状态。

    learnWebProgramming()   // Go 做到了。你也想做。
}

// http 包中的单个函数启动 Web 服务器。
func learnWebProgramming() {

    // ListenAndServe 的第一个参数是要监听的 TCP 地址。
    // 第二个参数是一个接口,具体是http.Handler。
    go func() {
        err := http.ListenAndServe(":8080", pair{})
        fmt.Println(err) // 不要忽略错误
    }()

    requestServer()
}
    

// 通过实现它的唯一方法 ServeHTTP,使配对成为一个 http.Handler。
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 使用 http.ResponseWriter 方法提供数据。
    w.Write([]byte("You learned Go in Y minutes!"))
}

func requestServer() {
    resp, err := http.Get("http://localhost:8080")
    fmt.Println(err)
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    fmt.Printf("\nWebserver said: `%s`", string(body))
}

Further reading

Everything in Go originates from the official Go website . There you can follow tutorials, play interactive games and read a lot. In addition to tours, these docs contain information on how to write clean and efficient Go code, package and command documentation, and release history.

The Go language specification itself is strongly recommended. It's easy to read and very short (as is the language definition now.)

You can play around with the code on the Go Playground. Try changing it and run it from your browser! Note that you can use https://play.golang.org as a REPL to test things and code in the browser without even having Go installed.

On the reading list for Go students is the source code of the standard library . Fully documented, it showcases Go, Go style, and Go idioms at their best in readability and understanding. Or you can click the function name in the documentation and show the source code!

Another good resource for learning Go is Go by example.

There are many excellent conference talks and video tutorials on Go on YouTube, here are three really good playlists tailored for beginner, intermediate and advanced Gophers:

  • Golang University 101 introduces basic Go concepts and shows you how to use Go tools for creating and managing Go code
  • Golang University 201 takes it to the next level, explaining important technologies like testing, web services, and APIs
  • Golang University 301 dives into more advanced topics such as the Go scheduler, implementation of maps and channels, and optimization techniques

Go Mobile adds support for mobile platforms (Android and iOS). You can write an all-Go native mobile application or a library that contains bindings from Go packages that can be called from Java (Android) and Objective-C (iOS). Check out the Go Mobile page for more information.


Yujiaao
12.7k 声望4.7k 粉丝

[链接]