侧边栏壁纸
博主头像
梦幻世界博主等级

行动起来,活在当下

  • 累计撰写 23 篇文章
  • 累计创建 2 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

DAY11:数组;切片slice

梦幻世界
2024-05-24 / 0 评论 / 0 点赞 / 62 阅读 / 18939 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-05-24,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

数组

基本介绍

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

数组定义的四种方式

package main

import "fmt"

func main() {
	var arr1 [3]int = [3]int{1, 2, 3}
	fmt.Println(arr1)
	var arr2 = [3]int{4, 5, 6}
	fmt.Println(arr2)
	// 可变参数
	var arr3 = [...]int{7, 8, 9, 10}
	fmt.Println(arr3)
	// 指定下标的方式
	var arr4 = [...]int{1: 1, 0: 3, 2: 4}
	fmt.Println(arr4)
	arr5 := [...]string{1: "tom"}
	fmt.Println(arr5)
}

数组的访问

数组名[下标] 比如:你要使用 a 数组的第三个元素 a[2]

数组遍历的两种方式

  1. 使用for len遍历

  2. 使用for range遍历

package main

import "fmt"

func main() {
	arr := [...]int{0: 100, 1: 200, 3: 300}
	// 使用for len的方式
	for i := 0; i < len(arr); i++ {
		fmt.Println("第一种遍历", arr[i])
	}
	// 使用for range的方式
	for i, v := range arr {
		fmt.Println("第二种遍历index", i, "value", v)
	}
}

内存分析

内存分析

总结:

  1. 数组的地址可以通过数组名来获取 &intArr

  2. 数组的第一个元素的地址,就是数组的首地址

  3. 数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 -> 8 int32->4.

package main

import "fmt"

func main() {
	arr := [...]int{1, 2, 3}
	fmt.Printf("数组的地址%p\n", &arr)
	for i, _ := range arr {
		fmt.Printf("数组第%v个元素的地址%v", i+1, &arr[i])
	}
}

注意事项

  1. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化

  2. var arr []int 这时 arr 就是一个 slice 切片

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

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

  5. 数值类型数组:默认值为 0

  6. 字符串数组: 默认值为 ""

  7. bool 数组: 默认值为 false

  8. 使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值(默认零值) 3 使用数组

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

  10. 数组下标必须在指定范围内使用,否则报 panic:数组越界,比如var arr [5]int 则有效下标为 0-4

  11. Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响1。如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)

  12. 长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度

package main

import "fmt"

// 值传递
func test1(arr [3]int) {
	arr[0] = 88
}

// 指针传递
func test2(arr *[3]int) {
	(*arr)[0] = 88
}

func main() {
	// 值传递是在两个栈内,不会修改这个值
	arr := [3]int{1, 2, 3}
	test1(arr)
	fmt.Println(arr)
	// 指针传递会修改值
	test2(&arr)
	fmt.Println(arr)
}

值传递内存分析

指针传递内存分析

添加图片注释,不超过 140 字(可选)

应用案例

练习1:请求出一个数组的最大值,并得到对应的下标

package main

import "fmt"

func main() {
	arr := [10]int{1, 2, 3, 4, 10, 5, 6, 7, 8, 9}
	var max int = arr[0]
	var index int = 0
	for i := 1; i < len(arr); i++ {
		if max < arr[i] {
			max = arr[i]
			index = i
		}
	}
	fmt.Println("最大值", max, "角标", index)
}

练习2:请求出一个数组的和和平均值。for-range

package main

import "fmt"

func main() {
	arr := [10]int{1, 2, 3, 4, 10, 5, 6, 7, 8, 9}
	var sum int = 0
	for _, value := range arr {
		sum = sum + value
	}
	avg := sum / len(arr)
	fmt.Println("平均值为", avg)
}

练习3:随机生成五个数,并将其反转打印 , 复杂应用 (rand.Intn(100)生成随机数0-100)

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	var arr [5]int
	for i := 0; i < len(arr); i++ {
		arr[i] = rand.Intn(100)
	}
	fmt.Println("交换前", arr)

	var temp int
	// 只需要遍历一半,否则又交换回去了
	for i := 0; i < len(arr)/2; i++ {
		temp = arr[i]
		arr[i] = arr[len(arr)-1-i]
		arr[len(arr)-1-i] = temp
	}
	fmt.Println("交换后", arr)
}

