diff --git a/eBook/07.1.md b/eBook/07.1.md index 40ae176..3d2d202 100644 --- a/eBook/07.1.md +++ b/eBook/07.1.md @@ -1,7 +1,251 @@ -#7.1 函数介绍 +#7.1 声明和初始化 +##7.1.1 概念 +数组是具有相同`唯一类型`的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以[5]int和[10]int是属于不同类型的。数组的编译时值初始化是按照数组顺序完成的(如下)。 + +**注意事项** +如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型(参考[第11章](11.9.md)。当使用值时我们必须先做一个类型判断(参考[第11章](11.3.md)。 + +数组元素可以通过`索引`(位置)来读取(或者修改),索引从0开始,第一个元素索引为0,第二个索引为1,以此类推。(数组以0开始在所有类C语言中是相似的)。元素的数目,也称为`length`或者数组大小必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组长度最大为2Gb。 + +声明的格式是: var identifier [len]type + +例如: var arr1 [5]int + +在内存中的结构是:![](images/7.1_fig7.1.png?raw=true) + +每个元素是一个整形值,当声明数组时所有的元素都会被自动初始化为默认值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章](13.0.md) + +runtime error: index out of range + +由于索引的存在,遍历数组的方法自然就是使用for-construct: + +- 通过for初始化数组项 +- 通过for打印数组元素 +- 通过for依次处理元素 + +示例 7.1 [for_arrays.go](exmaples/chapter_7/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章](07.2.md)来说也同样适用。 + +**问题 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](examples/chapter_7/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.1:array_value.go: 证明当数组赋值时,发生了数组内存拷贝。 +练习7.2:for_array.go: 写一个循环并用下标给数组赋值(从0到15)并且将数组打印在屏幕上。 +练习7.3:fibonacci_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章](07.2.md) ##链接 - [目录](directory.md) -- 上一节:[函数](06.0.md) -- 下一节:[函数参数与返回值](06.2.md) \ No newline at end of file +- 上一节:[数组与分片](07.0.md) +- 下一节:[分片slices](07.2.md) \ No newline at end of file