Go学习第七章——数组arr,切片slice和映射map

news2024/11/17 17:24:57

Go数组arr,切片slice和映射map

      • 1 数组
        • 1.1 快速入门
        • 1.2 数组的内存布局
        • 1.3 四种初始化数组的方式
        • 1.4 数组的遍历
        • 1.5 注意事项以及分析
        • 1.6 数组反转
        • 1.7 二维数组
      • 2 切片
        • 2.1 快速入门
        • 2.2 内存解析
        • 2.3 切片的使用和遍历
        • 2.4 注意事项和细节说明
        • 2.5 string和slice关系
        • 2.6 切片练习题
      • 3 map映射
        • 3.1 快速入门
        • 3.2 map的crud操作
        • 3.3 map的遍历
        • 3.4 map切片
        • 3.5 map排序
        • 3.6 使用细节
      • 4 综合题目(难度较大,可略~)

1 数组

数组可以存放多个同一类型数据。数组也是一种数据类型,在Go中,数组是值类型。

1.1 快速入门

数组定义格式:
var 数组名 [数组大小]数据类型

步骤:

  1. 定义数组
  2. 将数据存入数组
  3. 根据所需获取数据进行计算

案例讲解:

func main() {
    //实现的功能:给出五个学生的成绩,求出成绩的总和,平均数:
    //给出五个学生的成绩:--->数组存储:
    //1.定义一个数组:
    var scores [5]int
    //20 将成绩存入数组:
    scores[0] = 95
    scores[1] = 91
    scores[2] = 39
    scores[3] = 60
    scores[4] = 21
    //3.求和:
    //定义一个变量专门接收成绩的和:
    sum := 0
    for i := 0; i < len(scores); i++ { //i: 0,1,2,3,4
       sum += scores[i]
    }
    //4.平均数:
    avg := sum / len(scores)
    //输出
    fmt.Printf("成绩的总和为:%v,成绩的平均数为:%v", sum, avg)
}

输出结果:成绩的总和为:306,成绩的平均数为:61

1.2 数组的内存布局

首先我们来看一下数组的地址

func main() {
    //声明数组:
    var arr [3]int
    //获取数组的长度:
    fmt.Println(len(arr))
    //打印数组:
    fmt.Println(arr) //[0 0 0]
    //证明arr中存储的是地址值:
    fmt.Printf("arr的地址为:%p\n", &arr)
    //第一个空间的地址:
    fmt.Printf("arr第一个空间的地址为:%p\n", &arr[0])
    //第二个空间的地址:
    fmt.Printf("arr第二个空间的地址为:%p\n", &arr[1])
    //第三个空间的地址:
    fmt.Printf("arr第三个空间的地址为:%p\n", &arr[2])
}

输出结果:

3
[0 0 0]
arr的地址为:0xa00a0d0
arr第一个空间的地址为:0xa00a0d0
arr第二个空间的地址为:0xa00a0d4
arr第三个空间的地址为:0xa00a0d8

从上面的结果可以看出以下几点:

  1. 如果是int类型数组,默认不赋值都为0,那么可以得出,不赋值的情况,默认值就是基本数据类型的默认值。
  2. 获取数组的地址值,跟获取基本数据类型一样:&arr即可
  3. arr第一个空间的地址值跟直接获取的地址值相同:&arr = &arr[0]
  4. 因为int类型根据电脑是int32,那么一个int数据类型占用4个字节,所以第二个数组的空间地址值与第一个空数组就相差4个数,并且发现这是16进制的数。

在这里插入图片描述

1.3 四种初始化数组的方式
func main() {
    // 方式一
    var arr1 [3]int = [3]int{1, 2, 3}
    // 方式二
    var arr2 = [3]int{1, 2, 3}
    // 方式三
    var arr3 = [...]int{1, 2, 3}
    // 方式四
    var arr4 = [3]string{1: "Tom", 0: "Jack", 2: "marry"}

    fmt.Println(arr1)
    fmt.Println(arr2)
    fmt.Println(arr3)
    fmt.Println(arr4)
}

输出结果:

[1 2 3]
[1 2 3]         
[1 2 3]         
[Jack Tom marry]

一般情况,我们定义数组都是用这种方式:arr := [5]int{1,2,3}这样,后面两个会给默认值0

1.4 数组的遍历

方式一:常规的遍历

前面的方式就是,这里不写了

方式二:使用for-range结构遍历

(键值循环) for range结构是Go语言特有的一种的迭代结构,在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道,for range 语法上类似于其它语言中的 foreach 语句,一般形式为:

for index, value := range arr {
    ...
}

(1)arr就是你要遍历的数组名
(2)每次遍历得到的索引用index接收,每次遍历得到的索引位置上的值用value
(3)index、value的名字随便起名 i、v index、value
(4)index、value属于在这个循环中的局部变量
(5)你想忽略某个值:用_就可以了:

for _, val := range scores {
    ...
}

简单使用:

func main() {
	arr := [5]int{1, 2, 3}
	
	for k, v := range arr {
		fmt.Printf("数组第%v个的值为:%v\n", k, v)
	}

	for _, v := range arr {
		fmt.Printf("数组值为:%v\n", v)
	}
}

