[go-07]数组与切片

本文详细介绍了Go语言中数组的定义、内存布局、赋值、遍历以及数组与切片的区别。重点讲解了切片作为引用类型的特性,包括切片的创建、动态变化、拷贝和追加操作。此外,还讨论了如何处理string与切片的关系,以及切片的深入理解。
摘要由CSDN通过智能技术生成
一、数组
(1) 数组可以存放多个同一类型数据。数组也是一种数据类型,在 Go 中,数组是值类型。
二、定义一个数组
var Arr [2] float64
Arr[0] = 1.2
Arr[1] = 1.3
1 数组的定义
 
func goarr() {
    // 定义一个数组
    // var 数组名 [数组大小]数据类型
    var arr [3]int                // 2个容量,int类型
    fmt.Println("地址0是:", &arr[0]) // 0x1400001a0a8
    arr[0] = 1                    // 赋值
    arr[1] = 2
    arr[2] = 3

    fmt.Println(arr)
    fmt.Println("地址1是:", &arr[0]) // 0x1400001a0a8
    fmt.Println("地址2是:", &arr[1]) // 0x1400001a0b0
    fmt.Println("地址2是:", &arr[2]) // 0x1400001a0b8

}

2 数组在内存布局(重要)

a. 每个每个空间占用的字节数取决于数组类型
b. 数组在内存中是连续的
三、数组的使用
数组名 [ 下标 ] 比如:你要使用 a 数组的第三个元素 a[2]
// 数组的定义
func goarr1() {
    // 第一种
    var arr [3]int // 2个容量,int类型
    arr[0] = 1     // 赋值
    arr[1] = 2
    arr[2] = 3

    // 第二种
    var arr1 [3]int = [3]int{4, 5, 6}
    // 第三种
    var arr2 = [3]int{7, 8, 9}
    // 第四种可变
    var arr3 = [...]int{10, 11, 12}
    // 第五种
    arr4 := [3]string{"a", "b", "c"}
    // 第六种
    var arr5 = [3]int{1: 10, 0: 11, 2: 12}
    fmt.Println(arr1, arr2, arr3, arr4, arr5)
}
四、数组的遍历
(1) for i  := 0;  i  <=  len(Arr); i++ {}
(2) for index, val  := range arr {} index数组的下标,val对应的值,不需要用到下标时用_忽略
五、数组的使用
1) 数组是多个相同类型的数据 , 一个数组一旦声明 / 定义了 , 其长度是固定的 , 不能动态变化
2) var arr []int 这时 arr 就是一个 slice 切片
3) 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
4) 数组创建后,如果没有赋值,有默认值 ( 零值 )
数值类型数组:默认值为 0
字符串数组:
默认值为 ""
bool 数组: 默认值为 false
5) 使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值 ( 默认零值 ) 3 使用数组
6) 数组的下标是从 0 开始的
7) 数组下标必须在指定范围内使用,否则报 panic :数组越界,比如var arr [5]int 则有效下标为 0-4
8) Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响
9) 如想在其它函数中,去修改原来的数组,可以使用引用传递 ( 指针方式 )
10) 长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度
六、切片
1) 切片是引用类型 ,遵循引用传递。
2) 切片的 使用和数组类似 ,遍历切片、访问切片的元素和求切片长度 len(slice) 都一样。
3) 切片的长度可变,因此切片是一个 可以动态变化数组
4) 切片定义的基本语法 :
var 切片名 [] 类型
比如: var a [] int
func goslice() {
    // 定义一个切片
    arr := [6]int{1, 2, 3, 4, 5, 6}
    slice := arr[1:3] // 2, 3  左闭右开,包含1不包含3
    clice2 := arr[2:4]
    fmt.Println(slice, "切片的长度是:", len(slice), "切片的容量是:", cap(slice))
    fmt.Println("切片地址1:", &slice[0], "地址2:", &slice[1]) // 切片地址1: 0x140000b4038 地址2:0x140000b4040
    fmt.Println("原数组地址1:", &arr[1], "数组2:", &arr[2])    // 原数组地址1: 0x140000b4038 数组2: 0x140000b4040
    // 切片的底层其实就是一个数组

    goEdit(slice)
    fmt.Println(slice)  // [2 12] 切片是引用传值
    fmt.Println(clice2) // [13 4]
}

