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 历冰、朱荣鑫、黄迪璇
相关推荐
这孩子叫逆1 分钟前
Spring Boot项目的创建与使用
java·spring boot·后端
一丝晨光1 小时前
Java、PHP、ASP、JSP、Kotlin、.NET、Go
java·kotlin·go·php·.net·jsp·asp
coderWangbuer1 小时前
基于springboot的高校招生系统(含源码+sql+视频导入教程+文档+PPT)
spring boot·后端·sql
攸攸太上1 小时前
JMeter学习
java·后端·学习·jmeter·微服务
Kenny.志1 小时前
2、Spring Boot 3.x 集成 Feign
java·spring boot·后端
sky丶Mamba2 小时前
Spring Boot中获取application.yml中属性的几种方式
java·spring boot·后端
千里码aicood3 小时前
【2025】springboot教学评价管理系统(源码+文档+调试+答疑)
java·spring boot·后端·教学管理系统
程序员-珍3 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
liuxin334455663 小时前
教育技术革新:SpringBoot在线教育系统开发
数据库·spring boot·后端
数字扫地僧4 小时前
HBase与Hive、Spark的集成应用案例
后端