Golang 派生数据类型

一、数组类型

1. 数组简介和定义

数组是同一种数据类型元素的集合。 在 Go 语言中,数组从声明时长度就已确定且长度不能修改。声明一个数组的基本语法如下:

var 数组变量名 [元素数量]元素数据类型

需要注意的是长度是数组类型的一部分,因此即便两个数组中存储的元素类型是一样的,但是两个数组的长度不一样,那么这两个数组是两种类型的数据。

package main

import "fmt"

func main() {
	var a [3]int
	var b [4] int

	fmt.Printf("a的数据类型:%T\n", a) // a的数据类型:[3]int
	fmt.Printf("b的数据类型:%T\n", b) // b的数据类型:[4]int
}

2. 数组的初始化

数组类型是值类型,定义时就会被自动分配内存空间(即有长度),且对于每一个元素,如果没有进行初始化,默认使用元素数据类型的0值作为默认值。

package main

import "fmt"

func main() {
	var a [3]int
	var s [3]bool

	fmt.Printf("a的类型:%T,a的值:%v\n", a, a)  // a的类型:[3]int,a的值:[0 0 0]
	fmt.Printf("s的类型:%T,s的值:%v\n", s, s)  // s的类型:[3]bool,s的值:[false false false]
}

可以通过以下方式来实现数组的初始化:

2.1 初始化列表

使用初始化列表设置数组元素的值时,指定的值的个数不能大于数组的长度。如果指定的值的个数小于数组的长度,默认用元素数据类型对应的0值进行填充。

package main

import "fmt"

func main() {
	var numArray1 = [3]int{1, 2, 3} // 使用指定的初始值完成初始化
	var numArray2 = [3]int{1, 2}    // 指定的值的个数与长度不匹配时,默认用该类型的0值填充
	//var numArray3 = [3]int{1, 2, 3, 4} // 指定的值的个数不能大于数组的长度

	var stringArray = [3]string{"cdc", "tt", "tr"}

	fmt.Printf("numArray1:%v\n", numArray1)  // numArray1:[1 2 3]
	fmt.Printf("numArray2:%v\n", numArray2)  // numArray2:[1 2 0]
	fmt.Printf("stringArray:%v\n", stringArray)  // stringArray:[cdc tt tr]

}
2.2 自行推断长度

使用初始化列表设置数组元素的值时,每次都要确保提供的初始值和数组长度一致,一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度。

package main

import "fmt"

func main() {
	var numArray = [...]int{1, 2}
	var cityArray = [...]string{"北京", "上海", "深圳"}

	fmt.Printf("numArray的类型:%T  numArray的值:%v\n", numArray, numArray)  // numArray的类型:[2]int  numArray的值:[1 2]
	fmt.Printf("cityArray的类型:%T  cityArray的值:%v\n", cityArray, cityArray)  // cityArray的类型:[3]string  cityArray的值:[北京 上海 深圳]
}
2.3 使用索引

我们还可以使用指定索引值的方式来初始化数组。数组的长度为指定的最大的索引值+1,对于未指定值的索引,默认使用元素数据类型对应的0值填充。

package main

import "fmt"

func main() {
	data := [...]bool{0: true, 5: true}

	fmt.Printf("data的类型:%T  data的值:%v\n", data, data) // data的类型:[6]bool  data的值:[true false false false false true]
}

3. 访问数组

3.1 访问某个元素

可以通过下标(索引)获取数组的某个值或者修改某个值。

package main

import "fmt"

func main() {
	num := [4]int{1, 2, 3, 4}

	// 通过索引取值
	fmt.Println(num[2])  // 3

	// 通过索引修改值
	num[0] = 100
	fmt.Println(num)  // [100 2 3 4]
}
3.2 数组切片

数组支持通过切片操作访问部分元素。切片操作需要传入两个索引值,一个是开始切片的位置,一个是停止切片的位置,开始位置索引要小于结束位置。需要注意的是,使用切片操作后,得到的不再是数组类型数据,而是一个切片类型的数据。

package main

import "fmt"

func main() {
	var numArray = [10]int{1, 2, 3, 4, 5, 100, 200, 300, 400, 500}

	ret := numArray[2:5]
	fmt.Println(ret)
	fmt.Printf("ret的类型:%T\n", ret)  // ret的类型:[]int

	//ret = numArray[5:3]
	//fmt.Println(numArray)  // .\main.go:156:16: invalid slice index: 5 > 3
}
3.3 遍历数组
  • 通过长度遍历
