【Go】Go语言 数组

导读:本篇文章讲解 【Go】Go语言 数组,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com


一、简介

数组是一段长度固定的连续内存区域,拥有0 个或多个(不超过数组长度)相同数据类型的数据项序列。其中元素类型支持任意内置类型,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。

数组在声明(使用 [长度]类型 进行声明)的时候必须指定长度,可以修改数组元素,但是不可修改数组长度。
数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。

实际上,我们很少在代码中直接使用数组,数组在绝大部分的时候都是做为 slice 的底层存储,并不会直接使用。
数组的可比较性取决于元素的可比较性,如果元素是可比较的,那么数组也是可比较的,反之亦然。

Golang Array和以往认知的数组有很大不同:

  1. 数组:是同一种数据类型的固定长度的序列。
  2. 数组定义:var name [len]type,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
  3. 长度是数组类型的一部分,因此,var a [5]intvar a [10]int是不同的类型。
  4. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
for i := 0; i < len(a); i++ {
}
for index, v := range a {
}
  1. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
  2. 数组是 值类型 ,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。(与 C 数组变量隐式作为指针使用不同,Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据。)
  3. 支持 “==”、”!=” 操作符,因为内存总是被初始化过的。
  4. 指针数组 [n]*T,数组指针 *[n]T

二、优势

相对于声明 number0, number1, …, number99 的多个变量,使用数组形式 numbers[0], numbers[1] …, numbers[99] 更加方便且易于扩展。


三、数组的声明

Go 声明数组需要指定元素类型及元素个数,语法格式如下:

var name [SIZE]type

以上为一维数组的定义方式。
多维数组的声明:

var name [SIZE1][SIZE2]...[SIZEN]type

四、数组的初始化

1. 声明的同时初始化

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

2. 使用赋值操作符初始化声明数组

//一维数组的声明
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 */

特殊情况的说明:

  1. 数组长度不确定
    如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

    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}
    
  2. 只初始化个别元素(通过下标)
    如果设置了数组的长度,我们还可以通过指定下标来初始化元素:

    //  将索引为 1 和 3 的元素初始化
    balance := [5]float32{1:2.0,3:7.0}
    

    初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
    如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小。

3. 一维数组的初始化方法总结:

全局变量:
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}]

4. 多维数组的初始化方法总结:

全局变量:
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 数组是值类型,赋值和函数传参操作都会复制整个数组的数据。

注意:值拷贝行为会造成性能问题,通常会建议使用 slice,或数组指针:

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并没有改变

1. 使用 return 稍作改进

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,或 数组指针 来给函数传参。

2. 使用数组指针传参

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]

总结:想通过函数调用改变数组内容的两种方式

  1. return + 赋值
  2. 用 slice,或数组指针来传参(推荐)

参考链接

  1. 数组Array

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/119023.html

(0)
seven_的头像seven_bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!