06-Go语言基础之数组切片

Go数组Array

  • 数组是固定长度相同类型元素组成的序列

  • 一个数字由零或多个元素组成。

  • 数组在声明时就确定了长度,成员允许修改,但是长度无法变化。

  • 数组的长度是固定,因此Go更常用Slice(切片,动态增长或收缩序列)。

  • 数组是值类型,用索引下标访问每个元素,范围是0~len(数组)-1,访问越界会panic异常

  • 注意:赋值和传参是复制整个数组而不是指针

注意事项

  1. 数组的长度必须是常量,并且长度是数组类型的一部分.

  2. 数组支持索引访问 a[1]c[7],索引的合法范围:0~len(array)-1不支持负数索引

array定义语法

package main

import "fmt"

/*
语法 var 数组变量  [元素数量]Type

比如:var a [5]int, 数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变。

[5]int和[10]int是不同的类型。

var a [3]int
var b [4]int
a = b //不可以这样做,因为此时a和b是不同的类型

数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1,访问越界(下标在合法范围之外),则触发访问越界,会panic。


*/

var students [3]string

var isUp [5]bool

var score [3]int

func main() {

    fmt.Printf("类型:%T 值:%#v\n", students, students)
    fmt.Printf("类型:%T 值:%#v\n", isUp, isUp)
    fmt.Printf("类型:%T 值:%#v\n", score, score)
}

/*
给人类看的数据
类型:[3]string 值:[  ]
类型:[5]bool 值:[false false false false false]
类型:[3]int 值:[0 0 0]


给机器看的数据
类型:[3]string 值:[3]string{"", "", ""}
类型:[5]bool 值:[5]bool{false, false, false, false, false}
类型:[3]int 值:[3]int{0, 0, 0}

*/

语法练习

package main

import "fmt"

func main() {
    // 数组定义语法1
    //var initArray [3]int // 长度为3个元素,类型是int的数组,int默认值是0
    //
    //var numArray [4]int = [4]int{4, 5, 6} //定义数组且赋值 ,可以简写,省去类型,不够的元素,自动补充默认值
    //
    //var stuArray = [3]string{"张飞", "刘备", "关羽"}
    //
    //fmt.Printf("类型:%T ,值:%v\n", initArray, initArray)
    //fmt.Printf("类型:%T ,值:%v\n", numArray, numArray)
    //fmt.Printf("类型:%T ,值:%v\n", stuArray, stuArray)

    // 语法二
    // 上述数组都是提前指定了固定的数组长度,赋值也必须和定义时的长度一致,否则会报错
    // go数组也支持,让编译器自动判断元素长度
    //var initArray [3]int // 不赋值的话,还是得写死数组元素长度
    //
    //var numArray = [...]int{4, 5, 6, 7, 8, 9} //定义数组且赋值 ,可以简写,省去类型,自动判断元素个数
    //
    //var stuArray = [...]string{"张飞", "刘备", "关羽", "孙二娘"}
    //fmt.Printf("类型:%T ,值:%v\n", initArray, initArray)
    //fmt.Printf("类型:%T ,值:%v\n", numArray, numArray)
    //fmt.Printf("类型:%T ,值:%v\n", stuArray, stuArray)

    // 语法三,通过索引,初始化元素值,以及简短变量
    numArray2 := [...]int{1: 3, 3: 7}                 // 最大索引是3,也就是0,1,2,3
    fmt.Printf("类型:%T ,值:%v\n", numArray2, numArray2) // 类型:[4]int ,值:[0 3 0 7]

}

/*
类型:[3]int ,值:[0 0 0]
类型:[6]int ,值:[4 5 6 7 8 9]
类型:[4]string ,值:[张飞 刘备 关羽 孙二娘]

*/

数组遍历

有如下几种语法

package main

import "fmt"

func main() {
    var a1 = [...]int{1, 3, 5, 7}
    //通过索引取值
    for i := 0; i < len(a1); i++ {
        fmt.Println(a1[i]) //根据索引依次获取数组每一个元素
    }

    //for循环遍历数组,索引和值,index可以省略用占位符_
    for index, value := range a1 {
        fmt.Println(index, value)
    }

    // 取出索引
    for index := range a1 {
        fmt.Println("索引:", index)
    }

}

数组使用细节

package main

import "fmt"

