切片

Go的数组长度不可以改变,在某些特定的场景中就不太适用了。对于这种情况Go语言提供了一种由数组建立的、更加灵活方便且功能强大的包装(Wapper),也就是切片。与数组相比切片的长度不是固定的,可以追加元素。

切片本身不拥有任何数据,它们只是对现有数组的引用。

切片的定义

可以声明一个未指定大小的数组来定义切片:

var slice_name []type
//type用来指定切片内元素的数据类型

切片不需要声明长度。还可以使用make()函数来创建切片:

var slice_name []type = make([]type, len)
//len是切片的长度
slice_name := make([]type,len)
//make()函数定义时,也可以指定切片的容量,其中capacity为可选参数
make([]type, length, capacity)

切片的初始化

直接初始化

直接初始化一个切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3,对于下面这个切片,初始长度为3容量为3

slice_name := []int {1,2,3}
引用数组或切片
//初始化切片slice,是数组array的引用
slice := array[:]
//将array中从下标startIndex到下标endIndex-1下的元素创建为一个新的切片
slice := array[startIndex:endIndex]
//endIndex为空时,从索引为的startIndex元素到最后一个元素
slice := array[startIndex:]
//startIndex空时,从第一个元素(索引为0)到索引为endIndex-1的元素
slice := array[:endIndex]
//通过切片s初始化切片s1
s1 := s[startIndex:endIndex]
make()函数

使用make()函数创建切片时,默认情况下切片的元素的值为0。

切片的修改

切片它自己不拥有任何数据,他只是对底层数组的一种表示,对切片所做的任何修改都会反映在底层的数组当中。

当多个切片共用相同的底层数组时,每个切片的修改都将会反映在这个数组中。

package main
import (
"fmt"
)
func main() {
arr := [3]int {1,2,3}
slice_1 := arr[:]
slice_2 := arr[:]
fmt.Println("原始数组",arr)
slice_1[1] = 10
fmt.Println("修改slice_1之后的数组",arr)
slice_2[2] = 20
fmt.Println("修改slice_2之后的数组",arr)
}

运行结果

原始数组 [1 2 3]
修改slice_1之后的数组 [1 10 3]
修改slice_2之后的数组 [1 10 20]

从输出中可以清晰的看出,对切片的修改会反映在底层数组当中;当多个切片共享一个底层数组时,每个切片所作出的修改都会反映在数组中。

切片的长度和容量

切片的长度是切片中的元素数。切片的容量是从创建切片的索引开始的底层数组中的元素数。

可以内置函数len()测量切片的长度,内置函数cap()获取切片的容量。

package main
import (
"fmt"
)
func main() {
arr := [...]string{"A","B","C","D","E","F"}
s := arr[1:3]
fmt.Printf("切片的长度为%d,切片的容量为%d",len(s),cap(s))
s = s[:1]
fmt.Printf("切片的长度为%d,切片的容量为%d",len(s),cap(s)) }

输出结果为:

切片的长度为2,切片的容量为5

在上述程序中,s是由从arr的索引1到2创建的一个切片。因此切片s的长度为2

切片s是从arr[1]开始,即索引从1开始创建的。根据切片容量的定义,可以得知,s的容量为从此索引开始的底层数组arr中的元素数也就是5。

所以切片的长度为2,切片的容量为5。

追加和移除切片元素

追加元素

正如我们已经知道的,数组的长度是固定的,它的长度是不能增加。切片是动态的,使用append()函数可以将新元素追加到切片上。append() 函数的定义是 func append(s[]T,x ... T)[]T。此函数只能将元素追加到切片末尾。

x ... T在函数的定义中表示该函数接受参数x的个数是可变的。这些类型的函数被称为可变函数

append()函数的第二参数可以是一个数字,也可以是一组数组。例