输出结果:

数组第0个的值为:1
数组第1个的值为:2
数组第2个的值为:3
数组第3个的值为:0
数组第4个的值为:0
数组值为:1
数组值为:2
数组值为:3
数组值为:0
数组值为:0
1.5 注意事项以及分析
  1. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化

  2. var arr []int 这时arr就是一个slice切片,切片下一步讲,等等说。

  3. 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用

  4. 数组创建后,如果没有赋值,有默认值

    • 数值类型数组:默认: 0
    • 字符串数组:默认:“”
    • bool数组:默认值:false
  5. 使用数组的步骤 1. 声明数组并开辟空间 2.给数组各个元素赋值 3.使用数组

  6. 数组的下标是从0开始的

  7. 数组下标必须在指定范围内使用,否则报 panic;数组越界。

  8. Go的数组属于值类型,在默认情况下是值传递,因此会进行值拷贝。数组间不会互相影响。

  9. 如果想在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)

    案例讲解:

    // 通过普通方式,修改数组的值
    func updateArr(num int) {
        num = 10
    }
    
    // 通过指针方式,修改数组的值
    func updateArrPointer(num *int) {
        *num = 10
    }
    
    func main() {
        arr := [5]int{1, 2, 3}
        fmt.Println("普通值传递方式")
        fmt.Println("修改前,数组的值:", arr[0])
        updateArr(arr[0])
        fmt.Println("修改后,数组的值:", arr[0])
    
        fmt.Println("通过指针方式")
        fmt.Println("修改前,数组的值:", arr[0])
        updateArrPointer(&arr[0])
        fmt.Println("修改后,数组的值:", arr[0])
    }
    

输出结果:

普通值传递方式
修改前,数组的值: 1
修改后,数组的值: 1
通过指针方式
修改前,数组的值: 1
修改后,数组的值: 10
1.6 数组反转

要求:随机生成五个数,并将其反转打印
思路:

  1. 随机生成五个数 , rand.Intn() 函数
  2. 当我们得到随机数后,就放到一个数组 int数组
  3. 反转打印 , 交换的次数是 len / 2, 倒数第一个和第一个元素交换, 倒数第2个和第2个元素交换

概念一:随机数生成

在随机数生成中,种子(seed)是一个用于初始化随机数生成器的值。通过给定特定的种子,我们可以确保每次运行程序时生成的随机数序列是不同的。

在 Go 语言中,种子通常以整数形式表示,并且可以通过调用 rand.Seed(seed int64) 方法来设置种子值。种子值可以是任何整数,但是如果种子值相同,那么生成的随机数序列也将相同。

常用的设置种子的方法是使用当前时间作为种子。在这种情况下,我们可以使用 time.Now().UnixNano() 函数来获取当前时间的纳秒级别时间戳,并将其作为种子值传递给 rand.Seed(seed int64) 方法。

例如,rand.Seed(time.Now().UnixNano()) 将使用当前时间的纳秒级别时间戳作为种子值,从而使得每次运行程序生成的随机数序列都是不同的。这样做的目的是为了增加随机性,使得生成的随机数更加随机和分散。

需要注意的是,在实际应用中,如果我们需要可重复的随机数序列,可以使用相同的种子值来初始化随机数生成器。这样可以确保每次运行程序时,生成的随机数序列都是相同的。

概念二:生成随机数新方法

根据Go 1.17版本,新增了一个 math/rand.Source 接口和 math/rand.NewSource 函数,用于生成随机种子。在新版本中,随机数生成器的实现和种子值的管理分离开来,使得随机数生成器更加灵活而且易于维护。

所以生成随机数的代码

func main() {
	intArr := [5]int{}
	randGen := rand.New(rand.NewSource(time.Now().UnixNano()))

	for i := 0; i < len(intArr); i++ {
		intArr[i] = randGen.Intn(100)
	}

	fmt.Println("随机数:", intArr)
}

输出结果:随机数: [66 6 59 99 64]

最后的代码为:

func main() {
	//要求:随机生成五个数,并将其反转打印
	//思路
	//1. 随机生成五个数 , rand.Intn() 函数
	//2. 当我们得到随机数后,就放到一个数组 int数组
	//3. 反转打印 , 交换的次数是  len / 2, 倒数第一个和第一个元素交换, 倒数第2个和第2个元素交换

	intArr := [5]int{}
	len := len(intArr)

	for i := 0; i < len; i++ {
		intArr[i] = rand.Intn(100)
	}

	fmt.Println("交换前~=", intArr)
	//反转打印 , 交换的次数是  len / 2,
	//倒数第一个和第一个元素交换, 倒数第2个和第2个元素交换
	temp := 0 //做一个临时变量
	for i := 0; i < len/2; i++ {
		temp = intArr[len-1-i]
		intArr[len-1-i] = intArr[i]
		intArr[i] = temp
	}

	fmt.Println("交换后~=", intArr)
}

输出结果:

交换前~= [7 65 83 17 12]
交换后~= [12 17 83 65 7]
1.7 二维数组

二维数组是一种常见的数据结构,通常用于表示表格、矩阵等具有行列结构的数据。在 Go 语言中,二维数组的定义和初始化方式如下:

