您的位置:首页 > 编程语言 > Go语言

Go语言个人学习笔记(Pythonista)

2021-05-04 20:37 816 查看

go是个什么语言?

引自知乎

go是面向过程的语言, go的代码不讲究封装和整理,很多源码都是几千行代码呆在单个文件里面,表现出了典型的过程式语言的基本特征. c也是过程式的,go的语言特性跟c语言很相似,只是增加了gc,goroitine等常用工具而已,作者本身就对oop,fp等缺乏了解,只是根据c的经验,整理出了一些常用的工具,然后将其放到语言的runtime中去罢了.

另外go官网对于是否面询对象编程语言,他们自己说是:Yes and No。明显go是允许OO的编程风格的,但又缺乏一些常见类型继承结构和类对象。Go自己觉得这一套挺好的,更加的容易使用且通用性更强。很多时候我们用OO的思想来组织我们的项目,但需要注意的是继承关系是一种非常强的耦合,有时候会给以后的升级带来麻烦。大型项目中可能是非常有帮助的,当然小型的项目可能好处不是很明显。

接下来正式学习Go语言

包的引入

  • 和其他语言的单个文件就是一个包的组织方式不同,go的package不局限于一个文件,多个文件可以声明同一个名称的包,互相间可以直接调用,不用import,同一个package内全局变量和函数等不能同名。
  • go不要求package的名称和所在目录名相同,但建议最好保持相同。引入包的时候,go会使用子目录名作为包的路径,例如:import “root_dir/sub_dir/package”,
  • import导入包,调用其属性或方法时,其属性和方法都是大写字母开头的,写自己的方法和属性时也要遵循这个规则.
  • 一个文件夹下只能有一个package!!!
    package main
    import (
    // Python是要加逗号隔开,而且没有引号
    "fmt"
    "math"
    // 子包,Python是from...import...
    "math/rand"
    )
    func main() {
    fmt.Println(math.Pi)    // Python建议小写字母开头
    fmt.Println("My favorite number is", rand.Intn(10))
    }

基础数据类型

常见类型

