go语言中的切片详解

news2024/11/13 8:39:00

1.概念

在Go语言中,切片(Slice)是一种基于数组的更高级的数据结构,它提供了一种灵活、动态的方式来处理序列数据。切片在Go中非常常用,因为它们可以动态地增长和缩小,这使得它们比固定大小的数组更加灵活。

2.切片的内部实现

 切片是对数组的抽象表示,它包含三个要素:

1.  指向底层数组的指针
2.  切片的长度(元素数量)
3.  切片的容量(从开始到底层数组末尾的元素数量)

切片是一个有三个字段的数据结构,这些数据结构包含 Golang 需要操作底层数组的元数据: 

 这 3 个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。

3.切片创建和初始化

在 Golang 中可以通过多种方式创建和初始化切片。是否提前知道切片所需的容量通常会决定如何创建切片

通过 make() 函数创建切片
使用 Golang 内置的 make() 函数创建切片,此时需要传入一个参数来指定切片的长度:

slice := make([]int, length, capacity)

这里,length是切片的长度,capacity是切片的容量。如果未指定capacity,它将等于length。

// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice := make([]int, 5)

分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能
访问所有的数组元素。

注意,Golang 不允许创建容量小于长度的切片,当创建的切片容量小于长度时会在编译时刻报错:

// 创建一个整型切片
// 使其长度大于容量
myNum := make([]int, 5, 3)

分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能
访问所有的数组元素。

注意,Golang 不允许创建容量小于长度的切片,当创建的切片容量小于长度时会在编译时刻报错 

4.通过字面量创建切片

另一种常用的创建切片的方法是使用切片字面量,这种方法和创建数组类似,只是不需要指定[]运算符里的值。初始的长度和容量会基于初始化时提供的元素的个数确定:

// 创建字符串切片
// 其长度和容量都是 3 个元素
myStr := []string{"Jack", "Mark", "Nick"}
// 创建一个整型切片
// 其长度和容量都是 4 个元素
myNum := []int{10, 20, 30, 40}

当使用切片字面量创建切片时,还可以设置初始长度和容量。要做的就是在初始化时给出所需的长度和容量作为索引。下面的语法展示了如何使用索引方式创建长度和容量都是100个元素的切片:

// 创建字符串切片
// 使用空字符串初始化第 100 个元素
myStr := []string{99: ""}

区分数组的声明和切片的声明方式
当使用字面量来声明切片时,其语法与使用字面量声明数组非常相似。二者的区别是:如果在 [] 运算符里指定了一个值,那么创建的就是数组而不是切片。只有在 [] 中不指定值的时候,创建的才是切片。看下面的例子:

// 创建有 3 个元素的整型数组
myArray := [3]int{10, 20, 30}
// 创建长度和容量都是 3 的整型切片
mySlice := []int{10, 20, 30}

切片的复制:

当你有一个切片或者数组的时候,当希望将其元素作为单独的参数传递给一个函数时候,可以使用...操作符

func sum(x, y, z int) int {
    return x + y + z
}

numbers := []int{1, 2, 3}
result := sum(numbers...) // 将 numbers 切片中的元素作为参数传递给 sum 函数
fmt.Println(result) // 输出: 6

在这个例子中,numbers...将切片numbers中的元素1,2,3展开为sum函数的参数 

5.nil 和空切片

有时,程序可能需要声明一个值为 nil 的切片(也称nil切片)。只要在声明时不做任何初始化,就会创建一个 nil 切片

// 创建 nil 整型切片
var myNum []int

在 Golang 中,nil 切片是很常见的创建切片的方法。nil 切片可以用于很多标准库和内置函数。在需要描述一个不存在的切片时,nil 切片会很好用。比如,函数要求返回一个切片但是发生异常的时候。下图描述了 nil 切片的状态:

空切片和 nil 切片稍有不同,下面的代码分别通过 make() 函数和字面量的方式创建空切片: 

// 使用 make 创建空的整型切片
myNum := make([]int, 0)
// 使用切片字面量创建空的整型切片
myNum := []int{}