func main() {
    //数组是相同类型的多个元素,长度固定,无法扩容
    var a1 [3]int //初始化数组  [0,0,0]
    a1[1] = 7
    a1[0] = 4
    a1[2] = 1
    fmt.Printf("类型:%T ,值:%v\n", a1, a1) // 类型:[3]int ,值:[4 7 1]

    // 赋值必须和int类型对应
    // a1[2]=77.1 错误

    // 不得超出长度
    //a1[3]=5

}

数组是值类型(后面再说)

  1. 声明数组
  2. 给数组元素赋值
  3. 使用数组
  4. 数组索引从0开始,且不得越界否则panic
  5. Go数组是值类型,变量传递默认是值传递,因此会进行值拷贝
  6. 修改原本的数组,可以使用引用传递(指针),避免数据复制
package main

import (
    "fmt"
)

// 函数接收值类型,默认有值拷贝
func test(arr [3]int) {
    arr[0] = 66
}

// 函数修改原本数组,需要使用引用传递
func test2(arr *[3]int) {
    (*arr)[0] = 66 //可以缩写arr[0]=66 编译器自动识别,arr是指针类型
}

func main() {
    //声明arr数组,需要考虑传递函数参数时,数组的长度一致性
    arr := [3]int{11, 22, 33}
    //test函数不会修改数组
    test(arr)
    fmt.Println(arr)
    //test2修改了数组
    test2(&arr)
    fmt.Println(arr)
}

指针概念

指针数组: 每一个元素为指针类型的数组

数组指针: 获取变量数组变量的地址

[n]*T  指针数组 (每一个元素为指针类型)

*[n]T  数组指针 (获取数组的内存地址)

多维数组

Go语言支持多维数组,如二维数组(数组里又嵌套数组)

package main

import "fmt"

func main() {
    // 外层是3个元素的数组,每一个元素是2个元素的数组
    multiArray := [3][2]string{
        {"张三", "李四"},
        {"二狗", "胖蛋"},
        {"石头", "大狗"},
    }

    fmt.Printf("类型:%T, 值:%v\n", multiArray, multiArray)
    fmt.Printf("胖蛋出列:%v\n", multiArray[1][1])
    fmt.Printf("石头出列:%v\n", multiArray[2][0])
    fmt.Printf("李四出列:%v\n", multiArray[0][1])
}

/*
类型:[3][2]string, 值:[[张三 李四] [二狗 胖蛋] [石头 大狗]]
胖蛋出列:胖蛋
石头出列:石头
李四出列:李四

*/

二维数组遍历

for range

package main

import "fmt"

func main() {
    // 外层是3个元素的数组,每一个元素是2个元素的数组
    multiArray := [3][2]string{
        {"张三", "李四"},
        {"二狗", "胖蛋"},
        {"石头", "大狗"},
    }

    for i, v := range multiArray {
        fmt.Printf("i--%v,v--%v\n", i, v)
        //遍历内层数组
        for i2, v2 := range v {
            fmt.Printf("v--%v  , i2--%v ,v2--%v\n", v, i2, v2)
        }
    }

}

/*
i--0,v--[张三 李四]
v--[张三 李四]  , i2--0 ,v2--张三
v--[张三 李四]  , i2--1 ,v2--李四
i--1,v--[二狗 胖蛋]
v--[二狗 胖蛋]  , i2--0 ,v2--二狗
v--[二狗 胖蛋]  , i2--1 ,v2--胖蛋
i--2,v--[石头 大狗]
v--[石头 大狗]  , i2--0 ,v2--石头
v--[石头 大狗]  , i2--1 ,v2--大狗


*/

索引遍历

package main

import "fmt"

func main() {
    // 外层是3个元素的数组,每一个元素是2个元素的数组
    // 只有外层的数组,支持自动推导长度
    multiArray := [...][2]string{
        {"张三", "李四"},
        {"二狗", "胖蛋"},
        {"石头", "大狗"},
    }

    for i := 0; i < len(multiArray); i++ {
        //fmt.Printf("索引:%v ,值:%v\n", i, multiArray[i])
        //内层遍历
        tmp := multiArray[i]
        for i2 := 0; i2 < len(tmp); i2++ {
            fmt.Printf("%v --- 索引:%v ,值:%v\n", tmp, i2, tmp[i2])
        }
    }

}

