GO 语言基础学习
Drunkbaby Lv6

golang 基础学习

GO 语言基础学习

主要也是自己读书的一点笔记吧

0x01 前言

当时有一天在地铁上看微信读书的时候想起来:噢对我是时候写一点关于 Go 的笔记了,所以就有了这篇 Go 语言基础学习的文章。

本文笔记基于 《Go 语言入门经典》

0x02 环境搭建

用 Goland 搭建项目,每次创建项目之后,我们都需要对 Goland 进行一些配置,在 Goland 的右上方找到“Add Configuration”并单击。

选择 Directory,接着写 helloWorld 文件

package main  
  
import "fmt"  
  
func main()  {  
   fmt.Println("Hello World")  
}

0x03 GO 语言基础

1. 类型

字符串类型

package main  
  
import "fmt"  
  
func sayHello(s string) string  {  
   return "Hello " + s  
}  
  
func main()  {  
   fmt.Println(sayHello("Drunkbaby"))  
}

int 类型

package main  
  
import "fmt"  
  
func addition(x int, y int) int {  
   return x+y  
}  
  
func main()  {  
   fmt.Println(addition(2,4))  
}

bool 类型

package main  
  
import "fmt"  
  
func main()  {  
   var b bool  
   fmt.Println(b)  
   b = true  
   fmt.Println(b)  
}

数值类型

int:平平无奇

float:氛围 float32 与 float64,一般用 float64 居多

定义如下

var f float64 = 0.111

类型转换

  • 此处提及两个包,一个是 reflect,一个是 srtconv
package main  
  
import (  
   "fmt"  
   "reflect"   "strconv")  
  
func main()  {  
   var b bool = true  
   fmt.Println(reflect.TypeOf(b))  
   var s string = strconv.FormatBool(true)  
   fmt.Println(reflect.TypeOf(s))  
}

2. 变量

字符串变量

package main  
  
import "fmt"  
  
func main()  {  
   var s string = "Hello World"  
   fmt.Println(s)  
}

快捷声明变量

package main  
  
import "fmt"  
  
func main()  {  
   var (  
      s string = "Drunkbaby"  
      i int = 4  
   )  
   fmt.Println(s)  
   fmt.Println(i)  
}

声明变量后,就不能再次声明它,不然会报错。

变量零值

package main  
  
import "fmt"  
  
func main()  {  
   var i int  
   var f float64  
   var b bool  
   var s string  
   fmt.Printf("%v %v %v %q\n", i, f, b, s)  
}

编写简短变量

:= 符号是简短变量声明

package main  
  
import "fmt"  
  
func main()  {  
   s := "Hello World"  
   fmt.Println(s)  
}

简短变量声明是最常用的变量声明方式,还有一些比较简洁的变量声明

package main  
  
import "fmt"  
  
var s = "Hello World"  
  
func main()  {  
   i := 42  
   fmt.Println(s)  
   fmt.Println(i)  
}

有关作用域

和其他语言是一样的,大括号作为作用域的分割,看代码。

package main  
  
import "fmt"  
  
var s2 = "Hello World"  
  
func main()  {  
   fmt.Printf("Print 's2' variable from outer block %v\n", s2)  
   b := true  
   if b {  
      fmt.Printf("Printing 'b' variable from outer block %v\n", b)  
      i := 42  
      if b != false {  
         fmt.Printf("Printing 'i' variable from outer block %v\n", i)  
      }  
   }  
}

指针

package main  
  
import "fmt"  
  
func main()  {  
   s := "Hello World"  
   fmt.Println(&s)  
}

输出一个十六进制的内存地址

将变量作为值传递时,地址不变

package main  
  
import "fmt"  
  
func showMemoryAddress(x *int)  {  
   fmt.Println(x)  
   return  
}  
  
func main()  {  
   i := 1  
   fmt.Println(&i)  
   showMemoryAddress(&i)  
}

如果要使用指针指向变量的值,而非内存地址,应当在指针变量前加上星号。

func showMemoryAddress(x *int)  {  
   fmt.Println(*x)  
   return  
}  

声明常量

package main  
  
import "fmt"  
  
const greeting string = "Hello World"  
  
func main()  {  
   greeting = "Goodbye, fuck security"  
   fmt.Println(greeting)  
}

const 常量无法被修改,所以会报错

3. 函数

返回多个值

