热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Go语言学习笔记四数组与切片

数组

创建数组

  • 定义数组也准寻golang的基本定义习惯,变量类型在前,变量名在后
/**
创建一个数组
 */
func createArrays() {
    //第一种 定义数组 
    var arr1 [5]int  //不指定数组值,默认int为0

    //第二种 定义数组方式
    arr2 := [3]int{1, 2, 3} //设置初始值

    //可变长度的数组
    arr3 := [...]int{1, 2, 3, 4, 5}

    //二维数组
    var arr4 [4][5]int //创建一个4行,5列的二维数组
}

遍历数组

  • 遍历数组可以使用原始的fori形式,但是一般不这么做,golang中定义关键字range可以遍历数组
  • 为什么使用range
    • 意义明确,美观
    • c++没有这个能力
    • java/python中有类似的for each value 但是不能同时取出 k ,v
    //原始fori的形式
    for i:=0;i

数组是值类型

  • 注意这一点与Java不同,在Java中数组是引用类型(Golang学习笔记三有复习到Java中值类型与引用类型)
  • 值类型意味着,传递参数时数组是拷贝份,被调用函数中修改数组调用方法上不会被修改

golang值传递方式实验

func printArr(ints [5]int) { //使用数组作为参数,就需要指定数量 ,
    //[5]int类型如果传入[3]int就会报错.
    //如果不想被数量限制可以使用 [...]int作为参数类型
    for k, v := range ints {
        fmt.Println(k, v)
    }
}

func updateArr(ints [5]int) {
    ints[0] = 1000
    fmt.Println("-------------update success----------")
}

func main() {
    arr3 := [...]int{1, 2, 3, 4, 5}
    printArr(arr3)
    fmt.Println("---------------update before--------------")
    updateArr(arr3)
    fmt.Println("---------------update after--------------")
    printArr(arr3)
}

打印结果:

0 1
1 2
2 3
3 4
4 5
---------------update before--------------
-------------update success----------
---------------update after--------------
0 1 //下标0发现确实是没有改变的
1 2
2 3
3 4
4 5

golang指针传递方式

func updateArrByPointer(ints *[5]int) {
    ints[0] = 1000  //可以发现在golang中使用数组指针,直接使用变量名就好不需要再`*ints`来转换
    fmt.Println("-------------update success----------")
}
func main() {
    arr3 := [...]int{1, 2, 3, 4, 5}
    printArr(arr3)
    fmt.Println("---------------update before--------------")
    updateArrByPointer(&arr3) //传入arr指针
    fmt.Println("---------------update after--------------")
    printArr(arr3)
}

打印结果:

0 1
1 2
2 3
3 4
4 5
---------------update before--------------
-------------update success----------
---------------update after--------------
0 1000  //修改成功
1 2
2 3
3 4
4 5

对比Java修改数组

以下为Java代码

@Test
public void testAdd(){
    int[] ints = new int[5];//Java创建数组,初始不指定初始全部为0
    updateArr(ints);
    System.out.println(Arrays.toString(ints)); //打印结果[100, 0, 0, 0, 0]
}

private void updateArr(int[] ints) {
        ints[0]=100;
}

小结

  • [10]int与[5]int是不同类型
  • go语言中数组为值类型,Java中数组为引用类型,调用fun f(arr [10]int)拷贝 数组
  • 因为这些麻烦,一般在go语言中不直接使用数组,而是使用 切片
slice(切片)

如何定义一个slice

    arr := [...]int{0,1,2,3,4,5,6,7}
    s1 := arr[2:6]// [3 4 5 6]左闭右开,一般计算机语言中都是这么定义
    //slice不是值类型,底层是对其数组的封装

由上可知,slice定义是int[:] 关键是在数组括号中加上:冒号,更多定义形式:

