go语言基础 | 青训营笔记

89 阅读14分钟

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

1.1 什么是go语言

  1. 高性能、高并发
  2. 语法简单、学习曲线平缓
  3. 丰富的标准库
  4. 完善的工具链
  5. 静态链接
  6. 快速编译
  7. 跨平台
  8. 垃圾回收

1.2 哪些公司在使用Go语言

image.png

1.3 字节跳动为什么会全面拥抱Go语言

  1. 最初使用的 Python,由于性能问题换成了 Go
  2. C++ 不太适合在线 Web 业务
  3. 早期团队非 Java 背景
  4. 性能比较好
  5. 部署简单,学习成本低
  6. 内部 RPC 和 HTTP 框架的推广

2.1 环境安装

安装 Golang

Golang

配置集成开发环境

可以使用 Vscode 安装 go 插件或者使用 Goland

2.2 基础语法

1. Hello World

image.png

  • package main表示此文件属于main包,其也是程序的入口
  • import下面是main函数里面调用了fmt.Println输出hello,world

2. 变量

image.png

  • Go是强类型语言,每个变量都有自己的类型。常见的类型有 字符串型 整数型 浮点型 布尔型,
  • 其中字符串属于内置类型可以通过 “+” 来拼接 如上图中的 go := a + "foo"
  • 变量声明有两种方式:
  1. 通过var var_name type = value 其中 type 可以省略 go 会根据上下文推断类型
  2. 通过var_name := value
  • 常量声明就是把var编程const,根据上下文自动判断类型

3.if else

image.png

  • 和其他语言有区别的地方是if后面的判断条件无需加() 例如: if 7%2 == 0 {}
  • 与C和C++不同的是if后面必须使用大括号 {}

4.循环

image.png

Go语言没有while、do while循环,只有for循环

//这种for里面什么都不写称为死循环
for {
   fmt.Println("loop")
   break
}
//也可以使用经典的 C 循环
for j := 7; j < 9; j++ {
   fmt.Println(j)
}
//可以使用continue继续循环,也可以使用break跳出循环
for n := 0; n < 5; n++ {
   if n%2 == 0 {
      continue
   }
   fmt.Println(n)
}

5.switch

image.png

  • golang里的分支结构看起来和C、C++类似,同样的switch后面的变量也无需括号()
  • 有个很大不同点在c++switch case 不显式加break的话会继续跑完剩下的case语句;而在golang里面是不需要加的,就像上图中的执行完第15行会直接跳到24行
  • golang的switch更加强大,case后面可以是:变量结构体甚至可以取代任意的if语句
t := time.Now()
//switch后面不加变量
switch {
//在每个case后面写条件分支,相比多个if else 嵌套跟家通俗易懂
case t.Hour() < 12: 
   fmt.Println("It's before noon")
default:
   fmt.Println("It's after noon")
}

6.数组

image.png

  • 数组是个具有编号长度固定元素序列 比如 :var a [5]int是一个可以存放5个int类型的元素数组a,[5]表示。数组的长度索引值是从0开始的
  • 数组的创建方式:
//1.先声明再写入值
var a [5]int
a[4] = 100
//2.直接写入
b := [5]int{1, 2, 3, 4, 5}
  • 二维数组的创建方式:var towD [2][3]int其本质是一个包含多个一维数组的一维数组
  • 数组可以很方便的存取或读取特定索引上的值,不过在真实的业务代码里面,我们很少直接使用数组,因为它长度固定,更多用的是切片

7.切片

