系列文章目录
上一篇:Go 控制结构
下一篇:Go 函数
前言
本节,我们介绍 Go 中几种数据结构
数组
数组 是一种相同类型的 容器。Go 数组与 C 数组存在了巨大的不同
- Go 数组是值类型,使用一个数组类型的变量给另一个数组类型的变量赋值时,会完整的拷贝数组。C 数组类型的变量是一个常量,代表了指向第一个数组元素的指针
- Go 数组的长度也是其类型的一部分,只有存储相同类型元素和相同长度的数组才是相同的类型。例如,类型
[10]int
和[20]int
是不同的
类型 [n]T
是包含 n
个 T
类型元素的数组。声明数组也是使用 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
len−1。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 []int
和var slice = []int{}
var slice []int
仅仅只是声明一个切片,该切片还未关联任何底层数组,因此它也是一个长度为 0 0 0 容量为 0 0 0 的 空切片,此时它的值为nil
var slice = []int{}
关联了一个长度为 0 0 0 容量为 0 0 0 的 空切片,但是,此时,它的值不再是nil
了
切片表达式
从数组创建切片
切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。 切片表达式中的 low
和 high
表示一个索引范围(左包含,右不包含),也就是下面代码中从数组 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 2cap(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
类型的值表示。常量索引也必须在有效范围内。如果 low
和 high
两个指标都是常数,它们必须满足 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
}
需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如 int
和 string
类型的处理方式就不一样
向切片中添加另一个切片
要将一个切片的所有元素追加到另一个切片,请使用 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
是引用类型,默认值为 nil
。map
类型的变量,引用了内存中的一个 哈希表。如果将 map
类型的变量传递给更改 map
内容的函数,则更改将在调用方中可见。
它们 可以灵活地删除或添加元素。映射 不允许重复条目,同时 数据保持无序
创建空 map
声明 map
类型的语法如下:
var variableName map[KeyType]ValueType
KeyType
键类型:只能是 定义了相等性运算(==
)的类型。例如- 各种数值类型:
int, uint
将其固定长度的整数类型;float32
和float64
;complex64
和complex128
- 字符串类型:
string
- 数组
- 接口:
interence
,只要动态类型支持相等 - 结构体:
struct
- 各种数值类型:
ValueType
值类型:可以是任意类型
map
类型的变量默认初始值为nil
,一个值为 nil
的 map
没有对应的内存空间,无法存储值。因此需要 使用 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
- 如果
key
在mapName
中存在,则使用value
替代原来的值 - 如果
key
在mapName
中不存在,则创建新的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
}
key
为color
的值不存在,则返回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
并将其存放在一个切片中,最后对切片进行排序。依次取出排序后的 key
去 map
中获取值
元素为切片的 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)