ch9、数组和切片

本文详细介绍了Go语言中的数组和切片,包括数组的声明、初始化、遍历,以及切片的创建、截取、扩容和拷贝。强调了数组和切片的主要区别,如容量的可伸缩性和比较操作的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ch9、数组和切片

什么是数组?用于保存一组相同类型的数据结构。

1、数组声明、初始化

Go中数组定义之后就就有默认的初始值,默认初始值就是保存数据类型的默认值(零值)。

package array_test

import "testing"

func TestArrayInit(t *testing.T) {
	// 方式一: 先声明后使用,默认值是int默认值
	var arr [3]int
	// 方式二:声明即初始化为默认值,推荐使用该方法
	arr1 := [4]int{1, 2, 3, 4}
    // 方式三:通过...来声明并初始化,不使用...表示的是切片(slice)
	arr2 := [...]int{1, 3, 4, 5}
	arr1[1] = 5
	t.Log(arr[1], arr[2])
	t.Log(arr1, arr2)
}
  • 数组比较注意点

    • Go语言中数组长度也是数据类型的一部分

    • 如果元素类型支持==、!=操作时,那么数组也支持此操作

    package main
    
    import "fmt"
    
    func main() {
    	var arr1 [3]int = [3]int{1, 3, 5}
    	var arr2 [3]int = [3]int{1, 3, 5}
    	var arr3 [3]int = [3]int{2, 4, 6}
    	var arr4 [3]float64 = [3]float64{1.0, 3.0, 5.0}
    	// 首先会判断`数据类型`是否相同,如果相同会依次取出数组中`对应索引的元素`进行比较,
    	// 如果所有元素都相同返回true,否则返回false
    	fmt.Println(arr1 == arr2) // true
    	fmt.Println(arr1 == arr3) // false
    	// fmt.Println(arr1 == arr4) // error
    }
    
    • Go语言中的数组是值类型, 赋值和传参时会复制整个数组
    package main
    
    import "fmt"
    
    func main() {
    	var arr1 [3]int = [3]int{1, 3, 5}
    	var arr2 [3]int = arr1
    	arr2[0] = 666
    	fmt.Println(arr1) // [1 3 5]
    	fmt.Println(arr2) // [666 3 5]
    }
    

2、遍历

go语言,只支持for,故可以使用常规for循环进行遍历

但go提供了range来快速遍历,用法如下:

package array_test

import "testing"

func TestArrayTravel(t *testing.T) {
	arr3 := [...]int{1, 3, 4, 5}
	for i := 0; i < len(arr3); i++ {
		t.Log(arr3[i])
	}

	for idx, e := range arr3 { // 表示索引,值,如果不需要索引或值,如何表示?
		t.Log(idx, e)
	}

	for _, e := range arr3 { // 如果不需要索引或值,使用_变量来表示,代表“占位”
		t.Log(e)
	}
}

3、数组(array)截取(切片,slice,切片初始化的一种方法)

a[开始索引(包含):结束索引(不包含)],左闭右开

如:a[2:len(a)]

go语言不支持负数截取,截取后的类型变为切片(slice,不是数组array)

package array_test

import "testing"

func TestArraySection(t *testing.T) {
	arr3 := [...]int{1, 2, 3, 4, 5}
	t.Log(arr3[1:2])
	t.Log(arr3[1:len(arr3)])
	t.Log(arr3[2:])
	t.Log(arr3[:4])
	t.Log(arr3[:])
	//t.Log(-1:-3)	// 不能为负数
	sarr1 := arr3[1:len(arr3)]
	t.Logf("%T %T", sarr1, arr3) //[]int [5]int,%T表示类型
}

4、切片(slice)

声明中不带省略号…

切片的内部结构
在这里插入图片描述

其中,要注意len和cap并不一定一致,可以使用make关键字初始化不同的值(切片初始化的一种方法):

  • 切片可以简单的理解为长度可以变化的数组, 但是Go中的切片本质上是一个结构体

    • 切片源码
type slice struct {
	array unsafe.Pointer // 指向底层数组指针
	len   int            // 切片长度(保存了多少个元素)
	cap   int            // 切片容量(可以保存多少个元素)
}
package slice_test

import "testing"

func TestSliceInit(t *testing.T) {
	var s0 []int            // 没有省略号,表示切片
	t.Log(len(s0), cap(s0)) // 0,0
	s0 = append(s0, 1)      // 使用append必须赋值给具体的值(一般是本身),因为go只支持值传递
	t.Log(len(s0), cap(s0)) // 1,1

	s1 := [...]int{1, 2, 3, 4}
	t.Log(len(s1), cap(s1)) //4,4
}

func TestSliceInit1(t *testing.T) {
	s2 := make([]int, 3, 5) // len为3,3个元素会被初始化为零值
	t.Log(len(s2), cap(s2)) // 3,5
	for i := 0; i < cap(s2); i++ {
		t.Log(s2[i]) // runtime error: index out of range [3] with length 3 [recovered]
	}
}