func goEdit(slice []int) {
    slice[1] = 13
}
1. slice 的确是一个引用类型
2. slice 从底层来说,其实就是一个数据结构 (struct 结构体 )
type slice struct {
    ptr *[2]int
    len int
    cap int
}
七、切片的创建
// 切片的创建类型
func sliceCreate() {
    // 1 基于数组
    month := [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
    q2 := month[3:6]           // [4 5 6] 第二季度
    summer := month[5:8]       // 夏季
    all := month[:]            // 全年
    firsthalf := month[:6]     // 上半年
    secondhalf := month[6:]    // 下半年
    fmt.Println("第二季度:", q2)   //  [4 5 6]
    fmt.Println("夏季:", summer) //  [6 7 8]
    fmt.Println("全年:", all)
    fmt.Println("上半年年:", firsthalf) //  [1 2 3 4 5 6]
    fmt.Println("下半年年:", secondhalf)

    // 2 基于切片再切一刀
    q1 := firsthalf[0:3]
    q3 := firsthalf[6:9]                          // 这里可以看出本质上还是基于底层的数组
    fmt.Println("基于切片第一季度:", q1, "基于切片第三季度:", q3) // 基于切片第一季度: [1 2 3] 基于切片第三季度: [7 8 9]

    // 3 make 创建切片
    mySlice1 := make([]int, 5)                // 初始长度为5的切片
    mySlice2 := make([]int, 5, 10)            // 长度5 容量10
    mySlice3 := []int{1, 2, 3, 4, 5}          // 长度5 容量5
    fmt.Println(mySlice1, mySlice2, mySlice3) // 0 0 0 0 0] [0 0 0 0 0] [1 2 3 4 5]

}
make创建基本语法: var 切片名 []type = make([]type, len, [cap])
参数说明 : type: 数据类型 len : 大小 cap :指定切片容量, 可选, 如果你分配了 cap, 则要 cap>=len.
1) 通过 make 方式创建切片可以指定切片的大小和容量
2) 如果没有给切片的各个元素赋值,那么就会使用默认值 [int , float=> 0 string =>”” bool =>false]
3) 通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 访问各个元素
七、切片的遍历
mySlice3 := []int{1, 2, 3, 4, 5}          // 长度5 容量5

for i := 0; i < len(mySlice3); i++ {
    fmt.Println(mySlice3[i])
}

for k, val := range mySlice3 {
    fmt.Println(k, "----", val)
}
八、切片的使用细节
1) 切片初始化时  var autumn = month [ 8 : 11 ] 下标从0开始,左开右闭,包含第8个,不包含第11个
2) 切片初始化时,仍然不能越界。范围在 [0-len(arr)] 之间,但是可以动态增长 .
相关缩写
all := month[:]            // 全年
firsthalf := month[:6]     // 上半年
secondhalf := month[6:]    // 下半年
3) cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
4) 切片可以继续切片
firsthalf := month[:6]     // 上半年

