Go基础Slice教程详解

Go 语言切片(Slice)

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

声明Slice

带有 T 类型元素的切片由 []T 表示,其中T代表slice中元素的类型。切片在内部可由一个结构体类型表示,形式如下:

type slice struct {
 Length  int
 Capacity  int
 ZerothElement *byte
}

可见一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。通过len和cap函数分别返回slice的长度和容量。

创建Slice

直接声明创建 slice

[]<元素类型>{元素1, 元素2, …}

创建一个有 3 个整型元素的数组,并返回一个存储在 c 中的切片引用。

c := []int{6, 7, 8}

make() 函数创建 slice

s1 := make([]int, 5) //长度和容量都是 5
 s2 := make([]int, 3, 10) //长度是3,容量是10
 fmt.Println(cap(s1),s2)

基于底层数组数组或切片创建

基于现有的切片或者数组创建,使用[i:j]这样的操作符即可,她表示以i索引开始,到j索引结束,截取原数组或者切片,创建而成的新切片,新切片的值包含原切片的i索引,但是不包含j索引。注意i和j都不能超过原切片或者数组的索引

slice :=[]int{1,2,3,4,5}
 slice1 := slice[:]
 slice2 := slice[0:]
 slice3 := slice[:5]
 fmt.Println(slice1)
 fmt.Println(slice2)
 fmt.Println(slice3)

新的切片和原数组或原切片共用的是一个底层数组,所以当修改的时候,底层数组的值就会被改变,所以原切片的值也改变了。

slice := []int{1, 2, 3, 4, 5}
 newSlice := slice[1:3]
 newSlice[0] = 10
 fmt.Println(slice)
 fmt.Println(newSlice)

切片与数组的区别

1.切片不是数组,但是切片底层指向数组

2.切片本身长度是不一定的因此不可以比较,数组是可以的。

3.切片是变长数组的替代方案,可以关联到指向的底层数组的局部或者全部。

4.切片是引用传递(传递指针地址),而数组是值传递(拷贝值)

5.切片可以直接创建,引用其他切片或数组创建

6.如果多个切片指向相同的底层数组,其中一个值的修改会影响所有的切片

切片的修改

切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。

package main
import (
 "fmt"
)
func main() {
 arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 slice := arr[2:5]
 fmt.Println("array before", arr)
 for i := range slice {
  slice[i]++
 }
 fmt.Println("array after ", arr)
}

在上述程序的第 9 行,我们根据数组索引 2,3,4 创建一个切片 dslice 。for 循环将这些索引中的值逐个递增。当我们使用 for 循环打印数组时,我们可以看到对切片的更改反映在数组中。该程序的输出是

array before [0 1 2 3 4 5 6 7 8 9]
array after [0 1 3 4 5 5 6 7 8 9]

当多个切片共用相同的底层数组时,每个切片所做的更改将反映在数组中。

package main
import (
 "fmt"
)
func main() {
 array := [4]int{10, 20 ,30, 40}
 slice1 := array[:]
 slice2 := array[:]
 fmt.Println("array before change:", array)
 slice1[0] = 60
 fmt.Println("array after modification to slice slice1:", array)
 slice2[1] = 70
 fmt.Println("array after modification to slice slice2:", array)
}

在 9 行中, numa [:] 缺少开始和结束值。开始和结束的默认值分别为 0 和 len (numa) 。两个切片 nums1 和 nums2 共享相同的数组。该程序的输出是

array before change: [10 20 30 40]
array after modification to slice slice1: [60 20 30 40]
array after modification to slice slice2: [60 70 30 40]

从输出中可以清楚地看出,当切片共享同一个数组时,每个所做的修改都会反映在数组中。

切片的长度和容量

切片的长度是切片中的元素数。 切片的容量是从创建切片索引开始的底层数组中元素数。

package main
import (
 "fmt"
)
func main() {
 fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
 fruitslice := fruitarray[1:3]
 fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) // length of is 2 and capacity is 6
}

在上面的程序中, fruitslice 是从 fruitarray 的索引 1 和 2 创建的。 因此, fruitlice 的长度为 2 。

fruitarray 的长度是 7。 fruiteslice 是从 fruitarray 的索引 1 创建的。因此, fruitslice 的容量是从 fruitarray 索引为 1 开始,也就是说从 orange 开始,该值是 6 。因此, fruitslice 的容量为 6。该程序]输出切片的 **长度为 2 容量为 6 **。

切片可以重置其容量。任何超出这一点将导致程序运行时抛出错误。

