day1 Go语言基础语法 | 青训营笔记

100 阅读18分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

Go语言基础

1.简介

1.1 什么是Go语言

  • 高性能 高并发
  • 语法简单 编写方便 学习路线平缓(相较于C,C++)
  • 丰富且高性能的标准库
  • 完善的工具链
  • 静态链接
  • 快速编译
  • 跨平台
  • 高效的垃圾回收

1.2 有哪些公司在使用Go语言

  1. 腾讯,主要在新业务上尝试使用Go
  2. 百度,主要在运维方面使用到Go语言
  3. 七牛云,是国内第一家选Go做服务端的公司
  4. 京东,京东商城的列表页等是使用Go语言开发的
  5. 小米,小米商城等团队在使用Go语言

2.Go入门

2.1 环境安装

www.bilibili.com/video/BV1mE…

2.2 Hello World

package main

// 导入fmt包,fmt包中包含关于控制台输入输出相关方法
import "fmt"

func main() {
   // 控制台打印Hello World!
   fmt.Println("Hello World!")
}

2.3 var变量的使用

go语言的变量相较于传统的C,C++,Java,Python语言有多种不同

  1. 采用:=进行赋值操作 var g := 1
  2. 可以多个变量同时赋值 var b, c int = 1, 2
  3. 采取类型后置的方式
  4. 另外go语言中的float类型 有两种一种是float32占用四个字节(类似其他语言中的float),还有一种float64占用8个字节(double)
package main

import (
   "fmt"
   "math"
)

func main() {
   var a = "initial"

   var b, c int = 1, 2

   var d = true

   //类似java中的double,都占用八个字节
   var e float64

   //类似java中的float,都占用四个字节
   f := float32(e)

   g := a + " " +  "donkey"

   fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
   fmt.Println(g)                // initial donkey

   const s string = "constant"

   const h = 50000000

   const i = 3e20 / h

   fmt.Println(s, h, i, math.Sin(h), math.Sin(i))

}

2.4 for循环

go中的for循环的特点是:

  • 若for后没有跟条件那么是死循环
  • for循环后没有小括号
  • continue与break与其他语言用法一致
package main

import "fmt"

func main() {

   var i int = 1

   // 死循环
   for  {
      fmt.Println("dead loop")
      break
   }

   for j := 0; j < 9; j++ {
      fmt.Println("j = ", j)
   }

   for n := 0; n < 5; n++ {
      if n%2 == 0 {
         continue
      }
      fmt.Println("n = ", n)
   }

   for i <= 3 {
      fmt.Println("i = ", i)
      i++;
   }

}

2.5 if 判断

go中的for循环的特点是:

  • if后没有小括号
  • if中可以先执行语句再进行判断
package main

import "fmt"

func main() {

   if 7%2 == 0 {
      fmt.Println("7 is even")
   } else {
      fmt.Println("7 is odd")
   }

   if 8%4 == 0 {
      fmt.Println("8 is divisible by 4")
   }

   if num := 10; num < 0 {
      fmt.Println(num, "is negative")
   } else if num < 10 {
      fmt.Println(num, "has 1 digit")
   } else {
      fmt.Println(num, "has many digits")
   }

}

2.6 switch 条件分支

go中的for循环的特点是:

  • Go语言中条件每个条件分支中可以不用写break,不必担心处于上游的分支执行完误执行了下面分支的逻辑
  • switch语句中的变量可以是任何类型,在每个case后面也可以跟多个值,用逗号隔开,扩大了每个分支的判断范围
  • Go中的switch语句也支持在条件判断语句前执行简单的语句,这样就可以在一个switch语句中处理多个条件
package main

import (
   "fmt"
   "time"
)

func main() {

   a := 2
   switch a {
   case 1:
      fmt.Println("one")
   case 2:
      fmt.Println("two")
   case 3:
      fmt.Println("three")
   case 4, 5:
      fmt.Println("four or five")
   default:
      fmt.Println("other")
   }

   t := time.Now()

   switch {
   case t.Hour() < 12:
      fmt.Println("It's before noon")
   default:
      fmt.Println("It's after noon")
   }


}

