数组和容器

一个人想着一个人 提交于 2020-02-07 09:42:18

数组

因为数组的长度是固定的所以Go一般使用slice,类似于C/C++中的vector。数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0。而C/C++是随机值。
以下是数组的三种初始化方式,如果在数组的长度位置出现的是“…”省略号,则表示数组的长度是根据初始化值的个数来计算,数组的长度必须是常量表达式,编译的时候要计算。

var arr [3]int = [3]int{1,2,3}
arr := [...]int{1,2,3}
arr := [...]int{99: -1} //定义一个含有100个元素的数组r,
         //最后一个元素被初始化为-1,其它元素都是用0初始化

注意:数组的元素是可以比较的,数组也是可以比较的,每一个元素都一样的的数组相等。

数组传参

和C/C++一样,当把参数传给函数时,会生成一份拷贝,对于传统语言比如C/C++来说,数组会隐式转化为一个指针,C++可以使用引用传参。但是GO提供了更优雅的方式slice。

Slice

切片,代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度,是数组的引用。语法是:

var arr [3]int{1,2,3}
var array [4][2]int
fmt.Println(a, a[1:2])
// 声明字符串切片
var strList []string
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
// 输出3个切片
fmt.Println(strList, numList, numListEmpty)
// 输出3个切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))
// 切片判定空的结果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)
a := make([]int, 2)
b := make([]int, 2, 10)

append

切片的追加

var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包

var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片

在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。

var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片

append 函数返回新切片的特性,所以切片也支持链式操作,我们可以将多个 append 操作组合起来,实现在切片中间插入元素:

var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片

copy

slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置

多维切片

声明:

var sliceName [][]...[]sliceType

多维切片原理
多维切片的追加原理
在这里插入图片描述

List

列表使用 container/list 包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作。列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型,这既带来了便利,也引来一些问题,例如给列表中放入了一个 interface{} 类型的值,取出值后,如果要将 interface{} 转换为其他类型将会发生宕机。

list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的。

  1. 通过 container/list 包的 New() 函数初始化 list
    变量名 := list.New()

  2. 通过 var 关键字声明初始化 list
    var 变量名 list.List

在列表中插入元素
双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack。
注意
这两个方法都会返回一个 *list.Element 结构,如果在以后的使用中需要删除插入的元素,则只能通过 *list.Element 配合 Remove() 方法进行删除,这种方法可以让删除更加效率化,同时也是双链表特性之一。

l := list.New()
l.PushBack("fist")
l.PushFront(67)

方 法 功 能
InsertAfter(v interface {}, mark * Element) * Element 在 mark 点之后插入元素,mark 点由其他插入函数提供
InsertBefore(v interface {}, mark * Element) *Element 在 mark 点之前插入元素,mark 点由其他插入函数提供
PushBackList(other *List) 添加 other 列表元素到尾部
PushFrontList(other *List) 添加 other 列表元素到头部
从列表中删除元素
列表插入函数的返回值会提供一个 *list.Element 结构,这个结构记录着列表元素的值以及与其他节点之间的关系等信息,从列表中删除元素时,需要用到这个结构进行快速删除。

列表操作元素:

package main
import "container/list"
func main() {
    l := list.New()
    // 尾部添加
    l.PushBack("canon")
    // 头部添加
    l.PushFront(67)
    // 尾部添加后保存元素句柄
    element := l.PushBack("fist")
    // 在fist之后添加high
    l.InsertAfter("high", element)
    // 在fist之前添加noon
    l.InsertBefore("noon", element)
    // 使用
    l.Remove(element)
}

列表元素操作的过程

l.PushBack("canon")	canon
l.PushFront(67)	67, canon
element := l.PushBack("fist")	67, canon, fist
l.InsertAfter("high", element)	67, canon, fist, high
l.InsertBefore("noon", element)	67, canon, noon, fist, high
l.Remove(element)	67, canon, noon, high

遍历列表——访问列表的每一个元素
遍历双链表需要配合 Front() 函数获取头元素,遍历时只要元素不为空就可以继续进行,每一次遍历都会调用元素的 Next() 函数,代码如下所示。

l := list.New()
// 尾部添加
l.PushBack("canon")
// 头部添加
l.PushFront(67)
for i := l.Front(); i != nil; i = i.Next() {
    fmt.Println(i.Value)
}

Map

Go中map的底层是一个哈希表,一个典型的K/V的结构但是Go的map和C/C++的map还是有很大不同的,但相同的是他们的k都需要可以比较大小的类型,C/C++的map的底层是一个红黑树,map的下标语法将产生两个值;第二个是一个布尔值,用于报告元素是否真的存在。布尔变量一般命名为ok,特别适合马上用于if条件判断部分。由于底层的实现不通,Go的map不可以进行取地址操作,由于他的 底层是一个哈希表在插入元素数目到达一定量的情况下会扩容这样会造成野指针的问题。个人觉得Go和C/C++的map最大的不同是,Go的map迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同。这是故意的,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。如果要按顺序遍历key/value对,我们必须显式地对key进行排序,可以使用sort包的Strings函数对字符串slice进行排序。

range

range和C/C++的范围for功能一样但更为高级在slice中他可以返回数组下标和数组元素,在map中他可以返回k和v对于不关心的部分我们用_ 来占位就不返回。
注意
map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常。GO中没有set,通过Go的map实现:

set := make(map[string]bool)

map的key需要可以比较大小有时候我们需要把slice作为key,这时候需要把slice转化成string类型的。需要以下类似的转换函数。

.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!