package main

import "fmt"

func main() {
	num := [4]int{1, 2, 3, 4}

	for i := 0; i < len(num); i++ {
		fmt.Printf("索引值:%v,对应的元素值:%v\n", i, num[i])
	}
}
  • 使用 for + range 遍历
package main

import "fmt"

func main() {
	num := [4]int{100, 200, 300, 400}

	// 使用两个变量接收时,即获取索引,又获取值
	for index, value := range num {
		fmt.Printf("索引值:%v,对应的元素值:%v\n", index, value)
	}

	// 只用一个变量接收时,只能获取到索引值
	for index := range num {
		fmt.Printf("索引值:%v\n", index)
	}

	// 只获取值
	for _, value := range num {
		fmt.Printf("元素值:%v\n", value)
	}
}

4. 多维数组

数组中的元素也是数组,这种数组的嵌套类型称为多维数组。以二维数组为例:

package main

import "fmt"

func main() {
	cityArray := [2][3]string{
		{"南京", "上海", "北京"},
		{"深圳", "长沙", "重庆"}, // 多维数组使用列表初始化时,最后一行也必须加上逗号
	}

	// cityArray的类型:[2][3]string  cityArray的值:[[南京 上海 北京] [深圳 长沙 重庆]]
	fmt.Printf("cityArray的类型:%T  cityArray的值:%v\n", cityArray, cityArray)

	// 通过索引取值
	fmt.Println(cityArray[0][0])  // 南京
	fmt.Println(cityArray[0][1])  // 上海
	fmt.Println(cityArray[0][2])  // 北京
}

初始化多维数组时,也支持自动推断长度,但是只有第一层数组可以使用该方式:

package main

import "fmt"

func main() {
	// 错误示例  编译报错:use of [...] array outside of array literal
	//cityArray := [...][...]string{
	//	{"南京", "上海", "北京"},
	//	{"深圳", "长沙", "重庆"}, // 多维数组使用列表初始化时,最后一行也必须加上逗号
	//}
	//
	//fmt.Println(cityArray)


	// 正确示例
	cityArray := [...][3]string{
		{"南京", "上海", "北京"},
		{"深圳", "长沙", "重庆"}, // 多维数组使用列表初始化时,最后一行也必须加上逗号
	}

	fmt.Println(cityArray)  // [[南京 上海 北京] [深圳 长沙 重庆]]
}

遍历多维数组时,也要使用多层for循环嵌套遍历:

package main

import "fmt"

func main() {
	cityArray := [...][3]string{
		{"南京", "上海", "北京"},
		{"深圳", "长沙", "重庆"}, // 多维数组使用列表初始化时,最后一行也必须加上逗号
	}

	// 根据长度进行遍历
	for i := 0; i < len(cityArray); i++ {
		inner := cityArray[i] // 针对最外层的数组进行遍历,得到的第二层的每一个数组
		fmt.Printf("内层数组:%v\n", inner)

		// 针对第二层的每个数组进行遍历,得到的是每个元素的值
		for j := 0; j < len(inner); j++ {
			fmt.Printf("城市信息:%v\n", inner[j])
		}
	}

	// 使用 for+range 进行遍历
	for _, inner := range cityArray {
		fmt.Printf("内层数组:%v\n", inner)
		for _, city := range inner {
			fmt.Printf("城市信息:%v\n", city)
		}
	}
}

二、切片类型

1. 切片类型的引入

由于数组的长度是固定的,因此在使用数组时会带来一些限制。如果我们申请的长度太大会浪费内存,申请的太小又不够用,且数组无法动态的改变长度:

package main

import "fmt"

func main() {
	// 数组最多能存储3个元素,但是循环填充了5个值,会报错
	numArray := [3]int{}

	for i := 0; i <= 5; i++ {
		numArray[i] = i
	}

	// panic: runtime error: index out of range [3] with length 3
}

因此,Go 语言中会使用切片类型来解决上述问题。

2. 切片类型介绍

2.1 切片类型声明

切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。切片是一个引用类型,它的内部结构包含地址长度容量,可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。切片一般用于快速地操作一块数据集合。