2.7 array数组

数组的使用与其他语言中的使用无太大的差异,不在这里赘述了,仅需看下如何简单使用既可

package main

import "fmt"

func main() {

   var a [5]int
   a[4] = 100
   fmt.Println("get:", a[2])
   fmt.Println("len:", len(a))

   b := [5]int{1, 2, 3, 4, 5}
   fmt.Println(b)

   var twoD [2][3]int

   for i := 0; i < 2; i++ {
      for j := 0; j < 3; j++ {
         twoD[i][j] = i + j
      }
   }
   fmt.Println("2d: ", twoD)


}

2.8 slice切片

2.8.1 slice切片

  • Go语言中的切片有点类似于列表
  • Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
  • 通过数组的切片完成的数据的浅拷贝

2.8.2 浅拷贝与深拷贝

2.8.1. Go语言中的深拷贝

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2.8.2. Go语言中的浅拷贝

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。

2.8.3.本质区别

是否真正在底层另外分配了一段独立的堆空间用于存储拷贝的对象,如果仅是拷贝一个指向原有存储空间的指针,那么就算浅拷贝,若在底层创建了一个新对象并分配了一段与源对象不同的内存空间,那便是深拷贝。

//创建一个字符串数组
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
// c
fmt.Println("get: ", s[2])
// 3
fmt.Println("len: ", len(s))

s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)


c := make([]string, len(s))
//拷贝字符串数组(浅拷贝)
copy(c, s)
fmt.Println(c)

strings1 := "Hello world"
var strings2 = strings1;
//测试=是深拷贝还是浅拷贝? 深拷贝
// 拓展 值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool
strings2 += "!!!"
fmt.Println("string1:" + strings1)
fmt.Println("string2:" + strings2)

// 引用类型的数据,默认全部都是浅复制,Slice,Map。
// s的切片默认都是前闭后开区间
fmt.Println(s[0:3])
fmt.Println(s[:5])
fmt.Println(s[2:])

good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]

2.9 map散列表

go语言中的map与其他语言中的散列表实现没有什么大的区别,仅是有些方法由于go语言的特性能够返回多个结果

package main

import "fmt"

func main() {

   m := make(map[string]int)
   // key "one" value 1
   m["one"] = 1
   m["two"] = 2
   //输出map中所有的键值对
   fmt.Println(m)
   //输出map的键值对个数
   fmt.Println(len(m))
   //根据键取值 根据值取键通常使用range遍历方式实现,因为跟其他语言一样底层维护的是K底层是Set集合,而V只是普通的数组允许重复
   fmt.Println(m["one"])
   fmt.Println(m["unknow"])

   r, ok := m["unknow"]
   fmt.Println(r, ok)

   // 删除map m中的键为"one"的K-V对
   delete(m, "one")
   fmt.Println(m)

   m2 := map[string]int{"one": 1, "two": 2}
   var m3 = map[string]int{"one": 1, "two": 2}
   fmt.Println(m2, m3)



}

2.10 range

range与for搭配使用,与Java中的增强for循环类似,作用都是遍历切片,数组,map等可迭代容器中所有的元素,特点有:

  • 若遇到下划线,那么下划线的作用是占位符的作用,同时底层编译器可以根据下划线做一定的优化
  • 若遍历数组时,形如 for index, num := range nums 那么index表示当前遍历到的索引
  • 若遍历map时,形如for k, v := range m 可以同时获取到key value(可以据此找到value-key的对应关系 可能为n对1)
  • 若遍历map时,形如for k := range m 可以获取到key
package main

import "fmt"

