数组是一段长度固定的连续内存区域,拥有0 个或多个(不超过数组长度)相同数据类型的数据项序列。其中元素类型支持任意内置类型,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。
数组在声明(使用 [长度]类型
进行声明)的时候必须指定长度,可以修改数组元素,但是不可修改数组长度。
数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。
实际上,我们很少在代码中直接使用数组,数组在绝大部分的时候都是做为 slice 的底层存储,并不会直接使用。
数组的可比较性取决于元素的可比较性,如果元素是可比较的,那么数组也是可比较的,反之亦然。
Golang Array和以往认知的数组有很大不同:
var name [len]type
,比如:var a [5]int
,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。var a [5]int
和var a [10]int
是不同的类型。for i := 0; i < len(a); i++ {
}
for index, v := range a {
}
[n]*T
,数组指针 *[n]T
。相对于声明 number0, number1, …, number99 的多个变量,使用数组形式 numbers[0], numbers[1] …, numbers[99] 更加方便且易于扩展。
Go 声明数组需要指定元素类型及元素个数,语法格式如下:
var name [SIZE]type
以上为一维数组的定义方式。
多维数组的声明:
var name [SIZE1][SIZE2]...[SIZEN]type
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//一维数组的声明
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//多维数组的声明
a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}} /* 第三行索引为 2 */
特殊情况的说明:
数组长度不确定
如果数组长度不确定,可以使用 ...
代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
或
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
只初始化个别元素(通过下标)
如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
// 将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小。
全局变量:
var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}
局部变量:
a := [3]int{1, 2} // 未初始化元素值为 0。
b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
}
完整代码:
package main
import (
"fmt"
)
var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}
func main() {
a := [3]int{1, 2} // 未初始化元素值为 0。
b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
c := [5]int{2: 100, 4: 200} // 使用引号初始化元素。
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
}
fmt.Println(arr0, arr1, arr2, str)
fmt.Println(a, b, c, d)
}
输出结果:
[1 2 3 0 0] [1 2 3 4 5] [1 2 3 4 5 6] [ hello world tom]
[1 2 0] [1 2 3 4] [0 0 100 0 200] [{user1 10} {user2 20}]
全局变量:
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
局部变量:
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
代码:
package main
import (
"fmt"
)
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
func main() {
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
fmt.Println(arr0, arr1)
fmt.Println(a, b)
}
注意:数组的第 2 纬度不能用 “…”
输出结果:
[[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]] [[1 2 3] [7 8 9]]
[[1 2 3] [4 5 6]] [[1 1] [2 2] [3 3]]
数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:
var salary float32 = balance[9]
多维数组遍历:
package main
import (
"fmt"
)
func main() {
var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
for k1, v1 := range f {
for k2, v2 := range v1 {
fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
}
fmt.Println()
}
}
输出结果:
(0,0)=1 (0,1)=2 (0,2)=3
(1,0)=7 (1,1)=8 (1,2)=9
一维数组遍历:
实例:找出数组中和为给定值的两个元素的下标,例如数组[1,3,5,8,7],找出两个元素之和等于8的下标分别是(0,4)和(1,2)。
package main
import "fmt"
//找出数组中和为给定值的两个元素的下标,例如数组[1,3,5,8,7],
//找出两个元素之和等于8的下标分别是(0,4)和(1,2)
// 求元素和,是给定的值
func myTest(a [5]int, target int) {
// 遍历数组
for i := 0; i < len(a); i++ {
other := target - a[i]
// 向后遍历
for j := i + 1; j < len(a); j++ {
if a[j] == other {
fmt.Printf("(%d,%d)\n", i, j)
}
}
}
}
func main() {
b := [5]int{1, 3, 5, 8, 7}
myTest(b, 8)
}
输出结果:
(0,4)
(1,2)
内置函数 len 和 cap 都返回数组长度 (元素数量)。
package main
func main() {
a := [2]int{}
println(len(a), cap(a))
}
输出结果:
2 2
如果你想向函数传递 数组参数,你需要在函数定义时,声明形参为数组,我们可以通过以下两种方式来声明:
方式一
形参设定数组大小:
void myFunction(param [10]int)
{
.
.
.
}
方式二
形参未设定数组大小:
void myFunction(param []int)
{
.
.
.
}
实例:函数接收整型数组参数,另一个参数指定了数组元素的个数,并返回平均值:
package main
import "fmt"
func main() {
/* 数组长度为 5 */
var balance = [5]int {1000, 2, 3, 17, 50}
var avg float32
/* 数组作为参数传递给函数 */
avg = getAverage( balance, 5 ) ;
/* 输出返回的平均值 */
fmt.Printf( "平均值为: %f ", avg );
}
func getAverage(arr [5]int, size int) float32 {
var i,sum int
var avg float32
for i = 0; i < size;i++ {
sum += arr[i]
}
//提升精度
avg = float32(sum) / float32(size)
return avg;
}
输出结果:
平均值为: 214.399994
实例:求数组所有元素之和
package main
import (
"fmt"
"math/rand"
"time"
)
// 求元素和
func sumArr(a [10]int) int {
var sum int = 0
for i := 0; i < len(a); i++ {
sum += a[i]
}
return sum
}
func main() {
// 若想做一个真正的随机数,要种子
// seed()种子默认是1
//rand.Seed(1)
rand.Seed(time.Now().Unix())
var b [10]int
for i := 0; i < len(b); i++ {
// 产生一个0到1000随机数
b[i] = rand.Intn(1000)
}
fmt.Println(b)
sum := sumArr(b)
fmt.Printf("sum=%d\n", sum)
}
输出结果:
[282 887 600 15 171 14 136 610 651 912]
sum=4278
我们知道,Go 语言中的数组是 值类型 ,赋值和传参会复制整个数组的值,而不是指针 / 地址。因此函数内改变的是副本的值,不会改变本身的值。
与 C 数组变量隐式作为指针使用不同,Go 数组是值类型,赋值和函数传参操作都会复制整个数组的数据。
package main
import (
"fmt"
)
func test(x [2]int) {
fmt.Printf("x: %p\n", &x)
x[1] = 1000
fmt.Println(x)
}
func main() {
a := [2]int{}
fmt.Printf("a: %p\n", &a)
test(a)
fmt.Println(a)
}
输出结果:
a: 0xc00000a1b0
x: 0xc00000a1e0 //并没有如愿将a传进去,x是位于别的内存地址的另一个变量
[0 1000] //希望a成为的样子
[0 0] //然而a并没有改变
package main
import (
"fmt"
)
func test(x [2]int) [2]int {
fmt.Printf("x: %p\n", &x)
x[1] = 1000
fmt.Println(x)
return x
}
func main() {
a := [2]int{}
fmt.Printf("a: %p\n", &a)
a = test(a)
fmt.Println(a)
}
输出结果:
a: 0xc00000a1b0
x: 0xc00000a1e0
[0 1000]
[0 1000]
数组是 值类型 ,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
通常还是建议:使用 slice,或 数组指针 来给函数传参。
package main
import "fmt"
func printArr(arr *[5]int) {
arr[0] = 10
for i, v := range arr {
fmt.Println(i, v)
}
}
func main() {
var arr1 [5]int
printArr(&arr1)
fmt.Println(arr1)
arr2 := [...]int{2, 4, 6, 8, 10}
printArr(&arr2)
fmt.Println(arr2)
}
输出结果:
0 10
1 0
2 0
3 0
4 0
[10 0 0 0 0]
0 10
1 4
2 6
3 8
4 10
[10 4 6 8 10]
总结:想通过函数调用改变数组内容的两种方式