// 定义一个 3 行 4 列的二维数组
var arr2D [3][4]int

// 初始化二维数组
arr2D = [3][4]int{
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12},
}

上面的代码中,arr2D 是一个 3 行 4 列的二维数组,每个元素的类型都是 int。我们可以使用数组字面量初始化二维数组,每个数组字面量表示一行数据。

输出结果为:[[1 2 3 4] [5 6 7 8] [9 10 11 12]]

二维数组的遍历:

func main() {
    //定义二维数组:
    var arr [3][3]int = [3][3]int{{1, 4, 7}, {2, 5, 8}, {3, 6, 9}}
    fmt.Println(arr)
    fmt.Println("------------------------")
    //方式1:普通for循环:
    for i := 0; i < len(arr); i++ {
       for j := 0; j < len(arr[i]); j++ {
          fmt.Print(arr[i][j], "\t")
       }
       fmt.Println()
    }
    fmt.Println("------------------------")
    //方式2:for range循环:
    for key, value := range arr {
       for k, v := range value {
          fmt.Printf("arr[%v][%v]=%v\t", key, k, v)
       }
       fmt.Println()
    }
}

输出结果:

[[1 4 7] [2 5 8] [3 6 9]]
------------------------
1       4       7
2       5       8
3       6       9
------------------------
arr[0][0]=1     arr[0][1]=4     arr[0][2]=7
arr[1][0]=2     arr[1][1]=5     arr[1][2]=8
arr[2][0]=3     arr[2][1]=6     arr[2][2]=9

2 切片

切片是一个拥有动态长度的可索引序列,它是对底层数组的引用(所以是一个引用类型)。切片提供了一种灵活、方便的方式来管理数组,并允许数组可以自动扩容。切片通常用于表示动态长度的集合,如列表、队列和栈等。

2.1 快速入门
  1. 切片的英文slice

  2. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。

  3. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice)都一样。

  4. 切片的长度是可以变化的,因此切片是一个可以动态变化数组。

  5. 切片定义的基本语法:

    var 变量名 []类型,比如:var a []int,这里的[]不需要写长度,只要不写东西就是切片,写就是数组。

案例讲解:

func main() {
	//定义二维数组:
	var intArr [5]int = [...]int{1, 22, 33, 66, 99}
	//声明/定义一个切片
	//slice := intArr[1:3]
	//1. slice 就是切片名
	//2. intArr[1:3] 表示 slice 引用到intArr这个数组
	//3. 引用intArr数组的起始下标为 1 , 最后的下标为3(但是不包含3)
	slice := intArr[1:3]
	fmt.Println("intArr=", intArr)
	fmt.Println("slice 的元素是 =", slice)       //  22, 33
	fmt.Println("slice 的元素个数 =", len(slice)) // 2
	fmt.Println("slice 的容量 =", cap(slice))   // 切片的容量是可以动态变化

	// 切片是引用类型,也就是,实际存储的是intArr地址值
	fmt.Println()
	fmt.Printf("intArr[1]的地址=%p\n", &intArr[1])
	fmt.Printf("slice[0]的地址=%p slice[0]=%v\n", &slice[0], slice[0])

	// 修改了切片的数值,同样会把intArr的值修改,也就是直接修改了地址对应的数据值
	fmt.Println()
	slice[1] = 34
	fmt.Println("intArr=", intArr)
	fmt.Println("slice 的元素是 =", slice) //  22, 33
}

输出结果:

intArr= [1 22 33 66 99]             
slice 的元素是 = [22 33]            
slice 的元素个数 = 2                
slice 的容量 = 4                    
                                    
intArr[1]的地址=0x9d16004           
slice[0]的地址=0x9d16004 slice[0]=22
                                    
intArr= [1 22 34 66 99]             
slice 的元素是 = [22 34]      
2.2 内存解析

通过上面的代码,我们已经出门了解了slice,现在从底层内存了解一下

在这里插入图片描述

从这个图可以看出:

  1. slice在内存中的展示,的确是一个引用类型

  2. slice从底层来说,其实就是一个数据结构(struct结构体)

    type slice struct{
        ptr *[2] int // 存储被切的数组的地址值,例如:这里的22是被切的首部,那就存储它的地址值
    	len int      // 存储元素的数量
    	cap int		 // 存储切片的容量(底层数组的长度)
    }
    
2.3 切片的使用和遍历

方式1:定义一个切片,然后让切片去引用一个已经创建好的数组,比如:前面的案例就是这样的。

略。。。

方式2:通过make来创建切片。

基本语法:var 切片名 []type = make([], len, [cap])

参数说明:

type:就是数据类型 len:大小 cap(可选):指定切片的容量,如果你分配了cap,则要cap >=l en

func main() {
	// 基本语法
	var sliceName []int = make([]int, 5)
	fmt.Println(sliceName)

	// 创建一个带有 5 个元素的切片
	// 这样创建更舒服
	s := make([]int, 5)

	// 在切片中添加元素
	s = append(s, 1, 2, 3)
	fmt.Println("s切片的元素:", s)

	// 输出切片的长度和容量
	fmt.Println("长度为:", len(s), ",容量为:", cap(s))

	// 访问切片的元素
	fmt.Println("第一个元素为:", s[0])
}

