go语言数组的细枝末节

lioney · · 636 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

图片.png

1. 内部实现

在Go语言里,数组是一个长度固定的数据类型,用于存储一段具有相同的类型元素的连续块。存储的类型可以是内置类型,也可以是自定义类型。

数组是一种非常有用给的数据结构,因为其占用的内存是连续分配的。由于内存连续,CPU能把正在使用的数据缓存更久的时间。而且内存连续很容易计算索引,可以快速访问数组里的任意数据。

在Go语言中,数组是一种类似于整形,浮点型,字符串的基本数据类型,区别于C++语言的是,Go语言的数组在函数传参时是值传递的,因此要想通过某个函数修改数组的值,就必须通过传入数组的指针来实现,实际上Go语言中的所有函数传参都是值传递。

2. 声明和初始化

数组 的类型名是 [n ]elemetType ,其中 n 是数组长度, elementType 是数组元素类型 。 数组一般在创建时通过字面量初始化, 单独声明一个数组类型变量而不进行初始化是没有意义的。
不同初始化的方式:
    // 声明  
    var arr0 [3] int // 声明一个有两个类型的数组,但元素默认值都是0  
    arr1 := [...] float64{7.0, 8.1, 9.2} // [...]后面跟字面量初始化列表  

    // 初始化  
    arr2 := [3]int {1, 2, 3}   // 指定长度和初始化字面量  
    arr3 := [...]int {1, 2, 3}  // 不指定长度,但由后面的初始化列表  
    arr4 := [3]int {1:1, 2:3}   // 指定总长度,并通过索引值进行初始化,没有初始化的元素使用默认值  
    arr5 := [...]int {2:2, 5:4} // 不指定总长度,通过索引值进行初始化,没有初始化的元素使用默认值  
      // 数据长度由最后一个索引的元素值确定  
    fmt.Println("arr0:", arr0)  
    fmt.Println("arr1:", arr1)  
    fmt.Println("arr2:", arr2)  
    fmt.Println("arr3:", arr3)  
    fmt.Println("arr4:", arr4, "len=", len(arr4))  
    fmt.Println("arr5:", arr5, "len=", len(arr5))
编译运行结果
    arr0: [0 0 0]
    arr1: [7 8.1 9.2]
    arr2: [1 2 3]
    arr3: [1 2 3]
    arr4: [0 1 3] len= 3
    arr5: [0 0 2 0 0 4] len= 6

    Process finished with exit code 0

3. 数组的特点

  • 数组创建完长度就固定了,不能再追加元素
  • 数组是值类型的,数组赋值或作为函数参数都是值拷贝
  • 数组长度是数组类型的组成部分,[10]int和[20]int表示不同的类型
  • 可以根据数组创建切片(切片后面文章会介绍)
  • 在函数间传参是值传递的

4. 数组元素的访问

    arr := [...]int{1, 2, 3}
    // 方式一
    for i:=0; i<len(arr); i++ {
        fmt.Printf("arr[%d] = %d\n", i, arr[i])
    }
    // 方式二
    for k, v := range arr {
        fmt.Printf("arr[%d] = %d\n", k, v)
    }

5. 指针数组

    // 声明包含5个元素的指向整数的数组  
    // 用整形指针初始化索引为0和1的数组元素  
    arr := [2]*int{0: new(int), 1: new(int)}  

    // 为索引0和1的元素赋值  
    *arr[0] = 1;  
    *arr[1] = 2;  

    fmt.Println(arr)  
    for _, v := range arr {  
       // 对每个地址解引用,得到其指向的值  
      fmt.Println(\*v)  
    }
代码编译运行结果如下:
    [0xc00000a0c8 0xc00000a0e0]
    1
    2
指针数组的内存分布如下图所示:
图片.png

6. 数组之间的赋值

在Go语言里,数组是一个值。这意味着数组可以用在赋值操作中。变量名代表整个数组(和C++不同),同一类型的数组可以赋值给另一个数组,代码如下:
    // 声明第一个包含5个元素的字符串数组  
    var arr1 [5] string  
    // 声明第二个包含5个元素的字符串数组并并初始化  
    arr2 := [...]string{"Red", "Blue", "Green", "Yellow", "Pink"}  
    // 把arr2复制到arr1  
    arr1 = arr2
