Go语言数组、切片和映射

G语言有3种数据结构可以让用户管理集合数据:数组、切片和映射。

一、数组
       在Go语言里,数组是一个长度固定的数据类型,用于存储一段具有相同类型的元素的连续块。数组存储的类型可以使内置类型,如整型或者字符串,也可以是某种结构类型。
1. 声明和初始化
声明数组时需要指定内部存储的数据的类型,以及需要存储的元素的数量,这个数量也称为数组的长度。
1)声明一个数组,并设置为0值

var arr [5]int

2)使用数组字面量声明数组

arr := [5]int{10,20,30,40,50}

3)让Go自动计算声明数组的长度

arr := [ ]int{10,20,30,40,50}

4)声明数组并指定特定元素的值

arr := [5]int{1 : 10,2 : 20}

2. 使用数组
1)访问数组元素

arr := [5]int{10,20,30,40,50}
arr[2] = 35  //修改索引为2的元素的值

2)访问指针数组的元素

arr := [5]*int{0 : new(int), 1 : new(int)}
*arr[0] = 10
*arr[1] = 20

3)同类型数组赋值给另一个数组

var arr1 [5]string
arr2 := [5]string{"Red","Blue","Green","Yellow","Pink"}
arr1 = arr2 //前提是两个数组的类型必须相同

4)把一个指针数组赋值给另一个

var arr1 [3]*string
arr2 := [3]*string{new(string), new(string),new(string)}
*arr2[0]="Red"
*arr2[1]="Blue"
*arr2[2]="Green"
arr1 = arr2

复制之后,两个数组指向同一组字符串
3. 多维数组
1)声明二维数组

var arr [4][2]int
arr := [4][2]int{{10,11}, {20,21}, {30,31}, {40,41}}
arr := [4][2]int{1 : {20,21}, 3 : {40,41}}
arr := [4][2]int{1 : {0 : 20}, 3 : {1 : 41}}

2)访问二维数组的元素

var arr [2][2]int
arr[0][0] = 10

4. 在函数间传递数组
1)使用值传递,在函数间传递大数组

var arr [1e6]int
foo(arr)
func foo(arr [1e6]int){
...
}

每次函数foo被调用时,必须在栈上分配8MB的内存。之后,整个数组的值(8MB的内存)被复制到刚分配的内存里。
2)使用指针在函数间传递大数组

var arr [1e6]int
foo(&arr)
func foo(arr * [1e6]int){
...
}

       这次函数foo接受一个指向100万个整型值的数组的指针。现在将数组的地址传入函数,只需在栈上分配8字节的内存给指针就可以。这个操作会更有效的利用内存,不过因为传递的是指针,所以如果改变指针指向的值,会改变共享的内存。

二、切片
       切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数append来实现的。这个函数可以快速且高效地增长切片。还可以通过对切片再次切片来缩小一个切片的大小。因为切片底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。
1. 内部实现
       切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法。切片有3个字段的数据结构,这些数据结构包含Go语言需要操作底层数组的元数据。
这3个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。
2. 创建和初始化
1)make和切片字面量
一种创建切片的方法是使用内置的make函数。当使用make时,需要传入一个参数,指定切片的长度

slice := make([]string,5)   //创建一个字符串切片,其长度和容量都是5个元素
slice := make([]int,3,5)     //长度为3个元素,容量为5个元素
slice := []string{"Red","Blue","Green","Yellow","Pink"} //创建字符串切片
slice := []string{99 : ""}     //使用空字符串初始化第100个元素
//声明数组和声明切片的不同
array := [3]int{10,20,30}  //创建有3个元素的整型数组
slice := []int{10,20,30}    //创建长度和容量都是3的整型切片

2)nil和空切片

var slice []int                //创建nil整型切片
slice := make([]int,0)    //使用make创建空的整型切片
slice := []int{}

3)使用切片

slice := []int{10,20,30,40,50}
newSlice := slice[1 : 3]  //创建一个新切片,其长度为2个元素,容量为4个元素

        第一个切片slice能够看到底层数组全部5个元素的容量,不过之后的newSlice就看不到。对于newSlice,底层数组的容量只有4个元素。对底层数组容量是k的切片slice[i : j]来说
