数组是指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素(element),这种类型可以是任意的原始类型,比如 int、string 等,也可以是用户自定义的类型。一个数组包含的元素个数被称为数组的长度。在 Golang 中数组是一个长度固定的数据类型,数组的长度是类型的一部分,也就是说 [5]int 和 [10]int 是两个不同的类型。Golang 中数组的另一个特点是占用内存的连续性,也就是说数组中的元素是被分配到连续的内存地址中的,因而索引数组元素的速度非常快。

本文将介绍 Golang 数组的基本概念和用法,演示环境为 ubuntu 18.04 & go1.10.1。

Golang 数组的特点

我们可以把 Golang 数组的特征归纳为以下三点:

  • 固定长度:这意味着数组不可增长、不可缩减。想要扩展数组,只能创建新数组,将原数组的元素复制到新数组。
  • 内存连续:这意味可以在缓存中保留的时间更长,搜索速度更快,是一种非常高效的数据结构,同时还意味着可以通过数值的方式(arr[index])索引数组中的元素。
  • 固定类型:固定类型意味着限制了每个数组元素可以存放什么样的数据,以及每个元素可以存放多少字节的数据。

数组是个固定长度的数据类型,其长度和存储元素的数据类型都在声明数组时确定,并且不能更改。如果需要存储更多的元素,必须先创建一个更长的数组,然后把原来数组里的数据复制到新数组中。
数组占用的内存是连续分配的,比如我们创建一个包含 5 个整数元素的数组:

arr1 := [5]int{10,20,30,40,50}

数组在内存中的结构类似下图:

由于内存连续,CPU 能把正在使用的数据缓存更久的时间。而且在内存连续的情况下非常容易计算索引,也就是说可以快速迭代数组里的所有元素。原因是数组的类型信息可以提供每次访问一个元素时需要在内存中移动的距离,既然数组的每个元素的类型都相同,又是连续分配,因此就可以以固定的速度索引数组中的任意元素,并且速度非常快!

数组的声明与初始化

声明数组
声明数组时需要指定数组的长度和数组中元素的类型,比如声明一个包含 5 个元素,类型为 int 的数组:

var arr1 [5]int

这里强调一点,数组的类型是包含数组长度的,因此 [5]int 和 [10]int 是两个不同类型的数组。
在 Go 语言中声明变量时,总会使用对应类型的零值来初始化变量,数组也不例外。当声明数组变量时,数组内的每个元素被初始化为对应类型的零值。比如变量 arr1,它的 5 个元素都被初始化成了 int 类型的零值 0。使用 fmt.Println(arr1) 可以看到数组中元素的值:

package main
import "fmt"
func main(){var arr1 [5]intfmt.Println(arr1)     // 输出为:[0 0 0 0 0]
}

你可以把此时数组在内存中的状态想象为下图所示的样子:

使用字面量初始化数组
我们可以通过字面量在声明数组的同时快速的初始化数组:

arr2 := [5]int{10,20,30,40,50}

对于这种情况,还可以使用 … 代替数组的长度,让编译器根据实际的元素个数自行推断数组的长度:

arr3 := […]int{10,20,30,40,50}

如果设置了数组的长度,还可以通过指定下标的方式初始化部分元素:

//  用具体值初始化索引为 1 和 3 的元素
arr4 := [5]int{1:20,3:40}

数组的内容如下图所示:

访问与修改数组元素

和其它类 C 语言一样,Go 语言数组通过数组下标(索引位置)来读取或者修改数组元素。下标(索引)从 0 开始,第一个元素的索引为 0,第二个索引为 1,依次类推。元素的数目(数组长度)必须是固定的并且在声明数组时就指定(编译器需要知道数组的长度以便分配内存),数组长度最大为 2G。

访问数组元素
对于数组 arr 来说,第一个元素就是 arr[0],第二个元素是 arr[1],最后一个元素则是 arr[len(arr)-1]。下面的代码定义一个整型数组,然后通过 for 循环打印数组中的每个元素:

package main
import "fmt"
func main(){arr := [5]int{10,20,30,40,50}for i := 0; i < len(arr); i++ {fmt.Printf("At index %d is %d\n", i, arr[i])}
}

运行上面的代码,输出如下:

At index 0 is 10
At index 1 is 20
At index 2 is 30
At index 3 is 40
At index 4 is 50

除了使用 len() 函数通过索引遍历数组,还可以使用更方便的 range,结果都是一样的:

for index,value := range arr {fmt.Printf("At index %d is %d\n", index, value)
}

修改数组元素
要修改单个元素的值,直接通过下标访问元素并赋值就可以了:

arr := [5]int{10,20,30,40,50}
arr[2] = 35

指针数组
数组的元素除了是某个类型外,还可以是某个类型的指针,下面声明一个所有元素都是指针的数组,然后使用 * 运算符就可以访问元素指针所指向的值:

arr := [5]*int{0: new(int), 1: new(int)}

