本篇会详细讲解go语言中的array和slice,和平时开发中使用时需要注意的地方,以免入坑。

  Go语言中array是一组定长的同类型数据集合,并且是连续分配内存空间的。

  声明一个数组

  1. var arr [3]int

  数组声明后,他包含的类型和长度都是不可变的.如果你需要更多的元素,你只能重新创建一个足够长的数组,并把原来数组的值copy过来。

  在Go语言中,初始化一个变量后,默认把变量赋值为指定类型的zero值,如string 的zero值为"" number类型的zero值为0.数组也是一样的,声明完一个数组后,数组中的每一个元素都被初始化为相应的zero值。如上面的声明是一个长度为5的int类型数组。数组中的每一个元素都初始化为int类型的zero值 0

  可以使用array的字面量来快速创建和初始化一个数组,array的字面量允许你设置array的长度和array中元素的值

  1. arr := [3]{1, 2, 3}

  如果[]中用...来代替具体的长度值,go会根据后面初始化的元素长度来计算array的长度

  1. arr := [...]{1, 2, 3, 4}

  如果你想只给某些元素赋值,可以这样写

  1. arr := [5]int {1: 5, 3: 200}

  上面的语法是创建了一个长度为5的array,并把index为1的元素赋值为0,index为3的元素赋值为200,其他没有初始化的元素设置为他们的zero值

指针数组

  声明一个包含有5个整数指针类型的数组,我们可以在初始化时给相应位置的元素默认值。下面是给索引为0的元素一个新建的的int类型指针(默认为0),给索引为1的元素指向值v的地址,剩下的没有指定默认值的元素为指针的zero值也就是nil

  1. var v int =
  2. array := []*int{: new(int), : &v}
  3. fmt.Println(len(array))
  4. fmt.Println(*array[])
  5. fmt.Println(*array[])
  6. v2 :=
  7. array[] = &v2
  8. fmt.Println("------------------")
  9. for i, v := range array {
  10. fmt.Printf("index %d, address %v value is ", i, v)
  11. if v != nil {
  12. fmt.Print(*v)
  13. } else {
  14. fmt.Print("nil")
  15. }
  16. fmt.Println(" ")
  17. }

数组做为函数参数

  比如我个创建一个100万长度的int类型数组,在64位机上需要在内存上占用8M的空间,把他做为一个参数传递到一个方法内,go会复制这个数组,这将导致性能的下降。  

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. )
  6.  
  7. const size int = *
  8.  
  9. func sum(array [size]int) float64 {
  10. total := 0.0
  11. for _, v := range array {
  12. total += float64(v)
  13. }
  14. return total
  15. }
  16.  
  17. func main() {
  18. var arr [size]int
  19. fmt.Println(sum(arr))
  20. }

  当然go也提供了其他的方式,可以用指向数组的指针做为方法的参数,这样在传参的时候会传递array的地址,只需要复制8个字节,

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. )
  6.  
  7. const size int = *
  8.  
  9. func sum(array *[size]int) float64 {
  10. total := 0.0
  11. for _, v := range array {
  12. total += float64(v)
  13. }
  14. return total
  15. }
  16.  
  17. func main() {
  18. var arr [size]int
  19. fmt.Println(sum(&arr))
  20. }

slice

  slice可以被认为动态数组,在内存中也是连续分配的。他可以动态的调整长度,可以通过内置的方法append来自动的增长slice长度;也可以通过再次切片来减少slice的长度。

  slice的内部结构有3个字段,分别是维护的底层数组的指针,长度(元素个数)和容量(元素可增长个数,不足时会增长),下面我们定义一个有2个长度,容量为5的slice

  1. func main() {
  2. s := make([]int, , )
  3. fmt.Println("len: ", len(s))
  4. fmt.Println("cap: ", cap(s))
  5. s = append(s, )
  6. fmt.Println("--------")
  7.  
  8. fmt.Println("len: ", len(s))
  9. fmt.Println("cap: ", cap(s))
  10. fmt.Println("--------")
  11. s[] =
  12. for _, v := range s {
  13. fmt.Println(v)
  14. }
  15. }

  初始化slice后,他的长度为2,也就是元素个数为2,因为我们没有给任何一个元素赋值,所以为int的zero值,也就是0.可以用len和cap看一下这个slice的长度和容量。

  用append给这个slice添加新值,返回一个新的slice,如果容量不够时,go会自动增加容易量,小于一1000个长度时成倍的增长,大于1000个长度时会以1.25或者25%的位数增长。

 上面的代码执行完后,slice的结构如下

