切片

简介

  简单地说,切片就是一种简化版的动态数组。Go 数组的长度不可改变,而切片长度是不固定,切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方,但是数组的类型和操作都不够灵活,因此在Go代码中数组使用的并不多。而切片则使用得相当广泛,理解切片的原理和用法是一个Go程序员的必备技能。当进行append与copy函数操作时会对真实数据进行内存拷贝,append的时长度len超过申请的空间cap进行内存真实拷贝

初始化

package main

import "fmt"

func main() {
// 第一种方式 对比数组不指定size大小
var slice1 = [] int{1,2,3,4}
// 第二种方式
slice2 := [] int{1,2,3,4}
// 第三种方式 make生成空切片
var slice3 []int = make([]int,3,5)
// 第四种方式 简写 len=3 cap=5
slice4 := make([]int,3,5) fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)
fmt.Println(slice4)
} //[1 2 3 4]
//[1 2 3 4]
//[0 0 0]
//[0 0 0]

数据结构

我们先看看切片的结构定义,reflect.SliceHeader

type SliceHeader struct {
// 指向数组内存地址 赋值时拷贝的是数组地址
Data uintptr
// 长度
Len int
// 申请空间
Cap int
}

可以看出切片的开头部分和Go字符串是一样的,但是切片多了一个Cap成员表示切片指向的内存空间的最大容量(对应元素的个数,而不是字节数)。下图是x := []int{2,3,5,7,11}y := x[1:3]两个切片对应的内存结构。

赋值、切片与copy

赋值等同于把结构体SliceHeader 内的变量拷贝了一份,并未进行真实数据拷贝, Data与初始切片指向的是同一块内存地址

package main

import "fmt"

func main() {
a := [] int{1,2,3,4}
b := a
// Data ptr指向的内存地址一致
fmt.Printf("a[0] %p\n",&a[0])
fmt.Printf("b[0] %p\n",&b[0])
// 修改元素值相互影响
b[0] = 10
fmt.Println(a)
fmt.Println(b)
} //a[0] 0xc000054140
//b[0] 0xc000054140
//[10 2 3 4]
//[10 2 3 4]

切片时,新生成的切片Data指向初始切片位置元素的内存地址,对元素值进行修改时,相互影响

package main

import "fmt"

func main() {
a := [] int{1,2,3,4}
b := a[:]
// 全切片时Data ptr指向a的第一个元素的内存地址
fmt.Printf("a[0] %p\n",&a[0])
fmt.Printf("b[0] %p\n",&b[0])
// 修改元素值相互影响
b[0] = 10
fmt.Println(a)
fmt.Println(b)
} //a[0] 0xc000054140
//b[0] 0xc000054140
//[10 2 3 4]
//[10 2 3 4] 

copy函数进行操作时,会对真实数据进行内存拷贝,新的切片Data指向新的地址

package main

import "fmt"

func main() {
a := [] int{1,2,3,4}
b := make([]int, len(a), cap(a))
copy(b,a)
// Data ptr指向的新的内存地址
fmt.Printf("a[0] %p\n",&a[0])
fmt.Printf("b[0] %p\n",&b[0])
// 修改元素值相互不影响
b[0] = 10
fmt.Println(a)
fmt.Println(b)
} //a[0] 0xc000054140
//b[0] 0xc000054160
//[1 2 3 4]
//[10 2 3 4]

添加元素

内置的泛型函数append可以在切片的尾部追加N个元素:

package main

import "fmt"

func main() {
var a []int
// 追加1个元素
a = append(a, 1)
// 追加多个元素, 手写解包方式
a = append(a, 1, 2, 3)
// 追加一个切片, 切片需要解包
a = append(a, []int{1,2,3}...) fmt.Println(a)
} //[1 1 2 3 1 2 3]

不过要注意的是,在容量不足的情况下,append的操作会导致重新分配内存,可能导致巨大的内存分配和复制数据代价。即使容量足够,依然需要用append函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。

除了在切片的尾部追加,我们还可以在切片的开头添加元素:

package main

import "fmt"

func main() {
var a = []int{1,2,3}
// 在开头添加1个元素
a = append([]int{0}, a...)
// 在开头添加1个切片
a = append([]int{-3,-2,-1}, a...) fmt.Println(a)
} //[-3 -2 -1 0 1 2 3]

在开头一般都会导致内存的重新分配,而且会导致已有的元素全部复制1次。因此,从切片的开头添加元素的性能一般要比从尾部追加元素的性能差很多。