切片类型的基本声明语法如下:

package main

import "fmt"

func main() {
	var slice1 []int
	fmt.Printf("slice1的类型:%T  长度:%v  容量:%v\n", slice1, len(slice1), cap(slice1)) // slice1的类型:[]int  长度:0  容量:0
}

切片是引用类型,即声明过的切片如果不进行初始化,是不会被分配内存的:

package main

import "fmt"

func main() {
	var slice1 []int

    if slice1 == nil {
        fmt.Println("切片未分配内存,是空的") // 会走到这个判断
    }else {
        fmt.Println("切片已分配内存")
    }
}

所以要看一个切片是否有值,不能通过切片是否为空来判断,应该判断切片的长度是否为0

3. 切片的初始化

3.1 直接初始化
package main

import "fmt"

func main() {
	var numSlice1 = []int{1, 2, 3, 4}
	fmt.Printf("numSlice1的类型:%T  长度:%v  容量:%v  值:%v\n", numSlice1, len(numSlice1), cap(numSlice1), numSlice1) // numSlice1的类型:[]int  长度:4  容量:4  值:[1 2 3 4]
}
3.2 通过数组切片初始化
package main

import "fmt"

func main() {
	var numArray = [10]int{1, 2, 3, 4, 5, 100, 200, 300, 400, 500}
	var numSlice2 = numArray[:]   // 获取全部
	var numSlice3 = numArray[3:5] // 获取部分

	fmt.Printf("numSlice2的类型:%T  长度:%v  容量:%v  值:%v\n", numSlice2, len(numSlice2), cap(numSlice2), numSlice2)  // numSlice2的类型:[]int  长度:10  容量:10  值:[1 2 3 4 5 100 200 300 400 500]
	fmt.Printf("numSlice3的类型:%T  长度:%v  容量:%v  值:%v\n", numSlice3, len(numSlice3), cap(numSlice3), numSlice3)  // numSlice3的类型:[]int  长度:2  容量:7  值:[4 5]
}
3.3 通过 make 函数初始化
package main

import "fmt"

func main() {
	numSlice4 := make([]int, 5, 10)
	fmt.Printf("numSlice4的类型:%T  长度:%v  容量:%v  值:%v\n", numSlice4, len(numSlice4), cap(numSlice4), numSlice4) // numSlice4的类型:[]string  长度:5  容量:10  值:[0 0 0 0 0]
}

4. 切片的原理

4.1 切片本质

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。可以通过一个例子来探究一下切片操作的底层实现原理。

示例1,有一个长度为10,元素类型为整型的数组 a := [int]{1,2,3,4,5,6,7,8,9,10},现在对其进行切片操作得到一个切片类型 b := a[2:6] ,其底层其实是先创建一个切片 b,切片的起始位置指针指向了数组 a 的索引为 2 的元素,切片的结束位置指针指向了数组 a 的索引为 5 的元素,整个切片的长度为 结束位置索引 - 开始位置索引,即长度为4;切片的容量为 底层数组长度 - 切片开始位置索引,即容量为8。

我们通过代码来验证一下:

package main

import "fmt"

func main() {

	array1 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	slice1 := array1[2:6]

	fmt.Printf("slice1的类型:%T\n", slice1)      // slice1的类型:[]int
    fmt.Printf("slice1的值:%v\n", slice1)      // slice1的值:[3 4 5 6]
	fmt.Printf("slice1的长度:%d\n", len(slice1)) // slice1的长度:4
	fmt.Printf("slice1的容量:%d\n", cap(slice1)) // slice1的容量:8
}

底层原理的图示如下:

在这里插入图片描述

4.2 切片表达式

切片表达式从字符串、数组、指向数组或切片的指针构造子字符串或切片。它有以下两种变体:

4.2.1 简单切片表达式

简单切片表达式指定了 low 和 high 两个索引界限值(左包含,右不包含),得到的切片长度为 high - low,容量为 底层数组的长度 - low

package main

import "fmt"

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

}

为了方便起见,可以省略切片表达式中的任何索引。省略了 low 则默认为0,省略了 high 则默认为切片操作数的长度:

a[2:]  // 等同于 a[2:len(a)]
a[:3]  // 等同于 a[0:3]
a[:]   // 等同于 a[0:len(a)]

