golang的切片

切片

为什么需要切片

  1. 用于元素的个数不确定,所以无法通过数组的形式来进行统计。此时就需要切片
    切片,也因此可以粗略地理解为动态数组
  2. 数组的长度不能用变量来确定,这时候切片slice也就派上用场了

切片地基本介绍

  1. 切片的英文是slice
  2. 切片是数组的一种引用,这也说明切片是引用类型,在传递时,遵守引用传递的机制
  3. 切片的使用和数组类似,遍历、求长、访问都是一样的
  4. 切片的长度是可变的,因此说切片可以粗略理解为动态数组
  5. 切片定义的语法:
    var 切片名 []类型
    var a []int

切片的快速入门

var arr [5]int = [5]int{1, 2, 45, 6, 3}
a := arr[2:4]
fmt.Println(arr)
fmt.Println(a)
fmt.Printf(“a的长度%d\n”, len(a))
fmt.Printf(“a的容量%d\n”, cap(a))
fmt.Printf(“a指向的地址%v\n”, &a[0])
输出结果为:
[1 2 45 6 3]
[45 6]
a的长度2
a的容量3
a指向的地址0xc000010240

可见,a := arr[2:4],这段代码就是将arr数组的下标为2-3的元素。
其中cap()函数是用来求切片容量的,容量是指切片目前最多能存储的元素个数,并且这个容量是可以动态更改的

切片在内存中的形式

  1. slice在内存中有三个值,一个是slice所指向的第一个元素的地址,一个是这个slice的长度,还有一个是这个slice的容量
  2. 根据这第一个值,我们也很容易判断出slice是引用类型
  3. 其实,我们可以把slice看成一种结构体struct。其由这三个值组成。
    type slice struct {
    array *[n]T // 指向底层数组的指针
    len int // 当前长度
    cap int // 总容量
    }
  4. 另外,值得注意的一点是,由于slice是引用类型,所以当用slice[1]来修改值时,其所指的地址的值发送改变,也就是原数组的内容也会发送改变
    代码用例:
    var arr [5]int = [5]int{1, 2, 45, 6, 3}
    a := arr[2:4]
    fmt.Println(arr)
    fmt.Println(a)
    a[0] = 100
    fmt.Println(arr)
    fmt.Println(a)
    输出结果为:
    [1 2 45 6 3]
    [45 6]
    [1 2 100 6 3]
    [100 6]

切片的使用

  1. 定义一个切片,让切片去引用一个定义好的数组
  2. 用make来创建切片
    基本语法:var 切片名 []类型 = make([]类型,切片长度,切片容量)
    要求:容量必须大于等于长度
    创建完的切片里的元素都是默认值

这样定义出来的切片所指向的数组,是对外隐蔽的,只能通过slice来访问这个数组

  1. 定义切片的时候,直接指定具体的数组
    代码示例:
    func main() {
    var arr []int = []int{1, 2, 41, 21}
    fmt.Printf(“%v\n”, len(arr))
    fmt.Printf(“%v”, arr)
    }
  2. 方式一和方式二的区别:
    方式一:直接引用数组,是要求先定义好了一个数组,这个数组是可见的
    方式二:用make创建切片的同时,也会创建数组,这个数组是由切片在底层进行维护的数组,是不可见的,只能通过slice来对它进行访问修改等