长度:j - i
容量:k - i
对底层数组容量是5的切片slice[1 : 3]来说
长度:3 - 1 = 2
容量:5 - 1 = 4
两个切片共享同一个底层数组。如果一个切片修改了底层数组的共享部分,另一个切片也能感知到。

slice := []int{10,20,30,40,50}  //创建一个整型切片
newSlice := slice[1 : 3]         //创建一个新切片,长度为2,容量为4
newSlice[1] = 35                 //修改newSlice索引为1的元素,同时也修改了slice的索引为2的元素

切片只能访问到其长度内的元素。试图访问超出其长度的元素将会导致语言运行时异常。
与切片的容量相关联的元素只能用于增长切片。在使用这部分元素前,必须将其合并到切片的长度里。
3)切片增长
       Go语言内置的append函数会处理增加长度时的所有操作细节。要使用append,需要一个被操作的切片和一个要追加的值。当append调用返回时,会返回一个包含修改结果的新切片。函数append总是会增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决于被操作的切片的可用容量。

slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1 : 3]
newSlice = append(newSlice, 60)

       因为newSlice在底层数组里还有额外的容量可用,append操作将可用的元素合并到切片的长度,并对其进行赋值。由于和原始的slice共享同一个底层数组,slice中索引为3的元素的值被改动了。
如果切片的底层数组没有足够的可用容量,append函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。

slice := []int{10, 20, 30, 40}
newSlice := append(slice,50)

当这个append操作完成后,newSlice拥有一个全新的底层数组,这个数组的容量是原来的两倍。
3. 创建切片时的3个索引
       第三个索引用来控制新切片的容量。其目的并不是要增加容量,而是要限制容量。可以看到,允许限制新切片的容量为底层数组提供了一定的保护。

source := []string{"Apple", "Orange" ,"Plum" ,"Banana" ,"Grape"}  //长度和容量都是5个元素
slice := source[2 : 3 : 4]  //将第三个元素切片,并限制容量,其长度为1各元素,容量为2个元素

这个切片操作执行后,新切片从底层数组引用了1个元素,容量是2个元素
我们可以应用之前定义的公式来计算新切片的长度和容量,如下所示
slice[i : j : k]或[2 : 3 : 4]
长度:j - i  或 3 - 2=1
容量:k - i 或 4 - 2=2
       第一个值表示新切片开始的元素的索引位置,这个例子中是2.第二个值表示开始的索引位置2加上希望包括的元素的个数1,2+1的结果是3,所以第二个值就是3。为了设置容量,从索引位置2开始,加上希望容量中包含的元素个数2,就得到了第三个值4。
        如果试图设置的容量比可用的容量还大,就会得到一个语言运行时错误。
4)迭代切片
Go语言有个特殊关键字range,可以配合关键字for来迭代切片里的元素

slice := []int{10, 20, 30, 40, 50}
for index, value := range slice{
       fmt.Printf("Index: %d Value: %d\n",index, value)
}

        当迭代切片时,关键字range会返回两个值。第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本。range创建了每个元素的副本,而不是直接返回对该元素的引用。如果使用该值变量的地址作为指向每个元素的指针,就会造成错误
使用下划线忽略索引值

for _,value := range slice{
       fmt.Printf("Value: %d\n",value)
}

关键字range总是会从切片头部开始迭代。如果想对迭代做更多的控制,依旧可以使用传统的for循环。

for index := 2; index < len(slice); index++ {
       fmt.Printf("Index: %d Value: %d\n", index, slice[index])
}

4. 多维切片

slice := [][]int{{10},{100, 200}}       //创建一个整型切片的切片
slice[0] = append(slice[0], 20)

  Go语言使用append函数处理追加的方式很简明:先增长切片,再将新的整型切片赋值给外层切片的第一个元素。
5. 在函数间传递切片
       在函数间传递切片就是要在函数间以值得方式传递切片。由于切片的尺寸很小,在函数间复制和传递切片成本也很低。让我们创建一个大切片,并将这个切片以值得方式传递给函数foo。