package main
import (
 "fmt"
)
func main() {
 fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
 fruitslice := fruitarray[1:3]
 fmt.Printf("length of slice %d capacity %d\n", len(fruitslice), cap(fruitslice)) // length of is 2 and capacity is 6
 fruitslice = fruitslice[:cap(fruitslice)] // re-slicing furitslice till its capacity
 fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice))
}

在上述程序的第 11 行中, fruitslice 的容量是重置的。以上程序输出为,

length of slice 2 capacity 6
After re-slicing length is 6 and capacity is 6

追加切片元素

正如我们已经知道数组的长度是固定的,它的长度不能增加。 切片是动态的,使用 append 可以将新元素追加到切片上。append 函数的定义是

func append(s[]T,x ... T)[]T

append可以直接在切片尾部追加元素,也可以将一个切片追加到另一个切片尾部。

package main
import (
 "fmt"
)
func main() {
 str := []string{"a", "b", "c"}
 fmt.Println("strs:", str, " length:", len(str), "capacity:", cap(str))
 str = append(str, "d")
 fmt.Println("strs:", str, " length:", len(str), " capacity:", cap(str))
}

在上述程序中, str 的容量最初是 3。在第 10 行,我们给 str 添加了一个新的元素,并把 append(str, "d") 返回的切片赋值给 str。现在 str 的容量翻了一番,变成了 6。

strs: [a b c] length: 3 capacity: 3
strs: [a b c d] length: 4 capacity: 6

切片类型的零值为 nil 。一个 nil 切片的长度和容量为 0。可以使用 append 函数将值追加到 nil 切片。

package main
import (
 "fmt"
)
func main() {
 var strs []string //zero value of a slice is nil
 if strs == nil {
  fmt.Println("slice is nil going to append")
  strs = append(strs, "a", "b", "c")
  fmt.Println("string contents:",strs)
 }
}

在上面的程序 names 是 nil,我们已经添加 3 个字符串给 names 。该程序的输出是

slice is nil going to append
string contents: [a b c]

也可以使用 ... 运算符将一个切片添加到另一个切片。

package main
import (
  "fmt"
)
func main() {
  veggies := []string{"potatoes", "tomatoes", "brinjal"}
  fruits := []string{"oranges", "apples"}
  food := append(veggies, fruits...)
  fmt.Println("food:",food)
}

在上述程序的第 10 行,food 是通过 append(veggies, fruits...) 创建。程序的输出为 food: [potatoes tomatoes brinjal oranges apples]

特别需要注意的是如果新切片的长度未超过源切片的容量,则返回源切片,如果追加后的新切片长度超过源切片的容量,则会返回全新的切片。

func main() {
  s1 := []int{1,2,3,4,5}
  fmt.Printf("s1:%p %d %d %v\n",s1,len(s1),cap(s1),s1)
  s2 :=append(s1,6)
  fmt.Printf("s3:%p %d %d %v\n",s2,len(s2),cap(s2),s2)
  s3 := s1[0:4]
  fmt.Printf("s3:%p %d %d %v\n",s3,len(s3),cap(s3),s3)
  s4 := append(s3,6)
  fmt.Printf("s4:%p %d %d %v\n",s4,len(s4),cap(s4),s4)
  fmt.Printf("s1:%p %d %d %v\n",s1,len(s1),cap(s1),s1)
  s5 := append(s4,8)
  fmt.Printf("s5:%p %d %d %v\n",s5,len(s5),cap(s5),s5)
}

切片的函数传递

切片包含长度、容量和指向数组第零个元素的指针。当切片传递给函数时,即使它通过值传递,指针变量也将引用相同的底层数组。因此,当切片作为参数传递给函数时,函数内所做的更改也会在函数外可见。

package main
import (
  "fmt"
)
func subtactOne(numbers []int) {
  for i := range numbers {
    numbers[i] -= 2
  }
}
func main() {
  nos := []int{8, 7, 6}
  fmt.Println("slice before function call", nos)
  subtactOne(nos)                // function modifies the slice
  fmt.Println("slice after function call", nos) // modifications are visible outside
}

上述程序的行号 17 中,调用函数将切片中的每个元素递减 2。在函数调用后打印切片时,这些更改是可见的。如果你还记得,这是不同于数组的,对于函数中一个数组的变化在函数外是不可见的。

array before function call [8 7 6]
array after function call [6 5 4]

多维切片

类似于数组,切片可以有多个维度。

package main
import (
  "fmt"
)
func main() {
   pls := [][]string {
      {"C", "C++"},
      {"JavaScript"},
      {"Go", "Rust"},
      }
  for _, v1 := range pls {
    for _, v2 := range v1 {
      fmt.Printf("%s ", v2)
    }
    fmt.Printf("\n")
  }
}

程序的输出为,

C C++
JavaScript
Go Rust
copy

