Go 数组 切片 map

系列文章目录

上一篇:Go 控制结构

下一篇:Go 函数



前言

本节,我们介绍 Go 中几种数据结构


数组

数组 是一种相同类型的 容器。Go 数组与 C 数组存在了巨大的不同

  • Go 数组是值类型,使用一个数组类型的变量给另一个数组类型的变量赋值时,会完整的拷贝数组。C 数组类型的变量是一个常量,代表了指向第一个数组元素的指针
  • Go 数组的长度也是其类型的一部分,只有存储相同类型元素和相同长度的数组才是相同的类型。例如,类型 [10]int[20]int 是不同的

类型 [n]T 是包含 nT 类型元素的数组。声明数组也是使用 var 关键和 :=

使用 var 关键字进行数组声明

var array_name [length]datatype            // 给定固定的长度
var array_name = [length]datatype{values}  // 声明并给定数组的初始值

var array_name = [...]datatype{values}    // 长度通过初始值的个数确定

使用 := 方式声明数组

array_name := [length]datatype{values} // 给定固定的长度

array_name := [...]datatype{values} // 长度通过初始值的个数确定

比如:var a [5]int数组的长度必须是常量,并且长度 是数组类型的一部分。一旦定义,长度不能变。 [5]int[10]int 是不同的类型

var a [3]int
var b [4]int
a = b        // a 和 b 的类型是不同的,因此无法使用 b 对 a 进行赋值

[!NOTE] 注意
长度指定数组中要存储的元素个数。在 Go 中,数组的长度是固定的。数组的长度要么由一个数字定义,要么是推断出来的(即编译器根据值的数量决定数组的长度)。

Go 数组也是支持 下标访问,即索引。数组第一个元素的索引是 0 0 0 ,最后一个元素的索引是 l e n − 1 len-1 len1。Go 对数组进行了严格的索引越界检查,如果索引越界会 panic

a[2] = 10    // 下标访问
b[2] = a[2]

