美文网首页
golang快速入门

golang快速入门

作者: hewolf | 来源:发表于2019-06-20 18:02 被阅读0次

    语言介绍

    Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言

    罗伯特·格瑞史莫(Robert Griesemer),罗勃·派克(Rob Pike)及肯·汤普逊(Ken Thompson)于2007年9月开始设计Go,稍后Ian Lance Taylor、Russ Cox加入项目。Go是基于Inferno操作系统所开发的。Go于2009年11月正式宣布推出,成为开放源代码项目,并在LinuxMac OS X平台上进行了实现,后来追加了Windows系统下的实现。在2016年,Go被软件评价公司TIOBE 选为“TIOBE 2016 年最佳语言”

    Robert Griesemer, Rob Pike 和 Ken Thompson。Robert在开发Go之前是Google V8、Chubby和HotSpot JVM的主要贡献者;Rob主要是Unix、UTF-8、plan9的作者;Ken主要是B语言、C语言的作者、Unix之父。

    为什么会设计go语言

    设计Go语言是为了解决当时Google开发遇到的以下这些问题:

    大量的C++代码,同时又引入了Java和Python
    
    成千上万的工程师
    
    数以万计行的代码
    
    分布式的编译系统
    
    数百万的服务器
    

    其主要有以下几个方面的痛点:

    编译慢
    
    失控的依赖
    
    每个工程师只是用了一个语言里面的一部分
    
    程序难以维护(可读性差、文档不清晰等)
    
    更新的花费越来越长
    
    交叉编译困难
    

    所以,他们当时设计Go的目标是为了消除各种缓慢和笨重、改进各种低效和扩展性。Go是由那些开发大型系统的人设计的,同时也是为了这些人服务的;它是为了解决工程上的问题,不是为了研究语言设计;它还是为了让我们的编程变得更舒适和方便

    语法规则介绍

    包管理

    package main
    
    import (
        "fmt"
        "math/rand"
    )
    
    func main() {
        fmt.Println("My favorite number is", rand.Intn(10))
    }
    
    output:
    My favorite number is 1
    

    每个 Go 程序都是由包组成的。
    程序运行的入口是包 main
    这个程序使用并导入了包 "fmt" 和 "math/rand"
    按照惯例,包名与导入路径的最后一个目录一致。
    例如,"math/rand" 包由 package rand 语句开始。
    同一目录下只能用同一个包名

    • 导出名

    在导入了一个包之后,就可以用其导出的名称来调用它。
    在 Go 中,首字母大写的名称是被导出的。
    Foo 和 FOO 都是被导出的名称。名称 foo 是不会被导出的。
    foo-相当于php的private , Foo 和 FOO 相当于public

    package exports
    
    import "fmt"
    
    func Foo(){
        fmt.Println("it is Foo")
    }
    
    func FOO(){
        fmt.Println("it is FOO")
        foo()
    }
    
    func foo(){
        fmt.Println("it is foo")
    }
    

    函数

    package main
    
    import "fmt"
    
    func add(x int, y int) int {
        return x + y
    }
    
    func add2(x, y int) int{
        return x+y
    }
    
    func add3(x, y int) (z int){
        z = x+y
        return
    }
    
    func main() {
        fmt.Println(add(42, 13))
        fmt.Println(add2(42, 13))
        fmt.Println(add3(42, 13))
    }
    
    output:
    55
    55
    55
    

    函数格式 func 函数名(无参/参数1, 参数2...)(返回结果|无){ 函数体}
    执行发现这三个函数返回结果相同,只是格式不同
    add-函数符合基本的函数格式
    add2-对于同一类型参数,可以在最后一个参数后面指明参数类型
    add3-函数返回结果可以在返回结构中声明

    • 多值返回
    package main
    
    import "fmt"
    
    func swap(x, y string) (string, string) {
        return y, x
    }
    
    func main() {
        a, b := swap("hello", "world")
        fmt.Println(a, b)
    }
    
    output:
    world hello
    

    函数可以返回任意数量的返回值。
    swap 函数返回了两个字符串。

    变量

    package main
    
    import "fmt"
    
    var (
        a  bool
        b  string
        c  int        //有符号-等于cpu位数-- 如果是64位(-9223372036854775808 到 9223372036854775807)-`uname -m`
        c1 int8       //有符号-占用8bit(-128 到 127),int16,int32,int64类推
        d  uint       //无符号-等于cpu位数- 如果是64位(0 到 18446744073709551615)
        d1 uint8      //无符号-占用8bit(0 到 255), uint16,uint32,uint64类推
        e  rune       // int32 别名
        f  byte       // uint8 别名
        g  float32    //1.401298464324817070923729583289916131280e-45 -- 3.402823466385288598117041834516925440e+38 (23位小数f,8位偏置指数e,1位符号s)
        h  float64    //4.940656458412465441765687928682213723651e-324 -- 1.797693134862315708145274237317043567981e+308(52位小数f,11位偏置指数e,1位符号s)
        j  complex64  //32 位实数和虚数
        k  complex128 //64 位实数和虚数
    )
    
    var aa, bb, ee bool
    var ff, gg int = 5, 6
    var hh, ii = 5, true
    
    func main() {
        var aaa, bbb bool = true, false
        ccc, ddd := true, 18
        fmt.Println(a, b, c, c1, d, d1, e, f, g, h, j, k)
        fmt.Println(aa, bb, ee)
        fmt.Println(ff, gg)
        fmt.Println(hh, ii)
        fmt.Println(aaa, bbb)
        fmt.Println(ccc, ddd)
    }
    output:  
    false  0 0 0 0 0 0 0 0 (0+0i) (0+0i)
    false false false
    5 6
    5 true
    true false
    true 18
    

    go 基本的数据类型有bool类型,字符串类型,数字类型(复数类型)
    默认值分别为 false, "", 0(0+0i)
    变量定义可以包含初始值,每个变量对应一个。
    如果省略类型;变量从初始值中获得类型
    在函数中,:= 简洁赋值语句在明确类型的地方,可以用于替代 var 定义。
    函数外的每个语句都必须以关键字开始(varfunc、等等),:= 结构不能使用在函数外

    • 数字类型转换
    package main
    
    import (
        "fmt"
        "math"
        "strconv"
    )
    
    //StringToInt 字符串转整形
    func StringToInt(valstr string) int {
        val, err := strconv.Atoi(valstr)
        if err != nil {
            val = 0
        }
        return val
    }
    
    //IntToString 整形转字符串
    func IntToString(intval int) string {
        return strconv.Itoa(intval)
    }
    
    func main() {
        var x, y int = 3, 4
        var f float64 = math.Sqrt(float64(x*x + y*y))
        var z int = int(f)
        fmt.Println(x, y, z)
    
        a := IntToString(x) + "string"
        b := "168"
        c := StringToInt(b)
        fmt.Println(a, b+"s", c)
    }
    
    output:
    3 4 5
    3string 168s 168
    

    表达式 T(v) 将值 v 转换为类型 T
    数字类型一般可以直接显式转化,字符串和数字类型可以借助strconv包处理

    • 类型推导
    package main
    
    import "fmt"
    
    func main() {
        i := 42           // int
        f := 3.142        // float64
        g := 0.867 + 0.5i // complex128
        var j bool
        k := j
        fmt.Printf("The types is %T, %T, %T, %T\n", i, f, g, k)
    }
    output:
    The types is int, float64, complex128, bool
    

    在定义一个变量但不指定其类型时(使用没有类型的 var 或 := 语句), 变量的类型由右值推导得出。当右值定义了类型时,新变量的类型与其相同

    常量

    package main
    
    import "fmt"
    
    const Pi = 3.14
    
    const (
        Big   = 1 << 100
        Small = Big >> 99
    )
    
    func needInt(x int) int { return x*10 + 1 }
    func needFloat(x float64) float64 {
        return x * 0.1
    }
    
    func main() {
        const World = "世界"
        fmt.Println("Hello", World)
        fmt.Println("Happy", Pi, "Day")
    
        const Truth = true
        fmt.Println("Go rules?", Truth)
    
        fmt.Println(needInt(Small))
        fmt.Println(needFloat(Small))
        fmt.Println(needFloat(Big))
    }
    output:
    Hello 世界
    Happy 3.14 Day
    Go rules? true
    21
    0.2
    1.2676506002282295e+29
    

    常量的定义与变量类似,只不过使用 const 关键字。
    常量可以是字符、字符串、布尔或数字类型的值。
    常量不能使用 := 语法定义。
    一个未指定类型的数值常量可以作为不同数字类型传参

    循环

    package main
    
    import "fmt"
    
    func main() {
        sum := 0
        for i := 0; i < 10; i++ {
            sum += i
        }
        fmt.Println(sum)
    
        sum = 1
        for sum < 1000 {
            sum += sum
        }
        fmt.Println(sum)
    }
    
    output:
    45
    1024
    

    Go 只有一种循环结构——for 循环。
    基本的 for 循环除了没有了 ( ) 之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中做的一样,而 { } 是必须的。

    • 死循环
    package main
    
    import "fmt"
    
    func main() {
        for {
            fmt.Println("hello world")
        }
    }
    
    output:
    hello world
    hello world
    ....
    

    不手动停止的话,代码会一直运行下去

    if 语句

    package main
    
    import (
        "fmt"
        "math"
    )
    
    func sqrt(x float64) string {
        if x < 0 {
            return sqrt(-x) + "i"
        }
        return fmt.Sprint(math.Sqrt(x))
    }
    
    func main() {
        fmt.Println(sqrt(2), sqrt(-4))
    }
    output:
    1.4142135623730951 2i
    

    if 语句除了没有了 ( ) 之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中的一样,而 { } 是必须的。

    • if 的便捷语句
    package main
    
    import (
        "fmt"
        "math"
    )
    
    func pow(x, n, lim float64) float64 {
        if v := math.Pow(x, n); v < lim {
            return v
        }
        return lim
    }
    
    func pow2(x, n, lim float64) float64 {
        if v := math.Pow(x, n); v < lim {
            return v
        } else {
            fmt.Printf("%g >= %g\n", v, lim)
        }
        // 这里开始就不能使用 v 了
        return lim
    }
    
    func main() {
        fmt.Println(
            pow(3, 2, 10),
            pow(3, 3, 20),
            pow2(3, 2, 10),
            pow2(3, 3, 20),
        )
    }
    output:
    27 >= 20
    9 20 9 20
    

    跟 for 一样,if 语句可以在条件之前执行一个简单的语句。
    由这个语句定义的变量的作用域仅在 if 范围之内。
    在 if 的便捷语句定义的变量同样可以在任何对应的 else 块中使用。

    switch 语句

    package main
    
    import (
        "fmt"
        "runtime"
    )
    
    func main() {
        fmt.Print("Go runs on ")
        switch os := runtime.GOOS; os {
        case "darwin":
            fmt.Println("OS X.")
        case "linux":
            fmt.Println("Linux.")
        default:
            // freebsd, openbsd,
            // plan9, windows...
            fmt.Printf("%s.", os)
        }
    }
    output:
    Go runs on OS X
    

    switch 结构同其他语言一样
    switch 的条件从上到下的执行,当匹配成功的时候停止。
    每条case除非以 fallthrough 语句结束,否则分支会自动终止。

    • 没有条件的 switch
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        t := time.Now()
        switch {
        case t.Hour() < 12:
            fmt.Println("Good morning!")
        case t.Hour() < 17:
            fmt.Println("Good afternoon.")
        default:
            fmt.Println("Good evening.")
        }
    }
    output:
    Good evening.
    

    没有条件的 switch 同 switch true 一样。
    这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。

    defer 语句

    package main
    
    import "fmt"
    
    func echo(str string) string {
        defer fmt.Println("echo end")
        return "hello " + str
    }
    func main() {
        defer fmt.Println("world")
    
        fmt.Println("hello")
    
        echo("china")
    }
    output:
    hello
    echo end
    world
    

    defer 语句会延迟函数的执行直到上层函数返回。
    延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用

    • defer 栈
    package main
    
    import "fmt"
    
    func main() {
        fmt.Println("counting")
    
        for i := 0; i < 3; i++ {
            defer fmt.Println(i)
        }
    
        fmt.Println("done")
    }
    output:
    counting
    done
    2
    1
    0
    

    延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。

    派生类型

    指针

    package main
    
    import "fmt"
    
    func main() {
        i, j := 42, 2701
    
        p := &i         // point to i
        fmt.Println(*p) // read i through the pointer
        *p = 21         // set i through the pointer
        fmt.Println(i)  // see the new value of i
    
        p = &j         // point to j
        *p = *p / 37   // divide j through the pointer
        fmt.Println(j) // see the new value of j
    }
    output:
    42
    21
    73
    

    Go 具有指针。 指针保存了变量的内存地址。
    类型 *T 是指向类型 T 的值的指针。其零值是 nil
    var p int
    & 符号会生成一个指向其作用对象的指针。
    i := 42
    p = &i
    * 符号表示指针指向的底层的值。
    fmt.Println(
    p) // 通过指针 p 读取 i
    *p = 21 // 通过指针 p 设置 i
    这也就是通常所说的“间接引用”或“非直接引用”。

    结构体

    package main
    
    import "fmt"
    
    type Vertex struct {
        X int
        Y int
    }
    
    func main() {
        v := Vertex{1, 2}
        fmt.Println(v)
        v.X = 4
        fmt.Println(v)
        p := &v
        p.X = 1.5e9
        fmt.Println(v, v.X)
        fmt.Println(p, p.X)
        fmt.Println("------------------------")
        v1 := Vertex{1, 2}  // 类型为 Vertex
        v2 := Vertex{X: 1}  // Y:0 被省略
        v3 := Vertex{}      // X:0 和 Y:0
        p1 := &Vertex{1, 2} // 类型为 *Vertex
        fmt.Println(v1, v2, v3, p1)
        fmt.Printf("values is %+v, %+v, %+v, %+v \n", v1, v2, v3, p1)
        fmt.Printf("types is %T, %T, %T, %T \n", v1, v2, v3, p1)
        fmt.Printf("values is %#v, %#v, %#v, %#v \n", v1, v2, v3, p1)
    }
    output:
    {1 2}
    {4 2}
    {1500000000 2} 1500000000
    &{1500000000 2} 1500000000
    ------------------------
    {1 2} {1 0} {0 0} &{1 2}
    values is {X:1 Y:2}, {X:1 Y:0}, {X:0 Y:0}, &{X:1 Y:2} 
    types is main.Vertex, main.Vertex, main.Vertex, *main.Vertex 
    values is main.Vertex{X:1, Y:2}, main.Vertex{X:1, Y:0}, main.Vertex{X:0, Y:0}, &main.Vertex{X:1, Y:2} 
    

    一个结构体(struct)就是一个字段的集合。
    结构体字段使用点号来访问.
    结构体字段可以通过结构体指针来访问。

    数组

    package main
    
    import "fmt"
    
    func main() {
        var a [2]string
        a[0] = "Hello"
        a[1] = "World"
        fmt.Println(a[0], a[1])
        fmt.Println(a)
    }
    

    类型 [n]T 是一个有 n 个类型为 T 的值的数组。
    表达式 var a [10]int
    定义变量 a 是一个有十个整数的数组。
    数组的长度是其类型的一部分,因此数组不能改变大小
    这看起来是一个制约,但是请不要担心; Go 提供了更加便利的方式来使用数组。

    slice

    package main
    
    import "fmt"
    
    func main() {
        p := []int{2, 3, 5, 7, 11, 13}
        fmt.Println("p ==", p)
    
        for i := 0; i < len(p); i++ {
            fmt.Printf("p[%d] == %d\n", i, p[i])
        }
    }
    output:
    p == [2 3 5 7 11 13]
    p[0] == 2
    p[1] == 3
    p[2] == 5
    p[3] == 7
    p[4] == 11
    p[5] == 13
    

    一个 slice 会指向一个序列的值,并且包含了长度信息。
    []T 是一个元素类型为 T 的 slice。

    • 对 slice 切片
    package main
    
    import "fmt"
    
    func main() {
        p := []int{2, 3, 5, 7, 11, 13}
        fmt.Println("p ==", p)
        fmt.Println("p[1:4] ==", p[1:4])
    
        // 省略下标代表从 0 开始
        fmt.Println("p[:3] ==", p[:3])
    
        // 省略上标代表到 len(s) 结束
        fmt.Println("p[4:] ==", p[4:])
    }
    

    slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
    表达式 s[lo:hi]表示从 lo 到 hi-1 的 slice 元素,含两端。
    因此s[lo:lo]是空的,而 s[lo:lo+1]有一个元素。

    • 构造 slice
    package main
    
    import "fmt"
    
    func main() {
        a := make([]int, 5)
        printSlice("a", a)
        b := make([]int, 0, 5)
        printSlice("b", b)
        c := b[:2]
        printSlice("c", c)
        d := c[2:5]
        printSlice("d", d)
    }
    
    func printSlice(s string, x []int) {
        fmt.Printf("%s len=%d cap=%d %v\n",
            s, len(x), cap(x), x)
    }
    output:
    a len=5 cap=5 [0 0 0 0 0]
    b len=0 cap=5 []
    c len=2 cap=5 [0 0]
    d len=3 cap=3 [0 0 0]
    

    slice 由函数 make 创建。这会分配一个零长度的数组并且返回一个 slice 指向这个数组:
    a := make([]int, 5) // len(a)=5
    为了指定容量,可传递第三个参数到 make
    b := make([]int, 0, 5) // len(b)=0, cap(b)=5
    b = b[:cap(b)] // len(b)=5, cap(b)=5
    b = b[1:] // len(b)=4, cap(b)=4

    • nil slice
    package main
    
    import "fmt"
    
    func main() {
        var z []int
        fmt.Println(z, len(z), cap(z))
        if z == nil {
            fmt.Println("nil!")
        }
    }
    output:
    [] 0 0
    nil!
    

    slice 的零值是 nil
    一个 nil 的 slice 的长度和容量是 0。

    • 向 slice 添加元素
    package main
    
    import "fmt"
    
    func main() {
        var a []int
        printSlice("a", a)
    
        // append works on nil slices.
        a = append(a, 0)
        printSlice("a", a)
    
        // the slice grows as needed.
        a = append(a, 1)
        printSlice("a", a)
    
        // we can add more than one element at a time.
        a = append(a, 2, 3, 4)
        printSlice("a", a)
    }
    
    func printSlice(s string, x []int) {
        fmt.Printf("%s len=%d cap=%d %v\n",
            s, len(x), cap(x), x)
    }
    output:
    a len=0 cap=0 []
    a len=1 cap=1 [0]
    a len=2 cap=2 [0 1]
    a len=5 cap=6 [0 1 2 3 4]
    

    向 slice 添加元素是一种常见的操作,因此 Go 提供了一个内建函数 append。 内建函数的文档append 有详细介绍。

    func append(s []T, vs ...T) []T

    append 的第一个参数 s 是一个类型为 T 的数组,其余类型为 T 的值将会添加到 slice。
    append 的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。
    如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。
    返回的 slice 会指向这个新分配的数组。

    map

    package main
    
    import "fmt"
    
    type Vertex struct {
        Lat, Long float64
    }
    
    var m map[string]Vertex
    
    func main() {
        m = make(map[string]Vertex)
        m["Bell Labs"] = Vertex{
            40.68433, -74.39967,
        }
        fmt.Println(m["Bell Labs"])
    
        m := make(map[string]int)
    
        m["Answer"] = 42
        fmt.Println("The value:", m["Answer"])
    
        m["Answer"] = 48
        fmt.Println("The value:", m["Answer"])
    
        delete(m, "Answer")
        fmt.Println("The value:", m["Answer"])
    
        v, ok := m["Answer"]
        fmt.Println("The value:", v, "Present?", ok)
    }
    output:
    {40.68433 -74.39967}
    The value: 42
    The value: 48
    The value: 0
    The value: 0 Present? false
    

    map 映射键到值。
    map 在使用之前必须用 make 而不是 new 来创建;值为 nil 的 map 是空的,并且不能赋值
    在 map m 中插入或修改一个元素:
    m[key] = elem
    获得元素:
    elem = m[key]
    删除元素:
    delete(m, key)
    通过双赋值检测某个键存在:
    elem, ok = m[key]
    如果 key 在 m 中,ok 为 true 。否则, ok 为 false,并且 elem 是 map 的元素类型的零值。
    同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。

    range

    package main
    
    import (
        "fmt"
        "sort"
    )
    
    var pow = []int{1, 2, 4, 8}
    
    func main() {
        for i, v := range pow {
            fmt.Printf("2**%d = %d\n", i, v)
        }
    
        m := make(map[string]int)
        m["Answer"] = 42
        m["welcome"] = 48
    
        var mp []string
        for k, v := range m {
            fmt.Printf("key:%s, value:%d \n", k, v)
            mp = append(mp, k)
        }
    
        sort.Strings(mp)
        for _, k := range mp {
            fmt.Printf("key:%s, value:%d \n", k, m[k])
        }
        pow := make([]int, 3)
        for i := range pow {
            pow[i] = 1 << uint(i)
        }
        for _, value := range pow {
            fmt.Printf("%d\n", value)
        }
    }
    
    output:
    2**0 = 1
    2**1 = 2
    2**2 = 4
    2**3 = 8
    key:Answer, value:42 
    key:welcome, value:48 
    key:Answer, value:42 
    key:welcome, value:48 
    1
    2
    4
    

    for 循环的 range 格式可以对 slice 或者 map 进行迭代循环。
    可以通过赋值给 _ 来忽略序号和值。
    如果只需要索引值,去掉“, value”的部分即可。

    函数的闭包

    package main
    
    import "fmt"
    
    func adder() func(int) int {
        sum := 0
        return func(x int) int {
            sum += x
            return sum
        }
    }
    
    func main() {
        pos, neg := adder(), adder()
        for i := 0; i < 3; i++ {
            fmt.Println(
                pos(i),
                neg(-2*i),
            )
        }
    }
    output:
    0 0
    1 -2
    3 -6
    

    Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
    例如,函数 adder 返回一个闭包。每个闭包都被绑定到其各自的 sum 变量上。

    方法

    package main
    
    import (
        "fmt"
        "math"
    )
    
    type Vertex struct {
        X, Y float64
    }
    
    func (v *Vertex) Abs() float64 {
        return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
    
    func (v *Vertex) Scale(f float64) {
        v.X = v.X * f
        v.Y = v.Y * f
    }
    
    func (v Vertex) Scale2(f float64) {
        v.X = v.X * f
        v.Y = v.Y * f
    }
    
    
    
    type MyFloat float64
    
    func (f MyFloat) Abs() float64 {
        if f < 0 {
            return float64(-f)
        }
        return float64(f)
    }
    
    
    func main() {
        v := &Vertex{3, 4}
        fmt.Println(v.Abs())
        v.Scale(2)
        fmt.Printf("%#v \n", v)
        v.Scale2(2)
        fmt.Printf("%#v \n", v)
        v.Scale(2)
        fmt.Printf("%#v \n", v)
    
        f := MyFloat(-math.Sqrt2)
        fmt.Println(f.Abs())
    }
    output:
    5
    &main.Vertex{X:6, Y:8} 
    &main.Vertex{X:6, Y:8} 
    &main.Vertex{X:12, Y:16} 
    1.4142135623730951
    

    Go 没有类。然而,仍然可以在结构体类型上定义方法。
    方法接收者 出现在 func 关键字和方法名之间的参数中。
    你可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。
    但是,不能对来自其他包的类型或基础类型定义方法。
    方法可以与命名类型或命名类型的指针关联。
    刚刚看到的两个 Abs 方法。一个是在 *Vertex 指针类型上,而另一个在 MyFloat 值类型上。
    有两个原因需要使用指针接收者。

    • 首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。
    • 其次,方法可以修改接收者指向的值。

    尝试修改 Abs 的定义,同时 Scale 方法使用 Vertex 代替 *Vertex 作为接收者。
    当 v 是 Vertex 的时候 Scale 方法没有任何作用。Scale 修改 v。当 v 是一个值(非指针),方法看到的是 Vertex 的副本,并且无法修改原始值。Abs 的工作方式是一样的。只不过,仅仅读取 v。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。

    接口

    package main
    
    import (
       "fmt"
       "os"
    )
    
    type Reader interface {
       Read(b []byte) (n int, err error)
    }
    
    type Writer interface {
       Write(b []byte) (n int, err error)
    }
    
    type ReadWriter interface {
       Reader
       Writer
    }
    
    func main() {
       var w Writer
    
       // os.Stdout 实现了 Writer
       w = os.Stdout
    
       fmt.Fprintf(w, "hello, writer\n")
    
       // os.Stdin 实现了 Writer, 但不是标准输出
       w = os.Stdin
    
       fmt.Fprintf(w, "hello2, writer\n")
    }
    output:
    hello, writer
    

    接口类型是由一组方法定义的集合。
    接口类型的值可以存放实现这些方法的任何值。
    类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。
    隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
    因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义

    • Stringers
    package main
    
    import "fmt"
    
    type Person struct {
        Name string
        Age  int
    }
    
    func (p Person) String() string {
        return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
    }
    
    func main() {
        a := Person{"Arthur Dent", 42}
        z := Person{"Zaphod Beeblebrox", 9001}
        fmt.Println(a, z)
        fmt.Printf("%#v", a)
    }
    
    output:
    Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
    main.Person{Name:"Arthur Dent", Age:42}
    

    一个普遍存在的接口是 fmt 包中定义的 StringerStringer 是一个可以用字符串描述自己的类型。fmt包 (还有许多其他包)使用这个来进行输出

    错误

    package main
    
    import (
        "fmt"
        "time"
    )
    
    type MyError struct {
        When time.Time
        What string
    }
    
    func (e *MyError) Error() string {
        return fmt.Sprintf("at %v, %s",
            e.When, e.What)
    }
    
    func run() error {
        return &MyError{
            time.Now(),
            "it didn't work",
        }
    }
    
    func main() {
        if err := run(); err != nil {
            fmt.Println(err)
        }
    }
    output:
    at 2019-06-26 20:43:20.529809 +0800 CST m=+0.000310142, it didn't work
    

    Go 程序使用 error 值来表示错误状态。
    与 fmt.Stringer 类似,error 类型是一个内建接口:

    type error interface {
          Error() string
    }
    

    (与 fmt.Stringer 类似,fmt 包在输出时也会试图匹配 error。)

    通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil, 来进行错误处理。

    i, err := strconv.Atoi("42")
    if err != nil {
        fmt.Printf("couldn't convert number: %v\n", err)
    }
    fmt.Println("Converted integer:", i)
    

    error 为 nil 时表示成功;非 nil 的 error 表示错误。

    Readers

    package main
    
    import (
        "fmt"
        "io"
        "strings"
    )
    
    func main() {
        r := strings.NewReader("Hello, Reader!")
    
        b := make([]byte, 8)
        for {
            n, err := r.Read(b)
            fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
            fmt.Printf("b[:n] = %q\n", b[:n])
            if err == io.EOF {
                break
            }
        }
    }
    output:
    n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
    b[:n] = "Hello, R"
    n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
    b[:n] = "eader!"
    n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
    b[:n] = ""
    

    io 包指定了 io.Reader 接口, 它表示从数据流结尾读取。
    Go 标准库包含了这个接口的许多实现, 包括文件、网络连接、压缩、加密等等。
    io.Reader 接口有一个 Read 方法:

    func (T) Read(b []byte) (n int, err error)

    Read 用数据填充指定的字节 slice,并且返回填充的字节数和错误信息。 在遇到数据流结尾时,返回 io.EOF 错误。
    例子代码创建了一个 strings.Reader。 并且以每次 8 字节的速度读取它的输出。

    Web 服务器

    package main
    
    import (
        "fmt"
        "log"
        "net/http"
    )
    
    type Hello struct{}
    
    func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello!")
    }
    
    func main() {
        var h Hello
        err := http.ListenAndServe("localhost:4000", h)
        if err != nil {
            log.Fatal(err)
        }
    }
    

    包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求:

    package http
    type Handler interface {
        ServeHTTP(w ResponseWriter, r *Request)
    }
    

    在这个例子中,类型 Hello 实现了 http.Handler
    访问 http://localhost:4000/ 会看到来自程序的问候。

    图片

    package main
    
    import (
        "fmt"
        "image"
    )
    
    func main() {
        m := image.NewRGBA(image.Rect(0, 0, 100, 100))
        fmt.Println(m.Bounds())
        fmt.Println(m.At(0, 0).RGBA())
    }
    output:
    (0,0)-(100,100)
    0 0 0 0
    

    Package image 定义了 Image 接口:

    package image
    
    type Image interface {
        ColorModel() color.Model
        Bounds() Rectangle
        At(x, y int) color.Color
    }
    

    注意Bounds 方法的 Rectangle 返回值实际上是一个 image.Rectangle, 其定义在 image 包中。
    (参阅文档了解全部信息。)
    color.Colorcolor.Model 也是接口,但是通常因为直接使用预定义的实现 image.RGBAimage.RGBAModel而被忽视了。这些接口和类型由image/color 包定义。

    多线程

    goroutine

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func say(s string) {
        for i := 0; i < 3; i++ {
            time.Sleep(100 * time.Millisecond)
            fmt.Println(s)
        }
    }
    
    func main() {
        go say("world")
        say("hello")
    }
    output:
    hello
    world
    world
    hello
    world
    hello
    

    goroutine 是由 Go 运行时环境管理的轻量级线程。

    go f(x, y, z)

    开启一个新的 goroutine 执行

    f(x, y, z)

    f , x , yz 是当前 goroutine 中定义的,但是在新的 goroutine 中运行 f

    goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。sync 提供了这种可能,不过在 Go 中并不经常用到,因为有其他的办法。(在接下来的内容中会涉及到。)

    channel

    package main
    
    import "fmt"
    
    func sum(a []int, c chan int) {
        sum := 0
        for _, v := range a {
            sum += v
        }
        c <- sum // 将和送入 c
    }
    
    func main() {
        a := []int{7, 2, 8, -9, 4, 0}
    
        c := make(chan int)
        go sum(a[:len(a)/2], c)
        go sum(a[len(a)/2:], c)
        x, y := <-c, <-c // 从 c 中获取
    
        fmt.Println(x, y, x+y)
    }
    output:
    -5 17 12
    

    channel 是有类型的管道,可以用 channel 操作符 <- 对其发送或者接收值。

    ch <- v    // 将 v 送入 channel ch。
    v := <-ch  // 从 ch 接收,并且赋值给 v。
    (“箭头”就是数据流的方向。)
    

    和 map 与 slice 一样,channel 使用前必须创建:

    ch := make(chan int)
    

    默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。

    • 缓冲 channel
    package main
    
    import "fmt"
    
    func main() {
        c := make(chan int, 2)
        c <- 1
        c <- 2
        fmt.Println(<-c)
        fmt.Println(<-c)
    }
    output:
    1
    2
    

    channel 可以是 带缓冲的。为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel:

    ch := make(chan int, 100)
    

    向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。

    range 和 close

    package main
    
    import (
        "fmt"
    )
    
    func fibonacci(n int, c chan int) {
        x, y := 0, 1
        for i := 0; i < n; i++ {
            c <- x
            x, y = y, x+y
        }
        close(c)
    }
    
    func main() {
        c := make(chan int, 4)
        go fibonacci(cap(c), c)
        for i := range c {
            fmt.Println(i)
        }
    }
    output:
    0
    1
    1
    2
    

    range 和 close
    发送者可以 close 一个 channel 来表示再没有值会被发送了。
    接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么经过

    v, ok := <-ch
    

    之后 ok 会被设置为 false
    循环 for i := range c 会不断从 channel 接收值,直到它被关闭。
    注意: 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。 还要注意: channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 range

    select

    package main
    
    import "fmt"
    
    func fibonacci(c, quit chan int) {
        x, y := 0, 1
        for {
            select {
            case c <- x:
                x, y = y, x+y
            case <-quit:
                fmt.Println("quit")
                return
            }
        }
    }
    
    func main() {
        c := make(chan int)
        quit := make(chan int)
        go func() {
            for i := 0; i < 5; i++ {
                fmt.Println(<-c)
            }
            quit <- 0
        }()
        fibonacci(c, quit)
    }
    output:
    0
    1
    1
    2
    3
    quit
    

    select 语句使得一个 goroutine 在多个通讯操作上等待。
    select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个。

    • 默认选择
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        tick := time.Tick(100 * time.Millisecond)
        boom := time.After(500 * time.Millisecond)
        for {
            select {
            case <-tick:
                fmt.Println("tick.")
            case <-boom:
                fmt.Println("BOOM!")
                return
            default:
                fmt.Println("    .")
                time.Sleep(50 * time.Millisecond)
            }
        }
    }
    output:
        .
        .
    tick.
        .
        .
    tick.
        .
        .
    BOOM!
    

    select 中的其他条件分支都没有准备好的时候,default 分支会被执行。
    为了非阻塞的发送或者接收,可使用 default 分支:

    select {
    case i := <-c:
        // 使用 i
    default:
        // 从 c 读取会阻塞
    }
    

    相关文章

      网友评论

          本文标题:golang快速入门

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