切片有哪些注意事项是一定要知道的呢

这篇具有很好参考价值的文章主要介绍了切片有哪些注意事项是一定要知道的呢。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 引言

在之前我写了一篇 切片比数组好用在哪 的文章,仔细介绍了切片相比于数组的优点。但切片事实上也隐藏着一些潜在的陷阱和需要注意的细节,了解和掌握切片的使用注意事项,可以避免意外的程序行为。本文将深入探讨Go语言切片常见的注意事项,从而能够更好得使用切片。

2. 注意事项

2.1 注意一个数组可以同时被多个切片引用

当创建一个切片时,它实际上是对一个底层数组的引用。这意味着对切片的修改会直接影响到底层数组以及其他引用该数组的切片。这种引用关系可能导致一些意想不到的结果,下面是一个示例代码来说明这个问题:

package main

import "fmt"

func main() {
        array := [5]int{1, 2, 3, 4, 5}
        firstSlice := array[1:4] // 创建一个切片,引用了底层数组的索引1到3的元素
        secondSlice := array[1:3]
        fmt.Println("Original array:", firstSlice)  // 输出第一个切片 [2 3 4]
        fmt.Println("Original slice:", secondSlice) // 输出第二个切片 [2 3]

        // 修改切片的第一个元素
        firstSlice[0] = 10

        fmt.Println("Modified array:", firstSlice)  // 输出第一个切片 [10 3 4]
        fmt.Println("Modified slice:", secondSlice) // 输出第二个切片 [10 3]
}

在上述代码中,我们创建了一个长度为5的数组array和两个引用该数组的切片firstSlicesecondSlice。当我们修改第一个切片的第一个元素为10时,底层数组的对应位置的元素也被修改了。这里导致了数组和其他引用该数组的切片的内容也会受到影响。

如果我们有多个切片同时引用了同一个底层数组,同时我们并不想由于对某个切片的修改,影响到另外一个切片的数据,此时我们可以新创建一个切片,使用内置的copy函数来复制原切片元素的值。示例代码如下:

package main

import "fmt"

func main() {
        array := [5]int{1, 2, 3, 4, 5}
        slice := array[1:4]

        // 复制切片创建一个独立的底层数组
        newSlice := make([]int, len(slice))
        copy(newSlice, slice)

        fmt.Println("Original array:", array) // 输出原始数组 [1 2 3 4 5]
        fmt.Println("Original slice:", slice) // 输出初始切片 [2 3 4]
        fmt.Println("New slice:", newSlice)  // 输出新创建的切片 [2 3 4]
        
        // 修改newSlice的第一个元素
        newSlice[0] = 10

        fmt.Println("Modified array:", array)// 输出修改后的数组 [1 2 3 4 5]
        fmt.Println("Original slice:", slice)// 输出初始切片 [2 3 4]
        fmt.Println("New slice:", newSlice)// 输出修改后的切片 [10 3 4]
}

通过创建了一个新的切片newSlice,它拥有独立的底层数组,同时使用copy函数复制原切片的值,我们现在修改newSlice不会影响原始数组或原始切片。

2.2 注意自动扩容可能带来的性能问题

在Go语言中,切片的容量是指底层数组的大小,而长度是切片当前包含的元素数量。当切片的长度超过容量时,Go语言会自动扩容切片。扩容操作涉及到重新分配底层数组,并将原有数据复制到新的数组中。下面先通过一个示例代码,演示切片的自动扩容机制:

package main

import "fmt"

func main() {
        slice := make([]int, 3, 5) // 创建一个初始长度为3,容量为5的切片

        fmt.Println("Initial slice:", slice)        // 输出初始切片 [0 0 0]
        fmt.Println("Length:", len(slice))          // 输出切片长度 3
        fmt.Println("Capacity:", cap(slice))        // 输出切片容量 5

        slice = append(slice, 1, 2, 3)              // 添加3个元素到切片,长度超过容量

        fmt.Println("After appending:", slice)      // 输出扩容后的切片 [0 0 0 1 2 3]
        fmt.Println("Length:", len(slice))          // 输出切片长度 6
        fmt.Println("Capacity:", cap(slice))        // 输出切片容量 10
}

在上述代码中,我们使用make函数创建了一个初始长度为3,容量为5的切片slice。然后,我们通过append函数添加了3个元素到切片,导致切片的长度超过了容量。此时,Go语言会自动扩容切片,创建一个新的底层数组,并将原有数据复制到新的数组中。最终,切片的长度变为6,容量变为10。

但是切片的自动扩容机制,其实是存在性能开销的,需要创建一个新的数组,同时将数据全部拷贝到新数组中,切片再引用新的数组。下面先通过基准测试,展示没有设置初始容量和设置了初始容量两种情况下的性能差距:

package main

import (
        "fmt"
        "testing"
)

func BenchmarkSliceAppendNoCapacity(b *testing.B) {
        for i := 0; i < b.N; i++ {
                var slice []int
                for j := 0; j < 1000; j++ {
                        slice = append(slice, j)
                }
        }
}