/*
[张三 李四] --- 索引:0 ,值:张三
[张三 李四] --- 索引:1 ,值:李四
[二狗 胖蛋] --- 索引:0 ,值:二狗
[二狗 胖蛋] --- 索引:1 ,值:胖蛋
[石头 大狗] --- 索引:0 ,值:石头
[石头 大狗] --- 索引:1 ,值:大狗

*/

数组练习题

package main

import "fmt"

// 求数组 [2,4,6,8] 所有元素的和

func arraySum() {
    a1 := [...]int{2, 4, 6, 8, 10}
    sum := 0
    for _, v := range a1 {
        sum += v
    }
    fmt.Println("数组元素之和:", sum)
}

// 从数组 [1,3,5,7,8] 中找出 两元素之和为8的 下标,打印出如(0,3) , (1,2)
func arraySum2() {
    a2 := [...]int{1, 3, 5, 7, 8}
    // 嵌套循环,计算两数之和
    for i := 0; i < len(a2); i++ {
        for j := 0; j < len(a2); j++ {
            if a2[i]+a2[j] == 8 {
                fmt.Printf("找到啦:(%d,%d)", i, j)
            }
        }
    }
}

func main() {
    arraySum2() // (0,3)(1,2)(2,1)(3,0)

}

Go切片slice

  1. Go语言切片(Slice)
  2. 切片是可动态变化的序列,是对数组的引用引用类型,遵循引用传递的机制
  3. slice类型写作[ ]T,T是slice元素类型,var s1 []int,s1就是切片变量

slice前言

go数组长度固定,有较多局限性。

package main

import "fmt"

// 一个计算数组元素之和的函数
func arraySum(a [3]int) int {
    sum := 0
    for _, v := range a {
        sum += v
    }
    return sum
}

func main() {
    a := [3]int{1, 2, 3} //函数接收的数组,类型是固定死了,必须对应
    res := arraySum(a)
    fmt.Println("结果:", res)
}

// 但是我希望你这个函数,能随意接受我传入的不同长度的数组,是不是强大些。

切片是什么

  • 切片slice是一个拥有相同类型元素、可变长度的序列
  • slice是基于array类型做的一层封装,灵活、可以自动扩容。
  • slice是引用类型,内部结构包括、内存地址、长度、容量。
package main

import "fmt"

func main() {
    //各种类型slice初始化

    // 声明切片类型
    var a []string              //声明一个字符串切片
    var b = []int{}             //声明一个整型切片并初始化
    var c = []bool{false, true} //声明一个布尔切片并初始化
    var d = []bool{false, true} //声明一个布尔切片并初始化
    fmt.Printf("类型:%T 、值:%v\n", a, a)
    fmt.Printf("类型:%T 、值:%v\n", b, b)
    fmt.Printf("类型:%T 、值:%v\n", c, c)
    fmt.Printf("类型:%T 、值:%v\n", d, d)
    // 注意切片是引用类型,不支持直接比较值,如 c==d
    //数组是值类型,允许比较,如
    array1 := [...]int{1, 2, 3}
    array2 := [...]int{1, 2, 4} //注意数组的元素个数得一样, [3]int 和[4]int 是两种类型,无法比较!!!
    if array1 == array2 {
        fmt.Println("俩数组相等")
    } else {
        fmt.Println("俩数组值不等!!")
    }

}

/*
类型:[]string 、值:[]
类型:[]int 、值:[]
类型:[]bool 、值:[false true]
类型:[]bool 、值:[false true]

*/

切片属性

切片有自己的长度、容量、分别通过len()、cap()求值。

  • 切片的默认容量,是从切片的开始索引、到数组的最后

array > slice

package main

import "fmt"