func main() {

   nums := []int{2, 3, 4}
   sum := 0
   //这段代码是在遍历一个名为 "nums" 的切片(或者数组)
   //对于每一个遍历到的元素,它会将该元素的值加到 "sum" 变量中
   //如果遍历到的元素的值为 4,它会输出 "index: 2 num: 4",其中 i 是该元素在切片中的索引位置
   for i, num:= range nums{
      sum += num
      if num == 4 {
         fmt.Println("index:", i, "num:", num)
      }
   }
   // 9
   fmt.Println(sum)

   m := map[string]string{"a": "A", "b": "B"}
   //这段代码是在遍历一个名为 "m" 的map
   //对于每一个遍历到的键值对key value分别记录在 k 和 v中
   //这段代码也可以实现根据值找到对应的key(可能有多个,需要另外使用容器记录)
   for k, v := range m {
      fmt.Println(k, v)
   }

   //这段代码是在遍历一个名为 "m" 的map
   //对于每一个遍历到的键值对key记录在 k 中
   for k := range m {
      fmt.Println("key", k) // key a; key b
   }

}

2.11 func 方法

在 Go 中,方法是一组可重用的代码块,可以在程序中多次调用。可以像其他数据类型一样赋值给变量、作为参数和返回值

go中func的特点:

  • 可以有多个返回值,这意味着我们可以通过执行一个方法可以得到更多想要的信息,这点特别对错误处理有很大的帮助
  • 若返回的结果遇到了错误,那么字符串返回的是空,而err不为nil
  • 若返回的结果没有遇到了错误,那么err为nil

import "fmt"

func add(a int, b int) int {
   return a + b
}

func add2(a, b int) int {
   return a + b
}

//判断map中是否存在k的键如果存在返回它的值,否则返回空字符串 和 false
func exists(m map[string]string, k string) (v string, ok bool)  {
   v, ok = m[k]
   return v, ok
}

func main() {
   res := add(1, 2)
   fmt.Println(res)

   // A true
   v, ok := exists(map[string]string{"a":"A"}, "a")
   fmt.Println(v, ok)

   //  false
   v2, ok2 := exists(map[string]string{"b":"B"}, "a")
   fmt.Println(v2, ok2)
}

2.12 point指针

go中的指针用法与C,C++中的类似

package main

import "fmt"

func add2(n int) {
   n += 2
}

func add2ptr(n *int) {
   *n += 2
}

func main() {

   n := 5
   //传值,被调用的方法内会重新生成一个局部变量进行操作,不会改变主调函数中的变量值
   add2(2)
   fmt.Println(n)
   //传递地址,被调用的方法直接使用地址的值指向的变量进行操作,会改变注掉函数中的变量值
   add2ptr(&n)
   fmt.Println(n)

}

2.13 struct struct-method

go语言中的结构体使用与其他语言中的结构体,类的使用差不多,但是go语言中的结构体支持多种赋值方式,如下面例子所示

package main

import (
   "fmt"
)

type user struct {
   name    string
   password string
}

func main() {
   a := user{name: "zhangsan", password: "1024"}
   b := user{"lisi", "1025"}
   c := user{password: "1026"}
   c.name = "wangwu"
   var d user
   d.name = "zhaoliu"
   d.password = "1027"

   fmt.Println(a, b, c ,d)
   fmt.Println(checkPassword(a, "1024"))   // true
   fmt.Println(checkPassword2(&b, "1025")) // true
   fmt.Println(checkPassword2(&c, "1025")) // false

}

//不使用指针
func checkPassword(u user, password string) bool {
   return u.password == password
}

//使用指针
func checkPassword2(u *user, password string) bool {
   return u.password == password
}

go中的结构体方法(可能直译有点问题),我个人更偏向把它理解为Java中的成员方法

package main

import "fmt"

type user struct {
   name    string
   password string
}

func (u user) checkPassword(password string) bool {
   return u.password == password
}

func (u *user) resetPassword(password string)  {
   u.password = password
}

func main() {

   a := user{"wangwu", "1024"}
   a.resetPassword("2024")
   // true
   fmt.Println(a.checkPassword("2024"))

}