func BenchmarkSliceAppendWithCapacity(b *testing.B) {
        for i := 0; i < b.N; i++ {
                slice := make([]int, 0, 1000)
                for j := 0; j < 1000; j++ {
                        slice = append(slice, j)
                }
        }
}

在上述代码中,我们定义了两个基准测试函数:BenchmarkSliceAppendNoCapacityBenchmarkSliceAppendWithCapacity。其中,BenchmarkSliceAppendNoCapacity测试了在没有设置初始容量的情况下,循环追加元素到切片的性能;BenchmarkSliceAppendWithCapacity测试了在设置了初始容量的情况下,循环追加元素到切片的性能。基准测试结果如下:

BenchmarkSliceAppendNoCapacity-4          280983              4153 ns/op           25208 B/op         12 allocs/op
BenchmarkSliceAppendWithCapacity-4       1621177              712.2 ns/op              0 B/op          0 allocs/op

其中ns/op 表示每次操作的平均执行时间,即函数执行的耗时。B/op 表示每次操作的平均内存分配量,即每次操作分配的内存大小。allocs/op 表示每次操作的平均内存分配次数。

可以看到,在设置了初始容量的情况下,性能要明显优于没有设置初始容量的情况。循环追加1000个元素到切片时,设置了初始容量的情况下平均每次操作耗时约为712.2纳秒,而没有设置初始容量的情况下平均每次操作耗时约为4153 纳秒。这是因为设置了初始容量避免了频繁的扩容操作,提高了性能。

所以,虽然切片的自动扩容好用,但是其也是存在代价的。更好得使用切片,应该避免频繁的扩容操作,这里可以在创建切片时预估所需的容量,并提前指定切片的容量,这样可以减少扩容次数,提高性能。需要注意的是,如果你不知道切片需要多大的容量,可以使用适当的初始容量,然后根据需要动态扩容。

2.3 注意切片参数修改原始数据的陷阱

在Go语言中,切片是引用类型。当将切片作为参数传递给函数时,实际上是传递了底层数组的引用。这意味着在函数内部修改切片的元素会影响到原始切片。下面是一个示例代码来说明这个问题:

package main

import "fmt"

func modifySlice(slice []int) {
     slice[0] = 10
     fmt.Println("Modified slice inside function:", slice)
}

func main() {
     originalSlice := []int{1, 2, 3}
     fmt.Println("Original slice:", originalSlice)
     modifySlice(originalSlice)
     fmt.Println("Original slice after function call:", originalSlice)
}

在上述代码中,我们定义了一个modifySlice函数,它接收一个切片作为参数,并在函数内部修改了切片的第一个元素,并追加了一个新元素。然后,在main函数中,我们创建了一个初始切片originalSlice,并将其作为参数传递给modifySlice函数。当我们运行代码时,输出如下:

Original slice: [1 2 3]
Modified slice inside function: [10 2 3]
Original slice after function call: [10 2 3]

可以看到,在modifySlice函数内部,我们修改了切片的第一个元素并追加了一个新元素。这导致了函数内部切片的变化。然而,当函数返回后,原始切片originalSlice数据也受到影响。

如果我们希望函数内部的修改不影响原始切片,可以通过复制切片来解决。修改示例代码如下:

package main

import "fmt"

func modifySlice(slice []int) {
        newSlice := make([]int, len(slice))
        copy(newSlice, slice)

        newSlice[0] = 10
        fmt.Println("Modified slice inside function:", newSlice)
}

func main() {
        originalSlice := []int{1, 2, 3}
        fmt.Println("Original slice:", originalSlice)
        modifySlice(originalSlice)
        fmt.Println("Original slice after function call:", originalSlice)
}

通过使用make函数创建一个新的切片newSlice,并使用copy函数将原始切片复制到新切片中,我们确保了函数内部操作的是新切片的副本。这样,在修改新切片时不会影响原始切片的值。当我们运行修改后的代码时,输出如下:

Original slice: [1 2 3]
Modified slice inside function: [10 2 3]
Original slice after function call: [1 2 3]

可以看到,原始切片保持了不变,函数内部的修改只影响了复制的切片。这样我们可以避免在函数间传递切片时对原始切片造成意外修改。

3. 总结

本文深入探讨了Go语言切片的一些注意事项,旨在帮助读者更好地使用切片。

首先,切片是对底层数组的引用。修改切片的元素会直接影响到底层数组以及其他引用该数组的切片。如果需要避免修改一个切片影响其他切片或底层数组,可以使用copy函数创建一个独立的底层数组。

其次,切片的自动扩容可能带来性能问题。当切片的长度超过容量时,Go语言会自动扩容切片,需要重新分配底层数组并复制数据。为了避免频繁的扩容操作,可以在创建切片时预估所需的容量,并提前指定切片的容量。

