Go中的数组

数组是一段存储固定类型、固定长度连续内存空间,在声明时就需要确定其大小和存储类型。

go 复制代码
[size]Type

数组的使用:

go 复制代码
func useArray() {
	// 声明数组
	var a [3]string

	// 通过数组下标为数组成员赋值
	a[0] = "a1"
	a[1] = "a2"
	fmt.Println(a)

	// 通过下标访问数组成员
	fmt.Println(a[0])

	// 使用初始化列表进行初始化
	var b [2]int = [2]int{1, 2}
	c := [2]bool{true, false}
	d := [...]string{"a", "b", "c"}
	fmt.Println(b, c, d)
}

使用...,编译器会根据{}内成员的数量来推算数组的大小,将声明语句转换成[size]Type的类型。

Go语言的数组是值类型,而不是引用类型,当数组变量被赋值给一个新变量或者作为函数参数传递时,会进行复制。

go 复制代码
func useArray() {
	d := [...]string{"a", "b", "c"}

	// 赋值变量时会进行复制
	e := d
	e[0] = "z"

	// 传递给函数时也会进行复制
	x := f1(e)

	fmt.Println(d) // [a b c]
	fmt.Println(e) // [z b c]
	fmt.Println(x) // [y b c]
}

func f1(x [3]string) [3]string {
	x[0] = "y"
	return x
}

因为赋值时会进行复制,传参时也会进行复制,所以d、e、x有各自的数据,修改x的数据不会影响到e,修改e的数据也不会影响到d。因为值复制会比较消耗内存,所以在开发中一般都是用切片,切片用的是引用复制。

Go的编译器和运行时都会检查数组越界,在索引是非正整数,或者索引超过了数组的下标访问范围时会报错。

数组底层结构

数组往往是一块连续的内存(虚拟内存),元素集合连续地排列在这块内存中,可以使用位置下标,快速访问元素。

比如[3]int16{1, 2, 3},因为[size]Type类型中已经知道了存放的类型和元素的数量,2个字节存储一个元素,知道数组的起始地址1234,如果想要获取索引2位置的元素,直接通过起始地址 + 索引值 x 元素尺寸(1235 + 2 x 2 = 1239) 就能得到第2个元素的起始地址,再通过这个地址获取元素的值。

(图中的地址数字是随便写的,示意用)

Go语言数组的数据类型为:

go 复制代码
// Array contains Type fields specific to array types.
type Array struct {
    Elem  *Type // element type
    Bound int64 // number of elements; <0 if unknown yet
}

包含2个属性:数组的元素类型、数组中包含的元素个数。

如果在编译期间,无法确认数组的元素个数,编译器就会报错,

go 复制代码
func f2(i int) {
	a := [i]int{1, 2} // invalid array length icompiler
	fmt.Println(a)
}

这段代码只有运行起来才知道i是多少,光看代码是无法确认i的数量的,不像[...]int{1, 2, 3}直接能通过代码的内容推断出来有3个元素。

使用unsafePointer函数可以获取变量的内存地址,使用Sizeof函数可以获得变量所占的内存大小。关于上面已经说过的"使用位置下标,快速访问元素",可以看这个具体的例子:

go 复制代码
func f3() {
	a := [3]int16{500, 600, 700}

	p := &a

	fmt.Printf("指向数组的指针 %p \n", p) // 指向数组的指针 0xc000116032
	eleSize := unsafe.Sizeof(a[0])

	// 数组a 每个元素占 2 字节,一共有3个元素,所以数组a占6字节
	fmt.Printf("每个元素占 %v 字节,数组a一共占 %v 字节\n", eleSize, unsafe.Sizeof(a)) // 每个元素占 2 字节,数组a一共占 6 字节

	startLoc := unsafe.Pointer(&a[0])
	fmt.Printf("数组的起始地址 %v \n", startLoc) // 数组的起始地址 0xc000116032

	index2EleLoc := unsafe.Pointer(uintptr(startLoc) + 2*eleSize)
	fmt.Printf("索引2的元素的起始地址 %v \n", index2EleLoc) // 索引2的元素的起始地址 0xc000116036

	fmt.Println(*(*uintptr)(index2EleLoc)) // 根据索引2的元素的起始地址拿到索引2位置的元素 700
}

通过这个公式起始地址 + 索引值 x 元素尺寸,计算索引2位置的元素的位置并拿到该位置的元素。(图中为了展示直观,去掉了地址前面的位数)

参考地址

  1. The Go Programming Language Specification:go.dev/ref/spec
  2. 《深入Go语言------原理、关键技术与实战》by 历冰、朱荣鑫、黄迪璇
相关推荐
Yuan_o_20 分钟前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端
程序员一诺1 小时前
【Python使用】嘿马python高级进阶全体系教程第10篇:静态Web服务器-返回固定页面数据,1. 开发自己的静态Web服务器【附代码文档】
后端·python
桃园码工1 小时前
1-Gin介绍与环境搭建 --[Gin 框架入门精讲与实战案例]
go·gin·环境搭建
thatway19892 小时前
AI-SoC入门:15NPU介绍
后端
陶庵看雪2 小时前
Spring Boot注解总结大全【案例详解,一眼秒懂】
java·spring boot·后端
Q_19284999062 小时前
基于Spring Boot的图书管理系统
java·spring boot·后端
ss2732 小时前
基于Springboot + vue实现的汽车资讯网站
vue.js·spring boot·后端
一只IT攻城狮3 小时前
华为云语音交互SIS的使用案例(文字转语音-详细教程)
java·后端·华为云·音频·语音识别
星月前端3 小时前
springboot中使用gdal将表中的空间数据转shapefile文件
java·spring boot·后端
彭亚川Allen3 小时前
优化了2年的性能,没想到最后被数据库连接池坑了一把
数据库·后端·性能优化