后端 Go 语言基础 | 青训营笔记

14 阅读10分钟

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

走进 Go 语言基础语言

01 简介

1.1 什么是 Go 语言

  • Google出品
  • 特点
    • 高性能、高并发(和java、c++媲美的性能)
      • Go 语言内置了协程(Goroutine)和通道(Channel)两大并发特性,这使后端开发者进行异步编程变得非常容易,而不像其他语言以库的形式支持
    • 语法简单(类似于简化的C语言)
    • 丰富的标准库(稳定性、兼容性)
    • 完善的工具链(编译、代码格式化、错误检查……内置完整的单元测试框架)
    • 所有编译结构默认是静态链接(只需要拷贝编译后的唯一可执行文件)
    • 快速编译
    • 跨平台(各种奇怪平台:路由器都可以拷贝一个二进制文件运行)
    • 垃圾回收
//示例:10行代码实现一个可以承载静态文件访问的高并发、高性能的服务器
package main

import (
    "net/http"
)

func main() {
    http.Handle("/", http.FileServer(http.Dir(".")))
    http.ListenAndServer(":8080", nil)
}

1.2 哪些公司在使用 Go 语言

  • 一些大公司都用
  • 从业务维度看过语言已经在云计算、微服务、大数据、区块链、物联网等领域蓬勃发展。然后在云计算、微服务等领域已经有非常高的市场占有率
  • Docker、Kubernetes、Istio、etcd、prometheus 几乎所有的云原生组件全是用 Go 实现的。

1.3为什么字节跳动会选择 go语言

内部RPC和HTTP框架的推广

02 入门

2.1 开发环境-配置集成开发环境

  1. GoLand 编译器(jetbrains的)
  2. vscode 编辑器
  3. 基于云的开发环境 github.com/wangkechun/…

2.2 基础语法

解析见注释

1.Hello World

main.go

//go run main.go 运行
//go build main.go 编译成二进制
// ./main 运行生成的二进制文件
package main    //代表程序入口包main包的一部分

import (
   "fmt"    //输入输出字符串
)

func main() {
   fmt.Println("main - hello world")
   test1()
   test2()
}

func test1() {
   fmt.Println("test1 - hello world")
}

test.go

package main

import "fmt"

func test2() {
   fmt.Println("test2 - hello world")
}

2.变量

package main

import (
   "fmt"
   "math"
)

func main() {

   var a = "initial"   //自动推断变量类型

   var b, c int = 1, 2

    /**
    声明了变量不使用会报错
    而"_"在golang中表示空标识号
    */
    q := 1
    _ = q

   var d = true

   var e float64

   f := float32(e)
    /**
    相当于
    var f float32
    f = float32(e)
    */

   g := a + "foo"  //字符串直接用+号
   fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
   fmt.Println(g)                // initialapple

    //常量的声明
   const s string = "constant"
   const h = 500000000   //可以根据上下文自动确定类型
   const i = 3e20 / h
   fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}

go是强类型语言,数据类型分为四类:基础类型、复合类型、引用类型和接口类型。还可以分为以下四类:布尔型、数字类型、字符串类型、派生类型。

  • 数字类型_整数 |序号 | 类型和描述 | | -- | ----------------------------------------------------------------- | | 1 | uint8 无符号 8 位整型 (0 到 255) | | 2 | uint16 无符号 16 位整型 (0 到 65535) | | 3 | uint32 无符号 32 位整型 (0 到 4294967295) 10^9 | | 4 | uint64 无符号 64 位整型 (0 到 18446744073709551615) | | 5 | int8 有符号 8 位整型 (-128 到 127) | | 6 | int16 有符号 16 位整型 (-32768 到 32767) | | 7 | int32 有符号 32 位整型 (-2147483648 到 2147483647) | | 8 | int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) |
  • 数字类型_浮点数 |序号 | 类型和描述 | | -- | ---------------------------- | | 1 | float32 IEEE-754 32位浮点型数 | | 2 | float64 IEEE-754 64位浮点型数 | | 3 | complex64 32 位实数和虚数 | | 4 | complex128 64 位实数和虚数 |
  • 其他一些数字类型 | 序号 | 类型和描述 | | -- | -------------------------- | | 1 | byte 类似 uint8 | | 2 | rune 类似 int32 | | 3 | uint 32 或 64 位 | | 4 | int 与 uint 一样大小 | | 5 | uintptr 无符号整型,用于存放一个指针|

3.if-else

  • 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 := 9; num < 0 {
		fmt.Println(num, "is negative")
	} else if num < 10 {
		fmt.Println(num, "has 1 digit", "test") //Println参数好多,逗号隔开
	} else {
		fmt.Println(num, "has multiple digits")
	}
}

4.循环

package main

import "fmt"