image.png 切片不同于数组可以任意更改长度,然后也有更多丰富的操作。

  • 比如说我们可以用make来创建一个切片s := make([]string, 3)也可以直接赋值good := []string{"g", "o", "o", "d"}
  • 可以像数组一样去存取和读取
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:",s[2]) //c
fmt.Println("len:",len(s)) //3
  • 使用append来追加元素,这里注意append的用法,必须把append的结果赋值给原数组。因为slice的原理实际上是它有一个它存储了一个长度和一个容量,加一个指向一个数组的指针,在你执行append操作的时候,如果容量不够的话,会扩容并返回新的slice
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
  • 在创建slice的时候也可以初始化指定他的长度 例如:c:=make([]string,3)
  • 使用copy来拷贝另一个slice 例如:copy(c, s)
  • slice拥有像python一样的切片操作,比如 fmt.Println(s[2:5])代表取出第二个到第五个位置的元素,不包括第五个元素,不同于python的是这里不支持负数索引

8.map

image.png

  • 在其它语言里它可以叫做哈希或者字典。map是实际使用过程中最频繁用到的数据结构
  • 我们可以使用make来创建一个空mapm : = make(map[string]int)其中有两个类型,第一个是key的类型string,另一个是key的value的类型即int
  • 可以从里面存储或者取出键值对。可以用delete删除键值对
m["one"] = 1
m["two"] = 2
fmt.Println(m)           // map[one:1 two:2]
fmt.Println(len(m))      // 2
fmt.Println(m["one"])    // 1
fmt.Println(m["unknown"]) // 0
delete(m, "one") //删除键值对
  • 在从map里读取键值对的时候可以在后面接一个ok用来获取这个map里是否有这个key存在
r, ok := m["unknown"]
fmt.Println(r,ok) //0 false
  • golang的map是完全无序的,遍历的时候不会按照字母顺序,也不会按照插入顺序输出,而是随机顺序

9.range

image.png

  • 对于一个slice或者一个map的话,我们可以用range来快速遍历,这样代码更加简洁。range遍历的时候,对于数组会返回两个值,第一个是索引,第二个是对应位置的值,如果我们不需要索引的话,可以用下划线来忽略
for k, v := range m {
   fmt.Println(k, v) // a A; b B
}
for _, v := range m {
   fmt.Println("value", v) // value A; value B
}

10.函数

image.png

  • 这是一个golang里面最简单的实现两个变量相加的函数。不同与其他语言,变量类型是后置的
func add(a int, b int) int {
   return a + b
}
  • golang里面的函数原生支持返回多个值。在实际的业务逻辑代码里面几乎所有的函数都返回两个值,第一个是真正的返回结果,第二个值是一个错误信息。
func exists(m map[string]string, k string) (v string, ok bool) {
   v, ok = m[k]
   return v, ok //v返回结果,ok返回的信息
}

11.指针

image.png

  • go里面也支持指针,相比于C和C++里面的指针,支持的操作有限。指针的一个主要用途就是对传入的参数进行修改。比如下面这个:试图把一个变量n +2.但是这种写法是无效的。因为传入函数的参数实际上是一个拷贝,这个+2是对那个拷贝进行了+2,并不起作用。
func add2(n int) {
   n += 2
   fmt.Println(n) //7
}
  • 如果我们需要起作用的话需要把那个类型写成指针类型,那么为了类型匹配,调用的时候会加一个&符号
func add2ptr(n *int) {
   *n += 2
}
func main() {
   n := 5
   add2(n)
   fmt.Println(n) //5
   add2ptr(&n)
   fmt.Println(n) //7
}

12.结构体

image.png

  • 结构体是带类型的字段的集合。比如下面user结构包含了两个字段,namepassword
type user struct {
   name     string
   password string
}
  • 我们可以用结构体的名称去初始化一个结构体变量,构造的时候需要传入每个字段的初始值
a := user{name: "wang", password: "1024"}
  • 也可以用键值对的方式去指定初始值,这样可以只对一部分字段进行初始化。
b := user{"wang", "1024"}
c := user{name: "wang"}
  • 同样的结构体我们也能支持指针,这样能够实现对于结构体的修改,也可以在某些情况下避免一些大结构体的拷贝开销