输出结果:

[0 0 0 0 0]
s切片的元素: [0 0 0 0 0 1 2 3]
长度为: 8 ,容量为: 12
第一个元素为: 0

注意:通过这种方式创建的切片,其地址指向的数组是对外不可见,但在内存中就是会有占用这么一块内存空间。

方式3:定义一个切片的同时,直接指定具体数组

func main() {
    var sliceName []string = []string{"Tom", "Jack", "Mary"}
    // sliceName := []string{"Tom", "Jack", "Mary"}
    fmt.Println("sliceName", sliceName)
    fmt.Println("sliceName size=", len(sliceName))
    fmt.Println("sliceName cap=", cap(sliceName))
}

输出结构:

sliceName [Tom Jack Mary]
sliceName size= 3
sliceName cap= 3

切片的遍历

func main() {
	sliceName := []string{"Tom", "Jack", "Mary"}
	// 使用常规方式
	for i := 0; i < len(sliceName); i++ {
		fmt.Printf("slice[%v] = %v", i, sliceName[i])
	}
	fmt.Println()
	// 使用for--range方式遍历
	for i, v := range sliceName {
		fmt.Printf("slice[%v] = %v", i, v)
	}
}

输出结果:

slice[0] = Tomslice[1] = Jackslice[2] = Mary
slice[0] = Tomslice[1] = Jackslice[2] = Mary
2.4 注意事项和细节说明
  1. 切片初始化时,var slice = arr[startIndex:endIndex]

​ 说明:从arr数组下标为startIndex,取到下标为endIndex的元素(不含arr[endIndex])

  1. 切片初始化时,仍然不能越界。范围在[0-len(arr)]之间,但是可以动态增长。

    • var slice = arr[0:end] 可以简写: var slice = arr[:end]
    • var slice = arr[start:len(arr)] 可以简写:var slice = arr[start:]
    • var slice = arr[0:len(arr)] 可以简写:var slice = arr[:]
  2. cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。

  3. 切片可以继续切片

  4. 用切片的内置函数append,可以第切片进行动态追加

    切片append操作的底层原理分析:

    1. 切片append操作的本质就是对数组扩容
    2. go底层会创建一下新的数组newArr(安装扩容后大小)
    3. 将slice原来包含的元素拷贝到新的数组newArr
    4. slice重新引用到newArr
    5. 注意newArr是在底层来维护的,程序员不可见

    案例说明:

    在这里插入图片描述

通过这个图就可以看出,扩容后,go底层会创建行的数组,而且面对程序员不可见。

2.5 string和slice关系
  1. string底层是一个byte数组,因此string也可以进行切片处理

  2. string和切片在内存的形式,以"abcd"画出内存示意图

    在这里插入图片描述

  3. string是不可变的,也就是说不能通过 str[0] = 'z’方式来修改字符串

  4. 如果需要修改字符串,可以先将string -> []byte / 或者 []rune -> 修改 -> 重写转成string

func main() {
	//string底层是一个byte数组,因此string也可以进行切片处理
	str := "hello@atguigu"
	//使用切片获取到 atguigu
	slice := str[6:] 
	fmt.Println("slice=", slice)

	//string是不可变的,也就说不能通过 str[0] = 'z' 方式来修改字符串 
	//str[0] = 'z' [编译不会通过,报错,原因是string是不可变]

	//如果需要修改字符串,可以先将string -> []byte / 或者 []rune -> 修改 -> 重写转成string
	// "hello@atguigu" =>改成 "zello@atguigu"
	// arr1 := []byte(str) 
	// arr1[0] = 'z'
	// str = string(arr1)
	// fmt.Println("str=", str)

	// 细节,我们转成[]byte后,可以处理英文和数字,但是不能处理中文
	// 原因是 []byte 字节来处理 ,而一个汉字,是3个字节,因此就会出现乱码
	// 解决方法是 将  string 转成 []rune 即可, 因为 []rune是按字符处理,兼容汉字

	arr1 := []rune(str) 
	arr1[0] = '北'
	str = string(arr1)
	fmt.Println("str=", str)
}
2.6 切片练习题

写一个函数,可以打印一个斐波那契的数列,要求为数组格式

  1. 可以接收一个 n int
  2. 能够将斐波那契的数列放到切片中
  3. 提示, 斐波那契的数列形式:arr[0] = 1; arr[1] = 1; arr[2]=2; arr[3] = 3; arr[4]=5; arr[5]=8

思路

  1. 声明一个函数 fbn(n int) ([]uint64)
  2. 编程fbn(n int) 进行for循环来存放斐波那契的数列
func fbn(n int) ([]uint64) {
	//声明一个切片,切片大小 n
	fbnSlice := make([]uint64, n)
	//第一个数和第二个数的斐波那契 为1
	fbnSlice[0] = 1
	fbnSlice[1] = 1
	//进行for循环来存放斐波那契的数列
	for i := 2; i < n; i++ {
		fbnSlice[i] = fbnSlice[i - 1] + fbnSlice[i - 2]
	}

	return fbnSlice
}

func main() {
	fnbSlice := fbn(20)
	fmt.Println("fnbSlice=", fnbSlice)
}