func main() {
    arr := [...]int{0,1,2,3,4,5,6,7}
    s1 := arr[2:6]// [3 4 5 6]左闭右开,一般计算机语言中都是这么定义
    //slice不是值类型,底层是对其数组的封装
    fmt.Println("arr[2:6]:",s1)
    s2 := arr[:6]
    fmt.Println("arr[:6]:", s2)
    s3 := arr[2:]
    fmt.Println("arr[2:]:", s3)
    s4 := arr[:]
    fmt.Println("arr[:]:", s4)
}

打印信息:

arr[2:6]: [2 3 4 5]         //当左右都限制数字时,打印截取的是arr的下标 [2,5)元素,左闭右开
arr[:6]: [0 1 2 3 4 5]      //当不限制左边,不填写左边数字,实际打印截取arr下标[0,6)元素
arr[2:]: [2 3 4 5 6 7]      //不限制右边,不填写右边数字,实际打印截取arr下标[2,8)
arr[:]: [0 1 2 3 4 5 6 7]   //两边都不限制时,截取所有

如何使用slice

slice修改数组

在调用函数中修改切片内容

func updateSlice(ints []int) { //[]int 表示slice类型
    ints[2] = 100
}

func main() {
    arr := [...]int{0,1,2,3,4,5,6,7}
    s1 := arr[2:6]// [3 4 5 6]左闭右开,一般计算机语言中都是这么定义
    //slice不是值类型,底层是对其数组的封装
    s2 := arr[:6]
    s3 := arr[2:]
    s4 := arr[:]
    fmt.Println("s1:",s1)
    fmt.Println("------------updateSlice before----------")
    updateSlice(s1) //修改slice s1
    fmt.Println("------------updateSlice after----------")
    fmt.Println("s1:",s1)
    fmt.Println("s2:",s2)
    fmt.Println("s3:",s3)
    fmt.Println("s4:",s4)
    fmt.Println("arr:",arr)
    /**
     - slice不是值类型,go语言中除了slice所有都是值类型(slice的特殊之处就提现在这个地方)
        - slice在内部,是有一个数据结构的,是对arr的一个view(视图)
     */
}

控制台打印为:

s1: [2 3 4 5]
------------updateSlice before----------
------------updateSlice after----------
s1: [2 3 100 5]
s2: [0 1 2 3 100 5]
s3: [2 3 100 5 6 7]
s4: [0 1 2 3 100 5 6 7]
arr: [0 1 2 3 100 5 6 7]

奇妙的事情发生了,所有视图中,对应的arr下标中第4个元素都被修改成了100,由此实验,我们也可以更好的理解什么叫做slice为对应数组的视图.

如果觉得不懂我们可以一步步推导一下:

  1. s1 := arr[2:6]// [3 4 5 6] s1[2] 对应的 arr[4]
  2. fun updateSlice中将s1[2]修改成100,视图(顾名思义对arr数组的引用,而不是拷贝),所以对应的arr[4]被赋值为100(这里是语言表达便于,其实arr[4]就是s1[2]).
  3. 接着因为s2是对arr的[0,6)的视图,故arr[4]就是s2[4],所以s2[4]=100.
  4. 其他s3,s4同理

reslice

reslice顾名思义,就是在slice的基础上继续slice

func main() {
     arr := [...]int{0,1,2,3,4,5,6,7}
     s4 := arr[:]
     fmt.Println("arr[:]:", s4)
     s4 = s4[:6]
     fmt.Println("reslice 1 s4:",s4)
     s4 = s4[2:]
     fmt.Println("reslice 2 s4:",s4)
}

控制台打印

arr[:]: [0 1 2 3 4 5 6 7] 
reslice 1 s4: [0 1 2 3 4 5]
reslice 2 s4: [2 3 4 5] 

slice的拓展

思考下面代码会如何打印?

func main() {
    //拓展
    arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    s1 := arr[2:6] 
    s2 := s1[3:5]
    fmt.Println("s1,s2:",s1,s2)
}

先猜测一下,s1我们知道很简单[2 3 4 5],但是s2是对s1的slice而s1只有4个元素最大下标为3,s2却要取到其下标4,能否成功呢?

先执行看结果:

s1,s2: [2 3 4 5] [5 6] //打印结果是没有报错的,而且从结果分析,s1的第4下标元素是arr的第6下标

