Go 语言入门指南 | 豆包MarsCode AI刷题

120 阅读21分钟

Go 语言入门指南 | 豆包MarsCode AI刷题

一、Go 语言特色

  • 简洁、快速、安全
  • 并行、有趣、开源
  • 内存管理、数组安全、编译迅速

二、Go 语言结构

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释

首先,我们通过一个简单的程序来入门

package main
​
import "fmt"func main() {
    //输出"Hello, go world"
    fmt.Println("Hello, go world")
}
  1. 第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
  2. 下一行 import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
  3. 下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
  4. 下一行 // 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
  5. 下一行 fmt.Println(...) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 使用 fmt.Print("hello, world\n") 可以得到相同的结果。 Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。

ps:Go 语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响 Go 代码的正确解析

2.1. 命名

Go语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。大写字母和小写字母是不同的:heapSort和Heapsort是两个不同的名字。

Go语言中类似if和switch的关键字有25个;关键字不能用于自定义名字,只能在特定语法结构中使用。

break      default       func     interface   select
case       defer         go       map         struct
chan       else          goto     package     switch
const      fallthrough   if       range       type
continue   for           import   return      var

此外,还有大约30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。

内建常量: true false iota nil
​
内建类型: int int8 int16 int32 int64
          uint uint8 uint16 uint32 uint64 uintptr
          float32 float64 complex128 complex64
          bool byte rune string error
​
内建函数: make len cap new append copy close delete
          complex real imag
          panic recover

这些内部预先定义的名字并不是关键字,你可以在定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱。

如果一个名字是在函数内部定义,那么它就只在函数内部有效。如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性。

在习惯上,Go语言程序员推荐使用 驼峰式 命名,当名字由几个单词组成时优先使用大小写分隔,而不是优先用下划线分隔。

2.2变量

2.2.1 var声明变量

var声明语句可以创建一个特定类型的变量,然后给变量附加一个名字,并且设置变量的初始值。变量声明的一般语法如下:

var 变量名字 类型 = 表达式

其中“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。

可以在一个声明语句中同时声明一组变量,或用一组初始化表达式声明并初始化一组变量。如果省略每个变量的类型,将可以声明多个类型不同的变量(类型由初始化表达式推导):

var i, j, k int                 // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string
2.2.2简短形式声明变量

以“名字 := 表达式”形式声明变量,变量的类型根据表达式来自动推导。

anim := gif.GIF{LoopCount: nframes}
freq := rand.Float64() * 3.0
t := 0.0

同样的简短形式声明也可以同时声明多个变量

a, b, c := 5, 7, "abc"

ps:如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误。

var intVal int 
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明

2.3常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

const identifier [type] = value

你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

多个相同类型的声明可以简写为:

const c_name1, c_name2 = value1, value2

常量还可以用作枚举:

const (
    Unknown = 0
    Female = 1
    Male = 2
)
2.3.1iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

下面是来自time包的例子,它首先定义了一个Weekday命名类型,然后为一周的每天定义了一个常量,从周日0开始。在其它编程语言中,这种类型一般被称为枚举类型。

type Weekday int

const (
	Sunday Weekday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
)

周日将对应0,周一为1,如此等等。

2.4运算符

运算符用于在程序运行时执行数学或逻辑运算。

Go 语言内置的运算符有:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其他运算符

(上述运算,与c语言类似,这里不过多赘述,可以自己找找文档试一下)

2.5条件语句

Go 语言提供了以下几种条件判断语句:

2.5.1 if

go语言中的if 不需要加括号

if a > 1 {
   /* 在布尔表达式为 true 时执行 */
}

If 在布尔表达式为 true 时,其后紧跟的语句块执行,如果为 false 则不执行。

2.5.2 if...else
if a < 20 {
       /* 如果条件为 true 则执行以下语句 */
       fmt.Printf("a 小于 20\n" );
   } else {
       /* 如果条件为 false 则执行以下语句 */
       fmt.Printf("a 不小于 20\n" );
   }

你可以在 ifelse if 语句中嵌入一个或多个 ifelse if 语句。

2.5.3 switch

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。

switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。

switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var grade string = "B"
   var marks int = 90

   switch marks {
      case 90: grade = "A"
      case 80: grade = "B"
      case 50,60,70 : grade = "C"
      default: grade = "D"  
   }

   switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )     
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
   fmt.Printf("你的等级是 %s\n", grade );      
}

输出结果:

优秀!
你的等级是 A

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

Type Switch 语法格式如下:

switch i := x.(type) {
      case nil:  
         fmt.Printf(" x 的类型 :%T",i)                
      case int:  
         fmt.Printf("x 是 int 型")                      
      case float64:
         fmt.Printf("x 是 float64 型")          
      case func(int) float64:
         fmt.Printf("x 是 func(int) 型")                      
      case bool, string:
         fmt.Printf("x 是 bool 或 string 型" )      
      default:
         fmt.Printf("未知型")    
   }  