输出结果:

fnbSlice= [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765]

3 map映射

map是key-value数据结构,又称为字段或者关联数组。类似其他编程语言的集合,在编程中是经常用到的。

3.1 快速入门

切片的语法:var map变量名 map[keytype]valuetype

  1. key、value的类型:bool、数字、string、指针、channel 、还可以是只包含前面几个类型的接口、结构体、数组
  2. key通常为int 、string类型,value通常为数字(整数、浮点数)、string、map、结构体
  3. key:slice、map、function不可以,因为这几个没发用 == 来判断

map的特点:

  1. map集合在使用前一定要make
  2. map的key-value是无序的
  3. key是不可以重复的,如果遇到重复,后一个value会替换前一个value
  4. value可以重复的
func main() {
	//map的声明
	var a map[string]string
	//在使用map前,需要先make , make的作用就是给map分配数据空间
	a = make(map[string]string, 10)
    // 或者一步解决:a := make(map[string]string, 10)
	a["no1"] = "宋江" //ok?
	a["no2"] = "吴用" //ok?
	a["no1"] = "武松" //ok?
	a["no3"] = "吴用" //ok?
	fmt.Println(a)
}

输出结果:map[no1:武松 no2:吴用 no3:吴用]

声明map的三种方法:

  1. 如上所示,分开来使用
  2. 声明就直接make:var a = make(map[string]string, 10)
  3. 声明直接赋值:a := map[string]string{"no1": "北京","no2": "深圳",}

还可以套多一层,例如下面:

课堂练习:演示一个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, 3)
	studentMap["stu01"]["name"] = "tom"
	studentMap["stu01"]["sex"] = "男"
	studentMap["stu01"]["address"] = "北京长安街~"

	studentMap["stu02"] = make(map[string]string, 3) //这句话不能少!!
	studentMap["stu02"]["name"] = "mary"
	studentMap["stu02"]["sex"] = "女"
	studentMap["stu02"]["address"] = "上海黄浦江~"

	fmt.Println(studentMap)
	fmt.Println(studentMap["stu02"])
}

输出结果:

map[stu01:map[address:北京长安街~ name:tom sex:] stu02:map[address:上海黄浦江~ name:mary sex:]]
map[address:上海黄浦江~ name:mary sex:] 
3.2 map的crud操作

map增加和更新:

map[“key”] = value // 如果key还没有,就是增加,如果key存在就是修改。

map删除

delete(map, “key”),delete是一个内置函数,如果key存在,就删除改key-value,如果key不存在,不操作,但是也不会报错。

细节说明:

  1. 如果我们要删除map的所有key,没有一个专门的方法一次删除,可以遍历一下key,逐个删除
  2. 或者 map = make(…),make一个新的,让原来的成为垃圾,被gc回收

map查找

使用这种方式查找:要查找的值val, 结果(true/false) := map的名字["需要查找的key"]

func main() {
	heroes := make(map[string]string, 10)
	heroes["no1"] = "宋江"
	heroes["no2"] = "卢俊义"

	val, findRes := heroes["no1"]
	if findRes {
		fmt.Println("找到了val=", val)
	} else {
		fmt.Println("没有no1这个key")
	}
}

输出结果:找到了val= 宋江

说明:如果heroes这个map中存在”no1“,那么findRes就会返回true,否则返回false

3.3 map的遍历

map的遍历只能使用for-range的结构遍历

简单案例:

func main() {
    heroes := make(map[string]string, 10)
    heroes["no1"] = "北京"
    heroes["no2"] = "上海"

    for k, v := range heroes {
       fmt.Printf("k=%v v=%v\n", k, v)
    }
}

输出结果:

k=no1 v=北京
k=no2 v=上海

复杂案例:

func main() {
    studentMap := make(map[string]map[string]string)

    studentMap["stu01"] = make(map[string]string, 3)
    studentMap["stu01"]["name"] = "tom"
    studentMap["stu01"]["sex"] = "男"
    studentMap["stu01"]["address"] = "北京长安街~"

    studentMap["stu02"] = make(map[string]string, 3) //这句话不能少!!
    studentMap["stu02"]["name"] = "mary"
    studentMap["stu02"]["sex"] = "女"
    studentMap["stu02"]["address"] = "上海黄浦江~"

    for k1, v1 := range studentMap {
       fmt.Println("k1=", k1)
       for k2, v2 := range v1 {
          fmt.Printf("\t k2=%v2 v2=%v \n", k2, v2)
       }
       fmt.Println()
    }
}

输出结果:

k1= stu01
         k2=name2 v2=tom            
         k2=sex2 v2=男              
         k2=address2 v2=北京长安街~ 
                                    
k1= stu02                           
         k2=name2 v2=mary           
         k2=sex2 v2=女              
         k2=address2 v2=上海黄浦江~ 
3.4 map切片

切片的数据类型如果是map,则我们称为 slice of map,map切片,这样使用则map个数就可以动态变化了。

案例演示:

要求:使用一个map来记录monster的信息 name 和 age,

也就是说一个monster对应一个map,并且妖怪的个数可以动态的增加=>map切片

