GoLang基础数据类型-切片(slice)详解

                                                作者:尹正杰

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

  数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫slice,slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度,它是可变长的,可以随时往slice里面加数据。

一.什么是切片(slice)

  简单的说,数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是个指针。数组切片的数据结构可以抽象为以下3个变量:

  1>.一个指向原生数组的指针(point):指向数组中slice指定的开始位置;

  2>.数组切片中的元素个数(len):即slice的长度;

  3>.数组切片已分配的存储空间(cap):也就是slice开始位置到数组的最后位置的长度。

  从底层实现的角度来看,数组切片实际上仍然使用数组来管理元素,基于数组,数组切片添加了一系列管理功能,可以随时动态扩充存放空间,并且可以被随意传递而不会导致所管理的元素被重复复制。

二.定义切片

  其实定义一个切片和定义一个数组的方式很相似,不过很有意思的时候切片的定义方式到是蛮有意思的,它比数组要灵活的多,因为我们知道数组的长度和容量一旦在定义之后就无法被修改,但是切片可以,因此相比数组,切片更受程序员欢迎吧,但是我们不能否定数组的重要性,因为从底层实现的角度来看,Golang切片实际上仍然使用数组来管理元素。

1.用make方法初始化切片;

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func my_slice(s string ,x []int) {
fmt.Printf("`%s`切片的长度为:[%d] 切片容量为:[%d] 切片中的元素是:%v\n",s,len(x),cap(x),x)
} func main() {
var yinzhengjie []int //声明一个名称为“yinzhengjie”的切片,其默认长度均为零,但是可以并不意味着它不能存取更多的元素哟!
Golang_array := [5]int{1,3,5,7}
Golang_slice := make([]int,2,5) //表示定义一个长度为“2”,容量为“5”的切片
fmt.Printf("`%s`数组的长度为:[%d];数组的容量为:[%d];数组的元素是:%v\n","Golang_array",len(Golang_array),cap(Golang_array),Golang_array)
my_slice("Golang_slice",Golang_slice)
my_slice("yinzhengjie",yinzhengjie)
yinzhengjie = append(yinzhengjie, 100,200,300) //尽管之前的“yinzhengjie”这个切片长度为0,但是仍然可以往里面追加更多的元素。
my_slice("yinzhengjie",yinzhengjie)
} #以上代码执行结果如下:
`Golang_array`数组的长度为:[5];数组的容量为:[5];数组的元素是:[1 3 5 7 0]
`Golang_slice`切片的长度为:[2] 切片容量为:[5] 切片中的元素是:[0 0]
`yinzhengjie`切片的长度为:[0] 切片容量为:[0] 切片中的元素是:[]
`yinzhengjie`切片的长度为:[3] 切片容量为:[4] 切片中的元素是:[100 200 300]

2.用已有数组生成新切片

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"fmt"
) func main() {
primes := [8]int{2,3,5,7,9,11,13,15,} //定义一个数组
fmt.Printf("`primes`数组的值:%d\n",primes)
var sum []int = primes[1:4] //定义一个切片
fmt.Printf("`sum`切片的值:%d\n",sum)
fmt.Printf("`sum[0]`所对应的内存地址是:%x\n",&sum[0])
fmt.Printf("`primes[1]`所对应的内存地址是:%x\n",&primes[1])
var s1 []int
s1 = sum
fmt.Printf("`s1`切片对应的值为:%d\n",s1)
fmt.Printf("s1[0] == sum[0]为:%v\n",&s1[0] == &sum[0])
} #以上代码输出结果如下:
`primes`数组的值:[2 3 5 7 9 11 13 15]
`sum`切片的值:[3 5 7]
`sum[0]`所对应的内存地址是:c042046088
`primes[1]`所对应的内存地址是:c042046088
`s1`切片对应的值为:[3 5 7]
s1[0] == sum[0]为:true

3.切片的字面量 (初始化)

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func main() {
num := []int{100,200,300,400,500} //切片的初始化方法,专业术语叫做切片字面量。
fmt.Println(num) r := []bool{true,false,true,true}
fmt.Println(r)
} #以上代码输出结果如下:
[100 200 300 400 500]
[true false true true]