append(slice,1)或append(slice,1,2,3)或append(slice,slice2...)`

正如我们所了解的,切片的对数组的一种抽象、一种包装,但是数组本身的长度是固定的,那么切片是如何具有动态长度的?而且切片的容量是由他所引用的数组的长度决定的,那么当新的元素添加到切片当中,使得切片的长度变大以后超过了这个切片的容量,这个切片和它引用的数组发生了什么变化呢?

当新的元素添加到切片中时,系统会创建一个新的数组,将现有的数组元素复制到这个新的数组当中,并且返回这个新数组的新切片引用。并且新的切片容量为旧切片的两倍。

package main
import(
"fmt"
)
func main() {
arr := [...]string{"A","B","C","D","E","F"}
s1 := arr[:]
fmt.Println("s1",s1,"长度为",len(s1),"容量为",cap(s1))
s1 = append(s1,"G")
fmt.Println("添加一个元素后s1",s1,"长度为",len(s1),"容量为",cap(s1))
s1[0] = "1"
fmt.Println(arr,s1)
}

输出结果

s1 [A B C D E F] 长度为 6 容量为 6
添加一个元素后s1 [A B C D E F G] 长度为 7 容量为 12
[A B C D E F] [1 B C D E F G]

在上述程序当中,s1是由arr声明的切片,其初始长度为6,初始容量为6。当向其中添加一个新的元素,这时系统会在创建一个新的数组,并返回一个新的切片给s1,这时s1的长度为7,容量为12。可以看到容量翻了一倍。此时切片s1所表示的数组已经不是arr,而是系统新创建的数组。

追加元素到切片头部

如果想把元素追加到切片的开头,Go没有提供原生的函数。可以通过append()函数变相实现,这种方法实际上是合并两个切片。

nums := []int {1,2,3}
//... 三个点表示将切片元素展开传递给函数
nums = append([]int{4},nums...)
fmt.Println(nums)
//输出
[4,1,2,3]
移除元素

使用切片子集和append()函数变相实现。

nums := []int {1,2,3,4,5}
nums = append(nums[:2],nums[3:]...)
fmt.Println(nums)
//输出
[1,2,4,5]

切片的拷贝

从上面我们可以了解到切片是对一个数组的引用,因此不能向数组那样直接赋值给一个新的变量就会产生拷贝。

数组是值类型,对数组而言,将一个数组赋值给另一个新的数组,那么这里会产生一次拷贝。直白地说,赋值之后两个数组是不一样的他们指向地地址不同,一个数组发生变化,另一个数组不会发生变化。

切片是一个结构体,是对底层数组地一个引用。将切片的值赋值给另一个新的切片,这两个切片都是引用的同一个数组。对切片地修改会映射引用的底层数组上,那么修个一个切片的值,另一个切片的值也会发生变化。

如果希望两个切片引用的不是同一个数组,就需要用到copy()函数完成对切片的拷贝。

copy(dst, src []Type)第一个参数为目标切片,第二个参数为源切片。

package main
import(
"fmt"
)
func main() {
arr := [...]int{1,2,3}
s1 := arr[:]
s2 := s1
s1_copy := make([]int,3)
copy(s1_copy,s1)
s1[0] = 10
fmt.Println("s1值为:",s1,",s2值为:",s2,",s1_copy值为:",s1_copy) }

输出结果

s1值为: [10 2 3] ,s2值为: [10 2 3] ,s1_copy值为: [1 2 3]

上述代码中s1是对arr引用过的一个切片;s2是由s1赋值而来的切片;s1_copy是由s1拷贝而来的切片。由输出结果可知,赋值会将两个切片指向同一个底层数组,使用copy()函数会将源切片的值复制到新的切片当中。

切片在函数传递

我们可以认为,切片在内部可以由一个结构体类型表示。这是它的表现形式。

type slice struct {
Length int
Capactiy int
ZerothElement *byte
}

切片包含长度、容量和指向数组第零个元素的指针。当切片传递给函数时,即使他通过值传递,指针变量也将引用相同的底层数组。因此,当切片作为参数传递给函数时,函数内做的更改,也会在函数外可见。

在Go语言中我们通常不会把数组的指针作为参数在函数中传递,而是使用切片进行传递。切片就是一种对数组的引用,这样会使代码更加简洁。

多维切片

多维切片类似与多维数组,唯一的不同在于多维切片没有指明长度。

package main
import(
"fmt"
)
func main() {
nums := [][]int{
{1,2,3},
{10,20,30},
{100,200,300}
}
for _,v1 := range nums {
for _,v2 := range v1 {
fmt.Printf("%d,",v2)
}
fmt.Printf("\n")
}
}

运行结果

1,2,3,
10,20,30,
100,200,300,

tips

  1. 切片是对底层数组的引用。只要切片还在内存中,这个被引用的底层数组就不能被垃圾回收。如果我们需要对很大的一个数组的一个小片段进行处理,那么我们可以由这个数组创建一个切片,并开始处理切片。但是同时这个很大的输入仍然在内存中,一种解决方法就是使用copy()函数来生成一个切片副本,这样我们就可以使用新的切片,原始的数组就会被垃圾回收
  2. *切片的长度是当前切片可以访问元素的数量,而切片的容量则是当前切片拓展后能访问的元素的个数。当然这些可以访问的元素都是底层数组中的元素
  3. 切片是一个结构体,它由一个指向数组中元素的指针、长度、容量来组成的。因此切片这个结构体类型同时具有引用类型的特征和值类型的特征
  4. 使用append()函数为切片添加元素时,如果使切片的长度超过了容量时,会返回一个新的切片,这个新切片的容量不一定为旧切片容量的2倍,具体参考这里

映射

GO语言中的Map是一种键值对的无序列表。可以快速并高效地查找唯一键的值。

Map是一种集合,所以可以像迭代数组和切片那样迭代它。不过,Map是无序的集合,因为它底层是一个hash表,因此无法决定它的返回顺序。

Map的定义和初始化

使用make()函数

map_name := make(map[key_type]value_type)
map_name[key] = value

make(map[keyType]valueType, cap)cap表示容量,作为可选参数。

使用make()函数创建map创建时,可以指定一个合理的初始容量大小,这样就会申请一块合适的内存,避免在后续的使用中频繁扩浪费性能。

使用make()函数创建map,其中没有数据且不是零值。

map的零值为nil

使用字面值创建

var map_name = map[key_type]value_type{key1:v1,k2:v2}

map_name := map[key_type]value_type{key1:v1,k2:v2}

定义一个map后,如果不初始化map,那么就会创建一个nil mapnil map不能用来存放键值对。

//定义一个map但是不初始化值
var student map[int]string
//如果将值赋给这个map编译就会报错
student[1] = "bob"

运行结果

panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
/box/main.go:9 +0x43 Exited with error status 2

Map的数据操作

key-value

map中添加数据的语法与数组是类似的。

students[1] = "zs"

在Go语言中允许多返回值,在map中根据key获取value,在返回value的同时会返回一个boolean类型的值,这个值用表示地是map中是否存在对应key。如果不存在对应keyvalue,则返回mapvalue数据类型对应的零值。当然也可以只返回一个value

//根据key获取map中的数据
value := students[1]
//exists为true说明存在为false说明不存在,如过存在返回对应值,否则返回对应数据类型零值这里string零值为""空字符串
value,exists := students[1]

range()函数迭代Map

range()迭代map,返回key和value

package main
import "fmt"
func main() {
students := map[int]string{
1:"zs",
2:"ls",
3:"ww",
}
//使用range()函数迭代Map
for key, value := range students {
fmt.Printf("Key: %d Value: %s\n", key, value)
}
}

delete()函数删除Map中元素

delete(map,key)函数可以删除map中指定key的键值对。接上面的代码。delete()函数没有返回值。

//删除键为1的键值对
delete(map,1)
//输出students中所有键值对
for key, value := range students {
fmt.Printf("Key: %d Value: %s\n", key, value)
}

len()函数获取Map的长度

使用len()函数可以用来获取map的长度

l := len(students)

map的函数传递

与切片一样,映射也是一个引用类型,它们都都指向了底层的数据结构。切片指向的数据结构是数组,而映射指向的数据结构是散列表。当映射作为参数在函数之间传递时,是进行map指针的拷贝,相对于指针来说是值拷贝,相对于底层来说是引用传递。直白的说就是进行参数传递时它们引用的底层数据结构都是一个,也就是对与一个发生变化另一个也会变化。

package main

import (
"fmt"
) func main() {
students := map[int]string{
1:"zs",
2:"ls",
3:"ww",
}
fmt.Println("原始的students", students)
modified := students
modified[1] = "zss"
fmt.Println("修改之后的students", students) }

运行结果

原始的students map[1:zs 2:ls 3:ww]
修改之后的students map[1:zss 2:ls 3:ww]

Maps equality

映射之间不能使用==来进行比较操作。==只能用于检查映射是否为nil

package main
func main() {
map1 := map[string]int{
"one": 1,
"two": 2,
}
map2 := map1
if map1 == map2 {
}
}

输出结果:错误

./prog.go:11:10: invalid operation: map1 == map2 (map can only be compared to nil)

Go build failed.

Go语言基础五:引用类型-切片和映射的更多相关文章

  1. Go语言入门——数组、切片和映射(下)

    上篇主要介绍了Go语言里面常见的复合数据类型的声明和初始化. 这篇主要针对数组.切片和映射这些复合数据类型从其他几个方面介绍比较下. 1.遍历 不管是数组.切片还是映射结构,都是一种集合类型,要从这些 ...

  2. Go语言入门——数组、切片和映射

    按照以往开一些专题的风格,第一篇一般都是“从HelloWorld开始” 但是对于Go,思来想去,感觉真的从“HelloWorld”说起,压根撑不住一篇的篇幅,因为Go的HelloWorld太简单了. ...

  3. python自动化--语言基础五面向对象、迭代器、range和切片的区分

    面向对象 一.面向对象简单介绍: class Test(): #类的定义 car = "buick" #类变量,定义在类里方法外,可被对象直接调用,具有全局效果 def __ini ...

  4. C语言基础五 数组

    数组跟变量的区别? 数组是可以在内存中连续存储多个元素的结构,所有元素必须属于相同类型. 格式:元素类型 数组名[元素个数]: 数组的特点: 只能存放单一元素的数据,里面存放的数据成为元素. 数组的声 ...

  5. C语言基础五 数组的应用

    .根据用户输入的10人成绩并将其保存到数组中,求最高成绩,最低成绩和平均成绩 int scoure[10];//存储10个数据的数组 int i; int sum;//总成绩 int max,min, ...

  6. Go语言基础之切片

    Go语言基础之切片 本文主要介绍Go语言中切片(slice)及它的基本使用. 引子 因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性. 例如: func arraySum(x ...

  7. Go语言基础之5--数组(array)和切片(slince)

    一.数组(array) 1.1 数组定义 1)含义: 数组是同一类型的元素集合. 数组是具有固定长度并拥有零个或者多个相同数据类型元素的序列. 2)定义一个数组的方法: var 变量名[len] ty ...

  8. GO学习-(9) Go语言基础之切片

    Go语言基础之切片 本文主要介绍Go语言中切片(slice)及它的基本使用. 引子 因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性. 例如: func arraySum(x ...

  9. Java语言基础(五)

    Java语言基础(五) 一.浮点数 浮点数就是小数,其标准是IEEE 754,用指数和尾数表示 例如30000=3*10000=3*10^4  其中4是指数,3是尾数 Java中,浮点数有float ...

随机推荐

  1. PCIe引脚PRSNT与热插拔

    热插拔的基本目的是要让PCIe设备按照规定的顺序.原则,从系统中移除或插入到系统中来,并能正常的工作,且不影响系统的正常运行.事实上,PCIe"热插拔"的关键目的就是为前面面所提到 ...

  2. 提升站点SEO的7个建议

    1.使用HTTPS 谷歌曾发公告表示,使用安全加密协议(HTTPS),是搜索引擎排名的一项参考因素. 所以,在域名相同情况下,HTTPS站点比HTTP站点,能获得更好的排名. 在网络渠道分发或合作上, ...

  3. drools规则属性(rule attributes)的使用

    一.介绍 规则属性是您可以添加到业务规则以修改规则行为的附加规范. 在 DRL 文件中,您通常在规则条件和操作的上方定义规则属性,多个属性位于单独的行中,格式如下: rule "rule_n ...

  4. Python常用标准库(pickle序列化和JSON序列化)

    常用的标准库 序列化模块 import pickle 序列化和反序列化 把不能直接存储的数据变得可存储,这个过程叫做序列化.把文件中的数据拿出来,回复称原来的数据类型,这个过程叫做反序列化. 在文件中 ...

  5. CenterNet和CenterNet2笔记

    CenterNet和CenterNet2笔记 CenterNet是基于anchor-free的一阶段检测算法 CenterNet2是CenterNet作者基于两阶段的改进 CenterNet(Obje ...

  6. 秋招如何抱佛脚?2022最新大厂Java面试真题合集(附答案

    2022秋招眼看着就要来了,但是离谱的是,很多同学最近才想起来还有秋招这回事,所以纷纷临时抱佛脚,问我有没有什么快速磨枪的方法, 我的回答是:有! 说起来,临阵磨枪没有比背八股文更靠谱的了,很多人对这 ...

  7. 什么事JAVA

    1.什么是Java Java是一门面向对象的高级编程语言,不仅吸收了C++语言的各种优点,比如继承了C++语言面向对象的 技术核心.还摒弃了C++里难以理解的多继承.指针等概念,,同时也增加了垃圾回收 ...

  8. Redis集群搭建 三主三从

    Redis集群介绍 Redis 是一个开源的 key-value 存储系统,由于出众的性能,大部分互联网企业都用来做服务器端缓存.Redis在3.0版本之前只支持单实例模式 虽然支持主从模式,哨兵模式 ...

  9. Linux查看系统参数配置

    Linux查看系统参数 1.查看内存(以GB为单位) [root@rac1 ~]# free -g total :内存总数,物理内存总数 used :已使用内存 free :空闲的内存数 shared ...

  10. Weakmap详解

    先看一个例子 let obj = { name: 'toto' } // { name: 'toto' }这个对象能够被读取到,因为obj这个变量名有对它的引用 // 将引用覆盖掉 obj = nul ...