对于数组或字符串,使用简单切片表达式时,取值必须满足:0 <= low <= high <= len(a);对于切片而言,对切片再次进行切片时,索引上限并不是原切片的长度,而是原切片的容量。举个例子:

package main

import "fmt"

func main() {
	array1 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	slice1 := array1[2:6]
	fmt.Printf("slice1的值:%v  长度:%v  容量:%v\n", slice1, len(slice1), cap(slice1))  // slice1的值:[3 4 5 6]  长度:4  容量:8

	slice2 := slice1[2:6]
	fmt.Printf("slice2的值:%v  长度:%v  容量:%v\n", slice2, len(slice2), cap(slice2))  // slice2的值:[5 6 7 8]  长度:4  容量:6
}

针对于切片 slice1 ,值为 [3 4 5 6],再次对其切片操作取 [2:6] 时,按理已经超过 slice1 自身的长度,应该报越界错误,但是实际上 slice2 取值时并不是根据 slice1 的长度对应的底层数组元素([3 4 5 6])取值,而是根据 slice1 的容量对应的底层数组元素([3 4 5 6 7 8 9 10])取值。

4.2.2 完整切片表达式

相比简单切片表达式,完整切片表达式多了一个参数 max,用于规定切片操作得到的切片的容量。对于数组,指向数组的指针或切片支持完整切片表达式,字符串类型不支持。设置了 max 以后,切片的容量为 max-low

package main

import "fmt"

func main() {
	array1 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	slice1 := array1[2:6:8]
	fmt.Printf("slice1的值:%v  长度:%v  容量:%v\n", slice1, len(slice1), cap(slice1)) // slice1的值:[3 4 5 6]  长度:4  容量:6

	slice2 := array1[2:6:10]
	fmt.Printf("slice2的值:%v  长度:%v  容量:%v\n", slice2, len(slice2), cap(slice2)) // slice2的值:[3 4 5 6]  长度:4  容量:8
}

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

5. 切片常用方法

5.1 获取切片元素
  • 通过索引获取单个元素
package main

import "fmt"

func main() {
	s1 := []string{"AAA", "BBB", "CCC", "DDD", "EEE", "FFF"}
	fmt.Println(s1[0]) // AAA
	fmt.Println(s1[2]) // CCC
}
  • 通过切片操作获取部分元素
package main

import "fmt"

func main() {
	s1 := []string{"AAA", "BBB", "CCC", "DDD", "EEE", "FFF"}
	fmt.Println(s1[2:5]) // [CCC DDD EEE]
}
  • 遍历切片
package main

import "fmt"

func main() {
	s1 := []string{"AAA", "BBB", "CCC", "DDD", "EEE", "FFF"}
	
    // 根据切片长度遍历
	for i := 0; i < len(s1); i++ {
		fmt.Println(s1[i])
	}
	
    // for + range 遍历
    // 使用一个变量接收时,只获取索引值
	for index := range s1 {
		fmt.Printf("索引值:%v\n", index)
	}
	
    // 使用两个变量接收时,获取索引值和元素值
	for index, value := range s1 {
		fmt.Printf("索引值:%v  元素值:%v\n", index, value)
	}
	
    // 使用匿名遍历实现只获取元素值
	for _, value := range s1 {
		fmt.Printf("元素值:%v\n", value)
	}

}
5.2 切片新增元素
5.2.1 append 方法的使用

append 方法用于给切片的末尾添加新的元素,并返回一个新的切片。append 支持以下几种操作:

  • 新增单个元素
package main

import "fmt"

func main() {
	// 声明并初始化一个切片
	s1 := []string{"AAA", "BBB"}
	fmt.Printf("s1的地址:%p  s1的值:%v\n", s1, s1)  // s1的地址:0xc0000443c0  s1的值:[AAA BBB]

	s2 := append(s1, "CCC")
	fmt.Printf("s2的地址:%p  s2的值:%v\n", s2, s2)  // s2的地址:0xc000020080  s2的值:[AAA BBB CCC]
}
  • 新增多个元素
package main

import "fmt"

func main() {
	// 声明并初始化一个切片
	s1 := []string{"AAA", "BBB"}
	fmt.Printf("s1的地址:%p  s1的值:%v\n", s1, s1)  // s1的地址:0xc0000443c0  s1的值:[AAA BBB]

	s3 := append(s1, "CCC", "DDD", "EEE")
	fmt.Printf("s3的地址:%p  s3的值:%v\n", s3, s3)  // s3的地址:0xc000050050  s3的值:[AAA BBB CCC DDD EEE]
}
  • 新增另一个切片,使用 ... 将切片元素打散