package main  
  
import "fmt"  
  
func getPrize() (int, string) {  
   i := 2  
   s := "Drunkbaby"  
   return i, s  
}  
  
func main()  {  
   quantity, prize := getPrize()  
   fmt.Printf("You won %v %v\n", quantity, prize)  
}

不定参数函数

很有意思,是用三个点的,变量 numbers 是一个包含所有参数的切片。

代码如下

package main  
  
import "fmt"  
  
func sumNumbers(numbers... int) int {  
   total := 0  
   for _, number := range numbers {  
      total += number  
   }  
   return total  
}  
  
func main()  {  
   result := sumNumbers(1, 2, 3, 4)  
   fmt.Printf("The result is %v\n", result)  
}

具名返回值

按照声明顺序返回具体名称变量值。

package main  
  
import "fmt"  
  
func sayHi() (x, y string) {  
   x = "hello"  
   y = "world"  
   return  
}  
  
func main()  {  
   fmt.Println(sayHi())  
}

递归函数

package main  
  
import "fmt"  
  
func feedMe(portion int, eaten int) int {  
   eaten = portion + eaten  
   if eaten >= 5 {  
      fmt.Printf("I'm full! I've eaten %d\n", eaten)  
      return eaten  
   }  
   fmt.Printf("I'm still hungry! I've eaten %d\n", eaten)  
   return feedMe(portion, eaten)  
}  
  
func main()  {  
   feedMe(1, 0)
}

将函数作为值传递

package main  
  
import "fmt"  
  
func anotherFunction(f func() string) string {  
   return f()  
}  
  
func main()  {  
   fn := func() string {  
      return "function called"  
   }  
   fmt.Println(anotherFunction(fn))  
}

4. 控制流程

if

package main  
  
import "fmt"  
  
func main()  {  
   b := true  
   if b {  
      fmt.Println("b is true!")  
   }  
}

else

package main  
  
import "fmt"  
  
func main()  {  
   b := false  
   if b {  
      fmt.Println("b is true")  
   } else {  
      fmt.Println("b is false")  
   }  
}

elseif

package main  
  
import "fmt"  
  
func main()  {  
   i := 3  
   if i == 3 {  
      fmt.Println("i is 3")  
   } else if i == 2 {  
      fmt.Println("i is 2")  
   }  
}

运算符

  • 算术运算符
  • 逻辑运算符

switch,可以说是 if 的高级用法了

package main  
  
import "fmt"  
  
func main()  {  
   i := 2  
  
   switch i {  
   case 2:  
      fmt.Println("Two")  
   case 3:  
      fmt.Println("Three")  
   case 4:  
      fmt.Println("Four")  
   }  
}

default in switch 语句

package main  
  
import "fmt"  
  
func main()  {  
   s := "c"  
  
   switch s {  
   case "a":  
      fmt.Println("The letter is a!")  
   case "b":  
      fmt.Println("The letter is b!")  
   default:  
      fmt.Println("I don't recognize that letter")  
   }  
}

for 循环

package main  
  
import "fmt"  
  
func main()  {  
   i := 0  
   for i < 10 {  
      i++  
      fmt.Println("i is", i)  
   }  
}

简化写法,也是最常规的写法

package main  
  
import "fmt"  
  
func main()  {  
   for i := 0; i < 10; i++ {  
      fmt.Println("i is", i)  
   }  
}

包含 range 子句的 for 语句

package main  
  
import "fmt"  
  
func main()  {  
   numbers := []int{1,2,3,4}  
   for i, n := range numbers {  
      fmt.Println("The index of the loop is", i)  
      fmt.Println("The value from the array is", n)  
   }  
}

defer 语句

defer 是一个很有用的 Go 语言功能,它能够让程序在函数返回前执行另一个函数。函数在遇到 return 语句或到达函数末尾的时候返回。defer 语句通常用于执行清理操作或确保操作。

package main  
  
import "fmt"  
  
func main()  {  
   defer fmt.Println("I am run after the function completes")  
   fmt.Println("Hello World")  
}
  • 在执行完 main 函数后执行 defer 函数

5. 数组、切片和映射

数组的相关基础操作

package main  
  
import "fmt"  
  
func main()  {  
   var cheeses [2]string  
   cheeses[0] = "Silly"  
   cheeses[1] = "Drunkbaby"  
   fmt.Println(cheeses[0])  
   fmt.Println(cheeses[1])  
   fmt.Println(cheeses)  
}