由于append函数返回新的切片,也就是它支持链式操作。我们可以将多个append操作组合起来,实现在切片中间插入元素:

package main

import "fmt"

func main() {
var a []int
// 在第i个位置插入x
a = append(a[:i], append([]int{x}, a[i:]...)...)
// 在第i个位置插入切片
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...)
} //[-3 -2 -1 0 1 2 3]

append与内存地址变化

当指向append操作时,会对切片的真实数据进行内存拷贝,与初始切片互不影响

package main

import "fmt"

func main() {
a := [] int{1,2,3,4}
b := append(a, 5)
// 执行append时 Data ptr指向全新的内存地址
fmt.Printf("a[0] %p\n",&a[0])
fmt.Printf("b[0] %p\n",&b[0])
// 修改元素值相互不影响
b[0] = 10
fmt.Println(a)
fmt.Println(b)
} //a[0] 0xc000054140
//b[0] 0xc00006e100
//[1 2 3 4]
//[10 2 3 4 5]

  

删除元素

利用切片和append操作组合进行删除

package main

import "fmt"

func main() {
a := []int{1, 2, 3, 4, 5}
// 删除尾部1个元素
a = a[:len(a)-1]
fmt.Println(a)
// 删除头部1个元素
a = a[1:len(a)]
fmt.Println(a)
// 删除中间一个元素
a = append(a[:1],a[2:]...)
fmt.Println(a)
} //[1 2 3 4]
//[2 3 4]
//[2 4]

函数传参

切片作为参数传递是,与赋值一致,只拷贝了结构体中的变量,Data指向的是同一块地址

package main

import "fmt"

func change(list []int){
// 拷贝了 Data ptr指向的内存地址
fmt.Printf("list %p\n",&list[0])
// 对切片进行修改
list[0] = 100
} func main() {
list := [] int{1,2,3,4}
fmt.Printf("list %p\n",&list[0])
change(list)
// slice 受影响
fmt.Println(list)
} //list 0xc000054140
//list 0xc000054140
//[100 2 3 4]

  

Cap超出时才会重新内存分配

package main

import "fmt"

func main()  {
slice := []int{1,2,3,4}
slice1 := slice
slice2 := append(slice[:2],5) fmt.Println(slice2)
fmt.Println(slice1) fmt.Println(&slice2[2])
fmt.Println(&slice1[2])
} //[1 2 5]
//[1 2 5 4]
//0xc00000e370
//0xc00000e370

 

切片与Cap之间的关系

package main

import "fmt"

func main()  {
// len = cap
a := [] int {1,2,3,4,5}
fmt.Printf("len(a)=%d,cap(a)=%d\n",len(a),cap(a)) b := a[1:3]
fmt.Println("b =",b)
fmt.Printf("len(b)=%d,cap(b)=%d\n",len(b),cap(b)) c := a[:]
fmt.Println("c =",c)
fmt.Printf("len(c)=%d,cap(c)=%d\n",len(c),cap(c)) d := a[:4]
fmt.Println("d =",d)
fmt.Printf("len(d)=%d,cap(d)=%d\n",len(d),cap(d)) // len != cap
e := d[:2]
fmt.Println("e =",e)
fmt.Printf("len(e)=%d,cap(e)=%d\n",len(e),cap(e)) f := d[1:3]
fmt.Println("f =",f)
fmt.Printf("len(f)=%d,cap(f)=%d\n",len(f),cap(f))
} //len(a)=5,cap(a)=5
//b = [2 3]
//len(b)=2,cap(b)=4
//c = [1 2 3 4 5]
//len(c)=5,cap(c)=5
//d = [1 2 3 4]
//len(d)=4,cap(d)=5
//e = [1 2]
//len(e)=2,cap(e)=5
//f = [2 3]
//len(f)=2,cap(f)=4

  

