Files
the-way-to-go_ZH_CN/eBook/07.1.md
怪盗kidou 632a1a6ea9 更正其中的一个错误
原始 :for _,i:= range arr1
更正后:for i,_:= range arr1
2015-06-15 22:56:31 +08:00

260 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 7.1 声明和初始化
##7.1.1 概念
数组是具有相同 **唯一类型** 的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以[5]int和[10]int是属于不同类型的。数组的编译时值初始化是按照数组顺序完成的如下
**注意事项**
如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型(参考 [第 11 章](11.9.md))。当使用值时我们必须先做一个类型判断(参考 [第 11 章](11.3.md))。
数组元素可以通过 **索引**位置来读取或者修改索引从0开始第一个元素索引为 0第二个索引为 1以此类推。数组以 0 开始在所有类C语言中是相似的。元素的数目也称为长度或者数组大小必须是固定的并且在声明该数组时就给出编译时需要知道数组长度以便分配内存数组长度最大为 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],第三个元素是 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 结构:
- 通过 for 初始化数组项
- 通过 for 打印数组元素
- 通过 for 依次处理元素
示例 7.1 [for_arrays.go](examples/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循环中的条件非常重要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 结构对于切片(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`
这样的结果就是当把一个数组赋值给另一个时需要在做一次数组内存的拷贝操作例如
arr2 := arr1
arr2[2] = 100
这样两个数组就有了不同的值在赋值后修改 arr2 不会对 arr1 生效
所以在函数中数组作为参数传入时 func1(arr2)会产生一次数组拷贝func1 方法不会修改原始的数组 arr2
如果你想修改原数组那么 arr2 必须通过&操作符以引用方式传过来例如 func1(&arr2下面是一个例子
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]
另一种方法就是生成数组切片并将其传递给函数参见传递数组参数给函数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}`
`...` 可同样可以忽略从技术上说它们其实变化成了切片
第三种变化`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.4 [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 语言的多维数组是矩形式的唯一的例外是切片的数组参见第 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 将数组传递给函数
把第一个大数组传递给函数会消耗很多内存有两种方法可以避免这种现象
- 传递数组的指针
- 使用数组的切片
接下来的例子阐明了第一种方法
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 中并不常用通常使用切片。(参考 [第 7 章](07.2.md)
## 链接
- [目录](directory.md)
- 上一节[数组与切片](07.0.md)
- 下一节[切片](07.2.md)