如果在声明时给定了初始值,还可以 为初始值指定下标类似于 C99 中指示器 [[[指针#03f418|指示器]]]。但是,语法略有不同

例如,如下声明语句

a := [5]int{1: 1, 3: 5} // [0 1 0 5 0]

将索引为 1 的位置初始化为 1,索引为 3 的位置初始化为 5,其余位置初始化为 0

如果混用两种初始化方法

a := [5]int{1, 2 : 4, 3} // [1 0 4 3 0]

没有指定位置的按顺序初始化。也就是说,索引为 0 的位置初始化为 1,索引为 2 的位置初始化为 4,索引为 3 的位置初始化为 3

[!WARNING]

  • 数组支持 ==!= 操作符,因为内存总是被初始化过的
  • [n]*T表示指针数组,*[n]T表示数组指针

访问数组的元素

可以通过引用 索引号 来访问特定的数组元素。在Go中,数组 索引从 0 开始。这意味着 [0] 是第一个元素,[1] 是第二个元素,以此类推。

package main

import (
	"fmt"
)

func main() {
	prices := [3]int{10, 20, 30}

	fmt.Println(prices[0]) // 访问第一个元素
	fmt.Println(prices[2]) // 访问第三个元素
}

更改数组的元素

还可以通过引用索引号来更改特定数组元素的值。

package main  
  
import (  
    "fmt"  
)  
  
func main() {  
    prices := [3]int{10, 20, 30}  
  
    prices[2] = 50       // 修改索引 2 处的元素
    fmt.Println(prices) // [10 20 50]  
}

获取数组的长度

len() 函数 用于查找数组的长度

package main

import (
	"fmt"
)

func main() {
	arr1 := [4]string{"Volvo", "BMW", "Ford", "Mazda"}
	arr2 := [...]int{1, 2, 3, 4, 5, 6}

	fmt.Println(len(arr1)) // 4
	fmt.Println(len(arr2)) // 6
}

切片

数组具有固定大小且无法调整其大小,带来了很大的局限性。Go 提供了 切片 类型,它对数组进行了一层封装,提供了动态的 扩容机制,解决数组固定大小的问题

切片的底层实现原理

切片是一个 引用类型,它的内部结构包含 地址长度容量 的一种资源句柄。 切片一般用于快速地操作一块数据集合

  • 地址 是指向切片第一个元素的指针
  • 长度 是切片管理的元素的个数
  • 容量 是底层数组的长度减去切片管理的第一个元素在底层数组中的位置

![[Pasted image 20240410143142.png]]
![[Pasted image 20240410143148.png]]

在Go中,有几种创建切片的方法

  • 使用类型 []datatype{} 创建切片
  • 从数组创建切片,即 切片表达式
  • 使用 make() 函数

声明切片

切片的类型为 []T,为了声明一个切片,其语法如下

var slice []int // 仅仅只是声明的一个切片,它还没有管理底层数组

[!ERROR] 注意
仅仅只是声明的一个切片,它还没有底层数组被该切片管理。因此,对该切片进行访问时错误的

package main  
  
import "fmt"  
  
/* 创建切片的方法 */  
func main() {  
    var slice []int       // 仅仅只是声明的一个切片,它还没有管理任何底层数组  
    fmt.Println(slice[0]) // panic: runtime error: index out of range [0] with length 0  
    fmt.Println(slice == nil)  // true
}

切片是引用类型,默认值为 nil。**因此,声明切片就必须初始化之后才能被使用

创建切片

[]dataType{values} 给定初始值

该方式会根据 values 的个数为切片分配底层数组。例如,下列代码,创建长度为 3 3 3,容量为 3 3 3 的切片

var slice []int = []int{1,2,3}

下代码声明了一个长度为 0 0 0、容量为 0 0 0 的空切片

var slice = []int{}

在 Go 中,有两个函数可用于返回切片的 长度容量

  • len():返回切片的长度(切片中的元素数)
  • cap(): 返回切片的容量(切片可以增长或收缩到的元素数)
package main

import (
	"fmt"
)

func main() {
	myslice1 := []int{}        // 长度为 0 容量为 0 的空切片
	fmt.Println(len(myslice1)) // 0
	fmt.Println(cap(myslice1)) // 0
	fmt.Println(myslice1)      // []

	myslice2 := []string{"Go", "Slices", "Are", "Powerful"} // 长度为 4 容量为 4 的字符串切片
	fmt.Println(len(myslice2))                              // 4
	fmt.Println(cap(myslice2))                              // 4
	fmt.Println(myslice2)                                   // [Go Slices Are Powerful]
}

[!WARNING] 比较 var slice []intvar slice = []int{}

  • var slice []int 仅仅只是声明一个切片,该切片还未关联任何底层数组,因此它也是一个长度为 0 0 0 容量为 0 0 0空切片,此时它的值为 nil
  • var slice = []int{} 关联了一个长度为 0 0 0 容量为 0 0 0空切片,但是,此时,它的值不再是 nil

切片表达式

从数组创建切片

切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。 切片表达式中的 lowhigh 表示一个索引范围(左包含,右不包含),也就是下面代码中从数组 a 中选出 1 <= index <4 的元素组成切片 s,得到的切片 长度=high-low,容量等于得到的切片的底层数组的容量。

a := [5]int{1,2,3,4,5}
s := a[1: 3]  // s := a[low, hight]
  • len(s) 的值为 2 2 2
  • cap(s) 的值为 4 4 4

为了方便起见,可以省略切片表达式中的任何索引

  • 省略了 low 则默认为 0 0 0
  • 省略了 high 则默认为切片操作数的长度
a[2:]  // 等价于 a[2:len(a)]
a[:3] // 等价于 a[0:3]
a[:] // 等价于 a[0:len(a)]
从切片创建切片

对切片再执行切片表达式时(切片再切片),high 的上限边界是切片的容量 cap(a),而不是长度。常量索引必须是非负的 ,并且可以用 int 类型的值表示。常量索引也必须在有效范围内。如果 lowhigh 两个指标都是常数,它们必须满足 low <= high。如果索引在运行时超出范围,就会发生运行时 panic

package main

import "fmt"

func main() {
	array := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	
	slice := array[2:5]
	fmt.Println(slice) // [3 4 5]

	slice2 := slice[2:7] // 从切片中创建切片。height 的上限使其 cap
	fmt.Println(slice2)  // [5 6 7 8 9]
}
完整的切片表达式

对于数组指向数组的指针,或 切片a (注意不能是字符串)支持完整切片表达式:

a[low, high, max]
  • 构建与 a[low: high]相同类型、相同长度和元素的切片
  • 结果切片的 容量设置为 max-low

完整切片表达式需要满足的条件是 0 <= low <= high <= max <= cap(a),其他条件和简单切片表达式相同

package main

import "fmt"

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	t := a[1:3:5]                                                  // a[low:high:max]
	fmt.Printf("t: %v len(t): %v cap(t): %v\n", t, len(t), cap(t)) // t:[2 3] len(t):2 cap(t):4
}

make() 函数创建切片

上述两种创建切片的方法都存在局限性

  • []dayaType}{values} 创建切片需要知道初始值的个数
  • 切片表达式。需要一个底层数组
    为了能够创建任意长度和容量的切片,Go 提供了 make() 函数。用于创建切片时,需要提供三个参数
make([]T, size, cap)
  • []T:切片的类型
  • size:切片中元素的数量
  • cap :切片的容量
package main

import "fmt"

func main() {
	a := make([]int, 2, 10) // 长度为 2 容量为 10
	fmt.Println(a)      //[0 0]
	fmt.Println(len(a)) //2
	fmt.Println(cap(a)) //10

	a = make([]int, 10) // 长度和容量均为 10
	fmt.Println(a)
	fmt.Println(len(a))
	fmt.Println(cap(a))
}

切片的比较

由于切片是一种引用类型,切片之间是不能进行比较的。唯一至此的比较就是使用 ==!=nil 进行比较。这个比较并没有什么用。只能用于判断切片是否关联了底层数组

  • 一个 值为 nil 的切片并没有底层数组,一个 nil 值的切片的长度和容量都是 0 0 0
  • 然而,一个长度和容量都是 0 0 0 的切片不一定是 nil
var s1 []int         // len(s1)=0; cap(s1)=0; s1==nil  没有关联底层数组
s2 := []int{}        // len(s2)=0; cap(s2)=0; s2!=nil
s3 := make([]int, 0) // len(s3)=0; cap(s3)=0; s3!=nil

所以要判断切片是否为空,必须使用 len(slice) == 0 进行比较,不能使用 s == nil 来判断

切片的复制

切片不是值类型,使用一个切片给另一个切片进行赋值操作时,只是让两个切片共用同一个底层数组。相当于 浅复制

package main

import "fmt"

func main() {
	s1 := make([]int, 3) //[0 0 0]
	s2 := s1             //将 s1 直接赋值给 s2,s1 和 s2 共用一个底层数组
	s2[0] = 100
	fmt.Println(s1) //[100 0 0]
	fmt.Println(s2) //[100 0 0]
}

为了将一个切片拷贝到另一个切片,Go 提供了函数 copy() 完成 深复制 操作

copy(destSlice, srcSlice []T)
  • srcSlice: 数据来源切片
  • destSlice: 目标切片
package main  
  
import "fmt"  
  
func main() {    
    // copy()复制切片  
    a := []int{1, 2, 3, 4, 5}  
    c := make([]int, 5, 5)  
    copy(c, a)     //使用copy()函数将切片a中的元素复制到切片c  
    fmt.Println(a) //[1 2 3 4 5]  
    fmt.Println(c) //[1 2 3 4 5]  
    c[0] = 1000  
    fmt.Println(a) //[1 2 3 4 5]  
    fmt.Println(c) //[1000 2 3 4 5]  
}

[!NOTE] 注意
在复制之前,需要为目标切片准备好底层数组

访问和修改切片元素

和数组一样,我们可以通过 索引号 来访问特定的切片元素。在Go中,切片的索引从 0 0 0 开始。这意味着 [0] 是第一个元素,[1] 是第二个元素,以此类推, [len(s)-1] 是最后一个索引

package main

import (
	"fmt"
)

func main() {
	prices := []int{10, 20, 30}

	fmt.Println(prices[0]) // 10
	fmt.Println(prices[2]) // 30
}

还可以通过索引号来更改特定的切片元素。与数组一样

package main  
  
import (  
    "fmt"  
)  
  
func main() {  
    prices := []int{10, 20, 30}  
    fmt.Println(prices) // [10 20 30]  
    prices[2] = 50  
    fmt.Println(prices) // [10 20 50]  
}

向切片中添加元素

你可以使用 append() 函数将元素附加到切片的末尾。可以 一次添加一个元素,可以 添加多个元素,也可以 添加另一个切片中的元素

slice_name = append(slice_name, element1, element2, ...)

下面使用使用 append() 函数的一个例子

package main

import "fmt"

func main() {
	s := make([]int, 0, 10)
	s = append(s, 1)
	fmt.Println(s) // [1]
	s = append(s, 2, 3, 4)
	fmt.Println(s) // [1 2 3 4]

	temp := []int{5, 6, 7, 8, 9}
	s = append(s, temp...) //添加一个切片的中的所有元素
	fmt.Println(s)         // [1 2 3 4 5 6 7 8 9]
}

[!NOTE] 注意

每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素

当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略 进行“扩容”,此时该 切片指向的底层数组就会更换

“扩容”操作往往发生在append()函数调用时,所以我们 通常都需要用原变量接收 append 函数的返回值

由于切片的扩容策略,通过 var 声明的零值切片可以在 append() 函数直接使用,无需初始化

var s []int
s = append(s, 1, 2, 3)  // 无需对 s 进行初始化,append 函数会自动为其关联底层数组

切片扩容策略

下面的代码用于测试切片的扩容策略

package main

import "fmt"
func main() {
	//append()添加元素和切片扩容
	var numSlice []int
	for i := 0; i < 10; i++ {
		numSlice = append(numSlice, i)
		fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
	}
}

运行结果输出

[0]  len:1  cap:1  ptr:0xc00000a0b8
[0 1]  len:2  cap:2  ptr:0xc00000a100
[0 1 2]  len:3  cap:4  ptr:0xc000010200
[0 1 2 3]  len:4  cap:4  ptr:0xc000010200
[0 1 2 3 4]  len:5  cap:8  ptr:0xc00000e280
[0 1 2 3 4 5]  len:6  cap:8  ptr:0xc00000e280
[0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc00000e280
[0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc00000e280
[0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc00007c080
[0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc00007c080

每发生一次扩容,切片的地址都在发生变化。基本上切片 numSlice 的容量按照 1,2,4,8,16 这样的规则自动进行扩容,每次扩容后都是扩容前的 2 倍。

可以通过查看 $GOROOT/src/runtime/slice.go 源码,其中扩容相关代码如下:

// nextslicecap computes the next appropriate slice length.
func nextslicecap(newLen, oldCap int) int {
	newcap := oldCap
	doublecap := newcap + newcap  // 首先将容量翻倍
	// 如果申请的空间大于原来空间的两倍,则直接返回
	if newLen > doublecap {
		return newLen
	}
	
	// 设定一个阈值
	const threshold = 256
	// 原始容量小于阈值的将容量翻倍
	if oldCap < threshold {
		return doublecap
	}
	for {
		// 从小切片增长 2 倍过渡到大切片增长 1.25 倍。这个公式可以在两者之间实现平滑过渡
		newcap += (newcap + 3*threshold) >> 2

		// 我们需要检查 `newcap >= newLen` 和 `newcap` 是否溢出。
		// newLen 保证大于零,因此当 newcap 溢出时,`uint(newcap) > uint(newLen)`。 
		// 这样就可以通过相同的比较对两者进行检查。
		if uint(newcap) >= uint(newLen) {
			break
		}
	}

	// 当 newcap 计算溢出时,将 newcap 设置为请求的上限。
	if newcap <= 0 {
		return newLen
	}
	return newcap
}

需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如 intstring 类型的处理方式就不一样

向切片中添加另一个切片

要将一个切片的所有元素追加到另一个切片,请使用 append() 函数:

slice1 = append(slice1, slice2...)

[!WARNING] 注意
在将一个切片的所有元素追加到另一个切片中时,slice2 后面的...是必要的

package main

import "fmt"

func main() {
	myslice1 := []int{1, 2, 3}
	myslice2 := []int{4, 5, 6}
	myslice3 := append(myslice1, myslice2...)

	fmt.Printf("myslice3=%v\n", myslice3)  // myslice3=[1 2 3 4 5 6]
	fmt.Printf("length=%d\n", len(myslice3)) // length=6
	fmt.Printf("capacity=%d\n", cap(myslice3)) // capacity=6
}

从切片中删除元素

Go 语言中并 没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素

package main

import "fmt"

func main() {
	// 从切片中删除元素
	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
	// 要删除索引为2的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a) //[30 31 33 34 35 36 37]
}

再谈切片的本质

切片的本质就是对数组的一层封装删除切片中的元素,最终是在移动底层数组的元素

package main

import "fmt"

func main() {
	a := [...]int{30, 31, 32, 33, 34, 35, 36, 37}
	fmt.Printf("array ptr: %p\n", &a) // 0xc00000e240

	s := a[:]
	fmt.Printf("slice ptr: %p\n", s)                             // 0xc0000c4040
	fmt.Printf("slice: %v len: %v cap: %v\n", s, len(s), cap(s)) //  [30 31 32 33 34 35 36 37] 8 8

	s = append(s[:2], s[3:]...)                                  // 删除 切片的第2个元素 32
	fmt.Printf("slice ptr: %p\n", s)                             // 0xc0000c4040
	fmt.Printf("slice: %v len: %v cap: %v\n", s, len(s), cap(s)) // [30 31 33 34 35 36 37] 7 8
	fmt.Printf("array: %v\n", a)                                 // [30 31 33 34 35 36 37 37]
}

这段代码的输出结果为

array ptr: 0xc00000e240
slice ptr: 0xc00000e240
slice: [30 31 32 33 34 35 36 37] len: 8 cap: 8
slice ptr: 0xc00000e240
slice: [30 31 33 34 35 36 37] len: 7 cap: 8
array: [30 31 33 34 35 36 37 37]

可以通过查看 $GOROOT/src/runtime/slice.go 中,对 slice 的定义如下

type slice struct {  
    array unsafe.Pointer  // 指向底层数组的指针
    len   int  // 长度
    cap   int  // 容量
}

Map

map 是 Go 中一种方便且功能强大的内置数据结构,它 将一种类型的值(键)与另一种类型的值(元素或值)相关联

  • 可以是 定义相等运算符的任何类型,例如 整数浮点数复数字符串指针接口(只要动态类型支持相等)、结构数组
    • 切片不能用作映射键,因为未在切片上定义相等性

与切片一样,map 是引用类型,默认值为 nilmap 类型的变量,引用了内存中的一个 哈希表。如果将 map 类型的变量传递给更改 map 内容的函数,则更改将在调用方中可见。

它们 可以灵活地删除或添加元素。映射 不允许重复条目,同时 数据保持无序

创建空 map

声明 map 类型的语法如下:

var variableName map[KeyType]ValueType
  • KeyType 键类型:只能是 定义了相等性运算(==)的类型。例如
    • 各种数值类型: int, uint 将其固定长度的整数类型;float32float64complex64complex128
    • 字符串类型:string
    • 数组
    • 接口:interence,只要动态类型支持相等
    • 结构体:struct
  • ValueType 值类型:可以是任意类型

map 类型的变量默认初始值为nil,一个值为 nilmap 没有对应的内存空间,无法存储值。因此需要 使用 make() 函数来为 map 类型的变量分配内存

variableName = make(map[KeyType]ValueType, [cap])
  • map[KeyType]ValueType 表示映射的类型
  • cap 表示 map 的容量,该参数虽然不是必须的,但是我们应该在初始化 map 的时候就为其指定一个合适的容量, 以防程序运行时动态扩容降低程序效率
package main  
  
import "fmt"  
  
func main() {  
    var m map[string]int  // 声明一个 map 类型的变量  
    fmt.Println(m)        // map[]  
    fmt.Println(m == nil) // true  
    // m["a"] = 1            // panic: assignment to entry in nil map  
    m = make(map[string]int, 10) // 为 m 分配存储空间  
    fmt.Println(m == nil)        // false  
    m["a"] = 1  
    m["b"] = 2  
    m["c"] = 3  
    fmt.Println(m) // map[a:1 b:2 c:3]  
}

提供默认值创建 map

使用 var:= 在声明 map 类型的变量时,可以给定初始值

var a = map[KeyType]ValueType {
	key1: value1, 
	key2: value2,
	....,         // 结束行这个逗号不能缺少
}
b := map[KeyType]ValueType {
	key1: value1, 
	key2: value2,
	....,        // 结束行这个逗号不能缺少。除非将 } 放在此行
}
package main

import "fmt"

func main() {
	var a = map[string]string{
		"brand": "Ford",
		"model": "Mustang",
		"year":  "1964",
	}
	b := map[string]int{
		"Oslo":      1,
		"Bergen":    2,
		"Trondheim": 3,
		"Stavanger": 4,
	}

	fmt.Printf("a\t%v\n", a)
	fmt.Printf("b\t%v\n", b)
}

使用 make() 函数创建 map

为了能够方便的创建 map 类型的变量,Go 提供了 make() 函数用于为 map 分配内存空间

var a = make(map[KeyType]ValueType, [cap])
b := make(map[KeyType]ValueType, [cap])

[!NOTE] 注意
虽然 cap 不是必须的参数,但是在创建 map 时提供一个容量能有效避免程序在运行过程中分配内存导致的低效率问题

package main

import "fmt"

func main() {
	/* 使用 make() 函数创建 map */
	var a = make(map[string]string) // The map is empty now
	a["brand"] = "Ford"
	a["model"] = "Mustang"
	a["year"] = "1964"
	// a is no longer empty
	b := make(map[string]int)
	b["Oslo"] = 1
	b["Bergen"] = 2
	b["Trondheim"] = 3
	b["Stavanger"] = 4

	fmt.Printf("a\t%v\n", a)
	fmt.Printf("b\t%v\n", b)
}

更新和添加 map 的元素

上述内容,我们已经接触过如何添加 map 元素。更新或添加元素是通过以下方式完成的

mapName[key] = value
  • 如果 keymapName 中存在,则使用 value 替代原来的值
  • 如果 keymapName 中不存在,则创建新的 key 并将其映射为 value
package main

import "fmt"

func main() {
	/* 添加或更新 map 元素 */
	var a = make(map[string]string)
	a["brand"] = "Ford"
	a["model"] = "Mustang"
	a["year"] = "1964"

	fmt.Println(a) //map[brand:Ford model:Mustang year:1964]

	a["year"] = "1970" // Updating an element
	a["color"] = "red" // Adding an element

	fmt.Println(a) // map[brand:Ford color:red model:Mustang year:1970]
}

map 中删除元素

删除 map 中的元素需要使用函数 delete() 完成

delete(mapName, key)
  • mapName 中键为 key 的元素删除
package main

import "fmt"

func main() {
	/* 删除 map 元素 */
	var a = make(map[string]string)
	a["brand"] = "Ford"
	a["model"] = "Mustang"
	a["year"] = "1964"

	fmt.Println(a) // map[brand:Ford model:Mustang year:1964]

	delete(a, "year") // 删除 key = "year" 的元素

	fmt.Println(a) // map[brand:Ford model:Mustang]

}

获取 map 中的 key 对应的 value

通过 key 获取 value 的语法如下:可以通过 key 直接获取到 map 中保存的数据

value, ok := mapName[key]
  • value:就是 key 对应的数据
    • 如果 key 不存在,则返回 value 类型对应的默认值
  • ok:用于检测 key 的状态
    • 如果 key 存在,则 ok 的值为 true
    • 如果 key 不存在,则 ok 的值为 false

如果只想检查某个键是否存在,可以使用 匿名变量( _ ) 代替 value

package main

import "fmt"

func main() {
	var a = map[string]string{"brand": "Ford", "model": "Mustang", "year": "1964", "day": ""}

	val1, ok1 := a["brand"] // Checking for existing key and its value
	val2, ok2 := a["color"] // Checking for non-existing key and its value
	val3, ok3 := a["day"]   // Checking for existing key and its value
	_, ok4 := a["model"]    // Only checking for existing key and not its value

	fmt.Println(val1, ok1) //Ford true
	fmt.Println(val2, ok2) //  false
	fmt.Println(val3, ok3) //  true
	fmt.Println(ok4)       // true
}
  • keycolor 的值不存在,则返回 string 类型的默认值 ""

遍历 map

遍历 map 需要使用 range 关键字,它以 key, value 的形式返回 map 中的元素

package main

import "fmt"

func main() {
	a := map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}

	for k, v := range a {
		fmt.Printf("%v : %v, ", k, v)
	}
	// one : 1, two : 2, three : 3, four : 4,
}

key 顺序遍历 map

由于 map 中的元素是无须的,如果要按照 key 的某种顺序遍历,则需要先获取 map 中的所有 key 并将其存放在一个切片中,最后对切片进行排序。依次取出排序后的 keymap 中获取值

元素为切片的 map

map 中的一个 key 需要保存多个值时,需要将 key 对应的 value 做成一个切片。声明方式如下

var sliceMap1 map[keyType][]Type
package main

import "fmt"

func main() {
	var sliceMap1 map[string][]int
	sliceMap1 = map[string][]int{"北京": {1, 2, 3}, "上海": {4, 5, 6}}  // 初始化列表初始化
	fmt.Println(sliceMap1)
	
	var sliceMap2 map[string][]int
	sliceMap2 = make(map[string][]int, 3) // 先初始化map
	sliceMap2["北京"] = make([]int, 0, 3)  // 在初始化切片
	sliceMap2["北京"] = append(sliceMap2["北京"], 1, 2, 3)  // 向切片添加值
	fmt.Println(sliceMap2)
}

元素为 map 类型的切片

切片中的元素为 map 类型,定义格式

var slice []map[keyType]valueType
package main

import "fmt"

func main() {
	var slice1 []map[string]int
	slice1 = []map[string]int{{"dyp": 19}, {"dyy": 12}}  // 初始化列表初始化
	fmt.Println(slice1)
	var slice2 []map[string]int
	slice2 = make([]map[string]int, 3)  // 初始化切片
	slice2[0] = make(map[string]int, 3)  // 初始化map
	slice2[0]["dyp"] = 19
	slice2[0]["dyy"] = 12
	fmt.Println(slice2)
}

练习

声明一个包含5个整数的数组,并将其初始化为[1, 2, 3, 4, 5]

package main

import "fmt"

func main() {
	var arr = [...]int{1, 2, 3, 4, 5}
	fmt.Println(arr)
}

使用索引访问数组中的元素,并将第3个元素的值打印出来

package main

import "fmt"

func main() {
	var arr = [...]int{1, 2, 3, 4, 5}
	fmt.Println(arr[2])
}

Golang 中的切片是什么

切片是对数组的一层封装,属于引用类型。一个切片包含了一个指针,指向底层数组中的一个元素。同时,包含了切片当前管理元素的个数,称为长度;以及底层数组可以存放元素的数量,称为容量

如何创建一个包含元素 "apple""banana""orange" 的切片,并打印其中的元素。

package main

import "fmt"

func main() {
	var slice = []string{"apple", "banana", "orange"}
	for _, v := range slice {
		fmt.Println(v)
	}
}

如何通过切片创建一个新的切片,其中包含原始切片的前 3 3 3 个元素?请给出示例代码

package main

import "fmt"

func main() {
	var slice2 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	slice3 := slice2[:3]
	fmt.Println(slice3) // [1 2 3]
}

如何使用循环遍历一个切片,并打印出每个元素的值和索引?

package main

import "fmt"

func main() {
	var slice2 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	for index, value := range slice2 {
		fmt.Printf("index: %d value:%d\n", index, value)
	}
}

如何向切片中添加一个新的元素"grape",并删除切片中的第2个元素?请给出示例代码

package main

import "fmt"

func main() {
	var slice = []string{"apple", "banana", "orange"}
	slice = append(slice, "grape")
	slice = append(slice[:1], slice[2:]...)
	fmt.Println(slice)
}

Golang 中的 Map 是什么?

Map 是 Go 提供一种引用数据类型,其底层引用了一个 哈希表,作为一种 key: value 的映射。当出现哈希冲突的时候,Go 采用拉链法解决哈希冲突。

key 只能是定义了 相等性运算 的数据类型,例如 string int float32 float64 array interface

value 可以是任意数据类型

请使用代码示例说明如何创建一个 Map,并向其中添加键值对 "apple"->1"banana"->2"orange"->3

package main

import "fmt"

func main() {
	/* 方式 1 */
	var fruit map[string]int
	fruit = make(map[string]int)
	fruit["apple"] = 1
	fruit["banana"] = 2
	fruit["oranage"] = 3
	fmt.Println(fruit)
	
	/* 方式 2 */
	fruit := map[string]int{
		"apple":  1,
		"banana": 2,
		"orange": 3,
	}
	fmt.Println(fruit)
}

如何遍历一个Map,并打印出每个键值对的键和值?

package main

import "fmt"

func main() {
	fruit := map[string]int{
		"apple":  1,
		"banana": 2,
		"orange": 3,
	}
	fmt.Println(fruit)

	for key, value := range fruit {
		fmt.Printf("%s : %d\n", key, value)
	}
}

如何按照键的升序对 Map 进行排序,并打印排序后的键值对?

package main

import (
	"fmt"
	"sort"
)

func main() {


	fruit := map[string]int{
		"orange": 3,
		"apple":  1,
		"banana": 2,
	}
	
	var keys []string = []string{}
	// 获取 key
	for k := range fruit {
		keys = append(keys, k)
	}
	// 对 key 进行排序
	sort.Strings(keys)
	// 使用排序好的 key 从 map 中取值
	for _, key := range keys {
		fmt.Printf("Key: %s, Value: %d\n", key, fruit[key])
	}
}

如何检查切片中是否包含特定的元素"apple",并返回布尔值表示是否存在?请给出示例代码。

package main

import "fmt"

func main() {
	var slice = []string{"apple", "banana", "orange"}
	isContainApple := false
	for _, v := slice {
		if v == "apple" {
			isContainApple = true
		}
	}
	if isContainApple {
		fmt.Println("包含 apple")
	} else {
		fmt.Println("不包含 apple")
	}
}

总结

我们介绍了 Go 中 线性表(数组和切片) 和 哈希表(Map)

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程进阶之路

感谢打赏, 谢谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值