2.14 error 错误处理

  • Go语言中引入 error 接口类型作为错误处理的标准模式,如果函数要返回错误,则返回值类型列表中肯定包含 error。error 处理过程类似于C,java中的错误,可逐层返回,直到被处理。
  • error 接口有一个签名为 Error() string 的方法,所有实现该接口的类型都可以当作一个错误类型。Error() 方法给出了错误的描述,在使用 fmt.Println 打印错误时,会在内部调用 Error() string 方法来得到该错误的描述。一般情况下,如果函数需要返回错误,就将 error 作为多个返回值中的最后一个(但这并非是强制要求)。
    创建一个 error 最简单的方法就是调用 errors.New 函数,它会根据传入的错误信息返回一个新的 error
package main

import (
   "errors"
   "fmt"
)

type user struct {
   name    string
   password string
}

func findUser(users []user, name string) (v *user, err error)  {
   //此处下划线的作用是 1.忽略这个变量 2.占位符
   //使用下划线可以让编译器更好的优化,同时在有些场合下,必须使用下划线占位否则会报错
   for _, u := range users {
      if u.name == name {
         return &u, nil
      }
   }
   return nil, errors.New("not found")
}

func main() {
   u, err := findUser([]user{{"wangwu", "1024"}}, "wangwu")
   if err != nil {
      fmt.Println(err)
      return
   }
   // wangwu
   fmt.Println(u.name)

   if u, err := findUser([]user{{"wangwu", "1024"}}, "lisi"); err != nil {
      fmt.Println(err) // not found
      return
   } else {
      fmt.Println(u.name)
   }
}

2.15 string 字符串

Go中的string字符串的常用方法

  • strings.HasPrefix(s string, prefix string) bool:判断字符串s是否以prefix开头
  • strings.HasSuffix(s string, suffix string) bool:判断字符串s是否以suffix结尾。
  • strings.Index(s string, str string) int:判断str在s中首次出现的位置,如果没有出现,则返回-1
  • strings.LastIndex(s string, str string) int:判断str在s中最后出现的位置,如果没有出现,则返回-1
  • strings.Replace(str string, old string, new string, n int):字符串替换
  • strings.Count(str string, substr string)int:字符串计数
  • strings.Repeat(str string, count int)string:重复count次str
  • strings.ToLower(str string)string:转为小写
  • strings.ToUpper(str string)string:转为大写
  • strings.TrimSpace(str string):去掉字符串首尾空白字符
  • strings.Trim(str string, cut string):去掉字符串首尾cut字符
  • strings.TrimLeft(str string, cut string):去掉字符串首cut字符
  • strings.TrimRight(str string, cut string):去掉字符串首cut字符
  • strings.Field(str string):返回str空格分隔的所有子串的slice
  • strings.Split(str string, split string):返回str split分隔的所有子串的slice
  • strings.Join(s1 []string, sep string):用sep把s1中的所有元素链接起来
  • strings.Itoa(i int):把一个整数i转成字符串
  • strings.Atoi(str string)(int, error):把一个字符串转成整数
  • len(str string) 获取字符串长度
package main

import (
   "fmt"
   "strings"
)

func main() {

   a := "hello"
   // 是否存在"ll"子串 true
   fmt.Println(strings.Contains(a, "ll"))
   // 统计字符串中"l"子串的个数 2
   fmt.Println(strings.Count(a, "l"))
   // 判断字符出a是否"he"子串开头的 true
   fmt.Println(strings.HasPrefix(a, "he"))
   // 判断字符出a是否"llo"子串结尾的 true
   fmt.Println(strings.HasSuffix(a, "llo"))
   // 查找"ll"子串出现的初始索引 2
   fmt.Println(strings.Index(a, "ll"))
   // 把字符串拆分为多个子串,并使用sep连接
   fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
   // 将字符串重复count次 hellohello
   fmt.Println(strings.Repeat(a, 2))
   // 用 new 替换 s 中的 old,一共替换 n 个。
   // 如果 n < 0,则不限制替换次数,即全部替换 hEllo
   fmt.Println(strings.Replace(a, "e", "E", -1))
   // 将字符串s以sep分割成一个子串,返回值是一个字符串数组 [a b c]
   fmt.Println(strings.Split("a-b-c", "-"))
   // 全部转换为小写 hello
   fmt.Println(strings.ToLower(a))
   // 全部转换为大写 HELLO
   fmt.Println(strings.ToUpper(a))
   // 返回字符串长度
   fmt.Println(len(a))
   b := "你好"
   // 中文字符串根据字符集, 大多数字符集中文编码一个占3个字节 6
   fmt.Println(len(b)) // 6

}