区别:
1.  内存分配:
•  nil 切片:没有指向任何底层数组,没有任何内存分配。
•  空切片:指向一个长度为0的底层数组,但这个数组可能已经分配了内存(尽管长度为0)。


2.  函数返回值:
•  使用 nil 可以表示“没有值”,这在错误处理和可选值中很有用。
•  空切片通常用于表示一个有效的切片,但当前没有元素。

 

package main
import "fmt"

func main() {
	//创建一个空切片
	emptySlice :=make([]int,0)
	fmt.Println(emptySlice)

	//创建一个nil切片
	var nilSlice []int
	fmt.Println(nilSlice)

	//检查切片是否为空
	if len(emptySlice) == 0 {
		fmt.Println("emptySlice is empty")
	}
	if nilSlice == nil {
		fmt.Println("nilSlice is nil")
	}
	//尝试访问空切片的元素
	if len(emptySlice) > 0 {
		fmt.Println(emptySlice[0])
	}else {
		fmt.Println("emptySlice is empty")
	}

}

•  emptySlice是一个空切片,它有长度0,但是它指向一个内存地址。
•  nilSlice是一个nil指针,它没有指向任何内存地址,因此不能用来访问任何元素。


尝试访问nil切片的元素会导致程序崩溃(panic),因为nil没有指向有效的内存。而空切片虽然长度为0,但是它是指向一个有效的内存地址的,所以可以安全地对其进行操作,比如扩展或赋值。

6.为切片中的元素赋值

对切片里某个索引指向的元素赋值和对数组里某个索引指向的元素赋值的方法完全一样。使
用 [] 操作符就可以改变某个元素的值,下面是使用切片字面量来声明切片:

// 创建一个整型切片
// 其容量和长度都是 5 个元素
myNum := []int{10, 20, 30, 40, 50}
// 改变索引为 1 的元素的值
myNum [1] = 25

切片之所以被称为切片,是因为创建一个新的切片,也就是把底层数组切出一部分。通过切片创建新切片的语法如下:

slice[i:j]
slice[i:j:k]

其中 i 表示从 slice 的第几个元素开始切,j 控制切片的长度(j-i),k 控制切片的容量(k-i),如果没有给定 k,则表示切到底层数组的最尾部。下面是几种常见的简写形式:

slice[i:]  // 从 i 切到最尾部
slice[:j]  // 从最开头切到 j(不包含 j)
slice[:]   // 从头切到尾,等价于复制整个 slice

让我们通过下面的例子来理解通过切片创建新的切片的本质:

// 创建一个整型切片
// 其长度和容量都是 5 个元素
myNum := []int{10, 20, 30, 40, 50}
// 创建一个新切片
// 其长度为 2 个元素,容量为 4 个元素
newNum := slice[1:3]

执行上面的代码后,我们有了两个切片,它们共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分:

 

下面是详细的用法: 

package main

import "fmt"

func main() {
    // 创建一个原始切片
    original := []int{1, 2, 3, 4, 5}

    // 通过切片操作符创建新的切片
    a := original[:3] // 包含从索引0到索引2的元素
    b := original[2:] // 包含从索引2到末尾的元素
    c := original[:] // 创建一个新的切片,它是原始切片的一个副本

    // 修改原始切片
    original[0] = 10

    // 打印结果
    fmt.Println("Original slice:", original)  // 输出: [10 2 3 4 5]
    fmt.Println("Slice a:", a)               // 输出: [1 2 3]
    fmt.Println("Slice b:", b)               // 输出: [3 4 5]
    fmt.Println("Slice c:", c)               // 输出: [1 2 3 4 5]

    // 修改新的切片a
    a[0] = 100

    // 打印结果
    fmt.Println("Original slice after modifying a:", original)  // 输出: [10 2 3 4 5]
    fmt.Println("Slice a after modification:", a)               // 输出: [100 2 3]
}

共享底层数组的切片
需要注意的是:现在两个切片 myNum 和 newNum 共享同一个底层数组。如果一个切片修改了该底层数组的共享
部分,另一个切片也能感知到(请参考前图):