声明数组的变量之后,就不能给它再添加元素了,比如此处我想 cheeses[2]="boy" 就会报错

切片

一些书面语比较难以理解,我们可以讲的简易一些,切片≈数组

切片是底层数组中的一个连续片段,,通过它可以访问该数组中一系列带编号的元素,因此,切片可以让我们访问数组的特定部分,比较像索引吧。

在切片中添加元素

其实我并不喜欢这种说法,更像是给数组增加元素。

用的方法是直接 append,,很有意思,不像 Java 里面是 xxx.append(),可以同时添加多个元素

package main  
  
import "fmt"  
  
func main()  {  
   var cheeses = make([]string, 2)  
   cheeses[0] = "Silly"  
   cheeses[1] = "Drunkbaby"  
   cheeses = append(cheeses, "Boy", "too", "silly")  
   fmt.Println(cheeses)  
}
从切片中删除元素

也是用 append

package main  
  
import "fmt"  
  
func main()  {  
   var cheeses = make([]string, 3)  
   cheeses[0] = "Silly"  
   cheeses[1] = "Drunkbaby"  
   cheeses[2] = "Baby"  
   fmt.Println(len(cheeses))  
   fmt.Println(cheeses)  
   cheeses = append(cheeses[:2], cheeses[2+1:]...)  
   fmt.Println(len(cheeses))  
   fmt.Println(cheeses)  
}
切片元素复制

用的是 copy

package main  
  
import "fmt"  
  
func main()  {  
   var cheeses = make([]string, 2)  
   cheeses[0] = "Silly"  
   cheeses[1] = "Drunkbaby"  
   var smeelyCheeses = make([]string, 2)  
   copy(smeelyCheeses, cheeses)  
   fmt.Println(smeelyCheeses)  
}

映射

映射读起来比较别扭,比较像 Java 里面的 Map,也就是键值对

  • 创建空映射