使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。

  switch {
    case false:
            fmt.Println("1、case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("2、case 条件语句为 true")
            fallthrough
    case false:
            fmt.Println("3、case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("4、case 条件语句为 true")
    case false:
            fmt.Println("5、case 条件语句为 false")
            fallthrough
    default:
            fmt.Println("6、默认 case")
    }

输出结果是 2、case 条件语句为 true 3、case 条件语句为 false 4、case 条件语句为 true

2.5.4 select

select 是 Go 中的一个控制结构,类似于 switch 语句。

select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。

select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。

如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。

Go 编程语言中 select 语句的语法如下:

select {
  case <- channel1:
    // 执行的代码
  case value := <- channel2:
    // 执行的代码
  case channel3 <- value:
    // 执行的代码

    // 你可以定义任意数量的 case

  default:
    // 所有通道都没有准备好,执行的代码
}

以下描述了 select 语句的语法:

  • 每个 case 都必须是一个通道

  • 所有 channel 表达式都会被求值

  • 所有被发送的表达式都会被求值

  • 如果任意某个通道可以进行,它就执行,其他被忽略。

  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。

    否则:

    1. 如果有 default 子句,则执行该语句。
    2. 如果没有 default 子句,select 将阻塞,直到某个通道可以运行;Go 不会重新对 channel 或值进行求值。

select 语句等待两个通道的数据。如果接收到 c1 的数据,就会打印 "received one";如果接收到 c2 的数据,就会打印 "received two"。

以下实例中,我们定义了两个通道,并启动了两个协程(Goroutine)从这两个通道中获取数据。在 main 函数中,我们使用 select 语句在这两个通道中进行非阻塞的选择,如果两个通道都没有可用的数据,就执行 default 子句中的语句。

以下实例执行后会不断地从两个通道中获取到的数据,当两个通道都没有可用的数据时,会输出 "no message received"

package main

import "fmt"

func main() {
  // 定义两个通道
  ch1 := make(chan string)
  ch2 := make(chan string)

  // 启动两个 goroutine,分别从两个通道中获取数据
  go func() {
    for {
      ch1 <- "from 1"
    }
  }()
  go func() {
    for {
      ch2 <- "from 2"
    }
  }()

  // 使用 select 语句非阻塞地从两个通道中获取数据
  for {
    select {
    case msg1 := <-ch1:
      fmt.Println(msg1)
    case msg2 := <-ch2:
      fmt.Println(msg2)
    default:
      // 如果两个通道都没有可用的数据,则执行这里的语句
      fmt.Println("no message received")
    }
  }
}

2.6循环语句

go语言中只有for这一种循环

Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。

和 C 语言的 for 一样:

for init; condition; post { }

和 C 的 while 一样:

for condition { }

和 C 的 for(;;) 一样:

for { }

2.7函数

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   函数体
}
  • func:函数由 func 开始声明
  • function_name:函数名称,参数列表和返回值类型构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

以下实例为 max() 函数的代码,该函数传入两个整型参数 num1 和 num2,并返回这两个参数的最大值:

/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 声明局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}

Go 函数可以返回多个值,例如:

func swap(x, y string) (string, string) {
   return y, x
}

2.8数组

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。

相对于去声明 number0, number1, ..., number99 的变量,使用数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。

数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。

Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:

//以下定义了数组 balance 长度为 10 类型为 float32:
var balance [10]float32

在 Go 语言中,数组的大小是类型的一部分,因此不同大小的数组是不兼容的,也就是说 [5]int[10]int 是不同的类型。

以下定义了数组 balance 长度为 5 类型为 float32,并初始化数组的元素:

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
或
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:

var salary float32 = balance[4]

以上实例读取了数组 balance 第 5 个元素的值。

2.9指针

类似于c,但是比c简单。

一个指针变量指向了一个值的内存地址。

类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:

var var_name *var-type

var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:

var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮点型 */

下面是一个实例:

package main

import "fmt"

func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &a  /* 指针变量的存储地址 */

   fmt.Printf("a 变量的地址是: %x\n", &a  )

   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}

输出结果为:

a 变量的地址是: 20818a220 ip 变量储存的指针地址: 20818a220 *ip 变量的值: 20

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

2.10结构体

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

如果要访问结构体成员,需要使用点号 . 操作符,格式为:

结构体.成员名

2.11切片(Slice)

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

你可以声明一个未指定大小的数组来定义切片:

var identifier []type

切片不需要说明长度。

或使用 make() 函数来创建切片:

var slice1 []type = make([]type, len)

slice1 := make([]type, len)

也可以指定容量,其中 capacity 为可选参数。

make([]T, length, capacity)

len() 和 cap() 函数 切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

切片截取 可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]

append() 和 copy() 函数 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

在追加时,会先判断容量够不够,如果容量足够则容量不变;如果超出容量那么判断长度是否小于 1024 ,小于则容量 * 2,大于则容量 * 1.25

以下是一个测试例子

package main

import "fmt"