在Go语言中,切片是引用类型,这意味着它们指向底层数组的连续区域。当你通过切片操作创建一个新的切片时,新切片和原始切片可能共享同一个底层数组。如果新切片的容量足够,对新切片的修改可能会反映到原始切片上,因为它们可能指向相同的底层数组元素。

如果你需要一个与原始切片完全独立的副本,可以使用copy函数来创建一个新的底层数组。这样,对新数组的修改就不会影响原始数组。
下面是一个详细的示例,展示了如何使用copy函数创建一个完全独立的切片副本,并解释了为什么这样做可以避免对原始切片的意外修改。

package main

import "fmt"

func main() {
	original := []int{1,2,3,4,5}
	//使用切片操作符创建一个新的切片,它与原始切片共享同一个底层数组
	sharedSlice := original[:]

	independentSlice :=make([]int, len(original))
	copy(independentSlice, original)

	    // 打印原始切片和两个新切片的内容
			fmt.Println("Original slice:", original)       // 输出: [1 2 3 4 5]
			fmt.Println("Shared slice:", sharedSlice)      // 输出: [1 2 3 4 5]
			fmt.Println("Independent slice:", independentSlice) // 输出: [1 2 3 4 5]

			//修改原始切片
			original[0] = 10
			 fmt.Println("Modified original slice:", original) // 输出: [10 2 3 4 5]
			 fmt.Println("Shared slice after modification:", sharedSlice) // 输出: [10 2 3 4 5]

			 //修改独立切片
			 independentSlice[0] = 20

			 fmt.Println("Independent slice after modification:", independentSlice) // 输出: [20 2 3 4 5]
			 //修改独立切片之后,原始切片不受影响
			 fmt.Println("Original slice after independent modification:", original) // 输出: [10 2 3 4 5]
			 fmt.Println("Shared slice after independent modification:", sharedSlice) // 输出: [10 2 3 4 5]
	
}

代码运行结果: 

 7.切片扩容

Go 语言中的切片(slice)是一种动态数组,它允许你动态地增长和缩小。当你向切片添加元素,而切片的容量(capacity)不足以容纳更多元素时,Go 语言会自动进行扩容。扩容的具体机制如下:

  • 1.  初始容量:当你创建一个新的切片时,它会有一个初始的容量。如果你通过 make 函数创建切片,你可以指定切片的长度(length)和容量(capacity)。
  • 2.  扩容机制:当你向切片添加元素,超出当前容量时,Go 语言会进行扩容。扩容通常遵循以下规则:
  •   扩容后的容量通常是原容量的两倍,但具体增长因子可能因实现而异。
  •   如果切片的容量已经很大,扩容可能不会简单地翻倍,而是增加一个固定的数量。
  • 3.  扩容过程:扩容时,Go 语言会分配一个新的数组,并将原切片中的元素复制到新数组中。然后,新元素会被添加到新数组中。最后,切片的底层数组指针会被更新为指向新数组。
  • 4.  性能考虑:频繁的扩容可能会导致性能问题,因为每次扩容都需要分配新的内存并复制元素。为了避免这种情况,你可以通过预先分配足够的容量来减少扩容的次数。
  • 5.  手动扩容:虽然 Go 语言会自动管理切片的扩容,但你也可以手动扩容切片。例如,你可以使用 append 函数来添加元素,或者使用 copy 函数和新的切片来手动复制元素。

下面是一个简单的 Go 语言切片扩容的:

package main

import "fmt"

func main() {
    s := make([]int, 0, 1) // 创建一个长度为0,容量为1的切片
    for i := 0; i < 5; i++ {
        s = append(s, i)    // 向切片添加元素
        fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
    }
}

运行结果:

len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=3 cap=4 slice=[0 1 2]
len=4 cap=4 slice=[0 1 2 3]
len=5 cap=8 slice=[0 1 2 3 4]

函数 append() 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量(随着语言的演化,这种增长算法可能会有所改变)。

8.遍历切片

8.1使用for循环和索引

package main

import "fmt"

func main() {
    slice := []int{10, 20, 30, 40, 50}

    for i := 0; i < len(slice); i++ {
        fmt.Println("Element at index", i, "is", slice[i])
    }
}