var players= make(map[stringint)

对应的放 key value 并不是用 put 方法,而是如此

players["cook"] = 32
players["bairstow"] = 27
players["stokes"] = 26
从映射中删除元素
package main  
  
import "fmt"  
  
func main()  {  
   var players = make(map[string]int)  
   players["cook"] = 32  
   players["bairstow"] = 27  
   players["stokes"] = 26  
   delete(players, "cook")  
   fmt.Println(players)  
}

6. 结构体与指针

一个基础的结构体

结构体与类差不多,概念方面不再赘述

package main  
  
import "fmt"  
  
type Movie struct {  
   Name string  
   Rating float32  
}  
  
func main()  {  
   m := Movie{  
      Name: "Drunkbaby",  
      Rating: 10,  
   }  
   fmt.Println(m.Name, m.Rating)  
}

“实例化一个 struct 实例”

  • 和 new 一个 class 一样,比较简单
package main  
  
import "fmt"  
  
type Person struct {  
   Name string  
   Rating float32  
}  
  
func main()  {  
   var m Person  
   fmt.Printf("%+v\n", m)  
   m.Name = "Drunkbaby"  
   m.Rating = 10  
   fmt.Printf("%+v\n", m)  
}

“new 一个 struct 实例”

package main  
  
import "fmt"  
  
type Student struct {  
   Name string  
   Age float32  
}  
  
func main()  {  
   m := new(Student)  
   m.Name = "Drunkbaby"  
   m.Age = 10  
   fmt.Printf("%+v\n", m)  
}

嵌套结构体

结构体内包含结构体

package main  
  
import "fmt"  
  
type Superhero struct {  
   Name string  
   Age int  
   Address Address  
}  
  
type Address struct {  
   Number int  
   Street string  
   City string  
}  
  
func main()  {  
   e := Superhero{  
      Name: "Batman",  
      Age: 32,  
      Address: Address{  
         Number: 1007,  
         Street: "Mountain Drive",  
         City: "Gotham",  
      },  
   }  
   fmt.Printf("%+v\n", e)  
}

结构体的构造函数

  • 简单。。。
package main  
  
import "fmt"  
  
type Alarm struct {  
   Time string  
   Sound bool  
}  
  
func NewAlarm(time string) Alarm {  
   a := Alarm{  
      Time: time,  
      Sound: true,  
   }  
   return a  
}  
  
func main()  {  
   fmt.Printf("%+v\n", NewAlarm("07:00"))  
}

结构体进行比较

这个挺有意思哈哈

package main  
  
import "fmt"  
  
type Drink struct {  
   Name string  
   Ice bool  
}  
  
func main()  {  
   a := Drink{  
      Name: "Lemonade",  
      Ice: true,  
   }  
   b := Drink{  
      Name: "Lemonade",  
      Ice: true,  
   }  
   if a == b {  
      fmt.Println("a and b are the same")  
   }  
   fmt.Printf("%+v\n", a)  
   fmt.Printf("%+v\n", b)  
}

检查结构体类型

在 go 里面用的是 reflect,挺有意思的,在 Java 里面这也被称作反射,不愧是反射,能够看到结构体是什么

fmt.Println(reflect.TypeOf(a))  
fmt.Println(reflect.TypeOf(b))

区分指针引用和值引用

  • 这就是老师特别喜欢讲的,变了一个,另外一个会不会变

先说结论
值引用,两个无关联
指针引用,一个变另外一个跟着变

值引用

package main  
  
import "fmt"  
  
type work struct {  
   Name string  
   isWork bool  
}  
  
func main()  {  
   a := work{  
      Name: "Fire",  
      isWork: true,  
   }  
   b := a  
   b.isWork = false  
   fmt.Printf("%+v\n", b)  
   fmt.Printf("%+v\n", a)  
   fmt.Printf("%p\n", &a)  
   fmt.Printf("%p\n", &b)  
}

指针引用

两个一起变

package main  
  
import "fmt"  
  
type Teacher struct {  
   Name string  
   isWorking bool  
}  
  
func main()  {  
   a := Teacher{  
      Name: "Drunkbaby",  
      isWorking: true,  
   }  
   b := &a  
   b.isWorking = false  
   fmt.Printf("%+v\n", *b)  
   fmt.Printf("%+v\n", a)  
   fmt.Printf("%p\n", b)  
   fmt.Printf("%p\n", &a)  
}

将指向的指针(而不是本身)赋给 ,这是使用和号字符表示的。由于 b 是指针,因此必须使用星号字符对其进行引用

7. 创建方法与接口

方法,也就是类的函数,和 Java 当中的 “方法”是一样的,那么在 Go 中需要如何定义呢?

比如我有一个类(结构体),代码如下

package main  
  
type Movie struct {  
   Name string  
   Rating float64  
}  
  
func (m *Movie) summary() string {  
   return "1"  
}

需要我们在方法名前,加入 m *struct 才可以

声明与调用方法

package main  
  
import (  
   "fmt"  
   "strconv")  
  
type Movie struct {  
   Name string  
   Rating float64  
}  
  
func (m *Movie) summary() string {  
   r := strconv.FormatFloat(m.Rating, 'f', 1, 64)  
   return m.Name + ", " + r  
}  
  
func main()  {  
   m := Movie{  
      Name: "SpiderMan",  
      Rating: 3.2,  
   }  
  
   fmt.Println(m.summary())  
}

当然一个类可以有多个方法

值传递与指针传递

  • 方法传参,就是很平常。
package main  
  
import "fmt"  
  
type Triangle struct {  
   base float64  
   height float64  
}  
  
func (t Triangle) changeBase(f float64) {  
   t.base = f  
   return  
}  
  
func (t *Triangle) changeBasePointer(f float64) {  
   t.base = f  
   return  
}  
  
func main()  {  
   t := Triangle{  
      base: 3,  
      height: 1,  
   }  
   t.changeBase(4)  
   fmt.Println(t.base)  
   t.changeBasePointer(4)  
   fmt.Println(t.base)  
}
  • 如果需要修改原始结构体,就选指针。如果不需要修改原始结构体,就使用值。

接口

固定格式

type name interface {

}

// 在里面可以定义方法

我的理解是,接口里面定义方法,然后让具体的类去实现,但是这个类的实现就很微妙。。。。。。。。。。可以说基本没什么关联。无语

package main  
  
import (  
   "errors"  
   "fmt")  
  
type Robot interface {  
   PowerOn() error  
}  
  
type T850 struct {  
   Name string  
}  
  
func (a *T850) PowerOn() error {  
   return nil  
}  
  
type R2D2 struct {  
   Broken bool  
}  
  
func (r R2D2) PowerOn() error  {  
   if r.Broken {  
      return errors.New("R2D2 is broken")  
   } else {  
      return nil  
   }  
}  
  
func Boot(r Robot) error {  
   return r.PowerOn()  
}  
  
func main() {  
   t := T850{  
      Name: "The Terminator",  
   }  
  
   r := R2D2{  
      Broken: true,  
   }  
  
   err := Boot(&r)  
  
   if err != nil {  
      fmt.Println(err)  
   } else {  
      fmt.Println("Robot is powered on")  
   }  
  
   err = Boot(&t)  
  
   if err != nil {  
      fmt.Println(err)  
   } else {  
      fmt.Println("Robot is powered on")  
   }  
}

8. 字符串

一些基础的操作就不讲了,直接看一些我认为有意思的地方

使用缓冲区(stringBuffer)拼接字符串

package main  
  
import (  
   "bytes"  
   "fmt")  
  
func main()  {  
   var buffer bytes.Buffer  
  
   for i := 0; i < 500; i++ {  
      buffer.WriteString("z")  
   }  
  
   fmt.Println(buffer.String())  
}

原理同 Java

进制表示

小写

使用 ToLower() 接口

查找

使用 strings.Index(),如果找到,则输出第一个字串的索引开始;若找不到,则输出 -1

删除字符串中的空格

  • 删除前后空格
package main  
  
import (  
   "fmt"  
   "strings")  
  
func main()  {  
   fmt.Println(strings.TrimSpace(" I don't need all the space "))  
}

9. Map 相关操作

Map 是 key-value 的键值对数据结构,类似于集合的意思

创建 map (但必须分配空间)

原本逻辑上声明 map,从 Java 的角度来说没什么问题

package main  
  
import "fmt"  
  
func main() {  
   var a map[int]string  
   fmt.Println(a)  
}

但是这个程序,我们是不能进行赋值的,原因是并没有给此 map,也就是变量 a 分配内存空间,如果我们尝试赋值,如下语句,是会报错的

a[1]="Drunkbaby"

那么要如何才能分配空间并赋值呢?这里我们创建一个空的 map 需要用 make,完整有效代码如下

package main  
  
import "fmt"  
  
func main() {  
   // 创建空 map,却没有分配空间  
   var a map[int]string  
   // 在使用 map 前,需要先 make,make 的作用就是给 map 分配数据空间  
   a = make(map[int]string, 10)  
   a[1] = "Drunkbaby;"  
   fmt.Println(a)  
}

同样可以自己赋值 a[2] = "silly"

但是再写 a[1] = "quan9i",那么之前的 a[1] = "Drunkbaby" 会被覆盖

代码如下

package main  
  
import "fmt"  
  
func main() {  
   // 创建空 map,却没有分配空间  
   var a map[int]string  
   // 在使用 map 前,需要先 make,make 的作用就是给 map 分配数据空间  
   a = make(map[int]string, 10)  
   a[1] = "Drunkbaby;"  
   a[2] = "cute"  
   a[1] = "quan9i"  
   fmt.Println(a)  
}

结果如图,覆盖了 a[1],且是有序的

一般声明方式比较多的是这样

var cities = make(map[string]string)

// 或者是
cities := make(map[string]string)

简单练习案例

package main  
  
import "fmt"  
  
// 练习案例  
/*  
课堂练习:演示一个 key-value 的 value 是 map 的案例  
比如:我们要存放3个学生的信息,每个学生有 name 和 sex 信息  
思路:map[string]map[string]string  
*/  
  
func main() {  
   studentMap := make(map[string]map[string]string)  
   studentMap["stu01"] = make(map[string]string, 2)  
   studentMap["stu01"]["name"] = "Drunkbaby"  
   studentMap["stu01"]["sex"] = "男"  
  
   studentMap["stu02"] = make(map[string]string, 2)  
   studentMap["stu02"]["name"] = "D1ng"  
   studentMap["stu02"]["sex"] = "女"  
  
   fmt.Println(studentMap["stu01"])  
   fmt.Println(studentMap["stu02"])  
}
}