尝试打印s1[4]是否成功

    fmt.Println("s1[4]",s1[4])

可以看到报错是不能打印下标4元素的

panic: runtime error: index out of range

goroutine 1 [running]:
main.main()
    D:/IdeaProduct/learngo/container/slices.go:54 +0x15c

所以这里就需要引出slice的底层数据结构来解释:

slice底层数据结构:

Go语言学习笔记四--数组与切片

slice拓展过程:

Go语言学习笔记四--数组与切片

由上图可以看出len() cap()分别指什么位置,ptr指的是什么

  • len() 指slice的长度
  • cap() 指slice可拓展的长度
  • ptr 指slice在view的数组中开始位置

代码检验:

func main() {
    arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    s1 := arr[2:6]
    s2 := s1[3:5]
    fmt.Println("arr:",arr)
    fmt.Printf("s1 v:%v, len(s1):%d, cap(s1):%d\n",s1,len(s1),cap(s1))
    fmt.Printf("s2 v:%v, len(s2):%d, cap(s2):%d\n",s2,len(s2),cap(s2))
}
arr: [0 1 2 3 4 5 6 7]
s1 v:[2 3 4 5], len(s1):4, cap(s1):6    通过结果也印证了我们上诉所说的 len cap的概念
s2 v:[5 6], len(s2):2, cap(s2):3

slice其他操作,添加元素

append

func main() {
//添加
    arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    s1 := arr[2:6]
    s2 := s1[3:5]

    ss1 := append(s1, 10)
    ss2 := append(ss1, 10)

    fmt.Println("arr:",arr)
    fmt.Println("s1:",s1)
    fmt.Println("s2:",s2)
    fmt.Println("ss1:",ss1)
    fmt.Println("ss2:",ss2)
}
arr: [0 1 2 3 4 5 10 10]
s1: [2 3 4 5]
s2: [5 10]
ss1: [2 3 4 5 10] ss1   //是对s1的追加,我们发现其追加完后会再次返回一个slice
                        //且看到arr这个数组的后面两个值被改变了 而arr数组改变,s2也会就会跟着变化
ss2: [2 3 4 5 10 10]

我们再添加一个元素,这时已经超过的arr的长度

func main() {
    arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    s1 := arr[2:6]
    s2 := s1[3:5]

    ss1 := append(s1, 10)
    ss2 := append(ss1, 10)
    ss3 := append(ss2, 10)

    fmt.Println("arr:",arr)
    fmt.Println("s1:",s1)
    fmt.Println("s2:",s2)
    fmt.Println("ss1:",ss1)
    fmt.Println("ss2:",ss2)
    fmt.Println("ss3:",ss3)
    fmt.Printf("ss3 v:%v, len(ss3):%d, cap(ss3):%d\n",ss3,len(ss3),cap(ss3))
    fmt.Printf("arr v:%v, len(arr):%d, cap(arr):%d\n",arr,len(arr),cap(arr))
}
    arr: [0 1 2 3 4 5 10 10]
    s1: [2 3 4 5]
    s2: [5 10]
    ss1: [2 3 4 5 10]
    ss2: [2 3 4 5 10 10]
    ss3: [2 3 4 5 10 10 10]     //当我们继续添加一个元素,因为此时已经是超过arr的长度
                                //此时,打印发现arr不再变化,而返回的ss3是追加上的
    ss3 v:[2 3 4 5 10 10 10], len(ss3):7, cap(ss3):12
                                //打印出ss3的信息可以看出,其实已经是一个行的slice了,跟arr没有任何关系
                                //疑问 为什么 cap为12?
                                //要回答为什么是12我们得再做一个实验

解答cap得长度秘密