//无指针
func checkPassword(u user, password string) bool {
   return u.password == password
}
//指针
func checkPassword2(u *user, password string) bool {
   return u.password == password
}
func main() {
   //传入的a为上图示代码的a
   fmt.Println(checkPassword(a, "haha"))//false
   fmt.Println(checkPassword2(&a, "haha"))//false
}

13.结构体方法

image.png

  • 在golang里面可以为结构体去定义一些方法,类似于其他语言里的类成员方法/函数。
  • 比如上图所示,我们把上面一个例子的checkPassword的实现从一个普通函数,改成了结构体方法,这样用户可以像 a.checkPassword("xx")这样去调用。具体的语法如下(就是把第一个参数,加上括号,写到函数名称前面):
//结构体作为传入函数参数
func checkPassword(u user, password string) bool {
   return u.password == password
}
//将结构体放到func 后 方法名前 就构成了结构体方法
func (u user) checkPassword(password string) bool {
   return u.password == password
}

  • 在实现结构体的方法的时候也有两种写法,一种是带指针,一种是不带指针
//不带指针
func (u user) checkPassword(password string) bool {
   return u.password == password
}
//带指针
func (u *user) checkPassword(password string) bool {
   return u.password == password
}
  • 两者区别:
  1. 带指针:就可以对这个结构体去做修改
  2. 不带指针:实际上操作的是一个拷贝,无法对结构体进行修改。

14.错误处理

image.png

  • 错误处理在go语言里符合语言习惯的做法就是使用一个单独的返回值来传递错误信息。不同于Java的异常处理,go语言的处理方式,能够清楚地知道哪个函数返回了错误,并且用简单的if else来处理错误。(go的错误处理的优于其它语言的地方)
  • 在函数里面,我们可以在函数的返回值类型里面,末尾加一个error,就代表这个函数可能返回错误。同时函数也应多一个返回值
  1. 如果出现错误,return nil, error
  2. 如果没出错误,return 结果, nil
func findUser(users []user, name string) (v *user, err error) {
   for _, u := range users {
      if u.name == name {
         return &u, nil
      }
   }
   return nil, errors.New("not found")
}
  • 在调用这些返回错误的函数时,接收需要写两个变量比如下图中的uerr
  • 调用完之后需要判断err是否存在,如果存在需要做一些处理,比如打印异常信息然后退出返回函数,只有没有err才能得到真正的返回值,否则会出现空指针错误
func main() {
   u, err := findUser([]user{{"wang", "1024"}}, "wang")
   if err != nil {
      fmt.Println(err)
      return
   }
   fmt.Println(u.name) // wang

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

15.字符串操作

image.png

  • go语言里面的字符串操作,在标准库strings包里有很多常用的字符串工具函数比如:
  1. contains判断一个字符串里面是否有包含另一个字符串
  2. count字符串计数
  3. index查找某个字符串的位置
  4. join连接多个字符串
  5. repeat重复多个字符串
  6. replace替换字符串
  7. len()内置函数获取字符串的长度(一个字可能对应多个字符)

16.字符串格式化

image.png

  • 字符串格式化,在标准的FMT包里面有很多的字符串格式相关的方法,比如printf这个类似于C语言里面的printf函数。不同的是,在go语言里面,可以轻松使用%v来打印任意类型的变量,而不需要区分数字字符串。用%+v打印详细结果,%#v则更详细。使用%.2f来保留两位小数的浮点数
func main() {
   s := "hello"
   n := 123
   p := point{1, 2}
   fmt.Println(s, n) // hello 123
   fmt.Println(p)    // {1 2}

   fmt.Printf("s=%v\n", s)  // s=hello
   fmt.Printf("n=%v\n", n)  // n=123
   fmt.Printf("p=%v\n", p)  // 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}

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

17.JSON处理

image.png

  • go语言里面的JSON操作非常简单,对于一个已有的结构体,可以什么都不做,只要保证每个字段的第一个字符是大写,也就是golang里的公开字段
type userInfo struct {
   Name  string
   Age   int `json:"age"`
   Hobby []string
}
  • 那么这个结构体就能用json.Marshal去序列化,变成一个JSON的字符串。输出的时候需要string(xx)强制类型转换成字符串否则输出16进制的编码。
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)

fmt.Println(buf)         // [123 34 78 97...]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}

序列化之后的字符串也能够用json.unmarshal去反序列化到一个空的变量里面。这样默认序列化出来的字符串的话,它的风格是大写字符开头,而不是下划线,我们可以在后面用json:tag等语法去修改输出json结果里面的字段名,使其小写。

var b userInfo
   err = json.Unmarshal(buf, &b)
   if err != nil {
      panic(err)
   }
   fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
  • json.marshalIndent()简单的来说对json多了一些格式处理
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
   panic(err)
}
fmt.Println(string(buf))

