# 1-go基础 1. [https://go.dev/tour/](https://go.dev/tour/) (必读) , 中文版 https://tour.go-zh.org/ 2. [https://go.dev/doc/effective_go](https://go.dev/doc/effective_go) (必读) 3. https://github.com/Unknwon/the-way-to-go_ZH_CN ## 初始化 ```sh go mod init github.com/用户名/模块名 # 会生成go.mod, 相当于 npm 的 package.json # 删除不再依赖的模块 go mod tidy ``` ```go // hello-word.go package main import "fmt" // 标准库 func main() { fmt.Println("Hello World") } ``` 运行 `go run ./hello-word.go` ```go import ( "fmt" "math" ) // 分组导入 ``` 在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。例如,`Pizza` 就是个已导出名,`Pi` 也同样,它导出自 `math` 包。 ## 打印信息 Print: 轻量, 没有时间戳 > 开发阶段调试或者简单输出文本 ```go fmt.Println("Hello, World!") // 在末尾添加换行符 fmt.Print("Hello, ") // 不添加换行符号 fmt.Print("World!\n") fmt.Printf("Name: %s, Age: %d\n", "Alice", 30) // 格式化字符串 ``` Log: 默认带有时间戳, 可以输出日志所在的文件和行号, 可以触发程序的终止或异常 ```go log.Println("This is a log message.") log.Print("Logging without newline.") log.Printf("Name: %s, Age: %d", "Alice", 30) log.Fatal("Fatal error occurred.") // 打印信息后调用 os.Exit(1),程序会立即终止。适用于严重错误 log.Panic("Panic occurred.") // 打印信息后调用 `panic`,程序会抛出异常。适用于需要立即中断的错误 ``` ## 变量 ```go var a = 1 // var声明变量,并赋值; 全局变量 func main() { var b int // 声明变量后面再赋值,就得带上类型 b = 2 fmt.Println(b) str := "Hello World" // 函数里的变量 := 直接赋值,自动推断类型 fmt.Println(str) } ``` * `:=` 结构不能在函数外使用 * 没有明确初始化的变量声明会被赋予对应类型的 **零值**。 * 数值类型零值为 `0`, * 布尔类型零值为 `false`, * 字符串零值为 `""`(空字符串)。 * 常量 ```go //常量的声明与变量类似,只不过使用 const 关键字。 //常量可以是字符、字符串、布尔值或数值。 //常量不能用 := 语法声明。 const Pi = 3.14 ``` ## 基本类型 ```go func main() { a := 10 fmt.Printf("a 类型为: %T\n", a) // Printf格式化输出, %T占位 b := float32(10) fmt.Printf("b 类型为: %T\n", b) // 基本类型 // 整数 // int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex64 // byte // uint8 别名 // rune // int32 别名 // 浮点数 // float32 float64 // 复数 // complex64 complex128 // 布尔类型 // bool // 字符串 // string } ``` * 类型转换: Go 在不同类型的项之间赋值时需要显式转换 ```go i := 42 f := float64(i) u := uint(f) ``` ## 运算符 ```go // 加减乘除 + - * / // 取余 % // 比较 == != < <= > >= // 按位与 & // 逻辑运算符 && || // 逻辑非 ! ``` ## 分支 ### if else * 表达式外无需小括号 `( )` ```go func main() { a := 10 if a > 10 { fmt.Println("a > 10") } else if a > 5 { fmt.Println("5 < a <= 10") } else { fmt.Println("a <= 5") } // 局部变量声明 // 还能在if中直接用 := 赋值 ; 该语句声明的变量作用域仅在 `if` 和 `else` 之内。 if b := 10; b > 10 { fmt.Println("b > 10") } } ``` ### switch ```go // switch语句, case里不需要写break c := 10 switch c { case 10: fmt.Println("c == 10") case 5: fmt.Println("c == 5") default: fmt.Println("c != 10 && c != 5") } ``` ## 循环 ### for循环 * 三参数形式 * 和 C、Java、JavaScript 之类的语言不同,Go 的 `for` 语句后面的三个构成部分外没有小括号 ```go // 只有一种循环形式 // for循环 func main() { for i := 0; i < 10; i++ { fmt.Println(i) } // 可以用break来退出循环 for i := 0; i < 10; i++ { if i == 5 { break } fmt.Println(i) } // 可以用continue来跳过当前循环 for i := 0; i < 10; i++ { if i == 5 { continue } fmt.Println(i) } } ``` * range 形式(用于遍历数组、切片、map 等): ```go // 同时需要索引和值 for index, value := range array { // 使用 index 和 value } // 只需要值 for _, value := range array { // 只使用 value } // 只需要索引 for index := range array { // 只使用 index } ``` ### 模拟while循环 ```go // 模拟while循环 m := 1 for m < 5 { fmt.Println(m) m++ } ``` ## 函数 ```go func sum(a int, b int) int { return a + b } func swap(a, b int) (int, int) { // a b的类型是同一种;函数可以返回多个值 return b, a } func main() { fmt.Println(sum(1, 2)) a, b := swap(1, 2) fmt.Println(a, b) } ``` 没有参数的 `return` 语句会直接返回已命名的返回值,也就是「裸」返回值。 ### defer 推迟 defer 语句会将函数推迟到外层函数返回之后执行。 推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。 ```go func main() { defer fmt.Println("world") fmt.Println("hello") } ``` ## 数组 array > **同一类型**的元素的**固定长度**序列 * 在实践中,切片比数组更常用。 ```go // Array var arr [5]int // 声明一个包含5个整数的数组 arr[0] = 10 // 将数组的第一个元素设置为10 fmt.Println(arr[0]) // 输出: 10 a := [3]int{1, 2, 3} // 数据的长度是固定且不可变的 fmt.Println(a) ``` ## 切片 slice > 切片是对数组的抽象,它提供了更强大的功能和灵活性 > **切片的长度可以动态变化**,这是它与数组的主要区别 * 切片就像数组的引用, 切片并不存储任何数据, 更改切片的元素会修改其底层数组中对应的元素, 和它共享底层数组的切片都会观测到这些修改。 ```go // Slice // 如果要定义可变长度的数组,可以使用slice var slice []int // 声明一个整数切片, 这里的方括号里没有长度, 数组则是要求固定长度 slice = make([]int, 5) // 创建一个长度为5的切片 // 切片也可以通过数组的一部分创建 arr := [5]int{1, 2, 3, 4, 5} slice2 := arr[1:4] // 创建一个包含arr[1]到arr[3]的切片 // 用make函数创建, 初始长度为0 b := make([]int, 0) //`make` 函数会分配一个元素为零值的数组并返回一个引用了它的切片 e := make([]int, 5) // len(e)=5, cap(e)=5, [0,0,0,0,0] // 要指定它的容量,需向 make 传入第三个参数 a := make([]int, 0, 5) // len(a)=0, cap(a)=5, [] f := e[:2] // len(f)=2, cap(f)=5, [0, 0] g := f[2:5] // 追加元素 b = append(b, 1, 2, 3) fmt.Println(b) // 或者字面值 c := []int{1, 2, 3} // []里不指定长度 c[0] = 10 // 使用下标的方式修改 fmt.Println(c) // 选出一个半闭半开区间,包括第一个元素,但排除最后一个元素。 d := a[1:3] fmt.Println("d的值为:", d) ``` ### 切片的默认行为 ```go var a [10]int // 以下切片表达式和它是等价的 a[0:10] a[:10] a[0:] a[:] ``` ### 切片的长度 len 和容量 cap * 切片的长度就是它所包含的元素个数。 * 切片的容量是从==切片的起始==位置到==底层数组==的末尾 * 切片 `s` 的长度和容量可通过表达式 `len(s)` 和 `cap(s)` 来获取。 ```go func main() { s := []int{2, 3, 5, 7, 11, 13} printSlice(s) // 截取切片使其长度为 0 s = s[:0] printSlice(s) // 扩展其长度 s = s[:4] printSlice(s) // 舍弃前两个值 s = s[2:] printSlice(s) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } ``` 输出 ```go len=6 cap=6 [2 3 5 7 11 13] len=0 cap=6 [] len=4 cap=6 [2 3 5 7] len=2 cap=4 [5 7] ``` * `s[:i]` 创建一个从切片 s 的开始到索引 i 的新切片,不包括索引 i 的元素。 * `s[i:]` 创建一个从索引 i 到切片 s 的末尾的新切片,包括索引 i 的元素。 * 切片操作不会改变底层数组的容量,只是改变了切片的长度和起始位置。 使用场景: * **预分配切片**:在一些场景中,你可能知道要存储的元素个数并且希望避免动态扩展切片,可以通过预分配足够的容量来避免性能损失。 ```go data := make([]int, 50) // 长度和容量都为50 ``` * 优化性能:当你需要向切片中添加元素时,如果切片的容量已经满了,Go会创建一个新的底层数组并将现有元素复制过去。这是一个相对耗时的操作。如果你提前知道大致需要的容量,可以预先分配好,避免多次内存分配和数据复制操作。 ```go // 预先分配容量,避免多次扩容 s := make([]int, 0, 100) // 长度为0,容量为100 for i := 0; i < 100; i++ { s = append(s, i) } ``` * 切片的高效操作:利用切片的容量可以进行更高效的切片操作,而不必担心底层数组的频繁重新分配 ```go // 假设有一个初始切片 s := make([]int, 0, 10) // 长度为0,容量为10 // 可以安全地追加元素,直到达到容量限制 for i := 0; i < 10; i++ { s = append(s, i) } // 如果继续追加元素,则会触发容量的扩展 s = append(s, 10) // 触发扩展,容量将翻倍 ``` ### nil 的切片 切片的零值是 `nil`。 (零值: 没有赋值时的默认值) nil 切片的长度和容量为 0 且没有底层数组 ```go var s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } ``` ### 二维切片 切片可以包含任何类型,当然也包括其他切片。 ```go // 创建一个井字棋(经典游戏) board := [][]string{ []string{"_", "_", "_"}, []string{"_", "_", "_"}, []string{"_", "_", "_"}, } // 两个玩家轮流打上 X 和 O board[0][0] = "X" board[2][2] = "O" board[1][2] = "X" board[1][0] = "O" board[0][2] = "X" for i := 0; i < len(board); i++ { fmt.Printf("%s\n", strings.Join(board[i], " ")) } ``` ### 切片追加元素 切片(slice)的容量是动态增长的。当你向切片追加元素时,如果当前容量不足以容纳新元素,Go会自动扩容切片。切片的容量增长策略通常是当前容量的1.25倍 ```go package main import "fmt" func main() { var s []int printSlice(s) // 可在空切片上追加 s = append(s, 0) printSlice(s) // 这个切片会按需增长 s = append(s, 1) printSlice(s) // 可以一次性添加多个元素 s = append(s, 2, 3, 4) printSlice(s) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } ``` ```go // 输出 len=0 cap=0 [] len=1 cap=1 [0] len=2 cap=2 [0 1] len=5 cap=6 [0 1 2 3 4] ``` 分析添加 2,3,4的过程 ``` 初始状态:长度2,容量2。 追加第一个元素(2):长度变为3,容量变为4。 追加第二个元素(3):长度变为4,容量仍为4。 追加第三个元素(4):长度变为5,容量变为6。 ``` ### range遍历 `for` 循环的 `range` 形式可遍历切片或映射。 当使用 `for` 循环遍历切片时,每次迭代都会返回两个值。 第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。 ```go package main import "fmt" 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) } } ``` 可以将下标或值赋予 `_` 来忽略它。 ```go for i, _ := range pow for _, value := range pow ``` 若你只需要索引,忽略第二个变量即可。 ```go for i := range pow ``` ## 映射 map > 一种无序的键值对集合。映射的键必须是唯一的,且通常是可比较的类型(如字符串、数字等) > 相当于 js 里的对象 ```go // Map var m1 map[string]int // 声明一个键为字符串、值为整数的映射 m1 = make(map[string]int) // 初始化映射 m := map[string]int{"a": 1, "b": 2, "c": 3} // []里是key的类型,后面是value的类型 fmt.Println(m) fmt.Println(m["a"]) // 如果不需要初始值, 也可以用make函数创建 // 映射的零值是nil n := make(map[string]int) n["a"] = 1 n["b"] = 2 n["c"] = 3 fmt.Println(n) ``` ### 删除元素 ```go delete(m, key) ``` ### 检测某个键是否存在 ```go elem, ok = m[key] // 若 `key` 在 `m` 中,`ok` 为 `true` ;否则,`ok` 为 `false`。 // 若 `key` 不在映射中,则 `elem` 是该映射元素类型的零值。 ``` ## 结构体 struct ```go // Struct 结构体, 类似于js里的class类 type Student struct { Name string Age int Sex string } s := Student{"jqtm", 18, "male"} t := s s.Age = 19 // 使用.修改值 fmt.Println(s) fmt.Println(t) // s的修改不会影响t,因为t是s的副本 // Go 语言中结构体是值类型,赋值操作会创建一个新的副本,而不是引用 // 如果想要引用传递,可以使用指针 u := &s // go中通过&来获取变量指针地址 s.Age = 20 fmt.Println(s) fmt.Println(*u) // 使用*来解引用 u.Age = 21 // 修改值时可以省略*, go会自动进行解引用 fmt.Println(s) fmt.Println(*u) // 使用*来声明这是个指针,进行解引用 p := Point{1, 2} p.SetPoint(3, 4) fmt.Println(p) ``` ### 结构体添加方法 ```go // 结构体也能添加方法 type Point struct { X int Y int } func (p *Point) SetPoint(x, y int) { // 大部分情况需要*,否则只是修改副本;方法名首字母大写; 方法是定义在结构体的外部 p.X = x p.Y = y } ``` ## 接口 ```go // 接口 type Shape interface { // type 接口名 interface Print() // 在里面定义方法,不需要func } // 结构体实现接口 type Rectangle struct{} type Circle struct{} // 定义同名的方法 func (r Rectangle) Print() { fmt.Println("矩形") } func (c Circle) Print() { fmt.Println("圆形") } func main() { //接口的实现 var i Shape // 类型是接口 i = Rectangle{} // 赋值不同的结构,实现多态 i.Print() i = Circle{} i.Print() } ``` ## 错误处理 ```go func main() { // 错误处理 n, err := fmt.Println("Hello") // 错误是通过函数的多个返回值实现的 // 需要判断err是否为nil或者有没有值 if err != nil { // 执行正常代码 } else { // 执行异常代码 } } ``` ## 多线程 ```go func func1() { time.Sleep(500 * time.Millisecond) // Millisecond 毫秒 fmt.Println("func1") } func func2() { fmt.Println("func2") } func func3(ch chan string) { time.Sleep(2 * time.Second) ch <- "func3" // 发送数据到管道 } func func4(ch chan string) { time.Sleep(1 * time.Second) ch <- "func4" } func main() { // 多线程 Goroutines go func2() // 开启一个新的线程 func1() // 在主线程中执行,延迟了500毫秒,func2能够在主线程退出前打印 // 管道 Channels, 多个线程之间进行通信和同步 ch := make(chan string) // 使用make函数创建一个管道 go func3(ch) // 需要开启一个新的线程来接收数据 res1 := <-ch // 从管道中获取数据,接收操作是阻塞的,直到有数据发送过来 fmt.Println(res1) // 所以是先打印func3再打印func4 go func4(ch) res2 := <-ch fmt.Println(res2) go func3(ch) go func4(ch) for i := 0; i < 2; i++ { select { // select 可以同时接收多个管道数据,类似于switch case res := <-ch: fmt.Println(res) } } } ``` ## 模块管理 ```sh go get github.com/hekmon/transmissionrpc/v3 # 如何卸载 # 删除 go.mod 中的依赖行 # 清理未使用的依赖 go mod tidy ``` ## Vscode 断点调试 https://pengtech.net/golang/vscode_debug_golang.html 1. 安装 `delve` ```sh https://github.com/go-delve/delve/tree/master/Documentation/installation which dlv ``` 1. 点击调试图标, 创建 `.vscode/launch.json` ```json { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Launch golang program", "type": "go", "request": "launch", "mode": "auto", "program": "main.go" } ] } ```