func main() {

   i := 1
   for {     //for什么都不加就是死循环
      fmt.Println("loop")
      break
   }
   
   var j int = 7
   for j = 7; j < 9; j++ {
      fmt.Println(j)
   }

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

   for i <= 3 {
      fmt.Println(i)
      i = i + 1
   }
   /**只写后两个空,第一个分号不能省略
   for ; i <= 3; i++ {
      fmt.Println(i)
   }
   */
}

5.switch

package main

import (
   "fmt"
   "time"
)

func main() {

   a := 2
   switch a {   //a不用括号
   case 1:     //C语言不加break,会默认继续往下跑所有分支,而Golang不同,进入case执行完会默认退出switch
      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")
   }
    
    /**
    switch 代替 if-else
    switch后不跟变量,在每个case里面写条件分支
    */
   t := time.Now()
   switch {
   case t.Hour() < 12:
      fmt.Println("It's before noon")
   default:
      fmt.Println("It's after noon")
   }
}

6.数组

长度是固定的,切片更常使用

package main

import "fmt"

func main() {

   //var a [5]int
   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)
}

7.切片(slice)

可以任意改变长度

package main

import "fmt"

func main() {

   s := make([]string, 3)    //用make创建一个切片
   s[0] = "a"
   s[1] = "b"
   s[2] = "c"
   fmt.Println("get:", s[2])   // c
   fmt.Println("len:", len(s)) // 3

   s = append(s, "d")    //append增加元素,必须把append返回值赋值给原数组,因为切片实际上是存储了:一个长度 + 一个容量 + 一个存储数组的指针。
   s = append(s, "e", "f")    //如果容量不够就会扩容创建一个新的slice
   fmt.Println(s) // [a b c d e f]

   c := make([]string, len(s))    //创建指定长度的slice,然后把s赋值给新创建的slice
   copy(c, s)
   fmt.Println(c) // [a b c d e f]

   //切片操作
   fmt.Println(s[2:5]) // [c d e]
   fmt.Println(s[:5])  // [a b c d e]
   fmt.Println(s[2:])  // [c d e f]

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

8.map

其他语言可能叫做hash或者字典

golang的map是完全无序的,遍历的时候随机输出

package main

import "fmt"

func main() {
	m := make(map[string]int)    //make创建一个空map,需要两个类型:map[key]value
	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["unknow"]) // 0

	r, ok := m["unknow"]    //后面加一个ok,用来获取map里面是否有这个key存在
	fmt.Println(r, ok) // 0 false

	delete(m, "one")    //删除kv对

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

9.range

用range遍历切片和map

package main

import "fmt"

func main() {
	nums := []int{2, 3, 4}    //创建切片跟创建数组的区别在于 Type 前的“ [] ”中是否有数字
	sum := 0
	for i, num := range nums {    //第一个位置i是索引,第二个位置num是变量值。只有一个参数值遍历索引
		sum += num
		if num == 2 {
			fmt.Println("index:", i, "num:", num) // index: 0 num: 2
		}
	}
	fmt.Println(sum) // 9

	m := map[string]string{"a": "A", "b": "B"}
	for k, v := range m {    //遍历map的kv
		fmt.Println(k, v) // b B; a A
	}
	for k := range m {    //只遍历key
		fmt.Println("key", k) // key a; key b
	}
}

10.函数

package main

import "fmt"

// 函数声明中,变量类型是后置的
func add(a int, b int) int {
	return a + b
}

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

// 第一个括号是函数的传入值:一个map,一个string
// 第二个括号里面是返回值:一个string,一个bool
func exists(m map[string]string, k string) (v string, ok bool) {
	v, ok = m[k] //ok:k是否是map中的key, v:m[k]是什么
	return v, ok
}

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

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

11. 指针

相比c/c++指针操作更有限,主要用途就是对传入的参数进行修改

package main

import "fmt"

//无效,传入的拷贝参数
func add2(n int) {
	n += 2
}

func add2ptr(n *int) {    //无返回值
	*n += 2    //使用需要加星号
}

func main() {
	n := 5
	add2(n)
	fmt.Println(n) // 5
	add2ptr(&n)
	fmt.Println(n) // 7
}

12.结构体

package main

import "fmt"

type user struct {
	name     string
	password string
}

func main() {
	a := user{name: "wang", password: "1024"} //冒号传入初始值
	b := user{"wang", "1024"}                 //
	c := user{name: "wang"}                   //指定字段名字赋一部分字段值
	c.password = "1024"
	var d user      //未初始化的时候就是赋初值
	d.name = "wang" //读写结构体字段的形式
	d.password = "1024"

	fmt.Println(a, b, c, d)                 // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
	fmt.Println(checkPassword(a, "haha"))   // false
	fmt.Println(checkPassword2(&a, "haha")) // false
}

// 结构体作为参数,传值调用
func checkPassword(u user, password string) bool {
	return u.password == password
}

// 引用调用
func checkPassword2(u *user, password string) bool {
	return u.password == password
}

13.结构体方法

package main

import "fmt"

type user struct {
   name     string
   password string
}

// 定义结构体方法:func和函数名之间,在括号里面写上结构体变量
func (u user) checkPassword(password string) bool {
   return u.password == password
}

func (u *user) resetPassword(password string) {    //结构体指针类型,可以修改字段成员
   u.password = password
}

func main() {
   a := user{name: "wang", password: "1024"}
   a.resetPassword("2048")
   fmt.Println(a.checkPassword("2048")) // true
}

14.错误处理

不同于java异常处理,go使用返回值来处理错误信息

package main

import (
	"errors"
	"fmt"
)

type user struct {
	name     string
	password string
}

// 在返回值加一个error,可以用if-else处理错误
func findUser(users []user, name string) (v *user, err error) {
	for _, u := range users {
		if u.name == name {
			return &u, nil //同时return两个值,没有错误err为nil
		}
	}
	return nil, errors.New("not found") //若出现错误v为nil,err为新建一个error对象
}

func main() {
	u, err := findUser([]user{{"wang", "1024"}}, "wang") //接收有返回错误的函数需要error变量
	if err != nil {                                      //判断error是否存在,处理错误
		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.字符串操作

package main

import (
   "fmt"
   "strings"
)

func main() {
   a := "hello"
   fmt.Println(strings.Contains(a, "ll"))                // true, 字符串是否包含字段
   fmt.Println(strings.Count(a, "l"))                    // 2,字符串计计数
   fmt.Println(strings.HasPrefix(a, "he"))               // true
   fmt.Println(strings.HasSuffix(a, "llo"))              // true
   fmt.Println(strings.Index(a, "ll"))                   // 2,查找字符第一次出现的位置
   fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo,以某符号连接多个字符串
   fmt.Println(strings.Repeat(a, 2))                     // hellohello,重复多个字符串
   fmt.Println(strings.Replace(a, "e", "E", -1))         // hEllo
   fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
   fmt.Println(strings.ToLower(a))                       // hello
   fmt.Println(strings.ToUpper(a))                       // HELLO
   fmt.Println(len(a))                                   // 5,内置函数获取字符串长度
   b := "你好"
   fmt.Println(len(b))                                   // 6,这里一个中文对应了多个字符
}

16.字符串格式化

%v,打印任意类型的变量,%+v更详细的结果,%#v进一步详细的结果

package main

import "fmt"

type point struct {
   x, y int
}

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处理

package main

import (
   "encoding/json"
   "fmt"
)

// 保证结构体每个公开字段变量的首字母大写,就可以进行JSON操作
type userInfo struct {
   Name  string
   Age   int `json:"age"` //若需要小写字段
   Hobby []string
}

func main() {
   a := userInfo{Name: "wang", Age: 18, Hobby: []string{"golang", "TypeScript"}}
   buf, err := json.Marshal(a) //结构体序列化
   if err != nil {
      panic(err)
   }
   fmt.Println(buf)         // [123 34 78 97...]
   fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}   //类型转换

   buf, err = json.MarshalIndent(a, "", "\t") //Indent:缩进 (论英语的重要性)
   if err != nil {
      panic(err)
   }
   fmt.Println(string(buf))

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

18.时间处理

格式化固定时间:2006-01-02 15:04:05,1月2日3点4分5秒6年

package main

import (
   "fmt"
   "time"
)

func main() {
   now := time.Now()   //当前时间
   fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
   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  用time.Format()格式化一个时间
   //fmt.Println(t.Format("1-2 3:04"))   
   diff := t2.Sub(t)    //对时间做减法得到时间段
   fmt.Println(diff)                           // 1h5m0s
   fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
   t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")    //格式化输入时间
   if err != nil {
      panic(err)
   }
   fmt.Println(t3 == t)    // true
   fmt.Println(now.Unix()) // 1648738080 获取一个时间戳
}

19.数字解析

包strconv,数字字符串转换

package main

import (
   "fmt"
   "strconv"
)

func main() {
   f, _ := strconv.ParseFloat("1.234", 64)
   fmt.Println(f) // 1.234

   n, _ := strconv.ParseInt("111", 10, 64) //第一个参数:字符串,第二个参数:进制(为空自动推断)。第三个参数:64位
   fmt.Println(n)                          // 111

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

   n2, _ := strconv.Atoi("123") //Atoi快速把字符串转为数字
   fmt.Println(n2)              // 123

   n2, err := strconv.Atoi("AAA") //输入不合法,返回错误
   fmt.Println(n2, err)           // 0 strconv.Atoi: parsing "AAA": invalid syntax
}

20.进程信息

包os/exec

package main

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

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

   //获取or写入环境变量
   fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
   fmt.Println(os.Setenv("AA", "BB"))

   //快速启动子进程并获取输入输出
   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
}