slice := make([]int,1e6)
slice = foo(slice)
func foo(slice []int) []int {
...
return slice
}

       在64位架构的机器上,一个切片需要24字节的内存:指针字段需要8字节,长度和容量字段分别需要8字节。由于与切片关联的数据包含在底层数组里,不属于切片本身,所以将切片复制到任意函数的时候,对底层数组大小都不会有影响。复制时只会复制切片本身,不会涉及底层数组。
       在函数间传递24字节的数据会非常快速、简单。这也是切片效率高的地方。不需要传递指针和处理复杂的语法,只需要复制切片,按想要的方式修改数据,然后传递回一份新的切片副本。
 

三、映射
映射是一种数据结构,用于存储一系列无序的键值对。
1. 内部实现
映射是一个集合,可以使用类似处理数组和切片的方式迭代映射中的元素。但映射是无序的集合,意味着没有办法预测键值对被返回的顺序。即便使用同样的顺序保存键值对,每次迭代映射的时候顺序也可能不一样。无序的原因是映射的实现使用了散列表。
2. 创建和初始化
1)使用make声明映射
dict := make(map[string]int)  //创建一个映射,键的类型是string,值得类型是int
dict := map[string]string{"Red" : "#da1337", "Orange" : "#e95a22"} 
创建映射时,更常用的方法是使用映射字面量。映射的初始长度会根据初始化时指定的键值对的数量来确定。
映射的键可以是任何值。这个值的类型可以是内置的类型,也可以是结构类型,只要这个值可以使用==运算符做比较。切片、函数以及包含切片的结构类型由于具有引用语义,不能作为映射的键,使用这些类型会造成编译错误。
2)声明一个存储字符串切片的映射
dict := map[int][]string{}     //创建一个映射,使用字符串切片作为值
3. 使用映射
1)为映射赋值
键值对赋值给映射,是通过指定适当类型的键并给这个键赋一个值来完成的,如下所示

colors := map[string]string{}
colors["Red"] = "#da1337"

可以通过声明一个未初始化的映射来创建一个值为nil的映射。nil映射不能用于存储键值对,否则,会产生一个语言运行时错误。
2)从映射获取值并判断键是否存在

value, exists := colors["Blue"]
if exists {
      fmt.Println(value)
}
//只返回键对应的值,然后通过判断这个值是不是零值来确定键是否存在
value := colors['Blue']
if value != "" {
     fmt.Println(value)
}

3)使用range迭代映射

colors := map[string]string {        //创建一个映射
     "AliceBlue" : "#f0f8ff",
     "Coral"       : "#ff7f50",
     "DarkGray" : "a9a9a9",
     "ForestGreen" : "#228b22"
}
for key, value := range colors {
      fmt.Printf("Key: %s Value : %s\n", key , value)
}

4)从映射中删除一项

delete(colors, "Coral") //删除键为Coral的键值对
for key, value := range colors {
      fmt.Printf("Key: %s Value: %s\n",key, value)
}

4. 在函数间传递映射
       在函数间传递映射并不会制造出该映射的一个副本。实际上,当传递映射给一个函数,并对这个映射做了修改,所有对这个映射的引用都会察觉到这个修改。
1)在函数间传递映射

func main() {
       colors := map[string]string {  //创建一个映射,存储颜色以及颜色对应的十六进制代码
            "AliceBlue" : "#f0f8ff",
            "Coral"       : "#ff7f50",
            "DarkGray" : "a9a9a9",
            "ForestGreen" : "#228b22"
       }
       //显示映射里的所有颜色
       for key, value := range colors {
              fmt.Printf("Key: %s Value:  %s\n", key, value)
       }
       //调用函数来移除指定的键
       removeColor(colors, "Coral")
       //显示映射里的所有颜色
        for key, value := range colors {
              fmt.Printf("Key: %s Value:  %s\n", key, value)
       }
}

func removeColor(colors map[string]string, key string) {
    delete(colors, key)
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值