// 基于切片再切一刀
q1 := firsthalf[0:3]
q3 := firsthalf[6:9]     // 这里可以看出本质上还是基于底层的数组
fmt.Println("基于切片第一季度:", q1, "基于切片第三季度:", q3) // 基于切片第一季度: [1 2 3] 基于切片第三季度: [7 8 9]
6) append 内置函数,可以对切片进行动态追加
// 切片追加
func sliceAppend() {
    var slice3 = make([]int, 4, 4)
    slice3[0] = 100
    slice3[1] = 200
    slice3[2] = 300

    newSlice := append(slice3, 400, 500) // append() 函数向切片追加新元素

    appendSlice := []int{21, 22, 23}            // 长度和容量会自动扩容
    newSlice2 := append(slice3, appendSlice...) // 注意末尾的 ... 不能省略

    fmt.Println(slice3)    // [100 200 300 0]
    fmt.Println(newSlice)  // [100 200 300 0 400 500]
    fmt.Println(newSlice2) // [100 200 300 0 21 22 23]
    fmt.Println("slice3[0]:", &slice3[0], "地址new[0]:", &newSlice[0], "地址Slice2[0]:", &newSlice2[0])
    // 地址oldslice3[0]: 0x140000b8020 地址new[0]: 0x140000a0080 地址Slice2[0]: 0x140000a00c0 说明不在同一个数组中啦
}
切片 append 操作的底层原理分析 :
切片 append 操作的本质就是对数组扩容 go 底层会创建一下新的数组 newArr将 slice 原来包含的元素拷贝到新的数组 newArr slice 重新引用到 newArr 注意 newArr 是在底层来维护的,程序员不可见
7) 切片的拷贝操作
// 拷贝
func sliceCopy() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := make([]int, 3, 5)
    copy(slice2, slice1)
    fmt.Println(slice1, slice2)         // [1 2 3 4 5] [1 2 3]
    fmt.Println(&slice1[0], &slice2[0]) // 0x140000b40c0  0x140000b40f0
}
(1) copy(para1, para2) 参数的数据类型是切片
(2) 拷贝出来的与原来的首地址不一样,说明底层也拷贝了一个新的数组
8)  切片是引用类型,所以在传递时,遵守引用传递机制。
九、string 和 slice
1) string 底层是一个 byte 数组,因此 string 也可以进行切片处理
// 字符串的底层也是个切片
func strSlice() {
    str := "707217@qq.com"
    slice1 := str[3:7]
    fmt.Println(slice1) // 217@
}
2) string 和切片在内存的形式,以 “abcd"
3) string 是不可变的,也就说不能通过 str[0] = 'z' 方式来修改字符串
// arr1[1] = ' ' // 非字符报错
4) 如果需要修改字符串,可以先将 string -> []byte / 或者 []rune -> 修改 -> 重写转成 string
// 字符串的底层也是个切片
func strSlice() {
    str := "7072北17@qq.com"
    slice1 := str[3:7]
    fmt.Println(slice1) // 217@
    //slice2 := str[:]
    //slice2[0] = "京" // 报错,不能这么操作
    arr1 := []byte(str)
    fmt.Println(arr1) // [55 48 55 50 229 140 151 49 55 64 113 113 46 99 111 109]
    arr1[0] = 'z'
    // arr1[1] = '京' // 非字符报错
    fmt.Println(string(arr1)) // z072北17@qq.com

    arr2 := []rune(str)
    fmt.Println(arr2)
    arr2[0] = '京'
    str = string(arr2)
    fmt.Println(str) // 京072北17@qq.com
}
十、切片深入理解(巩固一下)
var arr = [3]int{1, 2, 3 // 这是个数组
var slice = []int{1, 2, 3} // 这是个切片
切片的创建
1)基于数组创建
切片的下标是 左闭右开
// 从数组中,切一点出来,变成切片
month := [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
q2 := month[3:6]        // 第二季度 包含第三个 不包含第6个
summer := month[5:8]    // 夏季
all := month[:]         // 全年
firsthalf := month[:6]  // 上半年
secondhalf := month[6:] // 下半年
fmt.Println(q2)         // [4 5 6]
fmt.Println(summer)     // [6 7 8]
fmt.Println(all)        // [1 2 3 4 5 6 7 8 9 10 11 12]
fmt.Println(firsthalf)  // [1 2 3 4 5 6]
fmt.Println(secondhalf) // [7 8 9 10 11 12]
month := [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
all := month[:]         // 全年
all[11] = 100  // 这里会把数组和切片的值改掉
fmt.Println(month)
fmt.Println(all)
图解
arr := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
q2 := arr[3:6]       // 第二季度 包含第三个 不包含第6个
fmt.Println(len(q2)) // 3
fmt.Println(cap(q2)) // 5
基于切片创建
month := [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
firsthalf := month[:6]      // 上半年
fmt.Println(len(firsthalf)) // 6
fmt.Println(cap(firsthalf)) // 12
// 基于切片再切一刀
q2 := firsthalf[3:6]
q3 := firsthalf[6:9] // 这里可以看出本质上还是基于底层的数组
fmt.Println(q2) // [4 5 6]
fmt.Println(q3) // [7 8 9]
make()创建切片
// make 创建切片
mySlice1 := make([]int, 5)       // 初始长度为5的切片
mySlice2 := make([]int, 5, 10)   // 长度5 容量10
mySlice3 := []int{1, 2, 3, 4, 5} // 长度5 容量5
for i := 0; i < len(mySlice1); i++ {
    fmt.Println(mySlice1[i]) // 0 0 0 0 0
}
for index, val := range mySlice3 {
    fmt.Println(index, "--", val, "&&") // 0 -- 1 && 1 -- 2 && 2 -- 3 &&  3 -- 4 &&  4 -- 5 &&
}
fmt.Println(mySlice2) // 0 0 0 0 0
切片扩容
var oldSlice = make([]int, 5, 10)
newSlice := append(oldSlice, 1, 2, 3) // append() 函数向切片追加新元素
appendSlice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} // 长度和容量会自动扩容
Slice2 := append(oldSlice, appendSlice...) // 注意末尾的 ... 不能省略
fmt.Println(oldSlice) // [0 0 0 0 0]
fmt.Println(newSlice) // [0 0 0 0 0 1 2 3]
fmt.Println(Slice2)   //[0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11 12]
切片的删除( 其实是通过切片的切片实现的「伪删除」)
slice3 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice3 = slice3[:len(slice3)-5]               // 删除 slice3 尾部 5 个元素
fmt.Println(slice3, len(slice3), cap(slice3)) // [1 2 3 4 5] 5 10
slice3 = slice3[5:]                           // 删除 slice3 头部 5 个元素
fmt.Println(slice3, len(slice3), cap(slice3)) // [] 0 5
切片对应的结构体
type slice struct {
 array unsafe.Pointer //指向存放数据的数组指针 
 len int //长度有多大 
 cap int //容量有多大
}
  • 39
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值