func main() {
    //玩法1:切片底层原理就是一个数组,所以切片可以基于数组,基于切片表达式,获得切片。
    // 切片表达式 [头:尾]  ,左闭 ,右开 ,得到的切片  长度 = 尾-头 ,容量cap同于数组的容量。

    a1 := [5]int{1, 3, 5, 7, 9}
    s1 := a1[1:3]
    fmt.Printf("切片s1类型:%T、值:%v、长度:%d、容量:%d\n", s1, s1, len(s1), cap(s1))
    //切片的默认容量,是从切片的开始索引、到数组的最后
    // 切片s1类型:[]int、值:[3 5]、长度:2、容量:4

    fmt.Printf("源数组a1类型:%T、值:%v、长度:%d、容量:%d\n", a1, a1, len(a1), cap(a1))
    // 源数组a1类型:[5]int、值:[1 3 5 7 9]、长度:5、容量:5

    //切片表达式,[low:high:max]  max 理解为high可取的最大值,最终切片的容量等于 max - low
    s2 := a1[1:3:5] // 头:尾:max
    fmt.Printf("切片s2类型:%T、值:%v、长度:%d、容量:%d\n", s2, s2, len(s2), cap(s2))

    // 切片表达式,简写
    s3 := a1[:]  // 复制,等于 a1[0:len(a1)]
    s4 := a1[2:] // 等于a1[2:len(a1)]
    s5 := a1[:3] // 等于a[0:3]
    fmt.Printf("切片s3类型:%T、值:%v、长度:%d、容量:%d\n", s3, s3, len(s3), cap(s3))
    fmt.Printf("切片s4类型:%T、值:%v、长度:%d、容量:%d\n", s4, s4, len(s4), cap(s4))
    fmt.Printf("切片s5类型:%T、值:%v、长度:%d、容量:%d\n", s5, s5, len(s5), cap(s5))

}

/*
切片s1类型:[]int、值:[3 5]、长度:2、容量:4
源数组a1类型:[5]int、值:[1 3 5 7 9]、长度:5、容量:5
切片s2类型:[]int、值:[3 5]、长度:2、容量:4
切片s3类型:[]int、值:[1 3 5 7 9]、长度:5、容量:5
切片s4类型:[]int、值:[5 7 9]、长度:3、容量:3
切片s5类型:[]int、值:[1 3 5]、长度:3、容量:5

*/

直接定义slice

package main

import "fmt"

// 玩法3:指定索引
func slice1() {
    s1 := []int{1, 2, 3}
    fmt.Printf("切片s1类型:%T、值:%v、长度:%d、容量:%d\n", s1, s1, len(s1), cap(s1))

    s2 := []int{5: 1} // 索引5的值为1 ,该slice,长度6,容量6
    fmt.Printf("切片s2类型:%T、值:%v、长度:%d、容量:%d\n", s2, s2, len(s2), cap(s2))
    /*
       切片s1类型:[]int、值:[1 2 3]、长度:3、容量:3
       切片s2类型:[]int、值:[0 0 0 0 0 1]、长度:6、容量:6

    */
}

func main() {
    //玩法2:直接定义slice
    //s1 := []int{1, 3, 5, 7, 9}
    //fmt.Printf("切片s1类型:%T、值:%v、长度:%d、容量:%d\n", s1, s1, len(s1), cap(s1))
    //
    //s2 := s1[1:2:4] // 头:尾:4-1(cap)
    //fmt.Printf("切片s2类型:%T、值:%v、长度:%d、容量:%d\n", s2, s2, len(s2), cap(s2))
    //
    slice1()
}

/*
切片s1类型:[]int、值:[1 3 5 7 9]、长度:5、容量:5
切片s2类型:[]int、值:[3]、长度:1、容量:3

*/

slice原理

  • slice是一个轻量级数据结构,提供访问数组子序列元素的功能。

  • slice由三个部分构成,指针、长度、容量

  • 指针:指针指向slice第一个元素对应的数组元素的地址。

  • 长度:slice元素的数量,不得超过容量。

  • 容量:slice开始的位置到底层数组的结尾

image-20221224164909320

package main

import "fmt"

