[TOC]
## 7.1.1 概念
数组是具有相同 **唯一类型** 的一组已编号且长度固定的数据项序列(这是一种同构的数据结构);这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以[5]int和[10]int是属于不同类型的。数组的编译时值初始化是按照数组顺序完成的(如下)。
**注意事项** 如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型(参考 [第 11 章](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/11.9.md))。当使用值时我们必须先做一个类型判断(参考 [第 11 章](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/11.3.md))。
数组元素可以通过 **索引**(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。(数组以 0 开始在所有类 C 语言中是相似的)。元素的数目,也称为长度或者数组大小必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组长度最大为 2Gb。
声明的格式是:
~~~
var identifier [len]type
~~~
例如:
~~~
var arr1 [5]int
~~~
在内存中的结构是:[![](https://github.com/Unknwon/the-way-to-go_ZH_CN/raw/master/images/7.1_fig7.1.png?raw=true)](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/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 章](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/13.0.md))
~~~
runtime error: index out of range
~~~
由于索引的存在,遍历数组的方法自然就是使用 for 结构:
* 通过 for 初始化数组项
* 通过 for 打印数组元素
* 通过 for 依次处理元素
示例 7.1 [for_arrays.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/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 章](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/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),下面是一个例子
示例 7.2 [pointer_array.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/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.1:array_value.go: 证明当数组赋值时,发生了数组内存拷贝。
练习7.2:for_array.go: 写一个循环并用下标给数组赋值(从 0 到 15)并且将数组打印在屏幕上。
练习7.3:fibonacci_array.go: 在第 6.6 节我们看到了一个递归计算 Fibonacci 数值的方法。但是通过数组我们可以更快的计算出 Fibonacci 数。完成该方法并打印出前 50 个 Fibonacci 数字。
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.1.md#712-数组常量)7.1.2 数组常量
如果数组值已经提前知道了,那么可以通过 **数组常量** 的方法来初始化数组,而不用依次使用 `[]=` 方法(所有的组成元素都有相同的常量语法)。
示例 7.3 [array_literals.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/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
~~~
在这里数组长度同样可以写成 `...` 或者直接忽略。
你可以取任意数组常量的地址来作为指向新实例的指针。
示例 7.4 [pointer_array2.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/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
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.1.md#713-多维数组)7.1.3 多维数组
数组通常是一维的,但是可以用来组装成多维数组,例如:`[3][5]int`,`[2][2][2]float64`。
内部数组总是长度相同的。Go 语言的多维数组是矩形式的(唯一的例外是切片的数组,参见第 7.2.5 节)。
示例 7.5 [multidim_array.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/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
}
}
}
~~~
## [](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.1.md#714-将数组传递给函数)7.1.4 将数组传递给函数
把第一个大数组传递给函数会消耗很多内存。有两种方法可以避免这种现象:
* 传递数组的指针
* 使用数组的切片
接下来的例子阐明了第一种方法:
示例 7.6 [array_sum.go](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/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.2 节](https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.2.md))。
- 前言
- 第一部分:学习 Go 语言
- 第1章:Go 语言的起源,发展与普及
- 1.1 起源与发展
- 1.2 语言的主要特性与发展的环境和影响因素
- 第2章:安装与运行环境
- 2.1 平台与架构
- 2.2 Go 环境变量
- 2.3 在 Linux 上安装 Go
- 2.4 在 Mac OS X 上安装 Go
- 2.5 在 Windows 上安装 Go
- 2.6 安装目录清单
- 2.7 Go 运行时(runtime)
- 2.8 Go 解释器
- 第3章:编辑器、集成开发环境与其它工具
- 3.1 Go 开发环境的基本要求
- 3.2 编辑器和集成开发环境
- 3.3 调试器
- 3.4 构建并运行 Go 程序
- 3.5 格式化代码
- 3.6 生成代码文档
- 3.7 其它工具
- 3.8 Go 性能说明
- 3.9 与其它语言进行交互
- 第二部分:语言的核心结构与技术
- 第4章:基本结构和基本数据类型
- 4.1 文件名、关键字与标识符
- 4.2 Go 程序的基本结构和要素
- 4.3 常量
- 4.4 变量
- 4.5 基本类型和运算符
- 4.6 字符串
- 4.7 strings 和 strconv 包
- 4.8 时间和日期
- 4.9 指针
- 第5章:控制结构
- 5.1 if-else 结构
- 5.2 测试多返回值函数的错误
- 5.3 switch 结构
- 5.4 for 结构
- 5.5 Break 与 continue
- 5.6 标签与 goto
- 第6章:函数(function)
- 6.1 介绍
- 6.2 函数参数与返回值
- 6.3 传递变长参数
- 6.4 defer 和追踪
- 6.5 内置函数
- 6.6 递归函数
- 6.7 将函数作为参数
- 6.8 闭包
- 6.9 应用闭包:将函数作为返回值
- 6.10 使用闭包调试
- 6.11 计算函数执行时间
- 6.12 通过内存缓存来提升性能
- 第7章:数组与切片
- 7.1 声明和初始化
- 7.2 切片
- 7.3 For-range 结构
- 7.4 切片重组(reslice)
- 7.5 切片的复制与追加
- 7.6 字符串、数组和切片的应用
- 第8章:Map
- 8.1 声明、初始化和 make
- 8.2 测试键值对是否存在及删除元素
- 8.3 for-range 的配套用法
- 8.4 map 类型的切片
- 8.5 map 的排序
- 8.6 将 map 的键值对调
- 第9章:包(package)
- 9.1 标准库概述
- 9.2 regexp 包
- 9.3 锁和 sync 包
- 9.4 精密计算和 big 包
- 9.5 自定义包和可见性
- 9.6 为自定义包使用 godoc
- 9.7 使用 go install 安装自定义包
- 9.8 自定义包的目录结构、go install 和 go test
- 9.9 通过 Git 打包和安装
- 9.10 Go 的外部包和项目
- 9.11 在 Go 程序中使用外部库
- 第10章:结构(struct)与方法(method)
- 10.1 结构体定义
- 10.2 使用工厂方法创建结构体实例
- 10.3 使用自定义包中的结构体
- 10.4 带标签的结构体
- 10.5 匿名字段和内嵌结构体
- 10.6 方法
- 10.8 垃圾回收和 SetFinalizer
- 第11章:接口(interface)与反射(reflection)
- 11.1 接口是什么
- 11.2 接口嵌套接口
- 11.3 类型断言:如何检测和转换接口变量的类型
- 11.4 类型判断:type-switch
- 11.5 测试一个值是否实现了某个接口
- 11.6 使用方法集与接口
- 11.7 第一个例子:使用 Sorter 接口排序
- 11.8 第二个例子:读和写
- 11.9 空接口
- 11.10 反射包
- 第三部分:Go 高级编程
- 第12章 读写数据
- 12.1 读取用户的输入
- 12.2 文件读写
- 12.3 文件拷贝
- 12.4 从命令行读取参数
- 12.5 用buffer读取文件
- 12.6 用切片读写文件
- 12.7 用 defer 关闭文件
- 12.8 使用接口的实际例子:fmt.Fprintf
- 12.9 Json 数据格式
- 12.10 XML 数据格式
- 12.11 用 Gob 传输数据
- 12.12 Go 中的密码学
- 第13章 错误处理与测试
- 13.1 错误处理
- 13.2 运行时异常和 panic
- 13.3 从 panic 中恢复(Recover)
- 13.4 自定义包中的错误处理和 panicking
- 13.5 一种用闭包处理错误的模式
- 13.6 启动外部命令和程序
- 13.7 Go 中的单元测试和基准测试
- 13.8 测试的具体例子
- 13.9 用(测试数据)表驱动测试
- 13.10 性能调试:分析并优化 Go 程序
- 第14章:协程(goroutine)与通道(channel)
- 14.1 并发、并行和协程
- 14.2 使用通道进行协程间通信
- 14.3 协程同步:关闭通道-对阻塞的通道进行测试
- 14.4 使用 select 切换协程
- 14.5 通道,超时和计时器(Ticker)
- 14.6 协程和恢复(recover)
- 第15章:网络、模版与网页应用
- 15.1 tcp服务器
- 15.2 一个简单的web服务器
- 15.3 访问并读取页面数据
- 15.4 写一个简单的网页应用
- 第四部分:实际应用
- 第16章:常见的陷阱与错误
- 16.1 误用短声明导致变量覆盖
- 16.2 误用字符串
- 16.3 发生错误时使用defer关闭一个文件
- 16.5 不需要将一个指向切片的指针传递给函数
- 16.6 使用指针指向接口类型
- 16.7 使用值类型时误用指针
- 16.8 误用协程和通道
- 16.9 闭包和协程的使用
- 16.10 糟糕的错误处理
- 第17章:模式
- 17.1 关于逗号ok模式
- 第18章:出于性能考虑的实用代码片段
- 18.1 字符串
- 18.2 数组和切片
- 18.3 映射
- 18.4 结构体
- 18.5 接口
- 18.6 函数
- 18.7 文件
- 18.8 协程(goroutine)与通道(channel)
- 18.9 网络和网页应用
- 18.10 其他
- 18.11 出于性能考虑的最佳实践和建议
- 附录