同样还可以这样取值

fmt.Println(studentMap["stu01"]["name"])

Map 的增删改查

增加和更新

根据 key 是否存在,如果 key 存在就是更新,不存在就是增加

package main  
  
import "fmt"  
  
func main() {  
   cities := make(map[string]string)  
  
   cities["no1"] = "北京"  
   cities["no2"] = "上海"  
   cities["no3"] = "广州"  
  
   fmt.Println(cities)  
  
   cities["no3"] = "上海 ~"  
   fmt.Println(cities)  
}

删除

package main  
  
import "fmt"  
  
// 增加与更新  
  
func main() {  
   cities := make(map[string]string)  
  
   cities["no1"] = "北京"  
   cities["no2"] = "上海"  
   cities["no3"] = "广州"  
  
   fmt.Println(cities)  
  
   // 演示删除  
   delete(cities, "no1")  
   fmt.Println(cities)  
   // 当 delete 指定的 key 不存在时,删除不会操作,也不会报错  
   delete(cities, "no4")  
   fmt.Println(cities)  
}
  • 删除细节说明

如果要删除 map 当中所有的 key,有如下两种方法

1、遍历所有的 key,逐一删除
2、直接 make 一个新的空间,让老空间被 GC

Map 查询

package main  
  
import "fmt"  
  