三.切片的追加

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func MyPrint(msg string,slice []string){
fmt.Printf("[ %s ]:\t长度:%v\t内存地址:%p\t是否为空(空为真):%v\t包含元素:%v",msg,len(slice),slice, slice==nil ,slice)
fmt.Println()
} func main() {
var yinzhengjie []string
MyPrint("原切片",yinzhengjie)
for i:=0;i<5;i++{
yinzhengjie=append(yinzhengjie,fmt.Sprintf("yzj%d",i));
}
MyPrint("追加后",yinzhengjie)
} #以上代码执行结果如下:
[ 原切片 ]: 长度:0 内存地址:0x0 是否为空(空为真):true 包含元素:[]
[ 追加后 ]: 长度:5 内存地址:0xc042056200 是否为空(空为真):false 包含元素:[yzj0 yzj1 yzj2 yzj3 yzj4]

四.切片的修改

  Golang的切片长得和数组很像,我们可以对一个数组做切片。要注意的是:当我们对一个数组做切片的时候,如果我们修改了切片下标所对应的值,那么被切片的数组的值也会跟着改变,因为他们都指向了同一块内存地址。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func main() {
names := [4]string{ //定义了一个字符串数组
"尹正杰",
"百度",
"谷歌",
"FQ",
}
fmt.Println(names) a := names[0:2]
b := names[1:3]
fmt.Println(a,b) b[0] = "xxx" //修改b的元素,会将names的对应的地址做相应的修改。
fmt.Println(a,b)
fmt.Println(names)
} #以上代码输出结果如下:
[尹正杰 百度 谷歌 FQ]
[尹正杰 百度] [百度 谷歌]
[尹正杰 xxx] [xxx 谷歌]
[尹正杰 xxx 谷歌 FQ]

五.切片的删除

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func MyPrint(msg string,slice []string){
fmt.Printf("[ %s ]:\t长度:%v\t内存地址:%p\t是否为空(空为真):%v\t包含元素:%v",msg,len(slice),slice, slice==nil ,slice)
fmt.Println()
} func main() {
var yinzhengjie []string
for i:=97;i<105;i++{
yinzhengjie=append(yinzhengjie,fmt.Sprintf("%v",string(i)));
}
MyPrint("删除前",yinzhengjie)
index:= 5
fmt.Println("删除的元素是:",yinzhengjie[index])
yinzhengjie=append(yinzhengjie[:index],yinzhengjie[index+1:]...) //你会发现删除的本质就是在之前的切片上做切割。
MyPrint("删除后",yinzhengjie)
} #以上代码执行结果如下:
[ 删除前 ]: 长度:8 内存地址:0xc042056180 是否为空(空为真):false 包含元素:[a b c d e f g h]
删除的元素是: f
[ 删除后 ]: 长度:7 内存地址:0xc042056180 是否为空(空为真):false 包含元素:[a b c d e g h]

六.切片的访问方式

1.通过range访问

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func MyPrint(msg string,slice []string){
fmt.Printf("[ %s ]:\t长度:%v\t内存地址:%p\t是否为空(空为真):%v\t包含元素:%v",msg,len(slice),slice, slice==nil ,slice)
fmt.Println()
} func main() {
var yinzhengjie []string
for i:=97;i<105;i++{
yinzhengjie=append(yinzhengjie,fmt.Sprintf("%v",string(i)));
} for k,v := range yinzhengjie{
fmt.Printf("yinzhengjie[%d]=%v\n",k,v) //range具有两个返回值,第一个返回值i是元素的数组下标,第二个返回值v是元素的值。
}
} #以上代码执行结果如下:
yinzhengjie[0]=a
yinzhengjie[1]=b
yinzhengjie[2]=c
yinzhengjie[3]=d
yinzhengjie[4]=e
yinzhengjie[5]=f
yinzhengjie[6]=g
yinzhengjie[7]=h