func main() {
    //数组是固定长度与容量,并且具有相同类型的一组值
    //此定义的数组长度为 5 ,那么容量也会固定为 5
    arr := [5]int{0, 1, 2, 3, 4}
    // 数组 p = 0xc00001c0f0,arr = [0 1 2 3 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))
    // 数组的索引都是从 0  开始的
    // 0 1
    fmt.Println(arr[0], arr[1])

    // 切片可以看做是一个可变长的数组
    // 切片可以看做是对数组的一个片段的引用
    // [1:3] 表示范围从数组的索引 1 至 数组的索引 3,它是不包括 3 的,可以简单理解为  index == 1 && index < 3
    // 当切片生成后,索引默认也是从 0 开始, 切片索引是 [0,1] 对应着数组的索引是 [1,2]
    arrSlice := arr[1:3]
    // 切片 p = 0xc00000c060,arr = [1 2],len = 2,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

    // 由于切片是对底层数据的一个引用,所以修改底层数组会更改切片的值
    arr[1] = 10
    // 切片 p = 0xc00000c060,arr = [10 2],len = 2,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

    // 同样的道理,修改切片的值也会影响到底层数组
    arrSlice[0] = 8
    // 切片 p = 0xc00000c060,arr = [8 2],len = 2,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 数组 p = 0xc00001c0f0,arr = [0 8 2 3 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

    // 对切片追加
    // 追加前的切片索引是 [0,1] 对应着 数组的 [1,2]
    // 在这里追加了一位,那么切片的索引是 [0,1,2] 数组对应的索引是 [1,2,3]
    // 所以追加的值,也会修改数组的值
    arrSlice = append(arrSlice, 11)
    // 切片 p = 0xc00008a020,arr = [8 2 11],len = 3,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 数组 p = 0xc000090060,arr = [0 8 2 11 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

    // 切片的容量为什么是 4
    // 在创建切片时,我们指定了对数组的 [1,3] 进行切片,这里切出来的长度为 2
    // 切片在创建时,如果不指定容量,那么容量会自动去计算
    // 创建时计算容量的公式为:创建时的长度 * 2
    // 追加时,会先判断长度够不够,如果够则容量不变,如果不够那么判断长度是否小于 1024 ,小于则容量 * 2,大于则 容量 * 1.25

    // 当我们切片的长度超过了原数组的长度
    // 我们可以看到,数组的值并没有被修改,按照我们上一步讲的来说,应该数组的最后一位会变为 12,但是并没有
    // 这是一个很重要的概念,当我们的切片容量大于底层数组容量时,会自动创建一个新的底层数组
    arrSlice = append(arrSlice, 12, 13, 14)
    // 切片 p = 0xc00000c060,arr = [8 2 11 12 13 14],len = 6,cap = 8
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 数组 p = 0xc00001c0f0,arr = [0 8 2 11 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

    //当我再去修改切片的值时,并不会去操作我们上面定义的数组了
    arrSlice[0] = 18
    // 切片 p = 0xc00008c020,arr = [18 2 11 12 13 14],len = 6,cap = 8
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 数组 p = 0xc000092060,arr = [0 8 2 11 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

}

2.12范围(Range)

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

for key, value := range oldMap {
    newMap[key] = value
}

以上代码中的 key 和 value 是可以省略。

如果只想读取 key,格式如下:

for key := range oldMap

或者这样:

for key, _ := range oldMap

如果只想读取 value,格式如下:

for _, value := range oldMap

数组和切片

遍历简单的切片,2%d** 的结果为 2 对应的次方数:

package main

import "fmt"

// 声明一个包含 2 的幂次方的切片
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
   // 遍历 pow 切片,i 是索引,v 是值
   for i, v := range pow {
      // 打印 2 的 i 次方等于 v
      fmt.Printf("2**%d = %d\n", i, v)
   }
}

range 迭代字符串时,返回每个字符的索引和 Unicode 代码点(rune)。

package main

import "fmt"

func main() {
    for i, c := range "hello" {
        fmt.Printf("index: %d, char: %c\n", i, c)
    }
}

2.13 集合(Map)

Map 是一种无序的键值对的集合。

Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。

在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 ""。

Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。

定义 Map:

可以使用内建函数 make 或使用 map 关键字来定义 Map:

/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)

其中 KeyType 是键的类型,ValueType 是值的类型,initialCapacity 是可选的参数,用于指定 Map 的初始容量。Map 的容量是指 Map 中可以保存的键值对的数量,当 Map 中的键值对数量达到容量时,Map 会自动扩容。如果不指定 initialCapacity,Go 语言会根据实际情况选择一个合适的值。

也可以使用字面量创建 Map:

// 使用字面量创建 Map
m := map[string]int{
    "apple": 1,
    "banana": 2,
    "orange": 3,
}

获取元素:

// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"]  // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值

修改元素:

// 修改键值对
m["apple"] = 5

获取 Map 的长度:

// 获取 Map 的长度
len := len(m)

遍历 Map:

// 遍历 Map
for k, v := range m {
    fmt.Printf("key=%s, value=%d\n", k, v)
}

删除元素:

// 删除键值对
delete(m, "banana")