func main() {
	monster := make([]map[string]string, 2) // 存入两个妖怪

	monster[0] = make(map[string]string, 2)
	monster[0]["name"] = "牛魔王"
	monster[0]["age"] = "500"

	monster[1] = make(map[string]string, 2)
	monster[1]["name"] = "玉兔精"
	monster[1]["age"] = "200"

	// 因为monster只配置了两个容量,所以如果还要添加就会越界,必须用另外的方法
	// 1.先定义monster信息
	newMonster := map[string]string{
		"name": "新的妖怪",
		"age":  "200",
	}
	// 2.再通过append接上
	monster = append(monster, newMonster, make(map[string]string))

	fmt.Println(monster)
}

输出结果:

[map[age:500 name:牛魔王] map[age:200 name:玉兔精] map[age:200 name:新的妖怪] map[]]
3.5 map排序
  1. golang中没有一个专门的方法针对map的key进行排序
  2. golang中的map默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历。得到的输出可能不一样。
  3. golang中map的排序,需要使用切片储存map,然后再用sort包进行排序。

在 Go 语言中,map 的 key 是无序的。这是由 Go 语言的实现决定的。map 的底层实现是哈希表,哈希表对 key 进行哈希运算,将 key 映射到底层数组的某个位置上,并存储对应的 value。由于哈希表的内部实现与 key 的顺序无关,因此 map 的 key 是无序的。

map 的无序性可以带来一些好处,例如更高的访问速度和更小的内存占用。由于哈希表是按照 key 所计算的哈希值来访问元素的,因此访问元素的速度是非常快的,不会因为 key 的顺序而降低速度。此外,由于底层数组存储的是键值对,而不是一个个具体的元素,所以在空间占用上,与 key 的顺序无关,可以充分利用底层数组的空间。

如果你需要有序的 key,可以使用 sort 包对 map 中的 key 进行排序。示例如下:

package main

import (
    "fmt"
    "sort"
)

func main() {
    a := map[string]string{
        "no1": "成都",
        "no3": "北京",
        "no2": "深圳",
    }

    keys := make([]string, len(a))
    i := 0
    for k := range a {
        keys[i] = k
        i++
    }

    sort.Strings(keys)

    for _, k := range keys {
        fmt.Println(k, a[k])
    }
}

在上述代码中,我们首先将 map a 中的 key 存储到一个切片中,并使用 sort 包对切片进行排序。然后,我们可以使用排序后的切片来遍历 map,并以有序的方式输出所有的 key 和对应的 value。

补充:

递增排序

sort.Strings(keys)

递减排序

sort.Slice(keys, func(i, j int) bool {
	return keys[i] > keys[j]
})

上面这个代码:使用 sort.Slice 函数对 keys 切片进行排序,并传入一个匿名的比较函数作为参数。在比较函数中,我们使用递减(大于)的方式进行比较,从而实现递减排序。

3.6 使用细节
  1. map是引用类型,遵守引用类型传递的机制,在一个函数接收map,修改后,会直接修改原来的map。
  2. map的容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说map能动态的增长键值对
  3. map的value也经常使用struct类型,更适合管理复杂的数据(比前面value是一个map更好),比如value为Student结构体。

以下代码将对第2句话进行讲解和验证:

package main

import "fmt"

func main() {
	a := make(map[string]string, 2)
	a["apple"] = "red"
	a["banana"] = "yellow"
	a["carrot"] = "orange"

	fmt.Println(a)
}

上述代码创建了一个初始容量为 2 的空 map,并依次向其中添加三个键值对。根据提示的特性,当 map 容量达到后,再想往 map 中增加元素时,它会自动扩容。

输出结果如下:

map[apple:red banana:yellow carrot:orange]

从输出结果中可以看出,map 成功地添加了三个键值对,并没有发生 panic。这证明了 map 的确能够动态增长键值对。

需要注意的是,尽管 map 可以动态增长键值对,但其增长策略是根据实现方式(哈希表)和负载因子(current size / current capacity)来决定的。当负载因子超过一定阈值时,map 会重新分配更大的内存空间,将已有的元素重新哈希到新的内存位置上,并拷贝到新的底层数组中。这个过程可能会导致一些性能消耗,因此,合理估算和设置初始容量可以提高 map 的性能。

4 综合题目(难度较大,可略~)

题目:某餐馆进行食物点菜记录,要求记录每桌点的菜品,桌号为1、2、3…,要求能够添加、查看和删除菜品,并且能够查询每桌点了多少菜。

解决思路:

  1. 使用 map 建立桌号与菜品的对应关系,key 是桌号,value 是一个切片,表示该桌点的菜品。
  2. 使用 map 存储每桌点菜的数量,key 是桌号,value 是整数,表示该桌点了多少菜。

实现步骤:

  1. 创建一个空的 map orders,用来记录每桌点的菜品。
  2. 创建一个空的 map total,用来记录每桌点了多少菜。
  3. 添加点菜功能:输入桌号和菜品名称,判断该桌号是否存在于 orders 中,如果存在,则在对应的切片中添加菜品,并在 total 中该桌号对应的值加一。如果不存在,则创建该桌号的切片,并添加菜品,同时在 total 中该桌号对应的值初始化为 1。
  4. 删除菜品功能:输入桌号和菜品名称,判断该桌号是否存在于 orders 中,如果存在,则在对应的切片中删除菜品,并在 total 中该桌号对应的值减一。如果删除后切片为空,则从 orderstotal 中删除该桌号。
  5. 查询菜品功能:输入桌号,从 orders 中查找该桌号对应的菜品切片,并输出菜品数量。