2.通过for循环来访问

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func MyPrint(msg string,slice []string){
fmt.Printf("[ %s ]:\t长度:%v\t内存地址:%p\t是否为空(空为真):%v\t包含元素:%v",msg,len(slice),slice, slice==nil ,slice)
fmt.Println()
} func main() {
var yinzhengjie []string
for i:=97;i<105;i++{
yinzhengjie=append(yinzhengjie,fmt.Sprintf("%v",string(i)));
} for i := 0; i < len(yinzhengjie); i++ {
fmt.Printf("yinzhengjie[%d]=[%s]\n",i,yinzhengjie[i])
}
} #以上代码执行结果如下:
yinzhengjie[0]=[a]
yinzhengjie[1]=[b]
yinzhengjie[2]=[c]
yinzhengjie[3]=[d]
yinzhengjie[4]=[e]
yinzhengjie[5]=[f]
yinzhengjie[6]=[g]
yinzhengjie[7]=[h]

 七.切片进阶知识(切片的指针)

  当我们用append追加元素到切片时,如果容量不够,go就会创建一个新的切片变量,这意味着内存地址也会跟着发生变化。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func MyPrint(msg []string) {
fmt.Printf("内存地址:%p \t\t长度:%v\t\t容量:%v\t\t包含元素:%v\n",msg,len(msg),cap(msg),msg)
} func main() {
var yinzhengjie []string
MyPrint(yinzhengjie)
for i:=0;i<10;i++{
yinzhengjie=append(yinzhengjie,fmt.Sprintf("%d",i)) //我们会发现随着切片的长度增大,容量也在增大,内存地址也发生变化啦!
MyPrint(yinzhengjie)
}
MyPrint(yinzhengjie)
} #以上代码执行结果如下:
内存地址:0x0 长度:0 容量:0 包含元素:[]
内存地址:0xc042008270 长度:1 容量:1 包含元素:[0]
内存地址:0xc042002740 长度:2 容量:2 包含元素:[0 1]
内存地址:0xc04200c2c0 长度:3 容量:4 包含元素:[0 1 2]
内存地址:0xc04200c2c0 长度:4 容量:4 包含元素:[0 1 2 3]
内存地址:0xc04204e200 长度:5 容量:8 包含元素:[0 1 2 3 4]
内存地址:0xc04204e200 长度:6 容量:8 包含元素:[0 1 2 3 4 5]
内存地址:0xc04204e200 长度:7 容量:8 包含元素:[0 1 2 3 4 5 6]
内存地址:0xc04204e200 长度:8 容量:8 包含元素:[0 1 2 3 4 5 6 7]
内存地址:0xc042000400 长度:9 容量:16 包含元素:[0 1 2 3 4 5 6 7 8]
内存地址:0xc042000400 长度:10 容量:16 包含元素:[0 1 2 3 4 5 6 7 8 9]
内存地址:0xc042000400 长度:10 容量:16 包含元素:[0 1 2 3 4 5 6 7 8 9]

  如果在make初始化切片的时候给出了足够的容量,append操作不会创建新的切片。当容量不够时,会自动将现在的容量翻倍。简直就是一言不合就翻倍啊土豪级别啊!

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func MyPrint(msg []string) {
fmt.Printf("内存地址:%p \t\t长度:%v\t\t容量:%v\t\t包含元素:%v\n",msg,len(msg),cap(msg),msg)
} func main() {
var yinzhengjie = make([]string,0,11)
MyPrint(yinzhengjie)
for i:=0;i<12;i++{
yinzhengjie=append(yinzhengjie,fmt.Sprintf("%d",i)) //注意,当容量不够使时,会自动将现在的容量翻倍。
MyPrint(yinzhengjie)
}
MyPrint(yinzhengjie)
} #以上地面执行结果如下:
内存地址:0xc04205c000 长度:0 容量:11 包含元素:[]
内存地址:0xc04205c000 长度:1 容量:11 包含元素:[0]
内存地址:0xc04205c000 长度:2 容量:11 包含元素:[0 1]
内存地址:0xc04205c000 长度:3 容量:11 包含元素:[0 1 2]
内存地址:0xc04205c000 长度:4 容量:11 包含元素:[0 1 2 3]
内存地址:0xc04205c000 长度:5 容量:11 包含元素:[0 1 2 3 4]
内存地址:0xc04205c000 长度:6 容量:11 包含元素:[0 1 2 3 4 5]
内存地址:0xc04205c000 长度:7 容量:11 包含元素:[0 1 2 3 4 5 6]
内存地址:0xc04205c000 长度:8 容量:11 包含元素:[0 1 2 3 4 5 6 7]
内存地址:0xc04205c000 长度:9 容量:11 包含元素:[0 1 2 3 4 5 6 7 8]
内存地址:0xc04205c000 长度:10 容量:11 包含元素:[0 1 2 3 4 5 6 7 8 9]
内存地址:0xc04205c000 长度:11 容量:11 包含元素:[0 1 2 3 4 5 6 7 8 9 10]
内存地址:0xc042064000 长度:12 容量:22 包含元素:[0 1 2 3 4 5 6 7 8 9 10 11]
内存地址:0xc042064000 长度:12 容量:22 包含元素:[0 1 2 3 4 5 6 7 8 9 10 11]

  如果不能准确预估切片的大小,又不想改变变量(如:为了共享数据的改变),这时候就要请出指针来帮忙了。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"fmt"
) func MyPrint(Slice []string,Pointer *[]string) {
fmt.Printf("内存地址:%p\t\t指针内存地址:%p\t\t长度:%v\t\t容量:%v\t\t包含元素:%v\n",Slice,Pointer,len(Slice),cap(Slice),Pointer)
} func main() {
var yinzhengjie []string
yzj := &yinzhengjie //注意:“yzj”就是“yinzhengjie”的指针。将地址都保存到了“yzj”中,因此我们通过该指针始终可以访问到真正的数据。
MyPrint(yinzhengjie,yzj)
for i:=0;i<10;i++{
*yzj=append(*yzj,fmt.Sprintf("%d",i)) //随着容量的增大,内存地址也发生变化啦,但是指针的内存地址始终没有变化。
MyPrint(yinzhengjie,yzj)
}
MyPrint(yinzhengjie,yzj)
} #以上代码执行结果如下:
内存地址:0x0 指针内存地址:0xc042002680 长度:0 容量:0 包含元素:&[]
内存地址:0xc042008270 指针内存地址:0xc042002680 长度:1 容量:1 包含元素:&[0]
内存地址:0xc042002740 指针内存地址:0xc042002680 长度:2 容量:2 包含元素:&[0 1]
内存地址:0xc04200c280 指针内存地址:0xc042002680 长度:3 容量:4 包含元素:&[0 1 2]
内存地址:0xc04200c280 指针内存地址:0xc042002680 长度:4 容量:4 包含元素:&[0 1 2 3]
内存地址:0xc04204e180 指针内存地址:0xc042002680 长度:5 容量:8 包含元素:&[0 1 2 3 4]
内存地址:0xc04204e180 指针内存地址:0xc042002680 长度:6 容量:8 包含元素:&[0 1 2 3 4 5]
内存地址:0xc04204e180 指针内存地址:0xc042002680 长度:7 容量:8 包含元素:&[0 1 2 3 4 5 6]
内存地址:0xc04204e180 指针内存地址:0xc042002680 长度:8 容量:8 包含元素:&[0 1 2 3 4 5 6 7]
内存地址:0xc042000400 指针内存地址:0xc042002680 长度:9 容量:16 包含元素:&[0 1 2 3 4 5 6 7 8]
内存地址:0xc042000400 指针内存地址:0xc042002680 长度:10 容量:16 包含元素:&[0 1 2 3 4 5 6 7 8 9]
内存地址:0xc042000400 指针内存地址:0xc042002680 长度:10 容量:16 包含元素:&[0 1 2 3 4 5 6 7 8 9]

