learn X in Y minutes with X=go

本文是一篇翻译,原文地址是:https://learnxinyminutes.com/docs/go/
强烈建议读原文,而不是在这里浪费时间


可以从 https://learnxinyminutes.com/docs/files/learngo.go 获取到代码


go 语言是为了解决问题被发明的,虽然使用 go 不是计算机科学界最新的趋势,但是却是解决真实世界问题的最新最快的方法(译注:go不是最新的,也不是最快的,这两者go都表现不错,是某种折中)


go 与命令式的静态类型语言有些相似的概念。它编译速度很快,运行速度也很快,通过增加一些容易理解的并发能力,可以充分利用当今的多核 CPU,也有很多功能是针对大规模程序设计的。


go 有一个很棒的标准库,社区也非常的活跃。


// 这是单行注释
/* 这是多行
注释 */


// 每个源文件都从一行 package 语句开始
// main 是一个特殊的名字,声明这是一个可执行程序,而不是一个库
package main
// import 声明这个文件内使用到的包 (译注:包名是个字符串,需要引号)
import (
    "fmt"       // go 标准库里的一个包
    "io/ioutil" // 一些 IO 工具函数
    m "math"    // 数学库,本文件可以用 m 这个别名来代替使用
    "net/http"  // 是的,这是一个 web server
    "os"        // OS 相关函数,比如和文件系统打交道
    "strconv"   // 字符串转换
)


// 函数定义。main 是特别的,它是一个程序的入口
// 不管你是否喜欢,go 使用大括号表示开始和结束


func main() {
    // Println 向 stdout 输出一行
    // 使用 `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()                            // < y 分钟, 学更多!
}


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


// 内置类型和字面值
func learnTypes() {
    // 短声明一般都能给你你想要的
    str := "Learn Go!" // 符串类型


    // (译注:反单引号)
    s2 := `A "raw" string literal
can include line breaks.` // 同样的类型(译注:代码中的字符串的意思是:原始字符串可以换行。)


    // 非 ascii 字面值。go 源码是 UTF-8 编码
    g := 'Σ' // 符号类型,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


    // 分片可以变长。数组和分片各有优点
    // 但是使用分片要更常见
    s3 := []int{4, 5, 9}    // 和 a5 做一下对比,这里没有省略号
    s4 := make([]int, 4)    // 分配 4 个 int 的分片,全部初始化为 0
    var d2 [][]float64      // 只是申明,什么都没有分配
    bs := []byte("a slice") // 类型转换语法


    // 因为分片是变长的,所以可以按需增加
    // 内置函数 append() 用来为分片增加元素
    // 第一个元素是需要增加元素的分片。通常,
    // 数组变量就地更新,如下所示。
    s := []int{1, 2, 3}     // Result is a slice of length 3.
    s = append(s, 4, 5, 6)  // Added 3 elements. Slice now has length of 6.
    fmt.Println(s) // Updated slice is now [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() // 声明指向 int 的指针 p,q
    fmt.Println(*p, *q)   // * 后接着指针值,这会打印两个 int 值


    // map 是动态增长的关系型数据类型,像别的语言的hash表或是字典类型
    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 有完整的垃圾回收。它有指针,但是不允许指针运算
// 你可能在空指针上犯错,但是不会因为指针的增加运算
func learnMemory() (p, q *int) {
    // 命名的返回值 p 和 q 是 int 指针类型
    p = new(int) // 内置函数 new 分配内存
    // 分配的 int 初始化为 0, p 不在是空(nil)
    s := make([]int, 20) // 分配 20 个 int 大小的单块内存
    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.
    }
    
    // 多级 if 可以用 switch 语句
    x := 42.0
    switch x {
    case 0:
    case 1:
    case 42:
        // 这里不会("fallthrough")向下执行
        /*
        有一个 `fallthrough` 的关键字,参考:
          https://github.com/golang/go/wiki/Switch#fall-through
        */
    case 43:
        // 不会运行到这里
    default:
        // 默认行为是可选的
    }
    // 和 if 一样,for 也不使用小括号
    // 在 for 和 if 里声明的变量都属于它们自己的本地作用域
    for x := 0; x < 3; x++ { // ++ 是语句(statement)
        fmt.Println("iteration", x)
    }
    // 这里 x == 42 


    // for 是 go 里唯一的循环语句,但是有别的可选的形式
    for { // 死循环
        break    // 开个玩笑
        continue // 不能运行到这里
    }


    // 你可以使用 range 来迭代访问数组,slice,字符串,map,或者是 channel
    // range 返回一个(channel)或两个值(数组,slice,字符串,map或是一个channel)
    for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
        // 对 map 里的每对值,打印键和值
        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
    }
    // 函数字面值(function literal)是闭包
    xBig := func() bool {
        return x > 10000 // 引用switch 上面声明的 x
    }
    x = 99999
    fmt.Println("xBig:", xBig()) // true
    x = 1.3e3                    // x == 1300
    fmt.Println("xBig:", xBig()) // false


    // 而且,函数字面值(function literal)可能被定义和内联调用
    // 只要满足以下条件,就可以作为函数参数:
    // a) 函数字面值(function literal)立即被调用
    // b) 返回值和参数类型匹配
    fmt.Println("Add + double two numbers: ",
        func(a, b int) int {
            return (a + b) * 2
        }(10, 2)) // 用参数 10 和 2 调用
    // => Add + double two numbers: 24


    // 当你需要的时候,你会喜欢它的
    goto love
