035-数组

接下来我们就要进入另一个非常非常重要的篇章——Composite Types(复合类型)。这一章,我们需要讨论 4 种复合类型:

  • array (数组)
  • slice (切片)
  • map(映射)
  • struct(结构体)

数组将是第一个我们需要正式学习的复合类型。 slice 和 map 前面其实我们都已经接触过了,不过那时候只是浅浅尝辄止。

1. 数组

数组,是一种有固定长度的同一类元素的有序集合。划一下重点:

  • 有固定长度
  • 同一类元素
  • 有序

学过 C/C++ 的朋友对数组肯定是非常的熟悉了,定义数组,那一定是有一个大小的(不要和我说 std::vector,我们讲的不是一个东西)。在 Go 里,数组也一定是有固定大小的。比如[5]int类型,就表示长度为 5 的数组,每个元素都是 int 类型。

2. 声明和初始化数组

2.1 语法规则

  • 方法一

声明数组的语法如下:

var a [N]type

其中,N 表示常量数字,type 就是数组元素的类型了。

  • 方法二

还有一种办法,那就是自动类型推导了:

var a := [N]type{e1, e2, e3, ..., en} 

上面这种办法要求使用一个*数组字面量来初始化变量 a. 这种方法的好处就是声明的时候还指定了值。

  • 方法四

数组字面量有一个特性,它可以不必显式的写数组的长度大小,而是以 ... 代替:

var a := [...]type{e1, e2, e3, ..., en} 

这样数组字面量里有几个元素,这个数组长度就是多少啦。

  • 方法五

你甚至还可以给任意位置的元素初始化值:

var a := [100]string{40:"hello", 50:"world"}

上面表示声明一个大小为 100 的 string 数组,并将下标为 40 的元素初始化为 “hello”,下标为 50 的元素初始化为 “world”.

好了,说了这么多,还是来几个例子比较实在。

2.2 实例

下面是数组常见的初始化方法,可谓是灵活多变啊。但无论怎样,都脱离不了数组有固定长度这个事实。

package main

import "fmt"

func main() {
    var a [3]string
    a[0] = "Allen"
    a[1] = "Luffy"
    a[2] = "Zoro"
    fmt.Printf("a = %v, len(a) = %v\n", a, len(a))

    var b [4]int = [4]int{1, 2, 3, 4}
    fmt.Printf("b = %v, len(b) = %v\n", b, len(b))

    var c = [3]float64{1, 3.3}
    fmt.Printf("c = %v, len(c) = %v\n", c, len(c))

    d := [3]string{"Sanji", "Nami", "Robin"}
    fmt.Printf("d = %v, len(d) = %v\n", d, len(d))

    e := [...]int{0, 1, 2, 3}
    fmt.Printf("e = %v, len(e) = %v\n", e, len(e))

    f := [3]string{0: "Chopper", 1: "Brook"}
    fmt.Printf("f = %v, len(f) = %v\n", f, len(f))

    g := [3]string{2: "Frank", 0: "Chopper", 1: "Brook"}
    fmt.Printf("g = %v, len(g) = %v\n", g, len(g))

    h := [...]int{10: 100}
    fmt.Printf("h = %v, len(h) = %v\n", h, len(h))

    var i [3]int = [...]int{1, 2, 3}
    fmt.Printf("i = %v, len(i) = %v\n", i, len(i))
}

最后输出结果如下:

a = [Allen Luffy Zoro], len(a) = 3
b = [1 2 3 4], len(b) = 4
c = [1 3.3 0], len(c) = 3
d = [Sanji Nami Robin], len(d) = 3
e = [0 1 2 3], len(e) = 4
f = [Chopper Brook ], len(f) = 3
g = [Chopper Brook Frank], len(g) = 3
h = [0 0 0 0 0 0 0 0 0 0 100], len(h) = 11
i = [1 2 3], len(i) = 3

3. 数组之间比较

Go 的数组另一个厉害的地方,它可以直接进行比较。不过不能比较大小,只能比较是否相等。

package main

import "fmt"