复制之后,两个数组完全一样,如下图所示。
图片.png
前面已经说过,数组变量的类型包括数组长度和每个元素的类型。只有这两部分都相同的数组,才是类型相同的数组,才能互相赋值,否则会报编译错误。

7. 指针数组的赋值

package main

import "fmt"

func main() {
    // 声明第一个包含3个元素的指向字符串的指针数组
    var arr1 [3] *string

    // 声明第二个包含3个元素的指向字符串的指针数组
    arr2 := [...]*string{new(string), new(string), new(string)}

    // 为每个元素赋值
    *arr2[0] = "Red"
    *arr2[1] = "Blue"
    *arr2[2] = "Green"

    // 把arr2复制到arr1
    arr1 = arr2

    fmt.Println(arr1)
    // 打印每个指针元素指向的值
    for k, v := range arr1 {
        fmt.Printf("arr[%d] = %s\n", k, *v)
    }
}
编译运行结果如下。
    arr1: [0xc0000441f0 0xc000044200 0xc000044210]
    arr[0] = Red
    arr[1] = Blue
    arr[2] = Green

    Process finished with exit code 0
赋值之后,两个数组指向同一个字符串,如下图所示。
图片.png

8.多维数组

数组本身只有一个维度,但可以组合多个维度创建多维数组,代码如下所示。
    package main

    import "fmt"

    func main() {
        // 声明一个二维整形数组,两个维度分别存储4个元素和2个元素
        var arr1 [4][2]int
        // 使用数组字面量来声明并初始化一个二维数组
        arr2 := [4][2] int{{1, 2}, {3, 4}, {5, 6}, {7, 8}}
        // 声明并初始化外层数组中索引为1和3的元素
        arr3 := [4][2] int{1: {30, 40}, 3: {50, 60}}
        // 声明并初始化外层数组和内层数组的单个元素
        arr4 := [4][2] int{1: {0: 10}, 2: {1: 40}}
        fmt.Println("arr1:", arr1)
        fmt.Println("arr2:", arr2)
        fmt.Println("arr3:", arr3)
        fmt.Println("arr4:", arr4)
    }
编译运行结果如下
    arr1: [[0 0] [0 0] [0 0] [0 0]]
    arr2: [[1 2] [3 4] [5 6] [7 8]]
    arr3: [[0 0] [30 40] [0 0] [50 60]]
    arr4: [[0 0] [10 0] [0 40] [0 0]]

    Process finished with exit code 0

9. 在函数间传递数组

根据内存和性能来看,在函数间传递数组是一个开销很大的操作。在函数之间传递变量时,总是以值的方式传递的。如果这个变量是一个数组,意味着整个数组,不管有多长,都会完整复制后传递给函数。
如果我们需要创建100万个int类型元素的数组,在64位架构上,需要800万字节,即8MB的内存,如果以值传递的方式将会特别耗内存,因此,对于这种大容量数组的传递,一般都是传入数组的指针,代码如下。
    package main

    // 函数foo接收一个指向100万个整形值的数组的指针
    func foo(arr *[1e6] int) {
        // ...
    }

    func main() {
        // 分配一个8MB的数组
        var arr [1e6]int

        // 将数组的地址传递给函数foo
        foo(&arr)
    }
现在将数组的地址传入函数,在栈空间分配上只需销毁8字节的内存空间。这个操作会更有效第利用内存,性能也更好。不过要意识到,因为现在传递的是指针,如果改变指针指向的值,会改变共享的内存,如果调用者不期望这种改变,就可能出现问题。

关于go语言数组,就啰嗦这么多了~~

我是lioney,年轻的后端攻城狮一枚,爱钻研,爱技术,爱分享。
个人笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎大家指出,也欢迎大家一起交流后端各种问题!

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:lioney

查看原文:go语言数组的细枝末节

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

636 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传