Go语言基础五:引用类型-切片和映射
切片
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
- 切片是对底层数组的引用。只要切片还在内存中,这个被引用的底层数组就不能被垃圾回收。如果我们需要对很大的一个数组的一个小片段进行处理,那么我们可以由这个数组创建一个切片,并开始处理切片。但是同时这个很大的输入仍然在内存中,一种解决方法就是使用
copy()
函数来生成一个切片副本,这样我们就可以使用新的切片,原始的数组就会被垃圾回收 - *切片的长度是当前切片可以访问元素的数量,而切片的容量则是当前切片拓展后能访问的元素的个数。当然这些可以访问的元素都是底层数组中的元素
- 切片是一个结构体,它由一个指向数组中元素的指针、长度、容量来组成的。因此切片这个结构体类型同时具有引用类型的特征和值类型的特征
- 使用
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 map
。nil 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
。如果不存在对应key
的value
,则返回map
中value
数据类型对应的零值。当然也可以只返回一个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语言基础五:引用类型-切片和映射的更多相关文章
- Go语言入门——数组、切片和映射(下)
上篇主要介绍了Go语言里面常见的复合数据类型的声明和初始化. 这篇主要针对数组.切片和映射这些复合数据类型从其他几个方面介绍比较下. 1.遍历 不管是数组.切片还是映射结构,都是一种集合类型,要从这些 ...
- Go语言入门——数组、切片和映射
按照以往开一些专题的风格,第一篇一般都是“从HelloWorld开始” 但是对于Go,思来想去,感觉真的从“HelloWorld”说起,压根撑不住一篇的篇幅,因为Go的HelloWorld太简单了. ...
- python自动化--语言基础五面向对象、迭代器、range和切片的区分
面向对象 一.面向对象简单介绍: class Test(): #类的定义 car = "buick" #类变量,定义在类里方法外,可被对象直接调用,具有全局效果 def __ini ...
- C语言基础五 数组
数组跟变量的区别? 数组是可以在内存中连续存储多个元素的结构,所有元素必须属于相同类型. 格式:元素类型 数组名[元素个数]: 数组的特点: 只能存放单一元素的数据,里面存放的数据成为元素. 数组的声 ...
- C语言基础五 数组的应用
.根据用户输入的10人成绩并将其保存到数组中,求最高成绩,最低成绩和平均成绩 int scoure[10];//存储10个数据的数组 int i; int sum;//总成绩 int max,min, ...
- Go语言基础之切片
Go语言基础之切片 本文主要介绍Go语言中切片(slice)及它的基本使用. 引子 因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性. 例如: func arraySum(x ...
- Go语言基础之5--数组(array)和切片(slince)
一.数组(array) 1.1 数组定义 1)含义: 数组是同一类型的元素集合. 数组是具有固定长度并拥有零个或者多个相同数据类型元素的序列. 2)定义一个数组的方法: var 变量名[len] ty ...
- GO学习-(9) Go语言基础之切片
Go语言基础之切片 本文主要介绍Go语言中切片(slice)及它的基本使用. 引子 因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性. 例如: func arraySum(x ...
- Java语言基础(五)
Java语言基础(五) 一.浮点数 浮点数就是小数,其标准是IEEE 754,用指数和尾数表示 例如30000=3*10000=3*10^4 其中4是指数,3是尾数 Java中,浮点数有float ...
随机推荐
- PCIe引脚PRSNT与热插拔
热插拔的基本目的是要让PCIe设备按照规定的顺序.原则,从系统中移除或插入到系统中来,并能正常的工作,且不影响系统的正常运行.事实上,PCIe"热插拔"的关键目的就是为前面面所提到 ...
- 提升站点SEO的7个建议
1.使用HTTPS 谷歌曾发公告表示,使用安全加密协议(HTTPS),是搜索引擎排名的一项参考因素. 所以,在域名相同情况下,HTTPS站点比HTTP站点,能获得更好的排名. 在网络渠道分发或合作上, ...
- drools规则属性(rule attributes)的使用
一.介绍 规则属性是您可以添加到业务规则以修改规则行为的附加规范. 在 DRL 文件中,您通常在规则条件和操作的上方定义规则属性,多个属性位于单独的行中,格式如下: rule "rule_n ...
- Python常用标准库(pickle序列化和JSON序列化)
常用的标准库 序列化模块 import pickle 序列化和反序列化 把不能直接存储的数据变得可存储,这个过程叫做序列化.把文件中的数据拿出来,回复称原来的数据类型,这个过程叫做反序列化. 在文件中 ...
- CenterNet和CenterNet2笔记
CenterNet和CenterNet2笔记 CenterNet是基于anchor-free的一阶段检测算法 CenterNet2是CenterNet作者基于两阶段的改进 CenterNet(Obje ...
- 秋招如何抱佛脚?2022最新大厂Java面试真题合集(附答案
2022秋招眼看着就要来了,但是离谱的是,很多同学最近才想起来还有秋招这回事,所以纷纷临时抱佛脚,问我有没有什么快速磨枪的方法, 我的回答是:有! 说起来,临阵磨枪没有比背八股文更靠谱的了,很多人对这 ...
- 什么事JAVA
1.什么是Java Java是一门面向对象的高级编程语言,不仅吸收了C++语言的各种优点,比如继承了C++语言面向对象的 技术核心.还摒弃了C++里难以理解的多继承.指针等概念,,同时也增加了垃圾回收 ...
- Redis集群搭建 三主三从
Redis集群介绍 Redis 是一个开源的 key-value 存储系统,由于出众的性能,大部分互联网企业都用来做服务器端缓存.Redis在3.0版本之前只支持单实例模式 虽然支持主从模式,哨兵模式 ...
- Linux查看系统参数配置
Linux查看系统参数 1.查看内存(以GB为单位) [root@rac1 ~]# free -g total :内存总数,物理内存总数 used :已使用内存 free :空闲的内存数 shared ...
- Weakmap详解
先看一个例子 let obj = { name: 'toto' } // { name: 'toto' }这个对象能够被读取到,因为obj这个变量名有对它的引用 // 将引用覆盖掉 obj = nul ...