Slice 的声明和初始化

  创建和初始化一个slice有几种不同的方式,下面我会一一介绍

使用make声明一个slice  

  1. slice1 := make([]int, )
  2. fmt.Println("len: ", len(slice1), "cap: ", cap(slice1), "array :", slice1)
  3. slice1 = append(slice1, )
  4. fmt.Println("len: ", len(slice1), "cap: ", cap(slice1), "array :", slice1)

  make([]int, 3) 声明了个长度为3的slice,容量也是3。下面的append方法会添加一个新元素到slice里,长度和容量都会发生变化。

  输出结果:

  1. len: cap: array : [ ]
  2. len: cap: array : [ ]

  也可以通过重载方法指定slice的容量,下面:

  1. slice2 := make([]int, , )
  2. fmt.Println("len: ", len(slice2), "cap: ", cap(slice2), "array :", slice2)

输出长度为3,容量为7

使用slice字变量

  使用字变量来创建Slice,有点像创建一个Array,但是不需要在[]指定长度,这也是Slice和Array的区别。Slice根据初始化的数据来计算度和容量

  创建一个长度和容量为5的Slice

  1. slice3 := []int{, , , , }
  2. fmt.Println("len: ", len(slice3), "cap: ", cap(slice3), "array :", slice3)

  也可以通过索引来指定Slice的长度和容量,

  下面创建了一个长度和容量为6的slice

  1. slice4 := []int{: }
  2. fmt.Println("len: ", len(slice4), "cap: ", cap(slice4), "array :", slice4)

  声明nil Slice,内部结构的指针为nil。可以用append给slice填加新的元素,内部的指针指向一个新的数组

  1. var slice5 []int
  2. fmt.Println("len: ", len(slice5), "cap: ", cap(slice5), "array :", slice5)
  3. slice5 = append(slice5, )
  4. fmt.Println("len: ", len(slice5), "cap: ", cap(slice5), "array :", slice5)

  

  创建空Slice有两种方式

  1. slice6 := []int{}
  2. fmt.Println("len: ", len(slice6), "cap: ", cap(slice6), "array :", slice6)
  3. slice6 = append(slice6, )
  4. fmt.Println("len: ", len(slice6), "cap: ", cap(slice6), "array :", slice6)
  5.  
  6. slice7 := make([]int, )
  7. fmt.Println("len: ", len(slice7), "cap: ", cap(slice7), "array :", slice7)
  8. slice7 = append(slice7, )
  9. fmt.Println("len: ", len(slice7), "cap: ", cap(slice7), "array :", slice7)

slice的切片

  

  1. // 创建一个容量和长度均为6的slice
  2. slice1 := []int{, , , , , }
  3. // 对slices1进行切片,长度为2容量为4
  4. slice2 := slice1[:]
  5. fmt.Println("cap", cap(slice2))
  6. fmt.Println("slice2", slice2)

  slice1的底层是一个容量为6的数组,slice2指底层指向slice1的底层数组,但起始位置为array的第一个元素也就是23.因为slices2从索引1开始的,所以无法访问底层数组索引1之前的元素,也无法访问容量之后的元素。可以看下图理解一下。

  新创建的切片长度和容量的计算

  对于一个新slice[x:y] 底层数组容量为z,

  x: 新切片开始的元素的索引位置,上面的slice1[1:3]中的1就是起始索引

  y:新切片希望包含的元素个数,上面的slice1[1:3],希望包含2个底层数组的元素 1+2=3

  容量: z-x 上面的slice1[1:3] 底层数组的容量为6, 6-1=5所以新切片的容量为5

