Golang的高级数据类型-切片(slice)实战篇

摘要:
否则,将追究法律责任。1、 切片的基本用法1˃。切片的定义和初始化。分配packagemainimportfuncmain(){/*什么是切片?切片是Go中的一种特殊数据结构。这种数据结构更易于使用和管理数据集。len:int类型对应于8字节cap:int类型相应于8字节*/s1:=[]int{100200300}//自动派生类型用于定义切片fmt。Printf/*向slice1切片添加(扩展)5个元素,

          Golang的高级数据类型-切片(slice)实战篇

                             作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

 

   切片(slice)是Go中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合,切片是围绕动态数组的概念构建的,可以按需自动增长。

  

 

一.切片的基本使用 

1>.切片的定义和初始化赋值

package main

import (
    "fmt"
)

func main() {

    /*
        什么是切片(slice):
            切片是Go中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合。
            切片是围绕动态数组的概念构建的,可以按需自动增长。

        切片的定义格式:
            语法一(定义空(nil)切片):
                var 切片名称 []数组类型

            语法二(通过make创建切片):
                var 切片名称 []数组类型 = make([]数据类型,长度,容量)

            语法三(自动推导类型初始化):
                切片名称 := []数组类型{元素1,元素2,...,元素N}
                切片名称 := make([]数据类型,长度,容量)

        切片的长度(len)与容量(cap):
            len(切片名称):
                计算切片有效数据个数,长度是已经初始化的空间,切片初始空间若为被赋值则使用对应数据类型的默认值。
            cap(切片名称):
                计算切片容量,容量是一家开辟的空间,包括一家初始化的空间和空闲的空间。

        常见数组类型的默认值:
            整型默认初始化值为0;
            浮点型默认初始化值为0;
            字符串类型默认初始化值为空串("");
            布尔类型默认初始化值为false;
    */

    //定义一个空(nil)切片,空切片不能添加数据
    var slice1 []int
    fmt.Printf("slice1的数据为:%d,长度为:%d,容量为:%d
", slice1, len(slice1), cap(slice1))

    //通过make创建切片
    var slice2 []int = make([]int, 3, 5)
    fmt.Printf("slice2的数据为:%d,长度为:%d,容量为:%d
", slice2, len(slice2), cap(slice2))

    //基于切片索引进行赋值时,其索引大小不能等于或超过切片的长度,否则就会报错"index out of range"
    slice2[1] = 666
    fmt.Printf("slice2的数据为:%d,长度为:%d,容量为:%d
", slice2, len(slice2), cap(slice2))

    //自动推导类型创建切片
    slice3 := []int{1, 2, 3, 4, 5}
    slice4 := make([]int, 2, 5)    //make函数可以理解为给slice4这个切片申请空间
    fmt.Printf("slice3的数据为:%d,长度为:%d,容量为:%d
", slice3, len(slice3), cap(slice3))
    fmt.Printf("slice4的数据为:%d,长度为:%d,容量为:%d
", slice4, len(slice4), cap(slice4))

}

Golang的高级数据类型-切片(slice)实战篇第1张

2>.切片作为函数参数(本质上是引用地址传递)

package main

import (
    "fmt"
)

func Demo(s1 []int32) {
    //修改切片元素的值
    s1[0] = 'Y'

    fmt.Printf("s1的数据为:[%s],内存地址为:[%p]
", string(s1), s1)
}

func main() {

    char := []rune{'', '', '', '', '', '', ''}
    fmt.Printf("char的数据为:[%s],内存地址为:[%p]
", string(char), char)

    /*
       在Go语言中,数组作为参数进行传递时值传递,而切片作为参数进行传递时引用传递。
           值传递:
               方法调用时,实参数把他的值传递给对应的形式参数,方法执行中形式参数值的改变不会影响实际参数的值。
           引用传递(也称为传地址):
               函数调用时,实际参数的引用(地址,而不是参数的值)被传递给函数中相对应的形式参数(实参与形参指向了同一块存储区域);
               在函数执行时,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变会影响实际参数的值。

       温馨提示:
           建议在开发中使用切片代替数组。
    */
    Demo(char)

    fmt.Printf("char的数据为:[%s],内存地址为:[%p]
", string(char), char) //主线程调用函数切片元素的值被修改
}

Golang的高级数据类型-切片(slice)实战篇第2张

3>.切片中的指针问题刨析

package main

import (
    "fmt"
)

func main() {

    //定义一个切片
    s1 := []rune{'', '', ''}

    /*
        C++转过来学Go还容易理解指针,但是从Python和Java转过来学Go时接触Go语言的小伙伴是不是很抵触指针呢?

        我很好奇格式化输出有一个"%p",但是我传递切片"s1"和"&s1"是不一样的,这是为什么呢?
    */
    fmt.Printf("char的数据为:[%s],内存地址为:[%p]
", string(s1), s1)

    fmt.Printf("char的数据为:[%s],内存地址为:[%p]
", string(s1), &s1)

    /*
        指针问题刨析:
            切片名s1是指向内存空间,所以s1其实存的是切片在内存中的存储地址。
            而"&s1"其实打印的是存储地址空间的地址(即s1变量本身的地址),换句话说,s1本身也是一个变量,它也有自己的内存地址。
    */
}

Golang的高级数据类型-切片(slice)实战篇第3张

  问题:
    我们在使用"fmt"包中"Printf"方法中的"%p"进行格式化时,为什么数组都需要加取值符("&"),而切片却不用呢?
  问题刨析:
    首先,不是
"%p"就一定能够打印地址,变量本身存储的是地址,才能打印地址。接下来我们分析一下数组和切片的关系:       数组是可以直接存储数据的,如果想要打印数组的地址,首先得使用取值符("&")来取地址。       切片并不是直接存储数据的,切片需要先使用make申请空间,然后得到一个空间的引用地址才能基于该引用地址存储数据。也就是说切片本身就是一个引用地址。

二.切片扩容

package main

import (
    "fmt"
)

func main() {

    /*
       切片的扩容:
           切片的长度时不固定的,可以向已经定义的切片中追加数据。
           通过append函数可以在原切片的尾部添加元素

       切片的结构如下所示,其中unsafe.Pointer对应的类型为:
           type slice struct {
               array unsafe.Pointer
               len   int
               cap   int
           }
           接下来我们来刨析一下slice结构体:
               array:
                   存储的是数组指针,它指向了数组的内存地址。
                   其类型"unsafe.Pointer"对应“type Pointer *ArbitraryType”,而"ArbitraryType"对应的是"int",而int在64为操作系统上来讲就是int64,对应8字节。
               len:
                   int类型对应的是8字节
               cap:
                   int类型对应的是8字节
    */
    s1 := []int{100, 200, 300} //使用自动推导类型定义切片
    fmt.Printf("s1的长度为:%d,容量为:%d,内存地址为:%p
", len(s1), cap(s1), s1)

    /*
       向slice1切片中添加(扩容)了5个元素,尽管slice1之前的容量是3,但依旧是可以往里面添加数据的哟~而且并不会改变slice1的内存地址。
    */
    s1 = append(s1, 100)
    fmt.Printf("s1的长度为:%d,容量为:%d,内存地址为:%p
", len(s1), cap(s1), s1)

    s1 = append(s1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
    fmt.Printf("s1的长度为:%d,容量为:%d,内存地址为:%p
", len(s1), cap(s1), s1)

    /*
        如果切片扩容超过切片的容量,那么Go语言会该切片重新申请一个内存空间,把原来切片中的数据拷贝到新的地址空间,因此在生产环境中建议大家不要将切片的容量设置过小。

        切片的扩容规则(参考源码文件"runtime/slice.go"的"func growslice(et *_type, old slice, cap int) slice"函数):
            如果长度和容量相等分为下面三种情况:
              1>.长度和容量小于等于896时:
                再次为切片添加数据时,切片会自动扩容,扩容大小为上一次的2倍。
              2>.长度和容量大于896小于1024时:
                再次为切片添加数据时,切片会自动扩容,扩容大小固定为2048。
              3>.长度和容量大于等于1024时:
                再次为切片添加数据时,切片会自动扩容扩容为上一次的四分之一(1/4)。

    */
    s2 := make([]int, 896, 896)
    fmt.Printf("s2的长度为:%d,容量为:%d,内存地址为:%p
", len(s2), cap(s2), s2)
    s2 = append(s2, 1) //
    fmt.Printf("s2的长度为:%d,容量为:%d,内存地址为:%p
", len(s2), cap(s2), s2)

    s3 := make([]int, 897, 897)
    fmt.Printf("s3的长度为:%d,容量为:%d,内存地址为:%p
", len(s3), cap(s3), s3)
    s3 = append(s3, 2) //
    fmt.Printf("s3的长度为:%d,容量为:%d,内存地址为:%p
", len(s3), cap(s3), s3)

    s4 := make([]int, 1023, 1023)
    fmt.Printf("s4的长度为:%d,容量为:%d,内存地址为:%p
", len(s4), cap(s4), s4)
    s4 = append(s4, 2) //
    fmt.Printf("s4的长度为:%d,容量为:%d,内存地址为:%p
", len(s4), cap(s4), s4)

    s5 := make([]int, 1024, 1024)
    fmt.Printf("s5的长度为:%d,容量为:%d,内存地址为:%p
", len(s5), cap(s5), s5)
    s5 = append(s5, 2) //
    fmt.Printf("s5的长度为:%d,容量为:%d,内存地址为:%p
", len(s5), cap(s5), s5)

    s6 := make([]int, 1025, 1025)
    fmt.Printf("s6的长度为:%d,容量为:%d,内存地址为:%p
", len(s6), cap(s6), s6)
    s6 = append(s6, 2)
    fmt.Printf("s6的长度为:%d,容量为:%d,内存地址为:%p
", len(s6), cap(s6), s6)
}

Golang的高级数据类型-切片(slice)实战篇第4张

三.切片的截取

package main

import "fmt"

func main() {
    /*
        切片的截取:
            所谓截取就是从切片中获取指定的数据。

        切片常见的操作如下所示:
            "slice[n]":
                切片slice中索引位置的为n的选项。
            "slice[:]":
                从切片slice的索引位置0到len(slice)-1处获得的切片
            "slice[low:]":
                从切片slice的索引位置low到len(slice)-1处获得的切片
            "slice[:high]":
                从切片selice的索引位置0到high处所获得的切片,len=high-low
            "slice[low:high:max]":
                从切片slice的索引位置low到high处所获得的切片,len=high-low,cap=max-low
            "len(s)":
                表示切片s的长度,切片的长度总是小于等于(>=)切片容量的(cap(s))
            "cap(s)":
                表示切片s的容量,切片的容量总是大于等于(<=)切片的长度(len(s))
    */

    slice1 := []rune{'', '', '', '', '', '', ''}
    fmt.Printf("slice1的数据为:[%s],长度为:[%d],容量为:[%d]
", string(slice1), len(slice1), cap(slice1))

    s1 := slice1[2] //获取切片索引为2的元素
    fmt.Printf("s1的数据为:[%s]
", string(s1))

    s2 := slice1[:] //默认截取的长度和容量相等
    fmt.Printf("s2的数据为:[%s],长度为:[%d],容量为:[%d]
", string(s2), len(s2), cap(s2))

    s3 := slice1[3:] //设置截取的起始索引,左闭右开
    fmt.Printf("s3的数据为:[%s],长度为:[%d],容量为:[%d]
", string(s3), len(s3), cap(s3))

    s4 := slice1[:4] //设置截取的结束索引,左闭右开
    fmt.Printf("s4的数据为:[%s],长度为:[%d],容量为:[%d]
", string(s4), len(s4), cap(s4))

    s5 := slice1[1:3] //设置截取的起始索引和结束索引,左闭右开
    fmt.Printf("s5的数据为:[%s],长度为:[%d],容量为:[%d]
", string(s5), len(s5), cap(s5))

    s6 := slice1[1:3:6] //设置截取的起始索引和结束索引并指定容量
    fmt.Printf("s6的数据为:[%s],长度为:[%d],容量为:[%d]
", string(s6), len(s6), cap(s6))
}

Golang的高级数据类型-切片(slice)实战篇第5张

四.切片的浅拷贝(虽然Go语言支持二维数组,但不建议大家使用,推荐使用字典)

package main

import (
    "fmt"
)

func main() {

    /*
       Go语言的内置函数copy()可以将一个切片复制到另一个切片。
       如果加入的两个数组切片不一样,就会按其中较小的那个数组切片的元素个数进行复制。
    */
    src := []int32{'', '', '', '', '', '', ''}
    fmt.Printf("src的数据为:[%s],长度为:[%d],容量为:[%d],内存地址为:[%p]
", string(src), len(src), cap(src), src)

    //创建一个长度为5的元素大小的切片
    dest := make([]rune, 5)

    //切片拷贝,会根据较小的切片进行拷贝,由于dest的容量为5,而src的容量为7,将src的元素拷贝到dest时会按照容量较小的(即dest的容量)来进行拷贝
    copy(dest, src)
    fmt.Printf("dest的数据为:[%s],长度为:[%d],容量为:[%d],内存地址为:[%p]
", string(dest), len(dest), cap(dest), dest)

}

Golang的高级数据类型-切片(slice)实战篇第6张

五.删除切片(生产环境中不推荐大家删除切片的元素,如果非要删除建立考虑从尾部删除,具体原因看下面案例你就知道啦~)

package main

import (
    "fmt"
)

func main() {
    /*
           Go语言切片删除:
               Go语言并没有对删除切片提供相对应的函数,需要使用切片本身的特性来删除元素。

           切片删除的本质:
                以被删除的元素为起点,到删除的元素为终点,将前后两部分数据在内存重新连接起来。
                当删除一个切片的第一个元素时,那么第一个元素的存储空间会被释放,但此时后面的所有元素都得集体往前移动一个元素哟,这是很消耗性能的,尤其是在数据量大的情况下;
                当删除一个切片的最后一个元素时,那么最后一个元素的存储空间也会被释放,由于最后一个元素后面没有元素啦,因此不会产生数据的移动。

           温馨提示:
                如果切片元素过多,整个删除过程非常消耗性能,因为删除切片前面的元素,那么该切片前面的元素空出来后,后面的元素依次往前面移动,如果数据量很大的情况下效率极低。
                生产环境中,不建议大家对数据量较大的切片进行元素删除操作,如果数据有频繁的删除操作哦,建议换其它数据存储容器。
                删除切片的数据并不会修改切片的容量大小。
    */
    s1 := []rune{'2', '0', '2', '0', '', '', '', '', ''}
    fmt.Printf("删除前s1的数据为:[%s],长度为:[%d],容量为:[%d],内存地址为:[%p]
", string(s1), len(s1), cap(s1), s1)
    fmt.Printf("删除前s1各元素地址:[%p] [%p] [%p] [%p] [%p] [%p] [%p] [%p] [%p]  
", &s1[0], &s1[1], &s1[2], &s1[3], &s1[4], &s1[5], &s1[6], &s1[7], &s1[8])

    s1 = append(s1[:0], s1[4:]...) //删除下标前面四个的元素,左闭右开(即顾左不顾右),需要使用不定参格式
    fmt.Printf("删除后s1的数据为:[%s],长度为:[%d],容量为:[%d],内存地址为:[%p]
", string(s1), len(s1), cap(s1), s1)
    fmt.Printf("删除后s1各元素地址:[%p] [%p] [%p] [%p] [%p] 
", &s1[0], &s1[1], &s1[2], &s1[3], &s1[4])
}

Golang的高级数据类型-切片(slice)实战篇第7张

免责声明:文章转载自《Golang的高级数据类型-切片(slice)实战篇》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇使用systemctl start docker启动失败额!Java中用户线程和守护线程区别这么大?下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

xml中“ &amp;lt; &amp;gt; ”转义为“ &amp;amp;lt; &amp;amp;gt; ”问题处理

曾经也碰到过类似问题,解决方法是在发送或者解析报文前执行上面的方法将内容转义一下,现在我用dom4j组装如下的报文(报文体中内容传输时加密处理),大致介绍一下上面方法的使用,具体看代码。 importjava.io.StringReader; importjava.io.StringWriter; importjava.math.BigDecima...

JAVA:借用OpenOffice将上传的Word文档转换成Html格式

为什么会想起来将上传的word文档转换成html格式呢?设想,如果一个系统需要发布在页面的文章都是来自word文档,一般会执行下面的流程:使用word打开文档,Ctrl+A,进入发布文章页面,Ctrl+V。看起来也不麻烦,但是,如果文档中包含大量图片呢?尴尬的事是图片都需要重新上传吧? 如果可以将已经编写好的word文档上传到服务器就可以在相应页面进行展示...

java8中List根据某一字段去重

实体类: package test; public class User { private String userid; private String username; private String age; private String address; public User(String use...

Lua字符串及模式匹配

字符类基础函数举例介绍: string.len( ‘string’ ) string.lower( ‘string’ ) string.upper( ‘string’ ) string.rep( ‘a’ , 5 ) ==> aaaaa string.sub( ‘string’ , I , j ) string.sub(...

halcon+csharp多图像拼接实现

简单的来说,就是将 一类的图片最后拼接成为这样的结果 这个图片有点大呀。 基本步骤: 1、halcon进行仿射变化进行镜头畸变。这个可以参考halcon中一个二维码畸变的例子; 2、基于模版匹配找出偏移值,然后进行拼接。这个可以参考halcon中一个拼接的例子; 3、对交接处进行融合,这个是本文的关键。 首先,这个融合halcon中是没有方法的,所以要...

printf()函数不能直接输出string类型

因为string不是c语言的内置数据,所以直接printf输出string类型的是办不到的。 要这样输出: printf("%s ",a.c_str()); 举例: #include<bits/stdc++.h> using namespacestd; intmain(){ string a="人生"; printf("%s "...