love:


    learnFunctionFactory() // 函数返回函数是 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 里也可用带参数的函数字面值(function literal)实现
func sentenceFactory(mystring string) func(before, after string) string {
    return func(before, after string) string {
        return fmt.Sprintf("%s %s %s", before, mystring, after) // new string
    }
}


func learnDefer() (ok bool) {
    // 推迟语句在函数返回前执行
    defer fmt.Println("deferred statements execute in reverse (LIFO) order.")
    defer fmt.Println("\nThis line is being printed first because")
    // defer 经常用来关闭文件,这样打开文件和关闭文件的功能语句就会在一起
    return true
}


// 用一个方法 String 来定义一个 Stringer 的接口类型
type Stringer interface {
    String() string
}


// 定义结构体 pair,两个成员,名字是 x,y,类型是 int
type pair struct {
    x, y int
}


// 定义 pair 的一个成员函数, pair 实现了 Stringer 的接口,
// 因为 pair 实现了接口中的所有函数
func (p pair) String() string { // p 称为 "receiver"
    // Sprintf 是 fmt 里另一个 public 函数
    // 引用 p 的成员使用点语法
    return fmt.Sprintf("(%d, %d)", p.x, p.y)
}


func learnInterfaces() {
    // 大括号语法表示的是一个 "struct literal"。
    // 它会得到一个初始化后的结构体。:= 语法声明和初始化 p 为这个结构体
    p := pair{3, 4}
    fmt.Println(p.String()) // 调用 pair 类型的 p 的方法 String
    var i Stringer          // 声明 Stringer 接口
    i = p                   // 合法有效,又因 pair 实现了 Stringer
    // 调用 Stringer 类型的 i 的 String 方法,输出和上面相同
    fmt.Println(i.String())


    // fmt 包里的函数会调用需要打印的对象本身的 String 方法
    fmt.Println(p) // 输出同上,调用 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 为 false,因为 1 不在 map 里
        fmt.Println("no one there")
    } else {
        fmt.Print(x) // 如果在 map 里,x 就是对应的值.
    }
    // 错误值不仅仅表明简单的 “ok” 而是关于错误的更多信息
    if _, err := strconv.Atoi("non-int"); err != nil { // _ 丢弃返回值
        // 打印 'strconv.ParseInt: parsing "non-int": invalid syntax'
        fmt.Println(err)
    }
    // 我们等会儿会继续回到接口的内容。于此同时,我们学习并发
    learnConcurrency()
}


// c 是一个 channel,一个并发安全的通讯对象
// c is a channel, a concurrency-safe communication object.
func inc(i int, c chan int) {
    c <- i + 1 // 当一个 channel 出现在左边时,<- 表示一个发送操作符
}


// 我们使用 inc 来并发的让一些数字增加
func learnConcurrency() {
    //和先前创建 slice 相同的 make 函数。make 创建并初始化 slice,map 和 channel
    c := make(chan int)
    // 开始 3 个并发的 goroutine。数字会同时增加,
    // 如果机器有能力并且正确得到了正确的配置,可能时并行运行
    // 3 个数字都会发送到相同的 channel
    go inc(0, c) // go 是一个语句,但是会开启一个 goroutine
    go inc(10, c)
    go inc(-805, c)
    // 从 channel 中读取 3 个结果值,并打印出来
    // 结果到达的顺序是不确定的!
    fmt.Println(<-c, <-c, <-c) // channel 在右边, <- 表示一个接收操作符


    cs := make(chan string)       // 另一个channel,这个表示字符串
    ccs := make(chan chan string) // 这个表示一个字符串 channel
    go func() { c <- 84 }()       // 开启一个简单发送值的 channel
    go func() { cs <- "wordy" }() // 同上,不过是针对 cs
    
    // select 和 switch 有相似的语法,除了每个分支有个 channel 操作
    // select 会在通讯就绪的分支中随机挑选一个
    select {
    case i := <-c: // 收到的值可以赋给变量
        fmt.Printf("it's a %T", i)
    case <-cs: // 或者丢弃收到的值
        fmt.Println("it's a string")
    case <-ccs: // 空的 channel,通讯未就绪
        fmt.Println("didn't happen.")
    }
    // 到此,c 和 cs 里的值已经被取走,上面启动的两个 goroutine 中的一个
    // 已经完成,其它的继续阻塞


    learnWebProgramming() // 网络编程 go 做到了,你肯定也想。
}


// http 包里的一个函数就可以实现 web 服务
func learnWebProgramming() {


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


    requestServer()
}


// 实现 http.Handler 唯一接口 ServeHTTP,使 pair 成它成为一个 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))
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值