func main() {
    a := [2]int{1, 2}
    b := [...]int{1, 2}
    c := [2]int{1, 3}
    d := [3]int{1, 3}

    fmt.Printf("a = %v\nb = %v\nc = %v\nd = %v\n", a, b, c, d)
    fmt.Println("a == b:", a == b) // a == b: true
    fmt.Println("a == c:", a == c) // a == c: false
    // fmt.Println(c == d) // compile error
    // fmt.Println(a > b, a > c) // compile error
}

上面这个例子,数组 a, b, c 是相同的类型([2]int),因此是可以直接比较的。两个类型相同的数组,只有所有元素一一比较都相等时,最后才算相等。就像上面的例子,数组 a 和 b 相等,但是 a 和 c 不相等。

类型不一样的两个数组是无法进行比较的,编译就会报错,比如上面的拿 c 和 d 比较。

最后,比较大小也是不可以的,编译也会报错,即使数组类型相同也不行!

4. 数组之间的赋值运算

4.1 数组间赋值

Go 中的数组和其它语言不太一样,Go 的数组的复制,会创建一个新的数组,并把元素挨个复制到新数组里去:

x := [...]int{9, 9, 9}
fmt.Printf("x = %v\n", x)  // x = [9 9 9]
y := x
fmt.Printf("y = %v\n", y)  // y = [9 9 9]
y[0] = 1
y[1] = 2
y[2] = 3
fmt.Printf("x = %v\n", x)  // x = [9 9 9]
fmt.Printf("y = %v\n", y)  // y = [1 2 3]

上面这个例子将 x 赋值给 y 后,再尝试修改 y 中的元素,发现对原始的 x 没有任何影响。

再看另一个例子,它将数组类型做函数的参数:

package main

import "fmt"

func f(a [3]int) {
    fmt.Printf("a = %v\n", a)  // a = [9 9 9]
    a[0] = 1
    a[1] = 2
    a[2] = 3
    fmt.Printf("a = %v\n", a)  // a = [1 2 3]
}

func main() {
    x := [...]int{9, 9, 9}
    fmt.Printf("x = %v\n", x)  // x = [9 9 9]
    f(x)
    fmt.Printf("x = %v\n", x)  // x = [9 9 9]
}

这个特性说明了 Go 的数组之间的赋值是低效的,因此在 Go 里使用数组的场合并不多。但是为什么我们还要学它呢?一方面是确实有一些场合会使用它,另一方面是为了后面学习 slice 做铺垫。

4.2 指向数组的指针(pointer to an array)

如果你嫌上面的例子复制数组元素的开销很大,别忘记了,我们学过了 Go 的指针。

我们将 4.1 中的例子改写一下:

x := [...]int{9, 9, 9}
fmt.Printf("x = %v\n", x)  // x = [9 9 9]
y := &x
fmt.Printf("y = %v\n", *y) // *y = [9 9 9]
y[0] = 1
y[1] = 2
y[2] = 3
fmt.Printf("x = %v\n", x)  // x = [1 2 3]
fmt.Printf("y = %v\n", *y) // *y = [1 2 3]

我们发现,使用指向数组的指针后,通过指针就可以修改原始数组中的值了。再来改写 4.1 中的第二个例子:

package main

import "fmt"

func f(a *[3]int) {
    fmt.Printf("*a = %v\n", *a)  // *a = [9 9 9]
    a[0] = 1
    a[1] = 2
    a[2] = 3
    fmt.Printf("*a = %v\n", *a)  // *a = [1 2 3]
}

func main() {
    x := [...]int{9, 9, 9}
    fmt.Printf("x = %v\n", x)  // x = [9 9 9]
    f(&x)
    fmt.Printf("x = %v\n", x)  // x = [1 2 3]
}

5. 总结

  • 掌握数组的各种声明和初始化方法
  • 数组是可以比较相等,但是不能比较大小
  • 只有同类型的数组才可以比较
  • 数组之间的赋值,是按元素逐一复制的

仅管指针提供了对原始数组直接操作的办法,也节省了数组间复制的开销,但是数组用起来还是不太方便。在很多语言里,都提供了一种可以改变长度大小的数组,在需要的时候,容量可以动态增长,也可以收缩。

其实,这就是我们后面要说的 Slice —— 切片。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值