Go语言【数据结构】切片的更多相关文章

  1. go语言 rune切片

    go语言 rune切片 示例 package main import ( "fmt" ) //http://www.cnblogs.com/osfipin/ func main() ...

  2. go语言之切片即动态数组

    切片和数组的类型有什么不一样,我们可以打印一下,就可以知道两者的区别了,数组是容量的,所以中括号中有容量,切片的动态数组,是没有容量,这是数组和切片最大的区别 test8_4 := [20] int ...

  3. Python语言数据结构和语言结构(2)

    目录 1. Python预备基础 2. Python数据类型 3. Python条件语句 4. while循环和for循环 1. Python预备基础 1.1 变量的命名   变量命名规则主要有以下几 ...

  4. go中的数据结构切片-slice

    1.部分基本类型 go中的类型与c的相似,常用类型有一个特例:byte类型,即字节类型,长度为,默认值是0: bytes = []btye{'h', 'e', 'l', 'l', 'o'} 变量byt ...

  5. Go语言中切片的内部实现和基础功能

    切片是一种数据结构,这种数据结构便于使用和管理数据集合.切片是围绕动态数组的概念构建的,可以按需自动增长和缩小.切片的动态增长是通过内置函数append来实现的.这个函数可以快速且高效的增长切片.还可 ...

  6. 读谭浩强C语言数据结构有感(1)

    1.什么是数据结构? 数据结构,就是我们计算机内部的运算,编程语言的基础工作模式吧,个人总结的 = = !! 数据:说简单一点,就是计算机二进制机器码,然后通过一些复杂的操作,变为复杂的语言. 数据元 ...

  7. C语言数据结构----栈与递归

    本节主要说程序中的栈函数栈的关系以及栈和递归算法的关系. 一.函数调用时的栈 1.程序调用时的栈是也就是平时所说的函数栈是数据结构的一种应用,函数调用栈一般是从搞地质向低地址增长的,栈顶为内存的低地址 ...

  8. Go语言的切片

    Go 语言切片(Slice) Go 语言切片是对数组的抽象. Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组" ...

  9. 第二章 R语言数据结构

    R语言存储数据的结构包括:标量.向量.矩阵.数组.数据框和列表:可以处理的数据类型包括:数值型.字符型.逻辑型.复数型和原生型. 数据结构 向量 向量是用来存储数值型.字符型或逻辑型数据的一维数组.单 ...

  10. C语言---数据结构(内建,数组,自定义)

    数组是一组有序数据的集合,每个元素都属于同一个数据类型. 一维数组的定义: 类型符  数组名[常量表达式] 常量表达式中,可以包括常量和符号常量,int a[3+5]是合法的.但是不能包含int a[ ...

随机推荐

  1. 小程序开发:用Taro搭建框架

    1.node环境 1) 下载 . 官方地址:https://nodejs.org/en/ 或 https://nodejs.org/zh-cn/ 2)安装. 一路next......Install.直 ...

  2. bugku——蹭网先解开密码(EWSA,hashcat破解wifi握手包)

    题目地址:http://ctf.bugku.com/files/77decd384a172b9a2294e6c6acfd48cc/wifi.cap 下载是个.cap的文件,破解过wifi的童鞋肯定知道 ...

  3. 未能加载文件或程序集“Spire.Pdf, Version=4.8.8.2020, Culture=neutral, PublicKeyToken=663f351905198cb3”或它的某一个依赖项。未能授予最小权限请求

    问题:运行程序执行到代码报错:未能加载文件或程序集“Spire.Pdf, Version=4.8.8.2020, Culture=neutral, PublicKeyToken=663f3519051 ...

  4. Collaborative Spatioitemporal Feature Learning for Video Action Recognition

    Collaborative Spatioitemporal Feature Learning for Video Action Recognition 摘要 时空特征提取在视频动作识别中是一个非常重要 ...

  5. [TJOI2015]弦论(后缀自动机)

    传送门 题意: 对给定字符串\(s\),求其第\(k\)小子串,重复串被计入以及不被计入这两种情况都需考虑. 思路: 首先构建后缀自动机,之后就考虑在后缀自动机上\(dp\). 我们知道如果要考虑重复 ...

  6. 缓存中,2个注解:@cacheable 与 @cacheput 的区别

    @cacheable:只会执行一次,当标记在一个方法上时表示该方法是支持缓存的,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果. @cac ...

  7. android 发布时去除Log

    1) project.properties文件里,去掉下面一行的注释: # To enable ProGuard to shrink and obfuscate your code, uncommen ...

  8. Linux下使用cx_Oracle的一些配置

    在安装完成cx_Oracle后,import  cx_Oracle时报错,首先查看.bash_profile文件中环境变量配置 # .bash_profile # Get the aliases an ...

  9. MySQL 事务配置命令行操作和持久化

    MySQL 事务配置命令行操作和持久化 参考 MySQL 官方参考手册 提供 5.5 5.6 5.7 8.0 版本的参考手册 https://dev.mysql.com/doc/refman/5.5/ ...

  10. ActiveMQ传输协议

    ActiveMQ默认的传输协议是TCP 在activemq的配置文件 /conf/activemq.xml可对配置文件进行修改和查看