package main

import "fmt"

func main() {
	// 声明并初始化一个切片
	s1 := []string{"AAA", "BBB"}
	fmt.Printf("s1的地址:%p  s1的值:%v\n", s1, s1)  // s1的地址:0xc0000443c0  s1的值:[AAA BBB]

	strSlice := []string{"CCC", "DDD", "EEE"}
	s4 := append(s1, strSlice...)
	fmt.Printf("s4的地址:%p  s4的值:%v\n", s4, s4)  // s4的地址:0xc0000500a0  s4的值:[AAA BBB CCC DDD EEE]
}
5.2.2 使用 append 省略切片初始化

由于切片是引用类型,只声明不进行初始化不会被分配内存,因此无法直接赋值:

var slice1 []int
slice1[0] = 100  // panic: runtime error: index out of range [0] with length 0

由于 append 操作会返回一个新的初始化后的切片,因此通过var声明的零值切片可以在 append() 函数直接使用,无需初始化。

package main

import "fmt"

func main() {
	var slice1 []int
	slice1 := append(slice1, 1, 2, 3, 4, 5)
	fmt.Println(slice1)  // [1 2 3 4 5]
}
5.2.3 切片扩容策略

每个切片会指向一个底层数组,这个数组的长度够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行扩容,此时该切片指向的底层数组就会更换。扩容操作往往发生在 append() 函数调用,所以我们通常都需要用原变量接收 append 函数的返回值。

package main

import "fmt"

func main() {
	var s1 []int

	for i := 1; i <= 10; i++ {
		s1 = append(s1, i)
		fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", s1, len(s1), cap(s1), s1)
	}
}

运行结果:

[1]  len:1  cap:1  ptr:0xc00000c0c8
[1 2]  len:2  cap:2  ptr:0xc00000c120
[1 2 3]  len:3  cap:4  ptr:0xc0000101a0
[1 2 3 4]  len:4  cap:4  ptr:0xc0000101a0
[1 2 3 4 5]  len:5  cap:8  ptr:0xc000016200
[1 2 3 4 5 6]  len:6  cap:8  ptr:0xc000016200
[1 2 3 4 5 6 7]  len:7  cap:8  ptr:0xc000016200
[1 2 3 4 5 6 7 8]  len:8  cap:8  ptr:0xc000016200
[1 2 3 4 5 6 7 8 9]  len:9  cap:16  ptr:0xc00007e000
[1 2 3 4 5 6 7 8 9 10]  len:10  cap:16  ptr:0xc00007e000

切片的扩容策略如下:

  • 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
  • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
  • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
  • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

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

5.3 切片拷贝

由于切片是引用类型,所以通过赋值操作得到的新切片实际上和老切片指向的是同一块内存地址,对新切片中的元素进行修改也会影响老切片:

package main

import "fmt"

func main() {
	s1 := []int{1, 2, 3, 4, 5}
	fmt.Printf("s1的值:%v\n", s1)  // s1的值:[1 2 3 4 5]

	s2 := s1
	s2[0] = 100
	fmt.Printf("s2的值:%v\n", s2)  // s2的值:[100 2 3 4 5]
	fmt.Printf("s1的值:%v\n", s1)  // s1的值:[100 2 3 4 5]
}

如果想修改新切片但是不影响原切片,可以使用 copy 方法拷贝得到新切片而不是使用赋值方式:

package main

import "fmt"

func sliceHandle4() {
	s1 := []int{1, 2, 3, 4, 5}
	fmt.Printf("s1的值:%v\n", s1)  // s1的值:[1 2 3 4 5]

	s3 := make([]int, 5, 5)
	copy(s3, s1)
	s3[0] = 100
	fmt.Printf("s3的值:%v\n", s3)  // s3的值:[100 2 3 4 5]
	fmt.Printf("s1的值:%v\n", s1)  // s1的值:[1 2 3 4 5]
}
5.4 切片删除元素

切片操作中没有现成的删除方法,可以通过 append + 切片 操作来实现:

package main

import "fmt"