下面是具体的实现代码:

package main

import "fmt"

func main() {
	orders := make(map[string][]string)
	total := make(map[string]int)

	// 添加点菜
	addOrder(orders, total, "1", "鱼香肉丝")
	addOrder(orders, total, "1", "宫保鸡丁")
	addOrder(orders, total, "2", "水煮鱼")
	addOrder(orders, total, "3", "红烧肉")
	addOrder(orders, total, "3", "糖醋排骨")

	// 输出每桌点菜结果
	for table, dishes := range orders {
		fmt.Printf("桌号:%s,点了以下菜品:", table)
		for _, dish := range dishes {
			fmt.Printf(" %s,", dish)
		}
		fmt.Printf(" 共计%d道菜\n", total[table])
	}

	fmt.Println()

	// 删除一道菜
	deleteOrder(orders, total, "1", "宫保鸡丁")

	// 输出每桌点菜结果
	for table, dishes := range orders {
		fmt.Printf("桌号:%s,点了以下菜品:", table)
		for _, dish := range dishes {
			fmt.Printf(" %s,", dish)
		}
		fmt.Printf(" 共计%d道菜\n", total[table])
	}

	fmt.Println()

	// 查询菜品数量
	queryOrder(total, "2")
	queryOrder(total, "3")
}

func addOrder(orders map[string][]string, total map[string]int, table string, dish string) {
	_, ok := orders[table]
	if ok {
		orders[table] = append(orders[table], dish)
		total[table]++
	} else {
		orders[table] = []string{dish}
		total[table] = 1
	}
}

func deleteOrder(orders map[string][]string, total map[string]int, table string, dish string) {
	dishes, ok := orders[table]
	if ok {
		for i, d := range dishes {
			if d == dish {
				orders[table] = append(orders[table][:i], orders[table][i+1:]...)
				total[table]--
				if total[table] == 0 {
					delete(orders, table)
					delete(total, table)
				}
				break
			}
		}
	}
}

func queryOrder(total map[string]int, table string) {
	count, ok := total[table]
	if ok {
		fmt.Printf("桌号:%s,点了%d道菜\n", table, count)
	} else {
		fmt.Printf("桌号:%s,无点菜记录\n", table)
	}
}

代码很长,需要慢慢看~~~

Over!!!结束啦~~下一步就是面向对象,冲冲冲!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1129752.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于大数据的社交平台数据爬虫舆情分析可视化系统 计算机竞赛

文章目录 0 前言1 课题背景2 实现效果**实现功能****可视化统计****web模块界面展示**3 LDA模型 4 情感分析方法**预处理**特征提取特征选择分类器选择实验 5 部分核心代码6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于大数据…

充气膜结构的内压设计应考虑哪几种状态?

内压是充气膜结构独有的设计参数&#xff0c;也是结构形成刚度维持稳定的核心因素。同时&#xff0c;作为一种长期荷载保持结构在外荷载作用下具有的合理刚度&#xff0c;调整结构形态&#xff0c;以免产生过大变形、膜材失效褶皱等状况。结构的内压随不同气候条件进行调整&…

【MySQL-->数据操作】

文章目录 前言一、insert1.单行插入2.多行插入3.插入更新/替换 二、select1.全列查询2.指定列插入3.列别名4. 表达式计算5.去重6.where条件查询7.排序8.limit分页显示 三、update四、delete五、插入查询结果六、聚合函数六、聚合分组1.格式2.where和having的区别 前言 一、inse…

文心一言 VS 讯飞星火 VS chatgpt (120)-- 算法导论10.3 5题

五、用go语言&#xff0c;设 L 是一个长度为 n 的双向链表&#xff0c;存储于长度为 m 的数组key、prev 和next 中。假设这些数组由维护双链自由表 F的两个过程 ALLOCATE-OBJECT 和 FREE-OBJECT 进行管理。又假设 m 个元素中&#xff0c;恰有 n 个元素在链表 L 上&#xff0c;m…

BUUCTF 乌镇峰会种图 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 乌镇互联网大会召开了&#xff0c;各国巨头汇聚一堂&#xff0c;他们的照片里隐藏着什么信息呢&#xff1f;&#xff08;答案格式&#xff1a;flag&#xff5b;答案&#xff5d;&#xff0c;只需提交答案&#xff0…

Linux NFS的整体架构与核心代码解析

前面文章我们从应用层面对NFS进行了介绍&#xff0c;接下来的文章我们将进入实现层面。本文首先从整体上对Linux的NFS软件架构进行介绍&#xff0c;然后介绍代码与实际业务逻辑介绍一下NFS的处理流程。 NFS文件系统的架构分析 NFS分布式文件系统是一个客户端-服务端架构&#…

从力扣[203]理解递归思想

本文旨在通过使用递归方法的使用来进一步了解递归思想 class Solution {public ListNode removeElements(ListNode head, int val) {if (head null) {return head;}head.next removeElements(head.next, val);return head.val val ? head.next : head;} }既然要使用递归算法…

