Files
the-way-to-go_ZH_CN/eBook/07.1.md
chidouhu 9fa4f00725 07.1.md
2013-11-20 15:30:06 +08:00

8.2 KiB
Raw Blame History

#7.1 声明和初始化

##7.1.1 概念 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以[5]int和[10]int是属于不同类型的。数组的编译时值初始化是按照数组顺序完成的如下

注意事项 如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型(参考第11章。当使用值时我们必须先做一个类型判断(参考第11章

数组元素可以通过索引位置来读取或者修改索引从0开始第一个元素索引为0第二个索引为1以此类推。数组以0开始在所有类C语言中是相似的。元素的数目也称为length或者数组大小必须是固定的并且在声明该数组时就给出编译时需要知道数组长度以便分配内存数组长度最大为2Gb。

声明的格式是: var identifier [len]type

例如: var arr1 [5]int

在内存中的结构是:

每个元素是一个整形值当声明数组时所有的元素都会被自动初始化为默认值0.

arr1的长度是5索引范围从0到len(arr1)-1

第一个元素是arr1[0]第3个元素是arr1[2]总体来说索引i代表的元素是arr1[i]最后一个元素是arr1[len(arr1)-1]。

对索引项为i的数组元素赋值可以这么操作arr[i] = value所以数组是可变的

只有有效的索引可以被使用当使用等于或者大于len(arr1)的索引时如果编译器可以检测到会给出索引超限的提示信息如果检测不到的话编译会通过而运行时会panic:(参考第13章

runtime error: index out of range

由于索引的存在遍历数组的方法自然就是使用for-construct:

  • 通过for初始化数组项
  • 通过for打印数组元素
  • 通过for依次处理元素

示例 7.1 for_arrays.go

package main
import "fmt"

func main() {
	var arr1 [5]int

	for i:=0; i < len(arr1); i++ {
		arr1[i] = i * 2
	}

	for i:=0; i < len(arr1); i++ {
		fmt.Printf("Array at index %d is %d\n", i, arr1[i])
	}
}

输出结果:

Array at index 0 is 0
Array at index 1 is 2
Array at index 2 is 4
Array at index 3 is 6
Array at index 4 is 8

for-loop中的条件非常重要i < len(arr1)如果写成i <= len(arr1)的话会产生越界错误。

IDIOM:

for i:=0; i < len(arr1); i++
	arr1[i] = ...
}

也可以使用for-range的生成方式

IDIOM:

for i:= range arr1 {
...
}

在这里i也是数组的索引。当然这两种for-construct方式对于分片(slices)(参考第7章来说也同样适用。

问题 7.1 下面代码段的输出是什么?

a := [...]string{"a", "b", "c", "d"}
	for i := range a {
		fmt.Println("Array item", i, "is", a[i])
	}

Go语言中的数组是一种值类型不像C/C++中是指向首元素的指针),所以可以通过new()来创建: var arr1 = new([5]int)

那么这种方式和var arr2 [5]int的区别是什么呢arr1的类型是*[5]int而arr2的类型是[5]int。

这样的结果就是当把一个数组赋值给另一个时,需要在做一次数组内存的拷贝操作。例如:

arr := arr1
arr2[2] = 100

这样两个数组就有了不同的值在赋值后修改arr2不会对arr1生效。

所以在函数中数组作为参数传入时如func1(arr1)会产生一次数组拷贝func1方法不会修改原始的数组arr1。

如果你想修改原数组那么arr1必须通过&操作符以引用方式传过来例如func1(&arr1下面是一个例子

Example 7.2 pointer_array.go: package main import "fmt" func f(a [3]int) { fmt.Println(a) } func fp(a *[3]int) { fmt.Println(a) }

func main() {
	var ar [3]int
	f(ar) // passes a copy of ar
	fp(&ar) // passes a pointer to ar
}

输出结果:

[0 0 0]
&[0 0 0]

另一种方法就是生成数组分片slice并将其传递给函数参见传递数组参数给函数7.1.4节)

练习

练习7.1array_value.go: 证明当数组赋值时,发生了数组内存拷贝。 练习7.2for_array.go: 写一个循环并用下标给数组赋值从0到15并且将数组打印在屏幕上。 练习7.3fibonacci_array.go: 在6.6节我们看到了一个递归计算Fibonacci数值的方法。但是通过数组我们可以更快的计算出Fibonacci数。完成该方法并打印出前50个Fibonacci数字。

##7.1.2 数组常量 如果数组值已经提前知道了,那么可以通过数组常量的方法来初始化数组,而不用依次使用[]=方法。(所有的组成元素都有相同的常量语法)

Example 7.3 [array_literals.go][examples/chapter_7/array_literals.go]

package main
import "fmt"

func main() {
	// var arrAge = [5]int{18, 20, 15, 22, 16}
	// var arrLazy = [...]int{5, 6, 7, 8, 22}
	// var arrLazy = []int{5, 6, 7, 8, 22}
	var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}
	// var arrKeyValue = []string{3: "Chris", 4: "Ron"}

	for i:=0; i < len(arrKeyValue); i++ {
		fmt.Printf("Person at %d is %s\n", i, arrKeyValue[i])
	}
}

第一种变化: var arrAge = [5]int{18, 20, 15, 22, 16}

注意[5]int可以从左边起开始忽略[10]int {1, 2, 3} :这是一个有10个元素的数组除了前三个元素外其他元素都为0。

第二种变化: var arrLazy = [...]int{5, 6, 7, 8, 22}

...可同样可以忽略从技术上说它们其实变化成了slice。

第三种变化key: value syntax

var arrKeyValue = [5]string{3: "Chris", 4:Ron"}

只有索引3和4被赋予实际的值其他元素都被设置为空的字符串所以输出结果为

Person at 0 is
Person at 1 is
Person at 2 is
Person at 3 is Chris
Person at 4 is Ron

在这里数组长度同样可以写成...或者直接忽略。

你可以取任意数组常量的地址来作为指向新实例的指针。

Example 7.3 [pointer_array2.go][examples/chapter_7/pointer_array2.go]

package main
import "fmt"

func fp(a *[3]int) { fmt.Println(a) }

func main() {
	for i := 0; i < 3; i++ {
		fp(&[3]int{i, i * i, i * i * i})
	}
}

输出结果:

&[0 0 0]
&[1 1 1]
&[2 4 8]

几何点(或者数学向量)是一个使用数组的经典例子。为了简化代码通常使用一个别名:

type Vector3D [3]float32
var vec Vector3D

7.1.3 多维数组

数组通常是1维的但是可以用来组装成多维数组例如[3][5]int[2][2][2]float64

内部数组总是长度相同的。Go语言的多维数组是矩形式的唯一的例外是分片slice的数组参见7.2.5

Example 7.5 [multidim_array.go][examples/chapter_7/multidim_array.go]

package main
const (
	WIDTH  = 1920
	HEIGHT = 1080
)

type pixel int
var screen [WIDTH][HEIGHT]pixel

func main() {
	for y := 0; y < HEIGHT; y++ {
		for x := 0; x < WIDTH; x++ {
			screen[x][y] = 0
		}
	}
}

7.1.4 将数组传递给函数

把第一个大数组传递给函数会消耗很多内存。有两种方法可以避免这种现象:

  • 传递数组的指针
  • 使用数组的分片slice

接下来的例子阐明了第一种方法:

Example 7.6 [array_sum.go][examples/chapter_7/array_sum.go]

package main
import "fmt"

func main() {
	array := [3]float64{7.0, 8.5, 9.1}
	x := Sum(&array) // Note the explicit address-of operator
	// to pass a pointer to the array
	fmt.Printf("The sum of the array is: %f", x)
}

func Sum(a *[3]float64) (sum float64) {
	for _, v := range a { // derefencing *a to get back to the array is not necessary!
		sum += v
	}
	return
}

输出结果: The sum of the array is: 24.600000

但这在Go中并不常用通常使用分片slice。参考第7章

##链接