8.2 使用for循环和range

使用range关键字可以同时获取索引和值,这是遍历切片的常用方法

package main

import "fmt"

func main() {
     slice :=[]int{10,20,30,40,50}

		 for index,value := range slice{
			fmt.Printf("Index is %d and value is %d\n",index,value)
		 }
}

8.3 使用 for 循环和 range(只获取值)

如果你不需要索引,只关心值,可以使用以下方式简化代码。

用以下方式简化代码。
package main

import "fmt"

func main() {
    slice := []int{10, 20, 30, 40, 50}

    for _, value := range slice {
        fmt.Println("Value is", value)
    }
}

8.4 使用 for 循环和 range(只获取索引)

如果你只需要索引,可以使用下划线 _ 忽略值。

package main

import "fmt"

func main() {
    slice := []int{10, 20, 30, 40, 50}

    for index, _ := range slice {
        fmt.Println("Index is", index)
    }
}

8.5 使用 for 循环和 range(遍历字符串切片)

对于字符串切片,range 会返回每个字符串中的每个字符(作为 rune 类型)。

package main

import "fmt"

func main() {
    slice := []string{"hello", "world"}

    for _, word := range slice {
        for _, char := range word {
            fmt.Printf("%c ", char)
        }
        fmt.Println()
    }
}

9.切片操作

9.1 使用copy函数

copy 函数是标准库 copy 包中提供的一个函数,用于将一个切片的内容复制到另一个切片中。它接受两个切片作为参数:源切片和目标切片,并返回复制的元素数量。

package main

import "fmt"

func main() {
  src :=[]int{1,2,3,4,5}
	dst :=make([]int,5)

	//从src复制到dst
	copied :=copy(dst,src)
	fmt.Println("Copied element",copied)
	fmt.Println("Destination",dst)
}

9.2 使用 append 函数

  •   第一个参数是目标切片,用于接收新添加的元素。
  •  后续参数是要添加到切片中的元素。
  •   使用 append 进行切片复制
  • 在 append([]int(nil), original...) 中:
  •   []int(nil) 创建了一个类型为 []int 的空切片。这个切片的长度和容量都是0。
  •  original... 是 Go 语言的变参语法,它将 original 切片中的所有元素作为独立的参数传递给 append 函数。
package main

import "fmt"

func main() {
    original := []int{1, 2, 3, 4, 5}
    copy := append([]int(nil), original...)

    fmt.Println("Original slice:", original)
    fmt.Println("Copy slice:", copy)
}

在这个例子中,copy 切片是通过 append 函数创建的,它包含了 original 切片的所有元素。这种方法不仅代码简洁,而且性能也很好,因为它避免了不必要的内存分配和复制操作。
总结来说,append([]int(nil), original...) 是一种利用 append 函数的灵活性和智能内存管理来高效复制切片的方法。

9.3手动复制

你也可以通过遍历源切片并手动将每个元素赋值到新的切片中来实现复制。

package main

import (
    "fmt"
)

func main() {
    src := []int{1, 2, 3, 4, 5}
    dst := make([]int, len(src)) // 创建一个相同长度的新切片

    for i := range src {
        dst[i] = src[i]
    }

    fmt.Println("Source slice:", src)
    fmt.Println("Destination slice:", dst)
}

这种方法虽然直接,但效率较低,特别是对于大型切片。

注意事项

•  当使用 copy 函数时,确保目标切片有足够的容量来接收所有元素,否则它只会复制目标切片的容量允许的部分。
•  使用 append 函数时,虽然方便,但可能会因为扩容操作而增加额外的性能开销。
•  手动复制虽然控制更精细,但代码更复杂,且容易出错。
在实际应用中,选择哪种方法取决于具体的需求和性能考虑。对于大多数情况,copy 函数提供了一个简单而高效的方式来复制切片。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2148025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电子看板实时监控数据可视化助力工厂精细化管理