func main() {
    var s []int
    for i := 0; i <10; i++ {
        fmt.Printf("s v:%v, len(s):%d, cap(s):%d\n",s,len(s),cap(s))
        s = append(s, i)
    }
}
    s v:[], len(s):0, cap(s):0
    s v:[0], len(s):1, cap(s):1
    s v:[0 1], len(s):2, cap(s):2
    s v:[0 1 2], len(s):3, cap(s):4
    s v:[0 1 2 3], len(s):4, cap(s):4
    s v:[0 1 2 3 4], len(s):5, cap(s):8
    s v:[0 1 2 3 4 5], len(s):6, cap(s):8
    s v:[0 1 2 3 4 5 6], len(s):7, cap(s):8
    s v:[0 1 2 3 4 5 6 7], len(s):8, cap(s):8
    s v:[0 1 2 3 4 5 6 7 8], len(s):9, cap(s):16

        打印结果可以发现,其实cap是有规律得变化得.
        初始时我们创建了一个空的slice s ,再golang中没有null得概念,有nil(nil会初始化数据),但是nil是可以调用赋值的
        cap开始0 后面1 超过1cap后变成2 后面是4,由此可以看出,cap的长度是,当cap长度不足时,扩充2倍

create

     //create slice
    s1 = arr[:]
    s1 = make([]int, 12) //第一个参数指定len长度
    s1 = make([]int, 12,16) //第二个参数可以指定 cap

copy

    //copy slice
    arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    s1 = arr[:]
    copyArr := make([]int, 12,16)
    copy(copyArr, s1) //copy参数,第一个是被赋值的对象,第二个是数据源
    fmt.Println(copyArr)

delete(删除中间,删除头 尾)

删除中间元素

    fmt.Println(copyArr)
    //将copyArr 中第三个元素删掉
    copyArr = append(copyArr[:3], copyArr[4:]...)
    //删除其中元素是没有函数的
    //所以我们可以间接的实现,截取前半部分与后半部分,然后再合起来
    //我们看到append 第二个参数slice后面有...三个点,
    //这是因为在append函数第二个参数不能传入slice但是可以加上三个点就可以了
    fmt.Println(copyArr)
[0 1 2 3 4 5 6 7 0 0 0 0]
[0 1 2 4 5 6 7 0 0 0 0]

取出头元素/取出尾元素

    //取出头元素
    //popping from front
    fmt.Println("copyArr", copyArr)
    front := copyArr[0]
    copyArr = copyArr[1:]
    fmt.Println("front:", front)
    fmt.Println("after popping from front, copyArr:", copyArr)
    //取出最后一个元素
    //poping from back
    tail := copyArr[len(copyArr)-1]
    copyArr = copyArr[:len(copyArr)-1]

    fmt.Println("tail:" ,tail)
    fmt.Println("after poping from back ,copyArr:",copyArr)
copyArr [0 1 2 4 5 6 7 0 0 0 0]
front: 0
after popping from front, copyArr: [1 2 4 5 6 7 0 0 0 0]
tail: 0
after poping from back ,copyArr: [1 2 4 5 6 7 0 0 0]

总结slice是什么

  • slice不是值类型
  • slice是对数组的封装
  • slice是数组的一个view
  • 拓展slice时,可以超过len,但不能超过cap
  • 追加元素append函数,会返回一个新的slice,且如果append的slice在底层数组长度范围内时,会修改底层数组
  • 追加元素超过底层数组时,go会创建一个新的slice,且长度是不够cap的2倍
  • 创建slice时可以用make函数,并指定len与cap

作者所有的学习源码在 go学习源码github地址,如果觉得有用的话帮小智贡献一个star????


推荐阅读
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • C语言常量与变量的深入理解及其影响
    本文深入讲解了C语言中常量与变量的概念及其深入实质,强调了对常量和变量的理解对于学习指针等后续内容的重要性。详细介绍了常量的分类和特点,以及变量的定义和分类。同时指出了常量和变量在程序中的作用及其对内存空间的影响,类似于const关键字的只读属性。此外,还提及了常量和变量在实际应用中可能出现的问题,如段错误和野指针。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 多维数组的使用
    本文介绍了多维数组的概念和使用方法,以及二维数组的特点和操作方式。同时还介绍了如何获取数组的长度。 ... [详细]
author-avatar
ifx0448363
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有