//consle输出的结果如下
{
        "Name": "wang",
        "age": 18,
        "Hobby": [
                "Golang",
                "TypeScript"
        ]
}

18.时间处理

image.png

  • 在go语言里最常用的就是time.now()来获取当前时间,然后你也可以用time.Date去构造一个带时区的时间,构造完的时间。
  • time.Format("2006-01-02 15:04:05")可以格式一个时间到字符串,括号里格式是规定好的
  • time.parse("2006-01-02 15:04:05")time.Format相反将一个字符串时间解析成时间
now := time.Now()
fmt.Println(now) //2023-01-15 20:25:46.9077674 +0800 CST m=+0.003648501

t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t)// 2022-03-27 01:25:36 +0000 UTC

fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25

fmt.Println(t.Format("2006-01-02 15:04:05"))// 2022-03-27 01:25:36
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
  • 上面有很多方法来获取这个时间的年月日小时分钟秒,然后也能用.Sub去对两个时间进行减法,得到一个时间段,时间段又可以去得到他有多少个小时,多少分钟,多少秒。
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds())//65 3900
  • 在和某些系统交互的时候,我们经常会聊到时间戳。可以用.UNIX来获取时间戳。
fmt.Println(now.Unix()) // 1648738080

19.数字解析

image.png

  • 下面我们来学习一下字符串和数字之间的转换。在go语言当中,关于字符串和数字类型之间的转换都在STR conv(string convert的缩写)这个包下。
  • 我们可以用parselnt或者parseFloat来解析一个字parseInt参数
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234

n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111

n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096

  • 可以用Atoi把一个十进制字符串转成数字。
  • 可以用itoA把数字转成字符串。如果输入不合法,那么这些函数都会返回error
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123

n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax

20.进程信息

image.png

  • 在go里面,我们能够用os.Argv来得到程序执行的时候的指定的命令行参数。比如我们编译一个二进制文件,command。后面接abcd来启动,输出的就是一个长度为5的slice,第一个成员代表二进制自身的名字。
// 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]
  • 我们可以用so.getenv来读取环境变量。so.setenv来写入环境变量
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB"))
  • 同过exec.command来快速启动子进程并获取其输入输出
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

总结

一、本堂课重点内容:

  • 本堂课的知识要点有哪些?
    switch、切片slice、map、函数、指针、结构体方法、进程信息

二、详细知识点介绍:

  • 本堂课介绍了哪些知识点? 变量、ifelse、循环、switch、数组、切片、map、range、函数、指针、结构体以及方法、错误处理、字符串操作、字符串格式化、json处理、时间处理、数字解析、进程信息

三、实践练习例子:

  • 有什么实践举例帮助理解知识点?
    比如13-struct-method、11-point

四、课后个人总结:

  • 本章有什么知识点不容易掌握?
    结构体以及方法、进程信息、函数知识点
  • 什么地方容易与其他内容混淆?
    go的if不用括号而Java是需要的、go的传参是值拷贝要改变其值需要传入指针

五、引用参考:

  • 我参考了哪些外部博客/笔记/文章?
  • 文章中有什么地方是我参考引用了外部博客/笔记/文章的?
    blog.csdn.net/judgejames/…