二维数组

快速入门二维数组的声明

请用二维数组输出如下图形

0 0 0 0 0 0

0 0 1 0 0 0

0 2 0 3 0 0

0 0 0 0 0 0

语法: var 数组名 [大小][大小]类型

比如: var arr [2][3]int , 再赋值。

package main

import "fmt"

func main() {
	/**
	0 0 0 0 0 0
	0 0 1 0 0 0
	0 2 0 3 0 0
	0 0 0 0 0 0
	*/
	var arr [4][6]int
	arr[1][2] = 1
	arr[2][1] = 2
	arr[2][3] = 3

	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[i]); j++ {
			fmt.Print(arr[i][j], " ")
		}
		fmt.Println()
	}
}

二维数组定义的四种方式

二维数组在声明/定义时也对应有四种写法[和一维数组类似]

  1. var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值..},{初值..}}

  2. var 数组名 [大小][大小]类型 = [...][大小]类型{{初值..},{初值..}}

  3. var 数组名 = [大小][大小]类型{{初值..},{初值..}}

  4. var 数组名 = [...][大小]类型{{初值..},{初值..}}

二维数组遍历的两种方式

  1. 双层 for 循环完成遍历

  2. for-range 方式完成遍历

package main

import "fmt"

func main() {
	var arr [4][6]int
	arr[1][2] = 1
	arr[2][1] = 2
	arr[2][3] = 3
	// 使用双层for遍历
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[i]); j++ {
			fmt.Print(arr[i][j], " ")
		}
	}
	fmt.Println()

	// 使用range遍历
	for i, _ := range arr {
		for _, v2 := range arr[i] {
			//fmt.Printf("arr[%v][%v]=%v; ", i, i2, v2)
			fmt.Print(v2, " ")
		}
	}
}

内存分析

内存分析

应用案例

定义二维数组,用于保存三个班,每个班五名同学成绩, 并求出每个班级平均分、以及所有班级平均分

package main

import "fmt"

func main() {
	// 定义二维数组,用于保存三个班,每个班五名同学成绩, 并求出每个班级平均分、以及所有班级平均分
	var arr = [...][5]int{{9, 9, 9, 9, 9}, {10, 10, 10, 10, 10}, {8, 8, 8, 8, 8}}
	total := 0
	totalStu := 0

	for i := 0; i < len(arr); i++ {
		classTotal := 0
		for j := 0; j < len(arr[i]); j++ {
			classTotal += arr[i][j]
			totalStu += 1
		}
		classAvg := classTotal / len(arr[i])
		fmt.Println("第", i+1, "个班级的平均分", classAvg)
		total += classTotal
	}
	fmt.Println("总共的平均分为", total/totalStu)
}

切片slice

基本介绍

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

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

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

切片定义的三种方式及内存分析

方式1:

定义一个切片,然后让切片去引用一个已经创建好的数组

package main

import "fmt"

func main() {
	arr := [...]int{1, 22, 33, 66, 99}
	// 引用一个数组的角标一到三的元素,但不包含第三个
	slice := arr[1:3]
	fmt.Println("slice的元素", slice)
	fmt.Println("slice的长度", len(slice))
	// 容量不一定等于长度,是动态扩容的
	fmt.Println("slice的容量", cap(slice))
}

内存分析

对上面的分析图总结

1.slice 的确是一个引用类型

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

type slice struct {

ptr *[2]int

len int

cap int

}

方式2:

通过 make 来创建切片

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

参数说明: type: 就是数据类型 len : 大小 cap :指定切片容量,可选, 如果你分配了 cap,则要求 cap>=len

package main

import "fmt"

func main() {
	slice := make([]int, 4, 8)
	fmt.Println("slice的元素", slice)
	fmt.Println("slice的长度", len(slice))
	fmt.Println("slice的容量", cap(slice))
}

内存分析图

对上面代码的小结:

  1. 通过 make 方式创建切片可以指定切片的大小和容量

  2. 如果没有给切片的各个元素赋值,那么就会使用默认值[int , float=> 0 string =>”” bool => false]

  3. 通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去访问各个元素.

方式3:

定义一个切片,直接就指定具体数组,使用原理类似 make 的方式

package main

import "fmt"

func main() {
	var slice []int = []int{1, 2, 3, 4}
	fmt.Println("slice的元素", slice)
	fmt.Println("slice的长度", len(slice))
	fmt.Println("slice的容量", cap(slice))
}