new(TYPE) 函数会为一个 TYPE 类型的数据结构划分内存并执行默认的初始化操作,然后返回这个数据对象的指针,所以 new(int) 表示创建一个 int 类型的数据对象,同时返回指向这个对象的指针。

// 为索引为 0 和 1 的元素赋值
*arr[0] = 10
*arr[1] = 20

完成赋值后的结果如下:

我们还可以接着初始化剩下的元素并赋值:

arr[2] = new(int)
arr[3] = new(int)
arr[4] = new(int)
*arr[2] = 30

最后打印整个指针数组指向的内容:

for i := 0; i < len(arr); i++ {fmt.Printf("At index %d is %d\n", i, *arr[i])
}

结果如下:

At index 0 is 10
At index 1 is 20
At index 2 is 30
At index 3 is 0
At index 4 is 0

数组是值类型

在 Golang 中,数组是值类型,这意味着数组也可以用在赋值操作中。变量名代表整个数组,同类型的数组可以赋值给另一个数组:

var arr1 [3]string
arr2 := [3]string{"nick", "jack", "mark"}
// 把 arr2 的赋值(其实本质上是复制)到 arr1
arr1 = arr2

复制完成后两个数组的值完全一样,但是彼此之间没有任何关系:

前面我们不止一次地提到:数组的类型包括数组的长度和数组元素的类型。只有这两部分都一样才是相同类型的数组,也才能够互相赋值。下面的代码中,在类型不同的数组间赋值,编译器会阻止这样的操作并报错:

// 声明第一个包含 4 个元素的字符串数组
var arr1 [4]string
// 声明第二个包含 3 个元素的字符串数组,并初始化
arr2 := [3]string{"nick", "jack", "mark"}
// 将 arr2 赋值给 arr1
arr1 = arr2

编译器表示在赋值时不能把 type [3]string 当 type [4]string 用。

把数组赋值给其它数组时,实际上是完整地复制一个数组。所以,如果数组是一个指针型的数组,那么复制的将是指针,而不会复制指针所指向的对象。看下面的代码:

// 声明第一个包含 4 个元素的字符串数组
var arr1 [3]*string
// 声明第二个包含 3 个元素的字符串数组,并初始化
arr2 := [3]*string{new(string), new(string), new(string)}
*arr2[0] = "nick"
*arr2[1] = "jack"
*arr2[2] = "mark"
// 将 arr2 赋值给 arr1
arr1 = arr2

在赋值完成后,两个数组指向的是同一组字符串:

把数组传递给函数

在 Golang 中数组是一个值类型,所有的值类型变量在赋值和作为参数传递时都将产生一次复制操作。如果直接将数组作为函数的参数,则在函数调用时数组会被复制一份传递给函数。因此,在函数体中无法修改源数组的内容,因为函数内操作的只是源数组的一个副本。
如此一来,从内存和性能上来看,在函数间传递数组是一个开销很大的操作。因为无论这个数组有多长,都会完整复制,并传递给函数。下面的 demo 中会声明一个包含 100 万个 int64 类型元素的数组,这会消耗掉 8MB 的内存:

func showArray(array [1e6]int64){// do something
}
var arr [1e6]int64
showArray(arr)

每次函数 showArray 被调用时,必须在栈上分配 8MB 的内存。之后整个数组的值(8MB 内存) 被复制到刚刚分配的内存中。虽然 Golang 的运行时会自动处理这个复制操作,但这样做的效率实在是太低了,也太耗费内存!合理且高效的方式是只传入指向数组的指针,这样只需复制 8 个字节的数据到函数的栈上就可以了:

func showArray(array *[1e6]int64){// do something
}
var arr [1e6]int64
showArray(&arr)

这段代码中的 showArray 函数接收一个指向包含 100 万个 int64 值的数组的指针,调用函数时传入的参数则是指向数组的指针。现在只需在栈上分配 8 个字节的内存给这个指针就行了。
这个方法能够更有效地利用内存,性能也更好。需要注意的是,此时在函数内外操作的都是同一个数组中的元素,会互相影响。

多维数组

多维数组的典型用例是平面坐标(二维数组)和三维坐标(三维数组),这里我们简单介绍一下二维数组。
Golang 的数组本身只有一个维度,但是我们可以组合多个数组从而创建出多维数组,下面是声明二维数组的实例代码:

