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

  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.
posted @ 2021-12-16 20:10  newbe3three  阅读(142)  评论(0编辑  收藏  举报