修改切片导致的后果

  由于新创建的slice2和slice1底层是同一个数组,所以修改任何一个,两个slice共同的指向元素,会导致同时修改的问题

  

  1. // 创建一个容量和长度均为6的slice
  2. slice1 := []int{, , , , , }
  3. // 对slices1进行切片,长度为2容量为4
  4. slice2 := slice1[:]
  5. fmt.Println("cap", cap(slice2))
  6. fmt.Println("slice2", slice2)
  7.  
  8. //修改一个共同指向的元素
  9. //两个slice的值都会修改
  10. slice2[] =
  11. fmt.Println("slice1", slice1)
  12. fmt.Println("slice2", slice2)

  如图所示

  需注意的是,slice只能访问其长度范围内的元素,如果超出长度会报错。

  除了修改共同指向元素外,如果新创建的切片长度小于容量,新增元素也会导致原来元素的变动。slice增加新元素使用内置的方法append。append方法会创建一个新切片。

  1. // 创建一个容量和长度均为6的slice
  2. slice1 := []int{, , , , , }
  3. // 对slices1进行切片,长度为2容量为4
  4. slice2 := slice1[:]
  5. fmt.Println("cap", cap(slice2))
  6. fmt.Println("slice2", slice2)
  7.  
  8. //修改一个共同指向的元素
  9. //两个slice的值都会修改
  10. slice2[] =
  11. fmt.Println("slice1", slice1)
  12. fmt.Println("slice2", slice2)
  13.  
  14. // 增加一个元素
  15. slice2 = append(slice2, )
  16.  
  17. fmt.Println(slice1)
  18. fmt.Println(slice2)

如果切片的容量足够就把新元素合添加到切片的长度。如果底层的的数组容量不够时,会重新创建一个新的数组并把现有元素复制过去。

  1. slice3 := []int{, , }
  2. fmt.Println("slice2 cap", cap(slice3))
  3.  
  4. slice3 = append(slice3, )
  5. fmt.Println("slice2 cap", cap(slice3))

输出的结果为:

  1. slice2 cap
  2. slice2 cap

容量增长了一倍。

控制新创建slice的容量

  创建一个新的slice的时候可以限制他的容量

  1. // 创建一个容量和长度均为6的slice
  2. slice1 := []int{, , , , , }
  3. // 对slices1进行切片,长度为2容量为3
  4. slice2 := slice1[::]
  5. fmt.Println("cap", cap(slice2))
  6. fmt.Println("slice2", slice2)

  slice2长度为2容量为3,这也是通过上面的公式算出来的

  长度:y-x  3-1=2

  容量:z-x 4-1= 3

需要注意的是容量的长度不能大于底层数组的容量

绿颜色表示slice2中的元素,黄颜色表示容量中示使用的元素。但是需要注意的是,我们修改或者增加slice2容量范围内的元素个数依然会修改slice1。

  1. // 创建一个容量和长度均为6的slice
  2. slice1 := []int{, , , , , }
  3. // 对slices1进行切片,长度为2容量为3
  4. slice2 := slice1[::]
  5. fmt.Println("cap", cap(slice2))
  6. fmt.Println("slice2", slice2)
  7.  
  8. //修改一个共同指向的元素
  9. //两个slice的值都会修改
  10. slice2[] =
  11. fmt.Println("slice1", slice1)
  12. fmt.Println("slice2", slice2)
  13.  
  14. // 增加一个元素
  15. slice2 = append(slice2, )
  16.  
  17. fmt.Println(slice1)
  18. fmt.Println(slice2)