int, string, bool, float32    // Python是int, str, bool, float
uint, int64, float64
byte // uint8 的别名
  • 声明变量

    var i int
    var i, j int = 1, 2    // 同类型变量可以在一起声明, 且一次赋值多个变量
    var c, python, java = true, false, "no!"    // 可以省略类型,变量从初始值中获得类型
    i := 1    // 不用加var关键词的声明,compiler会自动识别, 这和Python差不多
    c, python, java := true, false, "no!”
    // Python也可以指定数据类型, i: int = 0, j: int = i
  • 声明常量

    const Pi int = 3.14    // 常量不能使用 := 语法定义,必须指定数据类型。是Python的全局变量吗?
  • 默认空值

    int:    0
    string:    ""
    bool:    false    // Python是False
    float32:    0.0
  • 类型转换

    var i int = 42
    var f float64 = float64(i)
    var u uint = uint(f)
  • string和int类型相互转换
  • string转成int: int, err := strconv.Atoi(string)
  • string转成int64:
      int64, err := strconv.ParseInt(string, 10, 64)
  • int转成string:
      string := strconv.Itoa(int)
  • int64转成string:

      string := strconv.FormatInt(int64,10)
      
      
  • 查看变量数据类型
    fmt.Printf(`%T`, num)
    或
    fmt.Println(reflect.TypeOf(num))
  • 逻辑处理

    逻辑运算符

    • && 逻辑 与 AND
    • || 逻辑 或 OR
    • ! 逻辑 非 NOT

    if, switch控制

    • if控制语句

      i := 2
      if i < 3 {
      j := 3
      }
    • if
      语句可以在条件之前执行一个简单的语句, 用分号;分隔
      if i := 2; i < 3 {
      j := 3
      }
      // 这比Python便捷

    // if else语句

    if i := 2; i < 3 {
    j := 3
    } else {
    j := i
    }
    // else if 使用switch实现
    • switch, Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch。
    • 1) 一分支多值

      当出现多个 case 要放在一起的时候,可以写成下面这样:
      //一分支,多值
      switch str {
      case "hello", "nihao":
      fmt.Printf("一分支,多值:%s \n", str)
      default:
      fmt.Println("hi")
      }
      不同的 case 表达式使用逗号分隔。
    • 2) 分支表达式
      case 后不仅仅只是常量,还可以和 if 一样添加表达式,代码如下:
      //分支表达式
      var num = 7
      switch {
      case num > 1 && num < 5:
      fmt.Println("小于5的数")
      case num > 5 && num < 10:
      fmt.Println("大于5,小于10的数")
      }

    代码输出如下:
    大于5,小于10的数

    - 使用switch实现if, else if, else

    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.")
    }
    }

    ## for循环

    // 感觉比Python的要统一些, 新语言比较大胆.
    func main() {
    // 实现0一直+到9,Σ(0-9)
    sum := 0
    for i := 0; i < 10; i++ {
    sum += i
    }

    // 类似while语句,累积小于1000的
    sum := 1
    for sum < 1000 {
    sum += sum
    }
    
    // while true 省略了循环条件,就成了死循环
    for {
    }

    }

    # 数据结构
    
    ## 数组 array
    
    - 定义变量 a 是有2个字符串的数组

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

    - 遍历数组

    func main() {
    var 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])
    }
    }

    ## 序列 slice
    
    - 定义slice, 类似单数据类型的Python列表

    var z []int // slice 的零值是

    nil

    s := []{1, 2, 3}

    - 对 slice 切片

    s[:2], s[0:0], s[1:2] // s[lo:hi] 表示从lo到hi-1的元素,不包含第hi个元素

    - 示例

    func main() {
    p := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("p ==", p)
    fmt.Println("p[1:4] ==", p[1:4])
    // 省略起始index代表从 0 开始
    fmt.Println("p[:3] ==", p[:3])
    // 省略结束index代表到 len(s) 结束
    fmt.Println("p[4:] ==", p[4:])
    }

    - range语句遍历序列,可以用for循环 配合 range语句对 slice 进行迭代循环。

    var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
    func main() {
    for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v) // 2的次方, i表示索引 0开始, v表示值
    }
    }

    - make 构造slice

    slice 由函数 make 创建。这会分配一个零长度的数组并且返回一个 slice 指向这个数组,【按python的range函数理解】:
    a := make([]int, 5) // len(a)=5, 长度为5, 容量为5的全0序列,[0 0 0 0 0]
    b := make([]int, 0, 5) // len(b)=0, cap(b)=5, 长度为0,容量为5的空序列, 占空间较少,[]
    b = b[:cap(b)] // len(b)=5, cap(b)=5, 展开所有容量,变成全0序列
    b = b[1:] // len(b)=4, cap(b)=4

    - 向 slice 添加元素

    func main() {
    var a []int
    a = append(a, 1) // 尾部追加
    a = append(0, a) // 头部追加
    a = append(a, 2, 3, 4) // 可以添加多个
    a = append(a, "a”) // 但不能是其他类型数据(报错)
    }

    - slice遍历range出来的是index和value,如果只需要索引,可以去掉“, value”的部分即可。仅需要value,则把i赋值给 _ 来忽略索引。

    func main() {
    pow := make([]int, 10)
    for i := range pow {
    pow[i] = 1 << uint(i)
    }
    for _, value := range pow {
    fmt.Printf("%d\n", value)
    }
    }

    - 多层序列嵌套

    func main() {
    dx, dy := 8, 4
    sdy := make([][]uint8, dy)
    for i := range sdy {
    sdy[i] = make([]uint8, dx)
    }
    fmt.Println(sdy)
    }
    [[0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0]]

    ## 映射表 map
    
    - 定义map, 相当于只有一种数据类型的Python字典

    mm := make(map[string]int) // make一个key是string类型,value是int类型的map
    mm["Answer"] = 42 // 赋值
    value := mm["Answer"] // 取值
    v1, ok := mm["Answer"] // 取值并判断元素是否存在, true, 不存在则返回false
    delete(mm, "Answer") // 删除元素,没有Python字典的pop方法

    - 遍历map,range出来的是key和value,统计单词中字母个数

    func main() {
    s := "Hello World"
    ss := strings.Split(s, "") // 分隔字符串
    fmt.Println(ss)
    wc := make(map[string]int)
    for _, v := range ss {
    wc[v]++
    }
    fmt.Println(wc)
    }
    [H e l l o W o r l d]
    map[ :1 H:1 W:1 d:1 e:1 l:3 o:2 r:1]

    # 高级数据类型
    
    ## 结构体 struct
    
    - 定义struct

    // 类似Python的class,指定了固定属性,使用时要“实例”
    // 声明的方式也很像Python的类
    type Vertex struct {
    X, Y int // 有2个int型元素的结构体
    // 也可以加入其它类型, Z string
    }

    - 赋值和取值

    var (
    v1 = Vertex{1, 2} // 类型为 Vertex, X:1 和 Y:2
    v2 = Vertex{X: 1} // Y:0 被省略
    v3 = Vertex{} // X:0 和 Y:0
    )
    func main() {
    fmt.Println(v1, v2, v3)
    v := Vertex{1, 2}
    v.X = 4 // 点号来访问
    fmt.Println(v.X)
    }

    - 重新分配(转换)

    // 需要按照bson包,go get -u labix.org/v2/mgo/bson
    type Task struct {
    id int
    name string
    age int
    sex int
    }
    type TaskForm struct {
    name string
    age int
    }
    // 转换
    task_form := TaskForm{”name”: ”bb”, “age”: 17}
    var _task Task
    jsonstr, := bson.Marshal(task_form)
    bson.Unmarshal(json_str, &_task)
    fmt.Println(_task)

    ## 结构体 与 map
    
    - map装入结构体, 即一个key对应的value值是一个struct类型数据. 类似Python字典套class.
    - map 在使用之前须用 make 来创建;否则其值为 nil 的 map 是空的,并不能赋值。

    type Vertex struct {
    Lat, Long float64
    }
    var m map[string]Vertex // 声明变量 名称叫m的map机构类型
    func main() {
    m = make(map[string]Vertex) // 使用前要make下
    m["Bell Labs"] = Vertex{
    40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])
    }

    - 也可以这样直接赋值, 跟结构体文法相似,不过必须有键名。

    type Vertex struct {
    Lat, Long float64
    }
    var m = map[string]Vertex{
    "Bell Labs": Vertex{
    40.68433, -74.39967,
    },
    "Google": Vertex{
    37.42202, -122.08408,
    },
    }
    func main() {
    fmt.Println(m)
    }

    - 也可以直接在文法的元素中省略结构名。

    import "fmt"
    type Vertex struct {
    Lat, Long float64
    }
    var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google": {37.42202, -122.08408},
    }
    func main() {
    fmt.Println(m)
    }

    # 指针 * &
    
    - 一元操作符 *[取值] &[取地址] ;
    - Go 具有指针。 指针保存了变量的内存地址。默认零值是 `nil`。
    - 个人理解其目的是节省变量传递时内存的消耗, 只传递一个地址(一串16进制值), 比传递整个变量(需要新开辟地址空间), 节省空间
    - 这也就是通常所说的“间接引用”或“非直接引用”。Python里没有指针
    
    - 定义指针

    var p int
    fmt.Println(p, &p) // 打印指针地址,此时p是没有值的,也就是还没有分配地址
    p = new(int) // 创建一块内存,并把内存地址分配给 指针p,不能直接分配值 p = 12 会报错,因为没有地址来存这个值
    // 或 p = &int
    fmt.Println(p, &p)
    fmt.Println(p)

    // 输出:
    指针p指向的地址 指针p的地址
    <nil> 0xc0000ae018
    0xc0000b4010 0xc0000ae018
    0 // 分配内存后的指针默认值为 0

    - 赋值,两种方式:改地址 和 改值

    i := 42
    p := &i // 第一种:指向新的地址
    fmt.Println(p, p)
    p = 21 // 第二种:修改成新的值, 地址不变
    fmt.Println(*p, p)

    // 输出:
    42 0xc00001e080
    21 0xc00001e080

    // 理解:*p == i, p == &i

    - 接下来指针会被用到更多的地方,例如传参和函数返回。
    
    # 过程式编程
    
    ## 函数
    
    - 函数的入参,也可以多个同类型变量公用一个类型来声明

    func add(x, y int) int {
    return x + y
    }

    - 函数可以返回多个值

    func swap(x, y string) (string, string) {
    return y, x
    }
    a, b := swap("hello", "world") // a, b := b, a

    - 函数也是值, 函数名可以当变量来传递给其他方法或函数

    // 有点类似Python的内置函数
    func main() {
    hypot := func(x, y float64) float64 { // 相当于 func hypot(x, y float64) float64
    return math.Sqrt(xx + yy)
    }
    fmt.Println(hypot(3, 4))
    }

    - 匿名函数

    // 如果只调用一次,则不用给变量赋值,直接调用,并不给函数命名,称之为匿名函数
    func main() {
    i2 := func(x, y int) int { return x + y }(1,2)
    fmt.Println(i2)
    }

    - 函数回调
    - 函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调 。

    func callback(a,b int) {
    fmt.Print(a+b)
    }
    func add(c int, f func(int, int)) {
    if c <= 4 {
    f(c,c)
    }
    }
    func main() {
    add(4, callback)
    }

    // 输出:
    8

    - 闭包函数
    - 一个函数中包含另一个函数,内部的函数被封闭在外部函数里,自成环境,则该外部函数称为闭包函数。
    - 调用该闭包函数时,返回的是内部函数,由它来接受闭包外传入的变量。 传递变量的访问和赋值,都在内部环境完成。
    - 有点类似Python的类,先“实例化”外部函数,再“调用”内部函数,传入的变量会作为“类属性”被记忆,不会因为多次调用而被初始化。

    // 闭包函数
    func adder() func(int) int {
    sum := 0 // 闭包函数首次调用默认值,再次被调用时,不会初始化该值。
    return func(x int) int {
    // 内部函数环境
    sum += x
    return sum
    }
    }
    func main() {
    pos, neg := adder(), adder() // 新分配内存, 并指向adder函数, 新的变量引用互不干扰
    for i := 0; i < 5; i++ {
    fmt.Println(
    // 执行闭包内部函数,由于没有给闭包函数传值,故sum初始值都是:0
    pos(i),
    neg(-2*i),
    )
    }
    }

    // 输出
    0 0
    1 -2
    3 -6
    6 -12
    10 -20

    ## 方法
    
    - go语言中没有类class的声明方法, 不过, 仍然可以把一些同类函数定义到一个类似”类”对象的结构体下面, 也就是成为类中的”方法”.
    - 函数式编程, 要实现对象编程那套真的好别扭

    type Vertex struct { // 类似结构体, 可定义多个方法
    X, Y float64
    }
    func (v Vertex) Abs() float64 {
    return math.Sqrt(v.Xv.X + v.Yv.Y)
    }
    func (v Vertex) Avg() float64 {
    return (v.X + v.Y) / 2 // 方法内, 可像类一样, 调用自己的属性值
    }
    func main() {
    v := Vertex{3, 4} // 类似对象的实例化
    fmt.Println(v.Abs()) // 类似对象的方法调用
    }

    - 可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。

    type MyFloat float64
    func (f MyFloat) Abs() float64 {
    if f < 0 {
    return float64(-f)
    }
    return float64(f)
    }
    func main() {
    f := MyFloat(-math.Sqrt2)
    fmt.Println(f.Abs())
    }

    - 方法可以与命名类型或命名类型的指针关联, 可以节省结构体”实例化”带来的内存开销.

    type Vertex struct {
    X, Y float64
    }
    func (v Vertex) Scale(f float64) {
    v.X = v.X f
    v.Y = v.Y f
    }
    func (v Vertex) Abs() float64 {
    return math.Sqrt(v.Xv.X + v.Yv.Y)
    }
    func main() {
    v := &Vertex{3, 4}
    v.Scale(5)
    fmt.Println(v, v.Abs())
    }

    ## 接口
    
    - 接口类型是(类似方法)由一组方法定义的集合, 方法实现了接口中的函数,则表示这些方法都该接口下。
    - 有点绕,方法已经存在了,干嘛还要弄个接口呢?想调用的时候,直接struct.func不就好了,实际意义在哪?
    - 那如果一次执行多个struct的func时,且func逻辑差不多的,是选择单个执行,还是放入数组中遍历执行?
    - 如果放在数组,那这个数组的属性是哪个struct呢?所以就有了接口,来代表这些struct;好比一个Python列表中含有多个不同类实例(只是这些类的方法是必须含有相同的方法名)。
    - 如果想传的方法又不想实现同一个函数,可以试试空接口:interface{},相当于所有方法和数据类型都实现了这个接口。

    //接口定义
    type Abser interface {
    Abs() float64
    }

    - 实现不同形状的体积计算接口

    // 接口
    type Shape interface {
    area() float64
    }
    // 方法
    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 getArea(shapes []Shape) float64 {
    return shape.area()
    }

    func main() {
    circle := Circle{x:0,y:0,radius:5}
    rectangle := Rectangle {width:10, height:5}
    fmt.Printf("all area: %f\n",getArea([]Shape{circle, rectangle}))
    }

    // 输出
    all area: 128.539816

    - 判断接口的具体数据类型

    // 如果类型不匹配则返回false
    func assert(i interface{}) {
    v, ok := i.(int)
    fmt.Println(v, ok)
    }
    func main() {
    var s interface{} = 56
    assert(s)
    var i interface{} = "Steven Paul"
    assert(i)
    }

    程序将输出:
    56 true
    0 false

    ## 空接口 与 map
    
    - 后端接口常常返回的是多个数据类型组合一起的map(转成json)

    data := make(map[string]interface{})
    data[“key”] = “value”
    data[“code”] = 0

    # 错误处理
    
    - go中错误必须自行处理, 处于传参严谨态度, 实际避免调用方的误传导致系统崩溃的问题; 没有提供Python那样的try...except...可供试错的语法.
    - Go 程序使用 error 值来表示错误状态。`error` 类型是一个内建接口:

    type error interface {
    Error() string
    }

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

    i, err := strconv.Atoi("42”) // 可以忽略i,部分
    if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    }
    fmt.Println("Converted integer:", i)
    // error 为 nil 时表示成功;非 nil 的 error 表示错误。

    - go语言没有try excepte等异常处理语法, 可以通过判断函数执行结果来处理error

    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)
    }
    }

    -函数通常返回错误作为最后一个返回值。 可使用errors.New来构造一个基本的错误消息,如下所示:

    func Sqrt(value float64)(float64, error) {
    if(value < 0){
    return 0, errors.New("Math: negative number passed to Sqrt")
    }
    return math.Sqrt(value) // 默认返回最后一次出参: nil == return math.Sqrt(value), nil
    }
    // 使用返回值和错误消息,如下所示
    result, err:= Sqrt(-1)
    if err != nil { // 错误值不等于空, 即: 有错误 == if err {}
    fmt.Println(err)
    }

    - 可以使用第三方包 github.com/pkg/errors 错误库
    
    # Go的并发
    
    ## goroutine协程
    
    - goroutine 是由 Go 运行时环境管理的轻量级线程。

    go f(x, y, z) // 声明
    f(x, y, z) // 开启一个新的 goroutine 执行

    - 定义和执行

    func say(s string) {
    for i := 0; i < 5; i++ {
    time.Sleep(100 * time.Millisecond)
    fmt.Println(s)
    }
    }
    func main() {
    go say("world") // 异步执行
    say("hello”) // 同步执行
    }

    - 思考
    - 有结果如何获取? 使用管道从线程中带出来(也就类似Python的Thread类中定义一个result方法取返回值)
    
    ## channel暂存管道
    
    - channel 是有类型的管道, 可以理解为一个特殊的队列

    ch <- v // 将 v 送入 channel ch, 在管道未流出前, 数据都是排队等待流出的(即阻塞)。
    v := <-ch // 从 ch 接收,并且赋值给 v。
    (“箭头”就是数据流的方向。)

    - 和 map 与 slice 一样,channel 使用前必须创建: `ch := make(chan int)`
    - 默认情况下,在另一端准备好之前,发送和接收都会阻塞。可以和go线程配合使用进行数据同步, 不用单独执行.

    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) // 后三个之和 先暂存到c管道
    go sum(a[len(a)/2:], c) // 前三个之和 后暂存到c管道
    x, y := <-c, <-c // 从 c 中获取, 管道内数据排队流出, 如何连续不断流出?
    fmt.Println(x, y, x+y)
    }

    - 缓冲管道(有容量的队列)

    cc := make(chan int, 2)
    cc <- 1
    cc <- 2
    fmt.Println(<-cc)
    fmt.Println(<-cc)

    - channel 可以是 带缓冲的[容量]。为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel: `ch := make(chan int, 100)`
    - 向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。如果满了之后仍然塞入数据则会报错, 如何判断是否满, 避免报错?
    
    - close 和 range 关闭管道并遍历管道

    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, 10)
    go fibonacci(cap(c), c)
    for i := range c {
    fmt.Println(i)
    }
    }
    发送者可以 close 一个 channel 来表示再没有值会被发送了。
    接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么经过
    v, ok := <-ch
    之后 ok 会被设置为

    false

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

    ## select
    
    - 个人理解

    选择通信信道的一个语法, case语句不同于switch的条件, 而必须是chan操作.
    select 语句使得一个 goroutine 在多个通讯操作上等待。
    select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个。
    当 select 中的其他条件分支都没有准备好的时候,

    default
    分支会被执行。

    - 示例

    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)
    }
    }
    }

    # defer
    
    - 在函数中,我们经常需要创建资源(如,数据库连接 / 文件句柄 / 锁等),为了在函数执行完毕后,可以及时释放资源,Go框架设计了defer。

    Note:

    当go执行到一个defer时,不会立即执行当前行defer {后半部分}的语句,而是将defer后面的语句压进栈中(可以理解为defer栈)
    当函数执行结束后,defer 语句出栈,遵循先入后出
    在defer将语句放入栈中时,也会将相关的值Copy,同时入栈
    defer最主要的价值在于,当函数执行完毕后,可以及时地释放函数创建的资源,如defer file.close()  / defer connect.close()

    示例:
    func sum(num1 int, num2 int) int {
    //暂时不执行,压进defer栈中
    defer fmt.Println("defer1 num1=", num1)
    defer fmt.Println("defer2 num2=", num2)
    // 执行
    res := num1 + num2
    fmt.Println("resFun=", res)
    return res
    }
    func main() {
    res := sum(10,20)
    // 当函数执行完毕后,defer按照先进后出的方式出栈
    fmt.Println("res=",res)
    }

    // 运行结果:
    resFun= 30
    defer2 num2= 20
    defer1 num1= 10
    res= 30

    # init函数
    
    - 每一个源文件都可以包含一个init函数,该函数在main函之前被go框架运行。通常可以在init函数中完成初始化工作。

    Note:
    如果一个文件同时包含全局变量定义 / init() / main()。其执行的流程是 全局变量定义 -> init() -> main()
    init函数最大的作用是完成一些初始化的工作

    var gVar = gTest()
    func gTest() string {
    fmt.Println("global variable:")
    return "global variable:"
    }
    func init() {
    fmt.Println("init function:")
    }
    func main() {
    fmt.Println("main function:")
    }

    // 运行结果:
    global variable:
    init function:
    main function:

    
                                                
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: