数组(Array)

本节我们介绍Golang中的数组(Array),下一节再介绍切片(Slice)。 数组是隶属同一种数据类型的元素的有序集合。例如集合5, 8, 9, 79, 76就可以用数组来表示。在Golang中,一个数组不允许存储不同数据类型的元素。

数组声明

Golang中数组的数据类型格式为[n]T。其中n表示数组元素个数,T表示元素类型,数组声明之后其长度不能再改变。注意这里的n也是属于数组类型的一部分,[3]int[4]int数据类型是不相同的。

有多种不同的方式可以声明数组,我们一个一个看。

package main

import "fmt"

func main() {  
    var a [3]int //int array with length 3
    fmt.Println(a)
}

Go Playground在线运行 这里var a [3]int声明了一个元素个数为3,元素类型为int的数组。这种方式声明的数组,其元素会自动初始化为对应元素类型的零值。这里数组a的元素类型为int,因此其元素自动初始化为0(int类型对应的零值为0)。该程序输入如下:[0 0 0]

数组的索引值从0length - 1。现在我们来给数组赋一些值。

package main

import "fmt"

func main() {  
    var a [3]int //int array with length 3
    a[0] = 12 // array index starts at 0
    a[1] = 78
    a[2] = 50
    fmt.Println(a)
}

Go Playground在线运行 a[0] = 12即给数组a的第一个元素赋值,该程序输出如下:[12 78 50]

Golang提供的变量简写声明方式(参考前面变量部分)同样适用于数组,格式如下:

package main 

import "fmt"

func main() {  
    a := [3]int{12, 78, 50} // short hand declaration to create array
    fmt.Println(a)
}

Go Playground在线运行 执行结果如下:[12 78 50]

这里我们使用简写方式时将数组的所有元素一个不漏的进行了手动初始化,如果我们无法给出所有元素的手动初始化,也不必全部给出,但简写形式要求至少需要给出一个元素的初始化值。

package main

import "fmt"

func main() {
    a := [3]int{12}
    fmt.Println(a)
}

Go Playground在线运行

语句a := [3]int{12}声明了长度为3的数组,但是我们并未全部给出初始值,仅给出了第一个元素的初始值12。剩下的另外两个元素会自动初始化为0。执行结果如下:[12 0 0]

声明数组时还可以将n换为...,这样Golang编译器会替你确认数组的长度,看个例子:

package main

import "fmt"

func main() {  
    a := [...]int{12, 78, 50} // ... makes the compiler determine the length
    var b [3]int
    b = a
    fmt.Println(a)
    fmt.Println(b)
}

Go Playground在线运行 这里我们声明数组变量a时使用的是[...]T,但是a的数组类型仍然是[3]int。执行结果如下:

[12 78 50]
[12 78 50]

注意,数组的长度n也是数组数据类型的一部分。 因此[5]int[25]int是不同的数据类型。当然这也能印证另外一点,数组声明之后其长度是不能再改变的。我们前面一段程序中声明了数组变量b,数据类型为[3]int,跟数组a的变量类型是一样的,因此类型兼容,才可以赋值,如果我们改成下面的程序,将数组b声明为var b [4]int就会报错:

package main

import "fmt"

func main() {  
    a := [...]int{12, 78, 50} // ... makes the compiler determine the length
    var b [4]int
    b = a
    fmt.Println(a)
    fmt.Println(b)
}

Go Playground在线运行 执行结果如下:

cannot use a (type [3]int) as type [4]int in assignment

从这点可以看出来,Golang的数组不是很灵活,因此提供了另外一种更加高级的数据类型切片(Slice),下一节我们再介绍。

值类型 vs 引用类型

学过C语言的应该都听过值传递引用传递,不了解的话可以参考这篇文章。这里说的变量传递其实就是将一个变量赋值给另外一个变量。

值传递是说变量赋值时直接传递变量的值,而不是变量的引用(或地址);而引用传递则是传递变量的引用,并不会传递变量本身的值。听起来比较抽象,其实非常简单,试想一下,假如某个数组变量存储了非常非常多的元素,每次传递该变量都将所有的元素考过来考过去效率肯定非常低,但是直接传递该数组变量的引用(或地址)效率就非常高。这里我们仅讨论下值传递和引用传递的不同,这二者并没有绝对的好坏,只不过不同的传递方式适用于不同的场景罢了,比如函数式编程就推崇函数调用时按值传递,函数应该是纯函数,每次以相同的参数去调用一个纯函数应该是幂等的。

Golang中数组是一种值类型的数据类型,意思是说如果我们将数组变量a赋值给数组变量b,那么数组a的所有元素会拷贝给数组b,这时如果我们对数组b做了修改,不会对原数组a有任何影响。看一下下面的程序:

package main

import "fmt"

func main() {  
    a := [...]string{"USA", "China", "India", "Germany", "France"}
    b := a // a copy of a is assigned to b
    b[0] = "Singapore"
    fmt.Println("a is ", a)
    fmt.Println("b is ", b) 
}

Go Playground在线运行 该程序中我们将数组a赋值给数组b,Golang会把数组a的所有元素拷贝一份给数组b,然后我们将数组b的第一个元素赋值为Singapore,并不影响数组a。执行结果如下:

a is  [USA China India Germany France]
b is  [Singapore China India Germany France]

如果我们把一个数组作为参数传递给一个函数,原理一样,在函数内如果对参数做了任何修改,也不会影响原数组。

package main

import "fmt"

func changeLocal(num [5]int) {  
    num[0] = 55
    fmt.Println("inside function ", num)

}
func main() {  
    num := [...]int{5, 6, 7, 8, 8}
    fmt.Println("before passing to function ", num)
    changeLocal(num) //num is passed by value
    fmt.Println("after passing to function ", num)
}

Go Playground在线运行 该程序中函数内部将参数num的第一个元素改为55,但是并不影响远数组,执行结果如下:

before passing to function  [5 6 7 8 8]
inside function  [55 6 7 8 8]
after passing to function  [5 6 7 8 8]

数组长度

Golang中通过len函数来获取数组的长度。

package main

import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    fmt.Println("length of a is",len(a))
}

Go Playground在线运行 输出如下:

length of a is 4

使用range遍历数组

我们可以使用for循环来遍历数组。

package main

import "fmt"

func main() {
    a:= [...]float64{67.7, 89.8, 21, 78}
    for i:= 0; i < len(a); i++ {
        fmt.Printf("%d th element of a is %.2f\n", i, a[i])
    }
}

Go Playground在线运行 该程序使用for语句按数组的索引从0开始访问数组。输出如下:

0 th element of a is 67.70
1 th element of a is 89.80
2 th element of a is 21.00
3 th element of a is 78.00

Golang针对数组提供了一种更加简洁的遍历方式,结合for语句和range

package main

import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    sum := float64(0)
    for i, v := range a {//range returns both the index and value
        fmt.Printf("%d the element of a is %.2f\n", i, v)
        sum += v
    }
    fmt.Println("\nsum of all elements of a",sum)
}

Go Playground在线运行 这里的for i, v := range a语句会返回数组元素的索引和值i, v。执行结果如下:

0 the element of a is 67.70
1 the element of a is 89.80
2 the element of a is 21.00
3 the element of a is 78.00

sum of all elements of a 256.5

如果你用不着索引值,可以使用占位标识符_

for _, v := range a { //ignores index  

}

当然,你可以通过该方式忽略元素值。

多维数组

目前为止,我们创建的数组都是一维数组。Golang当然也可以创建多维数组:

package main

import "fmt"

func printarray(a [3][2]string) {  
    for _, v1 := range a {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}

func main() {  
    a := [3][2]string{
        {"lion", "tiger"},
        {"cat", "dog"},
        {"pigeon", "peacock"}, //this comma is necessary. The compiler will complain if you omit this comma
    }
    printarray(a)
    var b [3][2]string
    b[0][0] = "apple"
    b[0][1] = "samsung"
    b[1][0] = "microsoft"
    b[1][1] = "google"
    b[2][0] = "AT&T"
    b[2][1] = "T-Mobile"
    fmt.Printf("\n")
    printarray(b)
}

Go Playground在线运行 这里我们首先通过变量声明的简写方式声明了一个二维数组a。这里需要注意,a数组初始值列表中最后的逗号不允许省略,否则Golang会自动在行尾插入分号导致语法错误,具体见这里:https://golang.org/doc/effective_go.html#semicolons。另外一个二维数组b声明后手动挨个进行了赋值。printarray函数使用了两个for range循环来打印数组值。该程序执行结果如下:

lion tiger 
cat dog 
pigeon peacock 

apple samsung 
microsoft google 
AT&T T-Mobile

这就是我们要介绍的数组的全部内容。由于数组的长度是固定不可变的,因此不够灵活。下一节介绍的切片就是为了解决这个问题的,实际上Golang中切片比数组更常用。

Last updated