初始化切片(没有…)后内存可增长,内存空间的增长状态如下:

package slice_test

import "testing"

func TestSliceAppend(t *testing.T) {
	s3 := []int{} // 没有省略号,表示切片
	for i := 0; i < 10; i++ {
		s3 = append(s3, i)
		t.Log(len(s3), cap(s3))
	}
	/*
		output:
		1 1
		2 2
		3 4
		4 4
		5 8
		6 8
		7 8
		8 8
		9 16
		10 16
	*/
}
  • 如果希望切片自动扩容,那么添加数据时必须使用append方法
    • append函数会在切片末尾添加一个元素, 并返回一个追加数据之后的切片
    • 利用append函数追加数据时,如果追加之后没有超出切片的容量,那么返回原来的切片, 如果追加之后超出了切片的容量,那么返回一个新的切片
    • append函数每次给切片扩容都会按照原有切片容量*2的方式扩容
package functionExercise

import "fmt"

func SliceAppendExamples() {
	//var sce = []int{1, 3, 5}
	var sce = make([]int, 2, 3)
	fmt.Println("初始化:", sce)         // [0 0]
	fmt.Println("初始化len:", len(sce)) // 2
	fmt.Println("初始化cap:", cap(sce)) // 3
	fmt.Printf("初始化addr: %p\n", sce) // 0xc0000ae0c0
	// 第一个参数: 需要把数据追加到哪个切片中
	// 第二个参数: 需要追加的数据, 可以是一个或多个
	sce[0], sce[1] = 1, 3
	fmt.Println("没有超出切片的容量:", sce)         // [1 3]
	fmt.Println("没有超出切片的容量len:", len(sce)) // 2
	fmt.Println("没有超出切片的容量cap:", cap(sce)) // 3
	fmt.Printf("没有超出切片的容量addr: %p\n", sce) // 0xc0000ae0c0
	sce = append(sce, 666, 777, 888)
	fmt.Println("追加数据后:", sce)      // [1 3 666 777 888]
	fmt.Println("追加数据后:", len(sce)) // 5
	fmt.Println("追加数据后:", cap(sce)) // 6
	fmt.Printf("追加数据前: %p\n", sce)  // 0xc0000cc060
}

注意:切片共享内存存储结构

package slice_test

import "testing"