详解go语言的array和slice 【一】的更多相关文章

  1. 详解go语言的array和slice 【二】

    上一篇已经讲解过,array和slice的一些基本用法,使用array和slice时需要注意的地方,特别是slice需要注意的地方比较多.上一篇的最后讲解到创建新的slice时使用第三个索引来限制sl ...

  2. 详解 Go 语言中的 time.Duration 类型

    swardsman详解 Go 语言中的 time.Duration 类型swardsman · 2018-03-17 23:10:54 · 5448 次点击 · 预计阅读时间 5 分钟 · 31分钟之 ...

  3. 详解Go语言调度循环源码实现

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客: https://www.luozhiyun.com/archives/448 本文使用的go的源码15.7 概述 提到"调度&q ...

  4. 详解Javascript中的Array对象

    基础介绍 创建数组 和Object对象一样,创建Array也有2种方式:构造函数.字面量法. 构造函数创建 使用构造函数的方式可以通过new关键字来声明,如下所示: 12 var arr = new ...

  5. Rserve详解,R语言客户端RSclient【转】

    R语言服务器程序 Rserve详解 http://blog.fens.me/r-rserve-server/ Rserve的R语言客户端RSclient https://blog.csdn.net/u ...

  6. LeetCode刷题 1. Two Sum 两数之和 详解 C++语言实现 java语言实现

    1. Two Sum 两数之和 Given an array of integers, return indices of the two numbers such that they add up ...

  7. 机器学习|线性回归算法详解 (Python 语言描述)

    原文地址 ? 传送门 线性回归 线性回归是一种较为简单,但十分重要的机器学习方法.掌握线性的原理及求解方法,是深入了解线性回归的基本要求.除此之外,线性回归也是监督学习回归部分的基石. 线性回归介绍 ...

  8. 详解C语言的类型转换

    1.自动类型转换 字符型变量的值实质上是一个8位的整数值,因此取值范围一般是-128-127,char型变量也可以加修饰符unsigned,则unsigned char 型变量的取值范围是0-255( ...

  9. for循环使用详解(c语言版)

    说明:c语言的很多语法部分都和JAVA,AS等相同 特别是for的用法. c语言中的for循环语句使用最为灵活,不仅可以用于循环次数已经确定的情况,而且可以用于循环次数不确定而只给出循环结束条件的情况 ...

随机推荐

  1. C/C++ 知识点---C语言关键字(32个)

    C/C++ 知识点 1.C语言关键字(32个) <1>.基本数据类型 [5] void :声明函数无返回值或无参数,声明空类型指针 char :声明字符型变量 int :声明整形变量 fl ...

  2. 【javascript】详解javascript闭包 — 大家准备好瓜子,我要开始讲故事啦~~

    前言: 在这篇文章里,我将对那些在各种有关闭包的资料中频繁出现,但却又千篇一律,且暧昧模糊得让人难以理解的表述,做一次自己的解读.或者说是对“红宝书”的<函数表达式/闭包>的那一章节所写的 ...

  3. fis3+vue+pdf.js制作预览PDF文件或其他

    人生第一篇博客,的确有点紧张,但有些许兴奋,因为这对于我来说应该是一个好的开始,以此励志在技术的道路上越走越远. 看过了多多少少的技术博客,给自己带来了很多技术上的收获,也因此在想什么时候自己也可以赠 ...

  4. 使用webpack热加载,开发多页面web应用

    我们一般使用webpack热加载开发SPA应用,但工作中难免会遇到一些多页面的demo或项目. 故参考 kingvid-chan 的代码,搭了一个使用HRM开发多页面web应用的脚手架,刚好也进一步学 ...

  5. Centos 6.5 安装 python3.6.2、pip9.0.1、ipython6.1

    说明:由于Cenots 6.5 默认是安装的 python 2.6.6  要想同一台主机使用多个python版本,不能影响原来的版本,因为系统很多还依赖于python,比如 yum    python ...

  6. org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML.问题思路

    15:36:34,549 WARN DefaultListableBeanFactory:1416 - Bean creation exception on FactoryBean type chec ...

  7. ubuntu下安装rubymine

    1.安装jdk 先查看系统有没有安装jdk,打开终端,输入以下命令: java -version 如果没有安装,在联网的环境下执行: $ -jdk 2.安装rubymine 从官网(http://ww ...

  8. 使用Groovy处理SoapUI中Json response

    最近工作中,处理最多的就是xml和json类型response,在SoapUI中request里面直接添加assertion处理json response的话,可以采用以下方式: import gro ...

  9. python requests 官方文档

    链接:http://docs.python-requests.org/zh_CN/latest/user/quickstart.html

  10. sed修炼系列(一):花拳绣腿之入门篇

    本文为花拳绣腿招式入门篇,主要目的是入门,为看懂sed修炼系列(二):武功心法做准备.虽然是入门篇,只介绍了基本工作机制以及一些选项和命令,但其中仍然包括了很多sed的工作机制细节.对比网上各sed相 ...