GoLang基础数据类型-切片(slice)详解的更多相关文章

  1. [五]基础数据类型之Short详解

      Short 基本数据类型short  的包装类 Short 类型的对象包含一个 short 类型的字段      原文地址:[五]基础数据类型之Short详解   属性简介   值为  215-1 ...

  2. [二]基础数据类型之Long详解

      Long   Long 基本数据类型long  的包装类 Long 类型的对象包含一个 long类型的字段     属性简介   值为  263-1 的常量,它表示 long 类型能够表示的最大值 ...

  3. [三]基础数据类型之Integer详解

        Integer 基本数据类型int  的包装类 Integer 类型的对象包含一个 int 类型的字段     属性简介 值为 2^31-1 的常量,它表示 int 类型能够表示的最大值 @N ...

  4. [七]基础数据类型之Float详解

        Float 基本数据类型float  的包装类 Float 类型的对象包含一个 float 类型的字段    属性简介 用来以二进制补码形式表示 float 值的比特位数 public sta ...

  5. [八]基础数据类型之Double详解

    Double 基本数据类型double  的包装类 Double 类型的对象包含一个 double 类型的字段   属性简介 用来以二进制补码形式表示 double 值的比特位数 public sta ...

  6. [九]基础数据类型之Boolean详解

      相对于其他的基础性 类型Boolean是很简单的 Boolean 基本数据类型boolean  的包装类 Boolean 类型的对象包含一个 boolean 类型的字段    属性简介 属性也比较 ...

  7. GoLang基础数据类型--->字典(map)详解

    GoLang基础数据类型--->字典(map)详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   可能大家刚刚接触Golang的小伙伴都会跟我一样,这个map是干嘛的,是 ...

  8. GoLang基础数据类型--->数组(array)详解

    GoLang基础数据类型--->数组(array)详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Golang数组简介 数组是Go语言编程中最常用的数据结构之一.顾名 ...

  9. Python数据类型及其方法详解

    Python数据类型及其方法详解 我们在学习编程语言的时候,都会遇到数据类型,这种看着很基础也不显眼的东西,却是很重要,本文介绍了python的数据类型,并就每种数据类型的方法作出了详细的描述,可供知 ...