/* 注意cap的值,以及是否更改了原数组的值 */
func TestShareMemory(t *testing.T) {
	month := []string{"Jan.", "Feb.", "Mar.", "Apr.", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."}
	Q2 := month[3:6]
	t.Log(Q2, len(Q2), cap(Q2)) // [Apr. May June] 3 9
	summer := month[5:8]
	t.Log(summer, len(summer), cap(summer)) //[June July Aug.] 3 7
	summer[0] = "ShareMemory"
	t.Log(Q2)    // [Apr. May ShareMemory],month[5]被改动了
	t.Log(month) // [Jan. Feb. Mar. Apr. May ShareMemory July Aug. Sept. Oct. Nov.
	// Dec.],说明month[5]被改变了
}
  • 除了append函数外,Go语言还提供了一个copy函数, 用于两个切片之间数据的快速拷贝
    • 格式: copy(目标切片, 源切片), 会将源切片中数据拷贝到目标切片中
package main

import "fmt"

func main() {
	var sce1 = []int{1, 3, 5}
	var sce2 = make([]int, 5)
	fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0
	fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060
	// 将sce2的指向修改为sce1, 此时sce1和sce2底层指向同一个数组
	sce2 = sce1
	fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0
	fmt.Printf("赋值后:%p\n", sce2) // 0xc0420600a0
	fmt.Println(sce1) // [1 3 5]
	fmt.Println(sce2) // [1 3 5]
	sce2[1] = 666
	fmt.Println(sce1) // [1 666 5]
	fmt.Println(sce2) // [1 666 5]
}
package main

import "fmt"

func main() {
	var sce1 = []int{1, 3, 5}
	var sce2 = make([]int, 5)
	fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0
	fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060
	// 将sce1中的数据拷贝到sce2中,, 此时sce1和sce2底层指向不同数组
	copy(sce2, sce1)
	fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0
	fmt.Printf("赋值后:%p\n", sce2) // 0xc042076060
	fmt.Println(sce1) // [1 3 5]
	fmt.Println(sce2) // [1 3 5 0 0]
	sce2[1] = 666
	fmt.Println(sce1) // [1 3 5]
	fmt.Println(sce2) // [1 666 5 0 0]
}
  • copy函数在拷贝数据时永远以小容量为准
  • 可以通过切片再次生成新的切片, 两个切片底层指向同一数组
  • 和数组不同, 切片只支持判断是否为nil, 不支持equal比较
  • 只声明当没有被创建的切片是不能使用的
package main

import "fmt"

func main() {
	// 数组声明后就可以直接使用, 声明时就会开辟存储空间
	var arr [3]int
	arr[0] = 2
	arr[1] = 4
	arr[2] = 6
	fmt.Println(arr) // [2 4 6]

	// 切片声明后不能直接使用, 只有通过make或语法糖创建之后才会开辟空间,才能使用
	var sce []int
	sce[0] = 2 // 编译报错
	sce[1] = 4
	sce[2] = 6
	fmt.Println(sce)
}
  • 字符串的底层是[]byte数组, 所以字符也支持切片相关操作
package main

import "fmt"

func main() {
	str := "abcdefg"
	// 通过字符串生成切片
	sce1 := str[3:]
	fmt.Println(sce1) // defg

	sce2 := make([]byte, 10)
	// 第二个参数只能是slice或者是数组
	// 将字符串拷贝到切片中
	copy(sce2, str)
	fmt.Println(sce2) //[97 98 99 100 101 102 103 0 0 0]
}
  • 值得注意点的是切片的本质就是一个指针指向数组, 所以指向切片的指针是一个二级指针
    在这里插入图片描述
package main

import "fmt"

func main() {
	// 1.定义一个切片
	var sce []int = []int{1, 3, 5}
	// 2.打印切片的地址
	// 切片变量中保存的地址, 也就是指向的那个数组的地址 sce = 0xc0420620a0
	fmt.Printf("sce = %p\n", sce)
	fmt.Println(sce) // [1 3 5]
	// 切片变量自己的地址, &sce = 0xc04205e3e0
	fmt.Printf("&sce = %p\n", &sce)
	fmt.Println(&sce) // &[1 3 5]
	// 3.定义一个指向切片的指针
	var p *[]int
	// 因为必须类型一致才能赋值, 所以将切片变量自己的地址给了指针
	p = &sce
	// 4.打印指针保存的地址
	// 直接打印p打印出来的是保存的切片变量的地址 p = 0xc04205e3e0
	fmt.Printf("p = %p\n", p)
	fmt.Println(p) // &[1 3 5]
	// 打印*p打印出来的是切片变量保存的地址, 也就是数组的地址 *p = 0xc0420620a0
	fmt.Printf("*p = %p\n", *p)
	fmt.Println(*p) // [1 3 5]

	// 5.修改切片的值
	// 通过*p找到切片变量指向的存储空间(数组), 然后修改数组中保存的数据
	(*p)[1] = 666
	fmt.Println(sce[1])
	fmt.Println(sce)
}

5、数组(array) VS. 切片(slice)

5.1、容量是否可伸缩

数组不可以;切片可以(append)

package slice_test

import "testing"

func TestSliceInit(t *testing.T) {
	var s0 []int
	t.Log(len(s0), cap(s0)) // 0,0
	s0 = append(s0, 1)      // 使用append必须赋值给具体的值(一般是本身),因为go只支持值传递
	t.Log(len(s0), cap(s0)) // 1,1

	s1 := [...]int{1, 2, 3, 4}
	t.Log(len(s1), cap(s1)) //4,4
}

func TestSliceInit1(t *testing.T) {
	s2 := make([]int, 3, 5) // len个元素会被初始化为零值
	t.Log(len(s2), cap(s2)) // 3,5
	for i := 0; i < cap(s2); i++ {
		t.Log(s2[i]) // runtime error: index out of range [3] with length 3 [recovered]
	}
}

func TestSliceAppend(t *testing.T) {
	s3 := []int{}
	for i := 0; i < 10; i++ {
		s3 = append(s3, i)
		t.Log(len(s3), cap(s3))
	}
	/*
		output:
		1 1
		2 2
		3 4
		4 4
		5 8
		6 8
		7 8
		8 8
		9 16
		10 16
	*/
}

5.2、是否可以比较

数组可以;切片不可以(仅可以和nil比较)

package slice_test

import "testing"

func TestSliceCompare(t *testing.T) {
	a := [...]int{1, 2, 3, 4}
	b := [...]int{2, 3, 4, 5}
	if a == b {
		t.Log("equal")
	} else {
		t.Log("unequal")
	}

	c := []int{1, 2, 3}
	d := []int{1, 2}
	d = append(d, 3)
	if c == d { // invalid operation: c == d (slice can only be compared to nil)
		t.Log("equal")
	}
}
8
		9 16
		10 16
	*/
}

5.2、是否可以比较

数组可以;切片不可以(仅可以和nil比较)

package slice_test

import "testing"

func TestSliceCompare(t *testing.T) {
	a := [...]int{1, 2, 3, 4}
	b := [...]int{2, 3, 4, 5}
	if a == b {
		t.Log("equal")
	} else {
		t.Log("unequal")
	}

	c := []int{1, 2, 3}
	d := []int{1, 2}
	d = append(d, 3)
	if c == d { // invalid operation: c == d (slice can only be compared to nil)
		t.Log("equal")
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值