func main(){
	s1 := []int{1, 2, 3, 4, 5}
    
	// 要删除索引为2的元素
	s1 = append(s1[:2], s1[3:]...)
	fmt.Println(s1)  // [1 2 4 5]
}

三、map类型

1. map类型简介和定义

Go 语言中提供的映射关系容器为 map,其内部使用散列表(hash)实现。map 是一种无序的基于 key-value 的数据结构,Go 语言中的 map 是引用类型,必须初始化才能使用。

声明一个 map 类型变量的基本语法如下:

// var 变量名 map[key的类型]value的类型
	
var m map[string]string

2. map类型初始化

  • 通过值填充进行初始化,使用该方式时注意每个键值对后面都要加逗号
package main

import "fmt"

func main() {
	m1 := map[string]string{
		"name":    "cdc",
		"city":    "南京",
		"address": "江宁区",
	}

	fmt.Printf("%#v\n", m1)  // map[string]string{"address":"江宁区", "city":"南京", "name":"cdc"}
}
  • 通过 make 方法初始化,使用该方式初始化时可以指定 map 的长度(长度非必填)
package main

import "fmt"

func main() {
	m2 := make(map[string]int, 8)
	m2["cdc"] = 100
	m2["ctt"] = 100
	m2["cee"] = 100
	m2["ccc"] = 100
	fmt.Printf("%#v\n", m2) // map[string]int{"ccc":100, "cdc":100, "cee":100, "ctt":100}
}

3. map类型常用方法

3.1 通过键名操作值

可以通过键名获取或者修改对应的值

package main

import "fmt"

func main(){
	userInfo := map[string]string{
		"name":    "cdc",
		"city":    "南京",
		"address": "江宁区",
	}


	// 通过键名获取值
	city := userInfo["city"]
	fmt.Println(city)  // 南京

	// 通过键名修改值
	userInfo["address"] = "雨花台"
	fmt.Println(userInfo)  // map[address:雨花台 city:南京 name:cdc]
}
3.2 判断键是否存在
package main

import "fmt"

func main() {
	userInfo := map[string]string{
		"name":    "cdc",
		"city":    "南京",
		"address": "江宁区",
	}

	// 如果键存在,ok为true,v为对应的值;不存在,ok为false,v为值类型的零值

	v, ok := userInfo["name"]
	fmt.Printf("v的值:%v  ok的值:%v\n", v, ok)  // v的值:cdc  ok的值:true

	v1, ok1 := userInfo["age"]
	fmt.Printf("v1的值:%v  ok1的值:%v\n", v1, ok1)  // v1的值:  ok1的值:false
}
3.3 遍历map类型

Go 语言中可以使用 for + range 对 map 进行遍历,主要分为以下几种情况:

  • 只用一个变量接收时,只获取键名
package main

import "fmt"

func main() {
	userInfo := map[string]string{
		"name":    "cdc",
		"city":    "南京",
		"address": "江宁区",
	}

	for key := range userInfo {
		fmt.Println(key)
	}
}

/*
执行结果:
name
city
address
*/
  • 使用两个变量接收时,能获取到键名和对应的值
package main

import "fmt"

func main() {
	userInfo := map[string]string{
		"name":    "cdc",
		"city":    "南京",
		"address": "江宁区",
	}

	for key, value := range userInfo {
		fmt.Printf("key: %v   value: %v\n", key, value)
	}
}

/*
执行结果:
key: name   value: cdc
key: city   value: 南京
key: address   value: 江宁区
*/
  • 只想遍历值时,可以使用匿名遍历接收键
package main

import "fmt"

func main() {
	userInfo := map[string]string{
		"name":    "cdc",
		"city":    "南京",
		"address": "江宁区",
	}

	for _, value := range userInfo {
		fmt.Println(value)
	}
}

/*
执行结果:
cdc
南京
江宁区
*/
3.4 删除键值对

可以使用 delete() 内置函数从 map 中删除一组键值对

package main

import "fmt"

func main() {
	userInfo := map[string]string{
		"name":    "cdc",
		"city":    "南京",
		"address": "江宁区",
	}

	delete(userInfo, "name")
	delete(userInfo, "age")  // 如果删除的键不存在也不会引发错误

	fmt.Printf("%#v\n", userInfo)  // map[string]string{"address":"江宁区", "city":"南京"}
}
3.5 和其他数据类型组合使用
  • 元素为map的切片
