我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Go语言中Slice常见陷阱与避免方法详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Go语言中Slice常见陷阱与避免方法详解

前言

Go 语言提供了很多方便的数据类型,其中包括 slice。然而,由于 slice 的特殊性质,在使用过程中易犯一些错误,如果不注意,可能导致程序出现意外行为。本文将详细介绍 使用 slice 时易犯的一些错误,帮助读者更好的使用 Goslice,避免犯错误。

slice 作为函数 / 方法的参数进行传递的陷阱

slice 作为参数进行传递,有一些地方需要注意,先说结论:

1、在函数里修改切片元素的值,原切片的值也会被改变

若想修改新切片的值,而不影响原切片的值,可以对原切片进行深拷贝:

通过 copy(dst, class="lazy" data-src []Type) int 函数将原切片的元素拷贝到新切片中:此函数在拷贝时,会基于两个切片中,最小长度为基础去拷贝,也就是初始化新切片时,长度必须大于等于原切片的长度

2、在函数里通过 append 方法,对切片执行追加元素的操作,可能会引起切片扩容,导致内存分配的问题,可能会对程序的性能 造成影响

为避免切片扩容,导致内存分配,对程序的性能造成影响,在初始化切片时,应该根据使用场景,指定一个合理 cap 参数。

3、在函数里通过 append 函数,对切片执行追加元素的操作,原切片里不存在新元素

若想实现执行 append 函数之后,原切片也能得到新元素;需将函数的参数类型由 切片类型 改成 切片指针类型

通过例子来感受一下上面结论的由来:

package main

import "fmt"