最后,需要注意切片作为参数传递给函数时,函数内部的修改会影响到原始切片。如果希望函数内部的修改不影响原始切片,可以通过复制切片来解决。

了解和掌握这些切片的注意事项和技巧,可以避免意外的程序行为。文章来源地址https://www.toymoban.com/news/detail-478159.html

到了这里,关于切片有哪些注意事项是一定要知道的呢的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 购买华为路由器时需要注意哪些事项

        一、路由器功能 路由器的功能:划分网段、QOS、联机限制、DHCP、IP/MAC绑定、IP群组、防火墙、端口、IP、网站管控、端口映射、UPNP、通信和谈、表里网MAC/IP地址修正、长途装备、域名解析、NAT、软件晋级、端口镜像、划分VLAN、系统日记、流量监控。 二、外网接入 企业网

    2024年02月05日
    浏览(47)
  • Allegro引流方式有哪些?Allegro平台注意事项

    正确的引流,你的平台才会让更多人发现,才能提高转化率,那么Allegro引流方式有哪些,Allegro平台注意事项是什么呢? Allegro引流方式有哪些 商品优化: 在Allegro上,关键是确保您的商品能够在搜索结果中排名靠前。优化您的商品标题、描述和,确保它们与用户的搜索

    2024年02月09日
    浏览(44)
  • 巡检过程中有哪些注意事项?智能巡检了解一下

    智能巡检系统是现场过程管理的生产力革命,由人工记录蝶化为掌上电脑运作,适用于设备运行值班记录、仓库/资产管理、设备巡检保养、安全巡更、机房值守、基站维护等一切重复性的工作管理。   安全巡检的目的在于识别信息系统存在的安全脆弱性、分析信息系统存在

    2024年02月07日
    浏览(50)
  • 2023年Java 毕业设计怎么选题,有哪些注意事项

    个人简介: 程序员徐师兄 ,7 年大厂程序员经历,擅长Java、微信小程序、Python、Android等,大家有这一块的问题可以一起交流! 各类成品java毕设 。javaweb,ssh,ssm,springboot等等项目框架,源码丰富,欢迎咨询。学习资料、程序开发、技术解答、代码讲解、文档报告,需要请

    2024年02月11日
    浏览(50)
  • 鲜果蓝产品在做营销时利用小红书发布图文笔记有哪些注意事项?

    笔记要有趣有质感:笔记是一种创意,它要有趣有质感,要有让人耳目一新的感觉,而不是仅仅把产品简单地介绍一遍就收工,要用文字的艺术把产品的特点表现出来,让读者看得赏心悦目,让读者更加热爱你的产品。 要用实物拍摄照片:要用实物拍摄照片,让读者有一个实

    2024年02月03日
    浏览(39)
  • 学习Linux的注意事项(使用经验;目录作用;服务器注意事项)

    本篇分享学习Linux过程中的一些经验 Linux严格区分大小写 Linux中所有内容以文件形式保存 ,包括硬件,Linux是以管理文件的方式操作硬件 硬盘文件是 /dev/sd[a-p] 光盘文件是 /dev/sr0 等 对于设置需要写入文件,命令行的设置在重启之后就会失效,只有下入文件才可以保存下来 文

    2024年02月11日
    浏览(74)
  • 弱电线布线注意什么?弱电线布线的注意事项

    弱电 弱电一般是指直流电路或音频、视频线路、网络线路、电话线路,直流电压一般在36V以内。家用电器中的电话、电脑、电视机的信号输入(有线电视线路)、音响设备(输出端线路)等用电器均为弱电电气设备。 弱电线的种类如:电话线、网络线、有线电视线及音响线

    2024年02月07日
    浏览(47)
  • RabbitMQ开发注意事项

    在使用 RabbitMQ 进行消息队列的开发过程中,有一些注意事项需要牢记: 安全性:确保正确配置 RabbitMQ 实例的安全性。限制对 RabbitMQ 服务器的访问权限,并使用安全的认证机制(如用户名和密码)来保护连接。 错误处理:在消费者端,务必处理可能发生的异常或错误情况。

    2024年02月07日
    浏览(49)
  • 低代码选型注意事项

    凭借着革命性的生产力优势,低代码技术火爆了整个IT圈。面对纷繁复杂的低代码和无代码产品,开发者该如何选择? 在研究低代码平台的年数上,本人已有3年,也算是个低代码资深用户了,很多企业面临低代码选型上的困难,选平台容易,换平台难。下面基于个人理解给大

    2024年02月03日
    浏览(58)
  • 算法竞赛个人注意事项

    浅浅记录一下自己在算法竞赛中的注意事项。 注意看数大小,数学库中的函数尽量加上 * 1.0 , 转成double,防止整型溢出。 , int 型相乘如果可能溢出,乘 * 1LL 。 数据范围大于1e6,注意用快读。 浮点数输入输出: 取模,注意取成负数的情况。 全 int ,但是数据太大,全转

    2024年02月09日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包