随机推荐

  1. 读书笔记(chapter3)

    进程管理 3.1进程 1.进程:进程就是处于执行期的程序,实际上,进程就是正在执行的程序代码的实时结果: 2.执行线程,简称线程,是进程中活动的对象(每个线程拥有独立的程序计数器.进程栈.和一组进程寄 ...

  2. mybatis中批量更新的问题

    问题:使用mybatis在执批量更新操作时,一直报错执行失败 解决方法: 首先打印了SQL语句,发现SQL语句拿出来执行没问题,也可以批量执行.SQL没问题,应该是配置的问题. 在网上查询和很多资料, ...

  3. div+css实现圆形div以及带箭头提示框效果

    .img{ width:90px; height:90px; border-radius:45px; margin:0 40%; border:solid rgb(100,100,100) 1px;& ...

  4. 请求数据传入(SpringMVC)

    1.    请求处理方法签名 Spring MVC 通过分析处理方法的签名,HTTP请求信息绑定到处理方法的相应人参中. Spring MVC 对控制器处理方法签名的限制是很宽松的,几乎可以按喜欢的任 ...

  5. spring 注入DI

    web  项目的搭建  以注入对象的方式

  6. JavaScript(ECMAScript) with 语句

    有同事,爱尝鲜,JavaScript ECMAScript with 语句,找了半天不知道局部变量的出处,原来是with语句搞得鬼. http://www.w3school.com.cn/js/pro ...

  7. [cnbeta]华为值多少钱,全世界非上市公司中估值最高的巨头

    华为值多少钱,全世界非上市公司中估值最高的巨头 https://www.cnbeta.com/articles/tech/808203.htm   小米.美团都曾表达过不想.不急于上市,但没人信,所以 ...

  8. css CSS常见布局解决方案

    CSS常见布局解决方案说起css布局,那么一定得聊聊盒模型,清除浮动,position,display什么的,但本篇本不是讲这些基础知识的,而是给出各种布局的解决方案.水平居中布局首先我们来看看水平居 ...

  9. 远程连接db2数据库

    在db2数据库中,需要理解catalog(编目)这个概念,理解前先说下db2数据库的体系结构:由系统(节点)也就是主机,下面是实例,实例下面是数据库,然后是表空间,再是数据库对象.现在假设你有一个数据 ...

  10. NodeJS 学习记录

    这里是我学习NodeJs的学习记录 URL:网址解析的好帮手 URL,URI 首先,URI是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源.而URL是u ...