方式 1 和方式 2 的区别(面试)

区别内存分析图

切片遍历的两种方式

  1. for len方式

  2. for range方式

package main

import "fmt"

func main() {
	slice := []int{1, 2, 3, 4}
	// for len 方式遍历
	for i := 0; i < len(slice); i++ {
		fmt.Println("for遍历slice第", i, "个元素,值为", slice[i])
	}
	// for range 方式遍历
	for i, v := range slice {
		fmt.Println("for range遍历slice第", i, "个元素,值为", v)
	}
}

注意事项

  1. 切片初始化时 var slice = arr[startIndex:endIndex]

  2. 从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。

  3. var slice = arr[0:end] 可以简写 var slice = arr[:end]

  4. var slice = arr[start:len(arr)] 可以简写: var slice = arr[start:]

  5. var slice = arr[0:len(arr)] 可以简写: var slice = arr[:]

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

  7. cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。

  8. 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一个空间供切片来使用

  9. 切片可以继续切片

  10. 用 append 内置函数,可以对切片进行动态追加

  11. 切片使用 copy 内置函数完成拷贝

  12. copy(para1, para2) 参数的数据类型是切片

  13. copy后内存是独立的,相互不影响

  14. 切片是引用类型,所以在传递时,遵守引用传递机制

package main

import "fmt"

func main() {
	// 切片可以继续切片
	var arr = [...]int{1, 2, 3, 4, 5}
	slice := arr[1:4]
	slice2 := arr[1:2]
	fmt.Println("slice", slice)
	fmt.Println("slice2", slice2)

	// 可以使用append函数进行动态添加
	slice3 := []int{3, 4, 5}
	slice3 = append(slice3, 10, 10)
	fmt.Println("slice3", slice3)
	// 可以append一个切片
	slice3 = append(slice3, slice3...)
	fmt.Println("slice3", slice3)

	// 可以使用copy进行拷贝
	var slice4 = make([]int, 1)
	copy(slice4, slice3)
	// 由于slice4容量为1,所以只会复制一个
	fmt.Println("slice4", slice4)
	// copy后slice4的内存是独立的
	slice4[0] = 99
	fmt.Println("slice4", slice4)
	fmt.Println("slice3", slice3)
}

切片遵循引用传递机制

append函数底层内存分析

内存分析图

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

  • 切片 append 操作的本质就是对数组扩容

  • go 底层会创建一下新的数组 newArr(安装扩容后大小)

  • 将 slice 原来包含的元素拷贝到新的数组 newArr

  • slice 重新引用到 newArr

  • 注意 newArr 是在底层来维护的,程序员不可见

copy的注意事项

思考题

说明: 上面的代码没有问题,可以运行, 最后输出的是 [1]

应用案例

说明:编写一个函数 fbn(n int) ,要求完成

1) 可以接收一个 n int

2) 能够将斐波那契的数列放到切片中

3) 提示, 斐波那契的数列形式: arr[0] = 1; arr[1] = 1; arr[2]=2; arr[3] = 3; arr[4]=5; arr[5]=8

package main

import "fmt"

func fbn(n int) []int {
	slice := make([]int, n)
	slice[0] = 1
	slice[1] = 1
	for i := 2; i < n; i++ {
		slice[i] = slice[i-2] + slice[i-1]
	}
	return slice
}

func main() {
	ints := fbn(20)
	fmt.Println("输出", ints)
}

string和slice的区别

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

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

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

  4. 我们转成[]byte后,可以处理英文和数字,但是不能处理中文原因是 []byte 字节来处理 ,而一个汉字,是3个字节,因此就会出现乱码

  5. 解决方法是 将 string 转成 []rune 即可, 因为 []rune是按字符处理,兼容汉字

package main

import "fmt"

func main() {
	// string底层为byte数组 阴恻也可以使用切片
	str := "hello"
	slice := str[1:4]
	fmt.Println(str)
	fmt.Println(slice)

	// 修改字符串两种方式
	// 使用byte[]
	byteSlice := []byte(str)
	byteSlice[0] = 'e'
	str = string(byteSlice)
	fmt.Println(str)
	// 使用rune的方式
	arr1 := []rune(str)
	arr1[0] = '倍'
	str = string(arr1)
	fmt.Println(str)
}

0

评论区