2.16 fmt标准输入输入库

Go 中的 fmt 包提供了打印函数将数据以字符串形式输出到控制台、文件、其他满足 io.Writer 接口的至以及其他字符串中

输出

  • Print 系列函数会将内容输出到系统的标准输出,区别在于Print 函数直接输入内容到终端,

  • Printf函数支持格式化输出字符串.

    • %v 值的默认格式表示
    • %+v 类似%v,但输出结构体时会添加字段名
    • %#v 类似%+v 但是会打印更加详细的信息
    • %T 打印值的类型
    • %% 百分号
  • Println 函数 自定帮我们添加换行符,输出内容独占一行.他不支持格式化出去

  • Fprint系列函数会将内容输出到一个io.Writer接口类中,我们通常用这个函数往文件中写入内容.

  • Errorf 函数根据format参数生成格式化字符并返回一个包含该字符串的错误.

package main

import "fmt"

type point struct {
   x, y int
}

func main() {
   s := "hello"
   n := 123
   p := point{1, 2}
   // hello 123
   fmt.Println(s, n)
   // {1 2}
   fmt.Println(p)

   // s=hello
   fmt.Printf("s=%v\n", s)
   // n=123
   fmt.Printf("n=%v\n", n)
   // p={1 2}
   fmt.Printf("p=%v\n", p)
   // p={x:1 y:2}
   fmt.Printf("p=%+v\n", p)
   // p=main.point{x:1, y:2}
   fmt.Printf("p=%#v\n", p)

   f := 3.141592653
   // 3.141592653
   fmt.Println(f)
   // 3.14
   fmt.Printf("%.2f\n", f)

}

2.17 json

go中的encoding/json包中的几个重要方法:

  • Marshal() 将一个元素或者对象进行序列化,返回序列化后的字节数组和错误信息,为了防止后续debug不出现错误,应对error的值进行处理
  • MarshalIndent方法可以对json序列化后的对象做一些格式化处理,同样需要错误处理
  • Unmarshal(buf, &b) 对字节数组buf进行反序列化,并把结果存入b中,同样需要错误处理
package main

import (
   "encoding/json"
   "fmt"
)

type userInfo struct {
   Name string
   Age int `json:"age"`
   Hobby []string
}

func main() {

   a := userInfo{"小江", 18, []string{"Golang", "TypeScript"}}
   // 序列化
   buf, err := json.Marshal(a)
   // 序列化错误
   if err != nil {
      panic(err)
   }
   // [123 34 78 97...] 为序列化后的字节数组
   fmt.Println(buf)
   // 将字节数组转换为字符串输出 {"Name":"小江","age":18,"Hobby":["Golang","TypeScript"]}
   fmt.Println(string(buf))

   // MarshalIndent方法可以对json序列化后的对象做一些格式化处理
   buf, err = json.MarshalIndent(a, "", "\t")
   if err != nil {
      panic(err)
   }
   fmt.Println(string(buf))

   var b userInfo
   err = json.Unmarshal(buf, &b)
   if err != nil {
      panic(err)
   }
   //main.userInfo{Name:"小江", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
   fmt.Printf("%#v\n", b)

}

2.18 time 时间处理

go中的time包可以使用和构造多种与时间相关的操作,并且可以将时间进行格式化,具体使用的时候查阅官方手册即可,以下为常见的方法

package main

import (
   "fmt"
   "time"
)