切片的遍历

  1. 传统for循环
  2. for-range语句遍历
    func main() {
    var n [5]int = [5]int{2, 12, 31, 12, 45}
    slice1 := n[1:3]
    for i := 0; i < len(slice1); i++ {
    fmt.Printf("%v “, slice1[i])
    }
    fmt.Println()
    for index, value := range slice1 {
    fmt.Printf(”%v,%v ", index, value)
    }
    }

切片的使用细节和注意事项

  1. 切片初始化时,var slice = arr[start:end]

其中切片slice是从arr数组的start下标开始,到end下标结束,但是不将end下标的元素也囊括在内

  1. 在使用切片时,也只能按照0-len(slice)-1的范围来,不能够直接来越界初始化,需要先动态增长先
  2. slice :=arr[下标1:下标2]
    1. 如果不写下标1,默认是0,从头开始
    2. 如果不写下标2,默认是end,把最后一位元素也算进去
    3. 所以要想表示切片获取整个数组,有如下几种简写
      1. slice :=arr[:]
      2. slice :=arr[0:]
      3. slice :=arr[:len(arr)]
  3. cap是一个内置函数,用于计算切片的容量,也就是最大可以存放多少元素
  4. 切片定义完后,本身还是空的,需要让其引用数组,或者用make函数创建一个空间供切片使用
  5. 切片可以继续切片
  6. append函数
    1. append的用法
      1. slice = append(slice, elem1, elem2, …)

      给slice切片添加后面几个元素,这里的…表示的是省略

      1. slice = append(slice, anotherSlice…)

      在slice后面接收另一个切片,这里的…是不能省略的,表示将后面的切片各个元素拆开加入
      不论append的哪种用法,都必须要有接收,不然相当于没有任何作用,因为append函数并不会去改变原来的切片

    2. 扩不扩容
      1. 当长度在append后>容量时才会扩容,否则不会
        2. 只有在扩容时,才会执行下面的(45678),因为如果不用扩容,就可以直接在原来的切片上更改,不需要额外创建新数组(拥有足够大的容量)
    3. append函数本质就是对数组的扩容
    4. append函数会创建一个新的数组newarr(是扩容后的大小)
    5. 将slice原来的元素拷贝到newarr
    6. 在newarr上添加新元素
    7. 返回新的切片(指向新数组),比如:slice重新引用到newarr
    8. newarr是底层维护的,程序员不可见
  7. 切片的拷贝
    copy函数
    语法copy(para1,para2)
    para1,para2这两个都需要是切片,并且是将para2的内容给到para1里面

此处不在乎二者的长度的大小关系,反正就是逐位去拷贝

拷贝并不会使这两个切片的空间有什么关联,依然是相互独立的

  1. 切片是引用类型,所以传递时遵循引用传递,向函数传递切片时,函数内部对切片的操作,也会对外部操作有影响
    代码示例:
    func test(a []int) {
    a[0] = 100
    }
    func main() {
    var a []int = make([]int, 4, 8)
    a[0] = 1
    a[1] = 2
    a[2] = 3
    a[3] = 4
    fmt.Println(a)
    test(a)
    fmt.Println(a)
    }
    输出结果:
    [1 2 3 4]
    [100 2 3 4]

string和slice的关系

  1. string的底层是一个byte数组,因此string也可以用切片来处理
  2. string的核心结构:
    type stringStruct struct {
    str unsafe.Pointer // 指向底层字节数组的指针
    len int // 字符串的字节长度(不是字符数)
    }
    这和切片的结构是很像的,只是没有容量,因为数组的容量大小就是长度大小,因为它长度不可变
  3. string本身是不可变的,不能通过str[0]="1"这种类似形式去修改(和java一样)
  4. 如果要修改string的值,需要先将string转换成[]byte或者[]rune,修改完后,再转成string

有中文的情况转成[]rune,因为[]rune是按照字符,一个中文占三四个字节

在Go语言中,切片(slice)是一个动态数组,它提供了对数组的部分或全部元素的访问和操作功能。切片的底层是通过数组实现的,但与数组相比,切片具有更强大的功能和灵活性。切片的底层实现原理是使用了一个结构体(runtime.slice)来表示切片的结构信息,包括指向底层数组的指针、切片的长度和容量等信息。切片的长度表示切片中实际存储的元素个数,而容量则表示切片底层数组的长度。切片可以根据需要动态扩容,当切片的长度超过了容量时,会重新分配更大的底层数组,并将原来的元素复制到新的底层数组中。这个过程是由Go语言的运行时系统自动完成的,不需要程序员手动操作。使用切片作为参数传递时,性能损耗很小,因为切片作为参数是传递的是一个结构体实例。切片作为参数具有比数组更强大的功能和灵活性,因此在Go语言中推荐使用切片而不是数组作为参数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Golang切片的底层实现原理](https://blog.youkuaiyun.com/m0_72560813/article/details/129231704)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值