在当今竞争激烈的制造业领域&#xff0c;工厂的精细化管理成为提高竞争力的关键。而电子看板实时监控数据可视化作为一种先进的管理工具&#xff0c;正为工厂的精细化管理带来巨大的助力。 一、工厂精细化管理的挑战 随着市场需求的不断变化和客户对产品质量要求的日益提高&am…

记一次键盘f2和f5键被自动触发情况

背景&#xff1a; 联想小新笔记本电脑内置键盘&#xff0c;其中f2键和f5键一直被自动触发&#xff0c;已尝试过更换输入法&#xff0c;重装系统&#xff0c;拆开键帽清灰依旧无效。考虑维修费或者更换键盘&#xff08;内置&#xff09;费都挺贵的&#xff0c;而且f2和f5作用也…

音视频入门基础:AAC专题(10)——FFmpeg源码中计算AAC裸流每个packet的pts、dts、pts_time、dts_time的实现

音视频入门基础&#xff1a;AAC专题系列文章&#xff1a; 音视频入门基础&#xff1a;AAC专题&#xff08;1&#xff09;——AAC官方文档下载 音视频入门基础&#xff1a;AAC专题&#xff08;2&#xff09;——使用FFmpeg命令生成AAC裸流文件 音视频入门基础&#xff1a;AAC…

uniapp小程序使用canvas画圆

<view class"container"><canvas canvas-id"arcCanvas" id"arcCanvas" class"arc-canvas" width"300" height"300"></canvas> </view> 最开始我使用…

【华为杯】第二十一届中国研究生数学建模竞赛

“华为杯”第二十一届中国研究生数学建模竞赛即将开始&#xff0c;梦想科研社给大家整理一些比赛信息&#xff0c;在正式开赛后&#xff0c;我们也会持续分享一些课题的分析以及代码&#xff0c;有需要的可以联系我们获取资料信息哦 一、时间节点 1.加密赛题开始下载时间&…

DPDK 简易应用开发之路 1:数据包接收与解析

本机环境为 Ubuntu20.04 &#xff0c;dpdk-stable-20.11.10 DPDK 应用基础 DPDK应用程序的一般处理流程如下&#xff1a; 初始化DPDK环境&#xff1a;调用rte_eal_init()初始化DPDK环境抽象层&#xff08;EAL&#xff09;&#xff0c;设置运行时环境和配置。 配置和绑定网卡…

2024最新版 Tuxera NTFS for Mac 2023绿色版图文安装教程

​ 在数字化时代&#xff0c;数据的存储和传输变得至关重要。Mac用户经常需要在Windows NTFS格式的移动硬盘上进行读写操作&#xff0c;然而&#xff0c;由于MacOS系统默认不支持NTFS的写操作&#xff0c;这就需要我们寻找一款高效的读写软件。Tuxera NTFS for Mac 2023便是其中…

超详图解 Apache HTTP Server(httpd)安装与验证

在OpenEuler 24.03系统中安装验证 Apache HTTP Server&#xff08;httpd&#xff09;的过程通常涉及以下步骤&#xff1a; 一、Apache HTTP Server&#xff08;httpd&#xff09;安装 1.检查是否已安装httpd: rpm -q httpd 2.更新系统包索引&#xff1a;更新您的系统包索引以…

基于深度学习的药品三期OCR字符识别

在药品生产线上,药品三期的喷码与条形码识别是保证药品追溯和安全管理的重要环节。传统的识别方法依赖于人工操作,不仅效率低下且容易出错。随着深度学习技术的不断发展,基于OCR(Optical Character Recognition,光学字符识别)的自动化识别系统逐渐成为主流。本文将以哪吒…

【Godot4.2】基于EasyTreeData解析的扩展Tree控件 - ETDTree

概述 基于EasyTreeData解析的扩展Tree控件。 EasyTreeData&#xff08;ETD&#xff09; EasyTreeData&#xff08;ETD&#xff09;是一种基于Tab缩进的简单层级结构数据&#xff0c;可以用于描述树形结构。能够被解析为Tree控件或表示树形结构的其他类或控件。 根目录 | 0节…

cadence SPB17.4 - allegro - 用板子外形创建整板铺铜