// 增加与更新  
  
func main() {  
   cities := make(map[string]string)  
  
   cities["no1"] = "北京"  
   cities["no2"] = "上海"  
   cities["no3"] = "广州"  
  
   fmt.Println(cities)  
  
   key, flag := cities["no1"]  
   if flag {  
      fmt.Println("no1 的 key 为", key)  
   }  
}

Map 的遍历

Map 的遍历不能简单的用 for 循环

package main  
  
import "fmt"  
  
// 增加与更新  
  
func main() {  
   cities := make(map[string]string)  
  
   cities["no1"] = "北京"  
   cities["no2"] = "上海"  
   cities["no3"] = "广州"  
  
   for k, v := range cities {  
      fmt.Printf("k=%v v=%v\n", k, v)  
   }  
}

和 python 基本上差不多

  • 那如果要遍历复杂的 Map 呢?就像我们之前讲的那一个案例,Map 套 Map
package main  
  
import "fmt"  
  
// 遍历循环一个较为复杂的 Map  
func main() {  
   studentMap := make(map[string]map[string]string)  
   studentMap["stu01"] = make(map[string]string, 2)  
   studentMap["stu01"]["name"] = "Drunkbaby"  
   studentMap["stu01"]["sex"] = "男"  
  
   studentMap["stu02"] = make(map[string]string, 2)  
   studentMap["stu02"]["name"] = "D1ng"  
   studentMap["stu02"]["sex"] = "女"  
  
   for k1, v1 := range studentMap {  
      fmt.Println(k1)  
      for k2, v2 := range v1 {  
         fmt.Printf("\t k2=%v v2=%v\n", k2, v2)  
      }  
   }  
}

Map 的切片

还是比较重要的,但是确实很繁琐,最重要的是理解分配内存和 append 追加的思想

比如我们现在 make 一个长度为 2 的 Map,那么如果它满了之后,我们还需要追加数据,应该怎么操作呢?

答案是用 append() 函数,具体实现可以看代码,很好理解

package main  
  
import "fmt"  
  
// Map 切片  
/*  
   要求:使用一个 Map 来记录 monster 的信息 name 和 age   也就是说一个 monster 对应一个 map   并且妖怪的个数可以动态增加 => Map 切片  
*/  
  
func main() {  
   // 1、声明一个 map 切片  
   monsters := make([]map[string]string, 2)  
   // 2、增加一个妖怪的信息  
   monsters[0] = make(map[string]string)  
   monsters[0]["name"] = "牛魔王"  
   monsters[0]["age"] = "500"  
  
   monsters[1] = make(map[string]string)  
   monsters[1]["name"] = "玉兔精"  
   monsters[1]["age"] = "400"  
   fmt.Println(monsters)  
  
   newMonsters := map[string]string{  
      "name": "append 追加的妖怪",  
      "age":  "200",  
   }  
   monsters = append(monsters, newMonsters)  
   fmt.Println(monsters)  
}

Map 排序

新版本会自动根据 key 排序

Map 使用小结

1、使用前需要 make
2、map 是引用类型,在一个函数接收 map 后,如果修改,则会修改源数据中的 map
3、map 的 value 一般是 struct,更适合管理复杂的数据

第三点可能比较难理解,不过简单想一想就明白了

 评论