数组
基本介绍
数组可以存放多个同一类型数据。数组也是一种数据类型,在 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]
数组遍历的两种方式
使用for len遍历
使用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)
}
}
内存分析
内存分析
总结:
数组的地址可以通过数组名来获取 &intArr
数组的第一个元素的地址,就是数组的首地址
数组的各个元素的地址间隔是依据数组的类型决定,比如 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])
}
}
注意事项
数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化
var arr []int 这时 arr 就是一个 slice 切片
数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
数组创建后,如果没有赋值,有默认值(零值)
数值类型数组:默认值为 0
字符串数组: 默认值为 ""
bool 数组: 默认值为 false
使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值(默认零值) 3 使用数组
数组的下标是从 0 开始的
数组下标必须在指定范围内使用,否则报 panic:数组越界,比如var arr [5]int 则有效下标为 0-4
Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响1。如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度
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()
}
}
二维数组定义的四种方式
二维数组在声明/定义时也对应有四种写法[和一维数组类似]
var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值..},{初值..}}
var 数组名 [大小][大小]类型 = [...][大小]类型{{初值..},{初值..}}
var 数组名 = [大小][大小]类型{{初值..},{初值..}}
var 数组名 = [...][大小]类型{{初值..},{初值..}}
二维数组遍历的两种方式
双层 for 循环完成遍历
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
基本介绍
切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
切片的长度是可以变化的,因此切片是一个可以动态变化数组。
切片定义的三种方式及内存分析
方式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))
}
内存分析图
对上面代码的小结:
通过 make 方式创建切片可以指定切片的大小和容量
如果没有给切片的各个元素赋值,那么就会使用默认值[int , float=> 0 string =>”” bool => false]
通过 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 的区别(面试)
区别内存分析图
切片遍历的两种方式
for len方式
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)
}
}
注意事项
切片初始化时 var slice = arr[startIndex:endIndex]
从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。
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[:]
切片初始化时,仍然不能越界。范围在 [0-len(arr)] 之间,但是可以动态增长
cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一个空间供切片来使用
切片可以继续切片
用 append 内置函数,可以对切片进行动态追加
切片使用 copy 内置函数完成拷贝
copy(para1, para2) 参数的数据类型是切片
copy后内存是独立的,相互不影响
切片是引用类型,所以在传递时,遵守引用传递机制
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的区别
string 底层是一个 byte 数组,因此 string 也可以进行切片处理
string 是不可变的,也就说不能通过 str[0] = 'z' 方式来修改字符串
如果需要修改字符串,可以先将 string -> []byte / 或者 []rune -> 修改 -> 重写转成 string
我们转成[]byte后,可以处理英文和数字,但是不能处理中文原因是 []byte 字节来处理 ,而一个汉字,是3个字节,因此就会出现乱码
解决方法是 将 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)
}
评论区