文章目录 cadence SPB17.4 - allegro - 用板子外形创建整板铺铜概述笔记先确定自己板子的 board Geometry/Design_Outline 是否有外形shape为了将软件提示看得更清楚&#xff0c;在每个操作之前&#xff0c;先将命令提示区内容先删了用Z-copy从外形层生成整板的铺铜备注END cad…

Maven 和 gradle JavaFX 项目的休眠行为差异

我一直在尝试将Hibernate与我的JavaFX Maven项目集成。它与Hibernate社区包、Jakarta和xerial配合得很好。我还将persistence.xml文件放在了src/main/resources/META-INF/persistence.xml。 我还尝试使用gradle创建另一个项目&#xff0c;并按照此maven项目的步骤操作&#xf…

《高等代数》行列式转置(应用)

说明&#xff1a;此文章用于本人复习巩固&#xff0c;如果也能帮助到大家那就更加有意义了。 注&#xff1a;1&#xff09;“行列式转置值不变”这一性质在求解行列式的过程中也有极大的作用。

代码随想录算法训练营第51天 | 岛屿数量、岛屿的最大面积

目录 岛屿数量 题目描述 输入描述 输出描述 输入示例 输出示例 提示信息 1. 深搜解法 2. 广搜解法 岛屿的最大面积 题目描述 输入描述 输出描述 输入示例 输出示例 提示信息 1. 深搜解法 2. 广搜解法 岛屿数量 题目描述 给定一个由 1&#xff08;陆地&…

Python和C++气候模型算法模型气候学模拟和统计学数据可视化及指标评估

&#x1f3af;要点 贝叶斯推理气候模型辐射对流及干湿能量平衡模型时间空间气象变化预测模型评估统计指标气象预测数据变换天气和气象变化长短期影响预估降低气候信息尺度评估算法气象行为模拟&#xff1a;碳循环、辐射强迫和温度响应温室气体排放碳循环温室诱导气候变化评估气…

影刀RPA实战:网页爬虫之苦瓜书籍数据

书籍常常被视为心灵的慰藉&#xff0c;因为它们能够在不同的层面上为人们提供支持和安慰。 1. 书籍对我们的重要性 书籍是人类知识的载体&#xff0c;也是智慧的结晶。它们不仅是学习的工具&#xff0c;更是人类心灵的慰藉。在忙碌的生活中&#xff0c;书籍能够提供知识、启发…

VMWare17.5.2中Windows7企业版安装VMWareTools失败及解决办法

一、问题产生环境 宿主机系统&#xff1a;Windows11专业版 x64 虚拟机版本&#xff1a;VMWare17.5.2 虚拟机系统&#xff1a;Windows 7企业版 x64&#xff08;sp1纯净版&#xff09; 二、问题表现现象 在Windows 7企业版系统安装完成后&#xff0c;点击虚拟机编辑&#xff0c;…

安科瑞智能塑壳断路器适用于物联网配电电网中-安科瑞黄安南

智能塑壳断路器是一款多功能、体积小、智能化的塑壳断路器&#xff0c;适用于物联网配电电网中。用于三相四线中性点直接接地的供电、用电系统&#xff0c;能全面采集功率、电能量、功率因数、谐波等用电参数;具有过载、短路、缺相、过压、欠压、剩余电流动作保护等功能&#x…

vscode 环境搭建

1. 插件离线安装 官网链接是&#xff1a;https://marketplace.visualstudio.com/vscode 下载需要的插件&#xff1a; vscode 离线安装 在打开的文件中选择扩展包&#xff0c;点击安装即可 2. 故障解决 参考&#xff1a;https://blog.csdn.net/weixin_63712639/article/det…

Modbus_RTU和Modbus库

目录 一.Modbus_RTU 1. 与Modbus TCP的区别 2. Modbus RTU特点 3. Modbus RTU协议格式 4. 报文详解 5. 代码实现RTU通信 1. 打开模拟的RTU从机 2. linux端使用代码实现和串口连接 2.1. 框架搭建 2.2 代码 二.Modbus库 1.库函数 一.Modbus_RTU 1. 与Modbus T…