func main() {
   s := []int{0, 2, 3}
   fmt.Printf("切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [0, 2, 3]
   sliceOperation(s)
   fmt.Printf("切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [1, 2, 3]
}

func sliceOperation(s []int) {
   s[0] = 1
   s = append(s, 4)
   fmt.Printf("切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 4 6 [1, 2, 3]
}

首先定义并初始化切片 s,切片里有三个元素;

调用 sliceOperation 函数,将切片作为参数进行传递;

在函数里修改切片的第一个元素的值为 1,然后通过 append 函数插入元素 4,此时函数里的切片 由于容量不够,s 的容量被扩大了,变成 原 cap * 2 = 3 * 2 = 6

打印结果已注释在代码里,通过打印结果可知:

  • 在函数里修改切片的第一个元素的值,原切片元素的值也会改变;
  • 在函数里通过 append 函数,向切片追加元素 4,原切片并没有此元素;
  • 函数里的切片扩容了,原切片却没有。

由于切片是引用类型,因此在函数修改切片元素的值,原切片的元素值也会改变。

有的人可能会产生以下两个疑问

1、既然切片是引用类型,为什么通过 append 追加元素,原切片 s 却没有新元素?

2、为什么函数里的切片扩容了,原切片却没有?

在探究这两个问题之前,我们需要了解切片的数据结构:

type slice struct {
   array unsafe.Pointer
   len   int
   cap   int
}

切片包含三个字段:array (指针类型,指向一个数组)、len (切片的长度)、cap (切片的容量)。

知道了切片的数据结构,我们通过图片来直观地看看切片 s

切片 s 没有被修改之前,在内存中是以上图所描述的形式存在,array 指针变量指向数组 [0, 2, 3],长度为 3,容量为 3

在执行 sliceOperation 函数之后,原切片 ssliceOperation 函数里的切片 s 如上图所示。

通过上上图和上图对比可知,底层数组 [0, 2, 3] 的第一个元素的值被修改为 1,然后追加元素 4,此时函数里的切片发生变化,长度 3 → 4,容量 3 → 6 变成原来的两倍,底层数组的长度也由 3 → 6

由于原切片s的长度为3array 指针所指向的区域只有 [1, 2, 3],这也是为什么在函数里新增了 元素 4,在原切片 s 里看不到的原因。

第一个问题解决了,我们来思考第二个问题的原因:

Go 中,函数 / 方法的参数传递方式为值传递main 函数将 s 传递过来,sliceOperation 函数用 s 去接收,此时的s为新的切片,只不过它们所指向的底层数组为同一个,长度和容量也是一样。而扩容操作是在新切片上进行的,因此原切片不受影响。

slice 通过 make 函数初始化,后续操作不当所造成的陷阱

使用 make 函数初始化切片后,如果在后续操作中没有正确处理切片长度,容易造成以下陷阱:

越界访问:如果访问超出切片实际长度的索引,则会导致 index out of range 错误,例如:

func main() {
   s := make([]int, 0, 4)
   s[0] = 1 // panic: runtime error: index out of range [0] with length 0
}

通过 make([]int, 0, 4) 初始化切片,虽说容量为 4,但是长度为 0,如果通过索引去赋值,会发生panic;为避免 panic,可以通过 s := make([]int, 4)s := make([]int, 4, 4) 对切片进行初始化。

切片初始化不当,通过 append 函数追加新元素的位置可能于预料之外

func main() {
   s := make([]int, 4)
   s = append(s, 1)
   fmt.Println(s[0]) // 0

   s2 := make([]int, 0, 4)
   s2 = append(s2, 1)
   fmt.Println(s2[0]) // 1
}

通过打印结果可知,对于切片 s,元素 1 没有被放置在第一个位置,而对于切片 s2,元素 1 被放置在切片的第一个位置。这是因为通过 make([]int, 4)make([]int, 0, 4) 初始化切片,底层所指向的数组的值是不一样的:

  • 第一种初始化的方式,切片的长度和容量都为 4,底层所指向的数组长度也是 4,数组的值为 [0, 0, 0, 0],每个位置的元素被赋值为零值s = append(s, 1) 执行后,s 切片的值为 [0, 0, 0, 0, 1]
  • 第二种初始化的方式,切片的长度为 0,容量为 4,底层所指向的数组长度为 0,数组的值为 []s2 = append(s2, 1) 执行后,s2 切片的值为 [1]
  • 通过 append 向切片追加元素,会执行尾插操作。如果我们需要初始化一个空切片,然后从第一个位置开始插入元素,需要避免 make([]int, 4) 这种初始化的方式,否则添加的结果会在预料之外。

性能陷阱

内存泄露

内存泄露是指程序分配内存后不再使用该内存,但未将其释放,导致内存资源被浪费。

切片引用切片场景:如果一个切片有大量的元素,而它只有少部分元素被引用,其他元素存在于内存中,但是没有被使用,则会造成内存泄露。代码示例如下:

  var s []int

  func main() {
     sliceOperation()
     fmt.Println(s)
  }

  func sliceOperation() {
     a := make([]int, 0, 10)
     a = append(a, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
     s = a[0:4]
  }

上述代码中,切片 a 的元素有 10 个,而切片 s 是基于 a 创建的,它底层所指向的数组与 a 所指向的数组是同一个,只不过范围为前四个元素,而后六个元素依然存在于内存中,却没有被使用,这样会造成内存泄露。为了避免内存泄露,我们可以对代码进行改造: s = a[0:4]s = append(s, a[0:4]...),通过 append 进行元素追加,这样切片 a 底层的数组没有被引用,后面会被 gc

扩容

扩容陷阱在前面的例子也提到过,通过 append 方法,对切片执行追加元素的操作,可能会引起切片扩容,导致内存分配的问题。

  func main() {
     s := make([]int, 0, 4)
     fmt.Printf("切片的长度:%d, 切片的容量:%d\n", len(s), cap(s)) // 4 4
     s = append(s, 1, 2, 3, 4, 5)
     fmt.Printf("切片的长度:%d, 切片的容量:%d\n", len(s), cap(s)) // 5 8
  }

切片扩容,可能会对程序的性能 造成影响;为避免此情况的发生,应该根据使用场景,估算切片的容量,指定一个合理 cap 参数。

到此这篇关于Go语言中Slice常见陷阱与避免方法详解的文章就介绍到这了,更多相关Go语言Slice内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Go语言中Slice常见陷阱与避免方法详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Go语言中Slice常见陷阱与避免方法详解

这篇文章主要为大家详细介绍的是Go语言中的Slice的常见陷阱以及如何避免这些错误,文中的示例代码讲解详细,感兴趣的小伙伴可以学习一下
2023-02-14

Go 语言 EOF 错误指南:避免常见陷阱

eof 错误在 go 语言中常见,发生在文件末尾读取时。处理方法包括:1. 明确检查 io.eof;2. 使用 io.eof 类型断言;3. 使用包装错误。处理 eof 错误可以防止程序意外崩溃,使其更健壮。Go 语言 EOF 错误指南:避
Go 语言 EOF 错误指南:避免常见陷阱
2024-04-08

Go语言项目开发中的常见陷阱与避坑指南

在Go语言项目开发中,我们可能会遇到一些常见的陷阱,这些陷阱可能会给我们的项目带来一些不必要的麻烦和困扰。因此,为了帮助大家更好地避免这些陷阱,本文将介绍一些常见的陷阱以及相应的避坑指南。一、内存泄漏Go语言的垃圾回收机制是其一大特点,但并
Go语言项目开发中的常见陷阱与避坑指南
2023-11-02

CMS插件开发中的常见陷阱及避免方法

CMS插件开发中可能遇到的常见陷阱有很多,包括安全漏洞、性能问题、兼容性问题等。本文将介绍几种常见的陷阱及其避免方法,帮助开发者开发出更安全、更可靠的插件。
CMS插件开发中的常见陷阱及避免方法
2024-02-14

PHP PDO常见错误及解决方案:避免开发中的陷阱

在使用 PHP PDO 进行数据库操作时,经常会遇到各种各样的错误。这些错误不仅会影响程序的正常运行,还可能导致安全问题。因此,了解常见的 PDO 错误及解决方案非常重要。
PHP PDO常见错误及解决方案:避免开发中的陷阱
2024-02-13

Python并发编程中的常见陷阱与解决方案,帮助你避开编程雷区

Python并发编程中存在许多常见的陷阱,例如死锁、竞态条件和资源耗尽。本文将探讨这些陷阱并提供解决方案,帮助您编写更健壮、更可靠的并发程序。
Python并发编程中的常见陷阱与解决方案,帮助你避开编程雷区
2024-02-05

避免陷阱!数据库连接池管理的常见错误与解决方案,助你网站稳定运行。

数据库连接管理是网站稳定运行的一个重要组成部分。如果数据库连接管理不当,可能会导致网站崩溃、数据丢失等严重问题。本文将介绍数据库连接管理的常见错误及其解决方案,帮助您避免这些错误,确保网站的稳定运行。
避免陷阱!数据库连接池管理的常见错误与解决方案,助你网站稳定运行。
2024-02-06

详解Go语言中init的使用与常见应用场景

Go语言中的init函数在程序启动时自动执行,可用于包级初始化、资源管理、注册插件等。它不接受参数,也不返回值。通过init函数,开发者可在程序启动时执行必要的初始化,确保依赖关系正确初始化,并实现顺序或并行初始化。遵循最佳实践,如保持简洁、避免依赖、处理错误和使用defer,可有效使用init函数。
详解Go语言中init的使用与常见应用场景
2024-04-02

Go语言项目开发的常见问题与解决方法

Go语言作为一种高性能、简洁易用的编程语言,越来越多的开发者开始选择它作为项目开发的首选语言。然而,在实际的项目开发过程中,我们也会遇到一些常见的问题。本文将介绍一些这样的问题,并提供相应的解决方法,帮助开发者更好地应对这些挑战。问题一:依
Go语言项目开发的常见问题与解决方法
2023-11-03

Go语言项目开发中的常见问题与解决方案

Go语言项目开发中的常见问题与解决方案Go语言作为一种简洁高效的开发语言,受到越来越多的开发者的青睐。在实际的项目开发中,开发者也会遇到一些常见的问题。本文将提供一些常见问题的解决方案,帮助开发者更好地应对挑战。一、依赖管理在Go语言项目中
Go语言项目开发中的常见问题与解决方案
2023-11-04

详解Go语言中方法与函数的异同

在 go 中,方法与类型相关,通过类型名.方法名调用,可修改接收者值;而函数独立于类型,直接通过函数名调用。方法与函数的区别:方法与类型相关,函数独立于类型。方法通过类型名.方法名调用,函数直接通过函数名调用。方法可修改接收者值,函数不可。
详解Go语言中方法与函数的异同
2024-04-03

详解Go语言各种常见类型的默认值和判空方法

本文主要介绍了详解Go语言各种常见类型的默认值和判空方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-05-16

Go语言返回值类型推断中常见的错误和解决方法

常见的类型推断错误包括:推断为 any 类型:使用明确类型注释解决。推断为不匹配的类型:使用明确返回类型注释解决。调用期间类型错误:强制转换返回值类型或使用类型断言解决。Go语言返回值类型推断中的常见错误和解决方法Go 语言的类型推断功能
Go语言返回值类型推断中常见的错误和解决方法
2024-04-29

详解Go语言中获取文件路径的不同方法与应用场景

在Go语言中,获取文件路径的方法有多种,每种都有其特定的应用场景。本文详细介绍了使用os.Getwd()获取当前工作目录、使用filepath.Abs()转换相对路径、使用filepath.Join()连接路径片段、使用io/ioutil.TempDir()创建临时目录、使用filepath.Dir()提取目录路径和使用filepath.Base()提取文件名等方法及其应用场景,旨在帮助开发者根据实际需要选择合适的方法进行文件路径操作。
详解Go语言中获取文件路径的不同方法与应用场景
2024-04-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录