切片持有对底层数组的引用。只要切片在内存中,数组就不能被垃圾回收。在内存管理方面,这是需要注意的。让我们假设我们有一个非常大的数组,我们只想处理它的一小部分。然后,我们由这个数组创建一个切片,并开始处理切片。这里需要重点注意的是,在切片引用时数组仍然存在内存中。

一种解决方法是使用copy 函数 来生成一个切片的副本。这样我们可以使用新的切片,原始数组可以被垃圾回收。

func copy(dst,src[]T)int`
package main
import (
  "fmt"
)
func main() {
  s1 :=[]int{1,2,3,4,5}
  fmt.Println("s1",s1)
  s2 := make([]int,len(s1))
  fmt.Println("s2",s2)
  copy(s2,s1)
  fmt.Println("s2",s2)
  s3 :=make([]int,len(s1)-2)
  copy(s3,s1);
  fmt.Println("s3",s3)
  s4 :=make([]int,len(s1)-1)
  copy(s4[1:3],s1[2:4]);
  fmt.Println("s4",s4)
}

打印结果:

s1 [1 2 3 4 5]
s2 [0 0 0 0 0]
s2 [1 2 3 4 5]
s3 [1 2 3]
s4 [0 3 4 0]

总结

以上所述是小编给大家介绍的Go基础Slice教程详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

您可能感兴趣的文章:

  • 深入理解golang的基本类型排序与slice排序
  • 深入解析Go语言编程中slice切片结构
  • Go语言入门教程之Arrays、Slices、Maps、Range操作简明总结
  • Go语言中的Slice学习总结
  • 理解Golang中的数组(array)、切片(slice)和map
  • Go语言中的Array、Slice、Map和Set使用详解
(0)

相关推荐

  • Go语言入门教程之Arrays、Slices、Maps、Range操作简明总结

    Arrays:数组 在go语言中数组array是一组特定长度的有序的元素集合. 复制代码 代码如下: package main import "fmt" func main() { //这里我们创建了一个长度为5的数组. 这一组数组的初值是zero-valued.整型就是0     var a [5]int     fmt.Println("emp:", a) //可以通过array[index] = value语法赋值     a[4] = 100     fmt

  • 深入理解golang的基本类型排序与slice排序

    前言 其实golang的排序思路和C和C++有些差别. C默认是对数组进行排序, C++是对一个序列进行排序, Go则更宽泛一些,待排序的可以是任何对象, 虽然很多情况下是一个slice(分片, 类似于数组),或是包含 slice 的一个对象. 排序(接口)的三个要素: 1.待排序元素个数 n : 2.第 i 和第 j 个元素的比较函数 cmp : 3.第 i 和 第 j 个元素的交换 swap : 乍一看条件 3 是多余的, c 和 c++ 都不提供 swap . c 的 qsort 的用法:

  • Go语言中的Slice学习总结

    概念 Slice切片是对底层数组Array的封装,在内存中的存储本质就是数组,体现为连续的内存块,Go语言中的数组定义之后,长度就已经固定了,在使用过程中并不能改变其长度,而Slice就可以看做一个长度可变的数组进行使用,最为关键的,是数组在使用的过程中都是值传递,将一个数组赋值给一个新变量或作为方法参数传递时,是将源数组在内存中完全复制了一份,而不是引用源数组在内存中的地址,为了满足内存空间的复用和数组元素的值的一致性的应用需求,Slice出现了,每个Slice都是都源数组在内存中的地址的一个

  • 理解Golang中的数组(array)、切片(slice)和map

    我比较喜欢先给出代码,然后得出结论 数组 复制代码 代码如下: package main import (     "fmt" ) func main() {     arr := [...]int{1, 2, 3}     //打印初始的指针     fmt.Printf("the pointer is : %p \n", &arr)     printPointer(arr) } func printPointer(any interface{}) {

  • Go语言中的Array、Slice、Map和Set使用详解

    Array(数组) 内部机制 在 Go 语言中数组是固定长度的数据类型,它包含相同类型的连续的元素,这些元素可以是内建类型,像数字和字符串,也可以是结构类型,元素可以通过唯一的索引值访问,从 0 开始. 数组是很有价值的数据结构,因为它的内存分配是连续的,内存连续意味着可是让它在 CPU 缓存中待更久,所以迭代数组和移动元素都会非常迅速. 数组声明和初始化 通过指定数据类型和元素个数(数组长度)来声明数组. 复制代码 代码如下: // 声明一个长度为5的整数数组 var array [5]int

  • 深入解析Go语言编程中slice切片结构

    数组转换成切片 复制代码 代码如下: a := [10]int{} fmt.Println(a)  s1 := a[:10]  //取前10个元素 [5:]取 5-最后的元素  fmt.Println(s1) slice测试 复制代码 代码如下: a := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'h'} sa := a[2:5] fmt.Println(string(sa)) sd1 := a[3:5] fmt.Println(string(sd1)) //看

  • Go基础Slice教程详解

    Go 语言切片(Slice) Go 语言切片是对数组的抽象. Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大. 声明Slice 带有 T 类型元素的切片由 []T 表示,其中T代表slice中元素的类型.切片在内部可由一个结构体类型表示,形式如下: type slice struct { Length int Capacity int Z

  • Linux中selinux基础配置教程详解

    selinux(Security-Enhanced Linux)安全增强型linux,是一个Linux内核模块,也是Linux的一个安全子系统. 三种模式: Enforcing:强制模式,在selinux运作时,已经开始限制domain/type. permissive: 警告模式,在selinux运作时,会有警告讯息,但不会限制domain/type的存取. disabled: 关闭模式. 可用getenforce查看selinux状态 selinux对文件的作用: 当开启selinux后,s

  • android studio 3.4配置Android -jni 开发基础的教程详解

    首先下载配置android studio ndk 1.打开sdkManager下载CMake和LLDB 2.配置ndk 项目新建 项目建立完毕后,工程目录如下,cpp文件夹是系统自动生成的 3.自定义 navite方法 接下来开始写自定义的一个native方法,新建一个Hello.java文件,里面写一个add求和的native方法,如下 生成c++头文件 然后在windows控制台Terminal进入hello.java所在的目录执行javac hello.java,如下 执行完毕后hello

  • Yii2.0 RESTful API 基础配置教程详解

    最近在做Yii2.0 RESTful API功能,找了好久的资料,才找到这类的教程,感谢该作者,以下内容根据我的项目实际情况做了一定的修改. 安装yii2.0 安装 Composer 后,您可以通过在 Web 可访问的文件夹下运行以下命令来 安装Yii应用程序模板: composer create-project --prefer-dist yiisoft/yii2-app-basic basic 初始化高级模板 cd advanced ./init 输入"./init"后会出现以下内

  • vue基础ESLint Prettier配置教程详解

    目录 引言 前言 安装 VsCode 插件 配置 VsCode "Workspace.json" 配置 vue 的 "package.json" 配置 vue 的 ".eslintrc.js" 配置 vue 的 ".prettierrc" 配置 "eslint --fix" 一键修复 配置 ESlint 提交时检测 测试配置效果 问题排查 尾声 引言 VsCode + Vue + ESLint + Pret

  • windows版本下mysql的安装启动和基础配置图文教程详解

    下载: 第一步 : 打开网址(进入官网下载) :https://www.mysql.com ,点击downloads之后跳转到https://www.mysql.com/downloads 第二步 :跳转至网址https://dev.mysql.com/downloads/,选择Community选项 第三步 :点击MySQL Community Server进入https://dev.mysql.com/downloads/mysql/页面,再点击5.6版本的数据库 第四步:windows操作

  • springboot整合websocket最基础入门使用教程详解

    项目最终的文件结构 1 添加maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <

  • C语言基础全局变量与局部变量教程详解

    目录 一:局部变量与全局变量 1.1:局部变量 1.2:全局变量 1.3:代码解释 1.4:const修饰的变量的修改 二:静态局部变量与静态全局变量 2.1:static关键字 2.2:静态局部变量 2.3:静态全局变量 2.4:汇总 三:全局函数与静态函数 3.1:全局函数 3.2:静态函数 3.3:汇总表 一:局部变量与全局变量 1.1:局部变量 局部变量:在函数内部定义的变量 ,auto可加可不加 作用域:从定义到本函数结束 生命周期:从定义到该函数结束 1.2:全局变量 全局变量:在函

  • Golang接口使用教程详解

    目录 前言 一.概述 二.接口类型 2.1 接口的定义 2.2 实现接口的条件 2.3 为什么需要接口 2.4 接口类型变量 三.值接收者和指针接收者 3.1 值接收者实现接口 3.2 指针接收者实现接口 四.类型与接口的关系 4.1 一个类型实现多个接口 4.2 多种类型实现同一接口 五.接口嵌套 六.空接口 七.类型断言 总结 前言 go语言并没有面向对象的相关概念,go语言提到的接口和java.c++等语言提到的接口不同,它不会显示的说明实现了接口,没有继承.子类.implements关键

  • ABP(现代ASP.NET样板开发框架)系列之二、ABP入门教程详解

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源项目:https://github.com/aspnetboilerplate ABP 的由来 "DRY--避免重复

随机推荐