package main

import "fmt"

func main() {
	// 这里只是对外层的切片进行了初始化
	var mapSlice = make([]map[string]string, 3)
	for index, value := range mapSlice {
		fmt.Printf("index:%d value:%v\n", index, value)
	}

	// 需要对切片中的map元素进行初始化
	mapSlice[0] = make(map[string]string, 10)
	mapSlice[0]["name"] = "cdc"
	mapSlice[0]["city"] = "南京"
	mapSlice[0]["address"] = "江宁区"
	mapSlice[1] = make(map[string]string, 10)
	mapSlice[1]["name"] = "tr"
	mapSlice[1]["city"] = "南京"
	mapSlice[1]["address"] = "雨花台"
	for index, value := range mapSlice {
		fmt.Printf("index:%d value:%v\n", index, value)
	}
}
  • 值为切片类型的map
package main

import "fmt"

func main() {

	// 这里只对外层的map进行了初始化
	var sliceMap = make(map[string][]string, 3)
	fmt.Println(sliceMap)  // map[]

	key := "name"
	value, ok := sliceMap[key]
	if !ok {
		// 对键对应的值类型进行初始化,即对切片进行初始化
		value = make([]string, 0, 2)
	}
	value = append(value, "cdc", "tr")
	sliceMap[key] = value
	fmt.Printf("%#v\n", sliceMap)  // map[string][]string{"name":[]string{"cdc", "tr"}}
}

4. 使用示例

  • 示例1:按学生序号输出其对应的分数
package main

import (
	"fmt"
	"math/rand"
	"time"
	"sort"
)

func main() {

	rand.Seed(time.Now().UnixNano())

	scoreMap := make(map[string]int, 100)
	for i := 1; i < 100; i++ {
		key := fmt.Sprintf("stu%03d", i) //生成stu开头的字符串
		value := rand.Intn(100)          // //生成0~99的随机整数
		scoreMap[key] = value
	}

	fmt.Println(scoreMap)

	// 将学号取出并存放到切片中
	stuSlice := make([]string, 0, 100)
	for key := range scoreMap {
		stuSlice = append(stuSlice, key)
	}

	fmt.Println(stuSlice)

	// 对切片进行排序
	sort.Strings(stuSlice)
	fmt.Println(stuSlice)

	// 按照排序后的顺序进行输出
	for _, stu := range stuSlice {
		fmt.Printf("学号:%v   分数:%v\n", stu, scoreMap[stu])
	}
}
  • 示例2:统计一个字符串中每个字母出现的次数(除空格)
package main

import "fmt"

func main() {
	str := "how do you do"

	strMap := make(map[string]int)
	for _, v := range str {
		key := string(v)

		if key != " " {
			_, ok := strMap[key]
			if !ok {
				strMap[key] = 0
			}
			strMap[key] += 1
		}
	}

	fmt.Printf("%#v\n", strMap)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]提供了关于Golang的四大数据类型及基本类型的介绍。其中,基本类型包括数字类型、布尔型和字符串类型。数字类型包括整型和浮点型,还有其他数字类型。布尔型的值只能是常量true或false。字符串类型分为解释字符串和非解释字符串。引用\[2\]进一步解释了Golang的基本类型、复合类型、引用类型和接口类型。基本类型包括数字、字符串和布尔型。复合类型包括数组和结构体,用于表示更复杂的数据结构。引用类型包括指针、切片、映射、通道、接口和函数类型。引用类型的变量被称为标头值。字符串也是一种引用类型。引用\[3\]提到了Golang常量的特点。虽然常量可以有确定的基本类型,但许多常量没有明确的基本类型。编译器为这些常量提供了比基本类型更高精度的算术运算。无类型的常量包括无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数和无类型的字符串。无类型的常量不仅提供更高的运算精度,还可以直接用于更多的表达式而不需要显式的类型转换。 所以,Golang的基本数据类型包括数字类型、布尔型和字符串类型。同时,Golang还有复合类型、引用类型和接口类型。 #### 引用[.reference_title] - *1* *2* [Golang数据类型](https://blog.csdn.net/MatChen/article/details/114639964)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Golang(六)[基础数据类型]](https://blog.csdn.net/weixin_42366378/article/details/105003056)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值