func main() {
   // 获取当前时间戳
   now := time.Now()
   fmt.Println(now)
   // 根据时间和时区构造时间戳
   t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
   t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
   // 2022-03-27 01:25:36 +0000 UTC
   fmt.Println(t)
   // 获取年月日(星期几)时分秒 2022 March 27 1 25
   fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute())
   // 根据格式打印时间 2022-03-27 01:25:36
   fmt.Println(t.Format("2006-01-02 15:04:05"))
   // 获取时间差
   diff := t2.Sub(t)
   // 1h5m0s
   fmt.Println(diff)
   // 65 3900
   fmt.Println(diff.Minutes(), diff.Seconds())
   // 转换格式
   t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
   if err != nil {
      panic(err)
   }
   // true
   fmt.Println(t3 == t)
   // 当前的时间戳 1648738080
   fmt.Println(now.Unix())
}

2.19 strconv 数字解析

strconv用于将字符串转换为整数或者浮点数,支持各种进制之间的转换,但是若输入非法则会出现错误,以下为常用的方法

package main

import (
   "fmt"
   "strconv"
)

func main() {

   // 将字符串转换为64位精度的浮点数
   f, _ := strconv.ParseFloat("1.234", 64)
   fmt.Println(f) // 1.234
   // 将十进制的字符串转换为64位精度的整数
   n, _ := strconv.ParseInt("111", 10, 64)
   fmt.Println(n) // 111
   //字符串 10进制(参数base位置填0则自动推测) 64位进度整数
   n, _ = strconv.ParseInt("0x1000", 0, 64)
   fmt.Println(n) // 4096
   n2, _ := strconv.Atoi("123")
   fmt.Println(n2)    // 123
   //输入不合法则返回错误
   n2, err := strconv.Atoi("AAA")
   fmt.Println(n2, err)   // 0 strconv.Atoi: parsing "AAA": invalid syntax

}

2.20 env 环境变量

Go语言的 os 包中提供了操作系统函数的接口,是一个比较重要的包。顾名思义,os 包的作用主要是在服务器上进行系统的基本操作,如文件操作、目录操作、执行命令、信号与中断、进程、系统状态等等。

常用函数

  • func Hostname() (name string, err error) 返回内核提供的主机名。
  • func Environ() []string 返回所有的环境变量,返回值格式为“key=value”的字符串的切片拷贝。
  • func Getenv(key string) string Getenv 函数会检索并返回名为 key 的环境变量的值。如果不存在该环境变量则会返回空字符串。
  • func Setenv(key, value string) error Setenv 函数可以设置名为 key 的环境变量,如果出错会返回该错误。
  • func Exit(code int) Exit 函数可以让当前程序以给出的状态码 code 退出。一般来说,状态码 0 表示成功,非 0 表示出错。程序会立刻终止,并且 defer 的函数不会被执行。
  • exec.Command() 返回一个*Cmd, 用于执行name指定的程序(携带arg参数)
package main

import (
   "fmt"
   "os"
   "os/exec"
)

func main() {

   //打印了当前程序命令行参数,os.Args是一个字符串可变数组,表示程序启动时传入的命令行参数
   // go run example/20-env/main.go a b c d
   fmt.Println(os.Args)           // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
   //获取环境变量的值
   fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
   //设置环境变量的值
   fmt.Println(os.Setenv("AA", "BB"))

   //exec.Command()返回一个*Cmd, 用于执行name指定的程序(携带arg参数)
   //CombinedOutput运行命令并返回其组合的标准输出和标准错误
   buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
   if err != nil {
      panic(err)
   }
   fmt.Println(string(buf)) // 127.0.0.1       localhost

}

3.课后个人总结:

Go语言中基础语法部分与其他语言大部分都一致,但是go在针对许多程序员的实际需求方面做了许多的优化,诸如方法可以有多个返回值,这个优化使得程序员可以很方便地进行错误处理,同时也提升了方法运行的效率(其他大多数语言一个方法仅能返回一个结果),但是相对来说也对使用者造成了一些负担,使用者可能需要了解go语言的底层实现,否则可能造成使用错误或者意义不清晰。

引用参考:

Go语言os包用法简述 (biancheng.net) 关于golang中下划线(_)的语义说明-阿里云开发者社区 (aliyun.com) Go语言 “ _ ”(下划线) - 简书 (jianshu.com)