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 历冰、朱荣鑫、黄迪璇
相关推荐
uzong5 小时前
技术故障复盘模版
后端
GetcharZp6 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程6 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研6 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi7 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国8 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy8 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack8 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9659 小时前
pip install 已经不再安全
后端
寻月隐君9 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github