// 声明一个二维整型数组,两个维度分别存储 4 个元素和 2 个元素
var arr [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
arr1 := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化外层数组中索引为 1 和 3 的元素
arr2 := [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化外层数组和内层数组的单个元素
arr3 := [4][2]int{1: {0: 20}, 3: {1: 41}}

下图展示了上面代码声明的二维数组在每次声明并初始化后包含的值:

为了访问单个元素,需要反复组合使用 [] 运算符,比如:

arr1[0][0] = 666

因为每个数组都是一个值,所以可以独立复制某个维度:

// 将 arr1 的索引为 1 的维度复制到一个同类型的新数组里
var arr4 [2]int = arr1[1]
// 将外层数组的索引为 1、内层数组的索引为 0 的整型值复制到新的整型变量里
var value int = arr1[1][0]

总结

数组在 Golang 中是作为高性能的基础类型设计的,因此对用户来说使用起来并不是特别方便,这一点在众多的开源代码中(数组用的少,slice 用的多)可以得到印证。其实基于数组实现的 slice 以其简单灵活的特性更易于被大家接受,这也正是 Golang 设计 slice 的初衷。本文介绍了数组这个幕后大英雄,后面的文章会介绍 slice 的用法。

参考:
Golang Array  types
《Go语言编程》
《Go语言实战》
go基础系列:数组

转载于:https://www.cnblogs.com/sparkdev/p/10704389.html

Golang 入门 : 数组相关推荐

  1. golang开发工程师-第一步:golang入门基础教学

    golang入门基础教学 前言 一.golang的优势何在? 二.goland破解教程 三.goland的使用教程 四.一个简单的go代码 五.变量的声明和赋值 六.数据类型的基本介绍 七.访问权限[ ...

  2. golang用数组作为函数参数

    在 golang,数组是值.作为参数传进函数时,传递的是数组的原始值拷贝,此时在函数内部是无法更新该数组的 package mainimport "fmt"func main() ...

  3. golang json数组拼接

    2016年06月16日 15:38:25 阅读数:2575 标签: golang json 数组  更多 个人分类: golang func main() {a := []byte(`{"P ...

  4. Golang入门(4):并发

    Golang入门(4):并发 摘要 并发程序指同时进行多个任务的程序,随着硬件的发展,并发程序变得越来越重要.Web服务器会一次处理成千上万的请求,这也是并发的必要性之一.Golang的并发控制比起J ...

  5. golang入门实战(二)

    golang入门实战 github 接上篇 接口数据时间格式 token校验中间件 多平台打包 未完待续 github 项目完整代码–github 接上篇 golang入门实战(一) 接口数据时间格式 ...

  6. 【Golang入门】二、Go语言快速开发

    需求:开发一个hello.go程序,要求输出"hello world". 采用VScode进行Golang的开发,因此这一系列博客的代码均在VScode编译器上编译实现. 这里我们 ...

  7. Golang学习——数组指针和指针数组的区别

    Golang中数组指针和指针数组区别 一.数组指针 1.语法 2.实例 3.通过指针访问数组 二.指针数组 1.语法 2.实例 区别二者,字面上只看 后两个字即可: 数组指针 :它是一个指针,但是数据 ...

  8. c语言的整形二维数组,C语言入门 — 数组,二维数组

    1.C语言入门 - 数组,结合上一篇文章<C语言入门 - 数组,一维数组>, 我们这里讲如何使用二维数组, 二维数组可以理解成多个一维数组组成的数组, 比如定义两个a[10]的数组可以这样 ...

  9. golang 入门--定义数组的方式

    1.定义 var 名称 [长度]数据类型 示例: // 先定义 var a [3]int// 再赋值 a[0] = 20 a[1] = 50 a[2] = 40fmt.Println(a) 2.隐式定 ...

最新文章

  1. 对口单招考试能用计算机吗,对口单招计算机能考试标准.doc
  2. zip、gz压缩文件查看命令zless、less
  3. 缓存nginx服务器的静态文件
  4. 线性规划 - 用单纯形法解决整数规划问题 - (Matlab、Lingo建模)
  5. python爬虫基础扫盲之urllib.pase解析URL
  6. VS Code常用快捷键汇总
  7. 多模态综述 | 一文了解Language-Vision预训练最新进展和新领域
  8. 用xslt 把xml转换成html的几个实例(1)
  9. 饱和气压与温度的关系_饱和水蒸气压计算公式,看懂的赶紧来
  10. vsftpd安装与配置
  11. Red Giant Trapcode Suite 16 for Mac(红巨星粒子插件)
  12. vue开发app端使用H5+下载文件流
  13. HBase 官方文档0.97.0
  14. python计算本息总和_python计算等额本金,等额本息
  15. NOI Linux 2.0 桌面背景展示
  16. 中蒙联合考古队发现青铜时代至清代岩画图案
  17. 看了本文让你laravel安装laravel-queue-rabbitmq一路顺风
  18. SE-第一章-整理-CCUT-ZY
  19. Ubuntu强制关机后无法启动
  20. vue vuex watch改变scrollTop

热门文章

  1. UVA 1210 Sum of Consecutive Prime Numbers
  2. 这个帖子要收藏,以后用得着--python 实时获取子进程输出
  3. 3520a SDL_tff库做bmp 也就是osd
  4. SQLite 入门教程(二)创建、修改、删除表
  5. Linux下的第一个驱动程序
  6. 使用 Dockerfile 定制镜像
  7. 06丨MongoDB基本操作
  8. oracle ajax储存过程分页,创建 Oracle 分页存储过程
  9. python 用if判断一个数是不是整数_五天学会Python基础02(下)
  10. 「应用管理与交付」为什么会成为云原生新的价值聚焦点?