LVS+keepalive高可用集群

keepalive简介 keepalive为LVS应用延伸的高可用服务。lvs的调度器无法做高可用。但keepalive不是为lvs专门集群服务的&#xff0c;也可以为其他的的代理服务器做高可用。 keepalive在lvs的高可用集群&#xff0c;主调度器和备调度器(可以有多个) 一主两备或一主一备。 VRRP: k…

【全国数据】全国各省点状地名(村)矢量数据下载

文章目录 全国数据预览分省数据预览 全国数据预览 分省数据预览 青海省&#xff1a; 甘肃省&#xff1a; 安徽省&#xff1a; 湖南省&#xff1a;

freeCAD不合并导入step文件

1.问题描述 在使用freeCAD导入step文件的时候&#xff0c;一开始会导入成一个成体&#xff0c;想隐藏某些部件&#xff0c;却只能隐藏整个装配体&#xff0c;就是图示位置无法展开。 2.解决方法 找到首选项把第5步里面的不打钩就可以了。 3.freeCAD的用处 这个主要的用处还是用…

NOIP2023模拟1联测22 爆炸

NOIP2023模拟1联测22 爆炸 题目大意 ​ 自己看 思路 当一个炸弹被引爆后&#xff0c;它的方向是固定的。如果被竖着引爆&#xff0c;那么应该选择横着引爆&#xff0c;否则选择竖着引爆&#xff0c;这是显然 的。 考虑对于每个炸弹 ( i , j ) (i , j) (i,j) 将第 i i i 行…

前端视角看 Docker : 加速开发和部署的利器

Docker 是一个开源的容器化平台&#xff0c;大大的降低了运维相关的工作。在日常开发中&#xff0c;中小公司很少有专职运维&#xff0c;所以在开发中通过使用 Docker&#xff0c;前端相关工作可以更加高效地构建、打包、部署和运行应用程序。此系列将从前端的视角出发&#xf…

LeetCode刷题---简单组(二)

文章目录 &#x1f352;题目一 14. 最长公共前缀&#x1f352;解法一&#x1f352;find函数 &#x1f352;题目二 13. 罗马数字转整数&#x1f352;解法一&#x1f352;题目三 9. 回文数&#x1f352;解法一 &#x1f352;题目一 14. 最长公共前缀 编写一个函数来查找字符串数组…

使用kettle进行正则表达式组件日志分析

使用Kettle&#xff08;Pentaho Data Integration&#xff09;进行日志分析是一种常见的数据处理任务&#xff0c;特别是当你需要从大量的日志文件中提取和分析数据时。以下是一般步骤&#xff1a; 准备数据源&#xff1a; 确保你有日志文件的数据源&#xff0c;这可以是本地文…

公司内部文件、文档、设计图、源代码、音视频等核心文件数据自动智能透明加密保护,防泄密软件 | 防止外泄系统

天锐绿盾是一种企业级数据加密解决方案&#xff0c;可以实现对办公终端电脑上的文件、文档、设计图、源代码、音视频等数据的透明加密&#xff0c;以防止数据泄露。 天锐绿盾的工作原理是采用内核级透明加密技术&#xff0c;在不影响员工正常工作的情况下&#xff0c;对需要保护…

Vulnhub系列靶机---mhz_cxf: c1f

靶机文档&#xff1a;&#xff1a;mhz_cxf: c1f 下载地址&#xff1a;Download (Mirror): 网卡配置 靶机开机后按住shift&#xff0c;出现界面如图&#xff0c;按e键进入安全模式&#xff1a; 找到ro&#xff0c;删除该行后边内容&#xff0c;并将ro 。。。修改为&#xff1a…

Python绘制玫瑰花

程序员的节日到了&#xff0c;给各位程序员花一朵玫瑰吧。 from matplotlib import cm import matplotlib.pyplot as plt import numpy as npfig plt.figure() ax fig.add_subplot(projection3d) [x, t] np.meshgrid(np.array(range(25)) / 24.0, np.arange(0, 575.5, 0.5)…

EtherCAT从站转modbus RTU协议转换网关用modbus slave测试的方法

远创智控YC-ECT-RTU通讯网关具有EtherCAT从站功能&#xff0c;主要功能是将EtherCAT网络和Modbus-RTU网络连接起来。在使用方面&#xff0c;本网关可以连接到EtherCAT总线中作为从站使用&#xff0c;也可以连接到Modbus-RTU总线中作为主站或从站使用。这款通讯网关还支持多种不…

百度Comate代码助手全新上线SaaS服务,助力企业释放10倍软件生产力

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。2022年度博客之星评选TOP 10&#x1f3c6;&#xff0c;Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作…

什么样的系统能够有效提升维修效率?报修工单管理系统哪家的好用?

维修派单系统的主要目标是为企业或组织提供一种更有效的方式来管理他们的维修任务。这个系统可以用来处理各种维修任务&#xff0c;包括分配任务、跟踪任务的执行情况以及评估任务的完成情况等等。通过使用这个系统&#xff0c;管理者可以全面地了解维修人员的工作情况&#xf…