func main() {
    //创建数组,Months月份,1月份到12月份
    months := [...]string{"", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
    //创建切片,对数组的引用
    s1 := months[4:7] //[April May June]
    s2 := months[6:9] //[June July August]
    fmt.Println(s1)
    fmt.Println(s2)

    //指针:指针指向slice`第一个元素`对应的`数组元素`的地址。
    fmt.Printf("s1切片内存地址:%p\n", &s1[0])

    fmt.Printf("s1切片对应数组元素的内存地址:%p\n", &months[4])
}

/*
[April May June]
[June July August]
s1切片内存地址:0xc0000a26c0
s1切片对应数组元素的内存地址:0xc0000a26c0

*/

image-20221224165945174

slice修改

package main

import (
    "fmt"
)

func main() {
    array1 := [...]int{1, 3, 5, 7, 9} // 数组
    s1 := array1[2:4]                 // 切片 [5,7]
    fmt.Println("源数组:", array1)
    fmt.Println("源切片:", s1)

    //切片读写操作目标是底层数组
    s1[0] += 100
    s1[1] += 200
    fmt.Println("修改后源数组:", array1)
    fmt.Println("修改后切片:", s1)
}

make()构造切片

image-20221224171634514

创建切片有2个方式

  • 基于数组创建slice,数组可见
  • 通过内置make()函数,创建slice,底层数组被隐藏
package main

import (
    "fmt"
)

/*
内置make函数,参数(类型,len,cap),注意cap大于len,容量可以省略,默认等于长度
切片有默认值
如果你创建切片时,可以确定存储的元素个数,建议一次性给足cap,否则会产生扩容时的消耗
*/
var slice0 []int = make([]int, 10)
var slice1 = make([]int, 2)
var slice2 = make([]int, 3, 10)

func main() {
    fmt.Printf("make全局slice0 :%v、长度:%d、容量:%d\n", slice0, len(slice0), cap(slice0))
    fmt.Printf("make全局slice1 :%v、长度:%d、容量:%d\n", slice1, len(slice1), cap(slice1))
    fmt.Printf("make全局slice2 :%v、长度:%d、容量:%d\n", slice2, len(slice2), cap(slice2))
    fmt.Println("--------------------------------------")
    slice3 := make([]int, 3)
    slice4 := make([]int, 5)
    slice5 := make([]int, 5, 7)
    // 若是超出index范围、会panic报错
    slice5[0] = 11
    slice5[1] = 22
    slice5[2] = 33
    slice5[3] = 44
    slice5[4] = 55

    fmt.Printf("make局部slice3 :%v、长度:%d、容量:%d\n", slice3, len(slice3), cap(slice3))
    fmt.Printf("make局部slice4 :%v、长度:%d、容量:%d\n", slice4, len(slice4), cap(slice4))
    fmt.Printf("make局部slice5 :%v、长度:%d、容量:%d\n", slice5, len(slice5), cap(slice5))

}

/*
make全局slice0 :[0 0 0 0 0 0 0 0 0 0]、长度:10、容量:10
make全局slice1 :[0 0]、长度:2、容量:2
make全局slice2 :[0 0 0]、长度:3、容量:10
--------------------------------------
make局部slice3 :[0 0 0]、长度:3、容量:3
make局部slice4 :[0 0 0 0 0]、长度:5、容量:5
make局部slice5 :[11 22 33 44 55]、长度:5、容量:7

*/

直接定义切片

package main

import "fmt"

func main() {
    //第三种方式,原理类似make,数组看不见,由make维护
    var s1 []int = []int{1, 2, 3, 4, 5}
    fmt.Println(s1)
    fmt.Println(len(s1))
    fmt.Println(cap(s1))
}

切片拷贝

若两个切片拷贝后,也共享同一个底层数组,修改切片会影响另一个切片。

image-20221224173100804

package main

import "fmt"

func main() {
    s1 := make([]int, 3) //[0 0 0]
    s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
    s2[0] = 100
    fmt.Printf("s1类型:%T、值:%v、长度:%d、容量:%d,底层数组内存地址:%p\n", s1, s1, len(s1), cap(s1), &s1[0])
    fmt.Printf("s2类型:%T、值:%v、长度:%d、容量:%d,底层数组内存地址:%p\n", s2, s2, len(s2), cap(s2), &s2[0])

}

/*
s1类型:[]int、值:[100 0 0]、长度:3、容量:3,底层数组内存地址:0xc0000260a8
s2类型:[]int、值:[100 0 0]、长度:3、容量:3,底层数组内存地址:0xc0000260a8

*/

切片遍历

和数组一样,支持for range

package main

import "fmt"

func main() {
    var arr [5]int = [...]int{11, 22, 33, 44, 55}
    s1 := arr[1:4]

    //for循环遍历
    for i := 0; i < len(s1); i++ {
        fmt.Printf("切片s1 i=%v 值=%v\n", i, s1[i])
    }
    fmt.Println()

    //for range方式遍历切片
    for i, v := range s1 {
        fmt.Printf("切片s1 i=%v 值v=%v\n", i, v)
    }
}

/*
切片s1 i=0 值=22
切片s1 i=1 值=33
切片s1 i=2 值=44

切片s1 i=0 值v=22
切片s1 i=1 值v=33
切片s1 i=2 值v=44

*/

切片练习

package main

import "fmt"

func main() {
    var array1 = [...]int{11, 22, 33, 44}
    slice1 := array1[1:3]
    slice2 := array1[1:]
    slice3 := array1[:]
    slice4 := slice3[:2]
    fmt.Println(slice1)
    fmt.Println(slice2)
    fmt.Println(slice3)
    fmt.Println(slice4)
}

/*
[22 33]
[22 33 44]
[11 22 33 44]
[11 22]

*/

切片扩容append()函数

  • Go语言内置的append()函数,可以让切片动态添加新元素,一次添加一个、多个、甚至追加另一个切片。

  • 每个切片都会指向一个底层数组,数组的容量还够就添加新元素,长度增大。

  • 底层数组的容量不能容纳新元素时,切片按照一定规则扩容
    • 此时切片指向的数组已更换
package main

import "fmt"

func main() {
    //创建切片
    var slice1 []int = []int{100, 200, 300}
    fmt.Printf("slice1容量=%v 长度=%v\n", cap(slice1), len(slice1))
    //给切片追加新元素,由于原本的len、cap都是3,再加薪元素,就触发了扩容
    slice1 = append(slice1, 400)
    fmt.Printf("新slice1值:%v、扩容后容量=%v 长度=%v\n", slice1, cap(slice1), len(slice1))

    // 切片扩容切片
    // slice1... 该语法糖代表展开切片元素
    slice2 := []int{1, 2, 3, 4}
    // 由于append扩容后的数组更换,我们一般会用源变量接收append返回值
    slice1 = append(slice1, slice2...)
    fmt.Printf("新slice1值:%v、扩容后容量=%v 长度=%v\n", slice1, cap(slice1), len(slice1))

}

/*
新slice1值:[100 200 300 400]、扩容后容量=6 长度=4
新slice1值:[100 200 300 400 1 2 3 4]、扩容后容量=12 长度=8

*/

查看slice扩容规则

image-20221224175927726

package main

import "fmt"

func main() {
    //append()添加元素和切片扩容
    var numSlice []int
    for i := 0; i < 100; i++ {
        numSlice = append(numSlice, i)
        fmt.Printf(" len:%d  cap:%d  内存地址:%p\n", len(numSlice), cap(numSlice), numSlice)
    }
}

支持追加多个元素

package main

import "fmt"

func main() {
    //append()添加元素和切片扩容
    var numSlice []int
    fmt.Printf("源切片:%v、长度:%d、容量:%d、内存地址:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
    numSlice = append(numSlice, 1, 2, 3, 4)
    fmt.Printf("新切片:%v、长度:%d、容量:%d、内存地址:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
    //若以如下语法糖追加新元素,不得传入多余参数
    s2 := []int{77, 88}
    numSlice = append(numSlice, s2...)
    fmt.Printf("新切片:%v、长度:%d、容量:%d、内存地址:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
    /*
       源切片:[]、长度:0、容量:0、内存地址:0x0
       新切片:[1 2 3 4]、长度:4、容量:4、内存地址:0xc0000220e0
       新切片:[1 2 3 4 77 88]、长度:6、容量:8、内存地址:0xc0000240c0

    */
}

切片扩容原理

image-20221224181010214

// 从小切片增长 2x 过渡
// 大切片增长 1.25 倍。 这个公式
// 在两者之间提供平滑的过渡。

测试

>>> 3408/2560
1.33125
>>> 2560/1792
1.4285714285714286
>>> 1792/1280
1.4
>>> 1280/848
1.509433962264151
>>> 848/512
1.65625
>>> 512/256
2.0
>>>

// 测试扩容机制
package main

import "fmt"

func main() {
    //append()添加元素和切片扩容
    var numSlice []int
    for i := 0; i < 3000; i++ {
        numSlice = append(numSlice, i)
        fmt.Printf(" len:%d  cap:%d  内存地址:%p\n", len(numSlice), cap(numSlice), numSlice)
    }
}

切片拷贝copy()函数

Go语言提供了内置的copy()函数专门用于拷贝切片。

为何需要copy()

package main

import "fmt"

func main() {
    //为什么需要copy()函数去完整拷贝新的切片
    a1 := []int{1, 3, 5, 7, 9}
    b1 := a1
    fmt.Printf("类型:%T、值:%v,地址:%p\n", a1, a1, a1)
    fmt.Printf("类型:%T、值:%v,地址:%p\n", b1, b1, b1)
    //修改b1,同时也会修改a1
    b1[0] = 111
    fmt.Printf("类型:%T、值:%v,地址:%p\n", a1, a1, a1)
    fmt.Printf("类型:%T、值:%v,地址:%p\n", b1, b1, b1)
}

/*
类型:[]int、值:[1 3 5 7 9],地址:0xc00001a180
类型:[]int、值:[1 3 5 7 9],地址:0xc00001a180
类型:[]int、值:[111 3 5 7 9],地址:0xc00001a180
类型:[]int、值:[111 3 5 7 9],地址:0xc00001a180
切片是引用类型,a、b都指向了同一个内存地址。
修改b导致了a也变化。

*/

使用copy

Go语言内置的copy()函数可以快速的将slice1的数据,复制到另一个新的slice2里。

//语法
copy(destSlice, srcSlice []T)

srcSlice: 数据来源切片
destSlice: 目标切片

案例

package main

import "fmt"

func main() {
    // 两个的独立的切片,拷贝数据
    a1 := []int{1, 3, 5, 7, 9}
    b1 := make([]int, len(a1))
    //完全拷贝
    copy(a1, b1) //a1数据,拷给了b1
    fmt.Printf("a1类型:%T、值:%v,地址:%p\n", a1, a1, a1)
    fmt.Printf("b1类型:%T、值:%v,地址:%p\n", b1, b1, b1)
    //修改b1,和a1无关了
    b1[0] = 111
    fmt.Printf("修改后a1类型:%T、值:%v,地址:%p\n", a1, a1, a1)
    fmt.Printf("修改后b1类型:%T、值:%v,地址:%p\n", b1, b1, b1)
}

/*
a1类型:[]int、值:[0 0 0 0 0],地址:0xc00011a000
b1类型:[]int、值:[0 0 0 0 0],地址:0xc00011a030
修改后a1类型:[]int、值:[0 0 0 0 0],地址:0xc00011a000
修改后b1类型:[]int、值:[111 0 0 0 0],地址:0xc00011a030

*/

删除切片元素技巧

Go语言本身没提供删除切片元素的方法,可以利用切片表达式,删除部分元素、

package main

import "fmt"

func main() {
    a1 := []int{1, 3, 5, 7, 9}
    //利用append特性,截取丢弃数据
    //删除元素5
    // 语法,a = append(a1[:index],a1[index+1:]...)
    a1 = append(a1[:2], a1[3:]...)
    fmt.Println(a1)
}

string和slice的技巧

1.string底层就是byte数组,因此string可以有slice一样用法。

package main

import "fmt"

func main() {
    str1 := "www.yuchaoit.cn"
    //字符串可以切片
    s1 := str1[:4]
    fmt.Println(s1)  // www.
}

2.如何修改字符串、转为切片后修改

package main

import "fmt"

func main() {
    str1 := "www.yuchaoit.cn"
    //类型强转 []byte(),这里是纯英文,若是汉字用[]rune处理
    s1 := []byte(str1)
    fmt.Printf("s1切片类型:%T、值:%v\n", s1, s1)
    //修改切片元素,修改最后一个元素
    s1[len(s1)-1] = 'c'
    fmt.Printf("修改后s1切片类型:%T、值:%v\n", s1, s1)
    //转回字符串
    str1 = string(s1)
    fmt.Printf("修改后str1字符串类型:%T、值:%v\n", str1, str1)

}

slice练习题

看题说答案

package main

import "fmt"

func main() {
    var a = make([]string, 5, 10) //[] 5个空字符串
    for i := 0; i < 10; i++ {
        a = append(a, fmt.Sprintf("%v", i))
    }
    fmt.Printf("go原生值:%#v\n", a)
    fmt.Printf("go普通值:%v\n", a)
}
Copyright © www.yuchaoit.cn 2025 all right reserved,powered by Gitbook作者:于超 2022-12-25 20:11:36

results matching ""

    No results matching ""