数组和切片

数组

介绍
数组可以存放多个同一类型数据。数值本身也是一种数据类型,在Go中,数组是值类型,故遵守值类型的使用方法,赋值,取值等。

数组定义和内存布局
1.数组定义
var name [容量]int类型
2.内存布局

数组的地址为其第一个元素的地址(数组的首地址)
数组相邻元素的地址间隔是依据数组的类型决定的,例如int64:隔8个字节,int32:隔4个字节
在这里插入图片描述

package main

import "fmt"

func main() {
	var intArr [3]int //int(64位操作系统)类型值占8个字节
	//定义数组后,和其他值类型相同,其默认值为零
	fmt.Println(intArr)
	intArr[0] = 10
	intArr[1] = 11
	intArr[2] = 12
	fmt.Println(intArr)
	fmt.Printf("intArr的地址=%p,inrArr[0]地址=%p,intArr[1]地址=%p,intArr[2]地址=%p\n",
		&intArr, &intArr[0], &intArr[1], &intArr[2])
}

output:
[0 0 0]
[10 11 12]
intArr的地址=0xc00000a150,inrArr[0]地址=0xc00000a150,intArr[1]地址=0xc00000a158,intArr[2]地址=0xc00000a160

常用的数组初始化方法

//四种初始化数组的方式
	var numArr01 [3]int = [3]int{1, 2, 3}
	fmt.Println("numArr01=", numArr01)

	var numArr02 = [3]int{1, 2, 3}
	fmt.Println("numArr02=", numArr02)

	//数组第一维可以不明确定义使用标准格式[...]表示
	var numArr03 = [...]int{1, 2, 3}
	fmt.Println("numArr03=", numArr03)
	
	var numArr04 = [...]int{1: 10, 0: 20, 2: 50}
	fmt.Println("numArr04=", numArr04)

	//类型推导
	strArr := [...]string{1: "Tom", 2: "Marry", 0: "Ming"}
	fmt.Println("strArr=", strArr)

Output:
numArr01= [1 2 3]
numArr02= [1 2 3]
numArr03= [1 2 3]
numArr04= [20 10 50]
strArr= [Ming Tom Marry]

注意:数组一维可以不明确指定其容量,使用标准格式[...]表示。

数组的遍历
1.for len(array) 类型

	strArr := [...]string{1: "Tom", 2: "Marry", 0: "Ming"}
	fmt.Println("strArr=", strArr)
	for i := 0; i < len(strArr); i++ {
		fmt.Printf("strArr[%d]=%s\n", i, strArr[i])
	}

Output:
strArr= [Ming Tom Marry]
strArr[0]=Ming
strArr[1]=Tom
strArr[2]=Marry

2.for range 方法:for index,value := range array{}
说明:
1) 第一个的返回值index是数组的下标
2)第二个value是在该下标位置的值
3)index、value都是for语句块内的局部变量
4)遍历数组元素的时候若不想使用index返回值,使用忽略标识符_
5)index 和value名词不固定,但推荐使用这两个

数组使用事项及注意细节
1.数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化。
2.var arr []int,这时的arr是一个slice切片。在Go中,数组定义时要求定义其长度(除了一维时可以使用[...]不明确表示)。
3.数组中的元素可以是任何数据类型,包括值类型和引用类型,但不能混合使用。
4.数组创建后,若没赋值操作,其有默认值:
数据类型数组默认值:0
字符串数组默认值:""
bool数组默认值:false
5.使用数组的步骤:1)声明数组并开辟空间;2)对数组进行赋值操作;3)使用数组,使用下标来访问数组中的元素。
6.数组的下标是从0开始的,下标必须在指定范围内使用,否则报panic,数组越界。例如var arr [5]int有效下标为0-4,且不支持使用负数倒取
7.Go的数组属于值类型,在默认情况下是值传递,因此会进行值拷贝。数组间不会相互影响

func test(arr [3]int) { //定义时不可用[...]int 必须的明确指定长度
	arr[0] = 15
}

func main() {
	//Go的数组属于**值类型**,在默认情况下是值传递,因此会进行值拷贝。
	// **数组间不会相互影响**。
	arr := [...]int{0: 11, 1: 12, 2: 13}
	test(arr)// [11 12 13]
	fmt.Println(arr)
}

Output:
[11 12 13]

  1. 若想在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)
func test01(arr *[3]int) { // 数值指针
	(*arr)[0] = 15 //必须使用()
}
func main() {
	arr := [...]int{0: 11, 1: 12, 2: 13}
	test01(&arr)
	fmt.Println(arr)
}

Output:
[1512 13]

Marking:若是想改编数组元素的内容优先使用指针(地址)传递,数据量一般也会较小,效率更高,速度快。
9. 长度是数组的一部分,在传递函数参数时,需要考虑数组的长度。

这里是引用

切片slice

切片的基本介绍
1.切片slice,是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递机制;
2.切片的使用和数组类似,遍历切片、访问切片的元素和求切的长度len(slice)都相同
3.切片的长度是可以变化的,因此切片可以理解成是一个动态变化数组
4.切片定义的基本语法:var 切片名 []类型,例如var a []int
快速入门案例

func main() {
	//演示切片的基本使用
	var intArr = [5]int{1, 2, 5, 15, 22}
	//声明/定义一个切片
	intSlice := intArr[1:3]
	//intSlice 为切片名;
	// intArr[1:3],表示intSlice引用到intArr这个数组从下标1到下标(3-1)的元素
	fmt.Println("intArr=", intArr)
	fmt.Println("intSlice的元素为:", intSlice)      //[2 5]
	fmt.Println("intSlice的长度为:", len(intSlice)) // 2
	fmt.Println("intSlice的容量为:", cap(intSlice)) //切片的容量是可以动态变化的,一般都会比长度大一些,增加了富余度
}

Output
intArr= [1 2 5 15 22]
intSlice的元素为: [2 5]
intSlice的长度为: 2
intSlice的容量为: 4

切片的内存布局
结合上述案例
在这里插入图片描述
1.slice本质上是一个引用类型,改变其值,会改变其切片的数组对象的值

intSlice[0] = 111
fmt.Println(“intArr=”, intArr) // [1 111 5 15 22]
fmt.Println(“intSlice的元素为:”, intSlice) //[111 5]

2.slice从底层分析,其是一个数据结构体(struct 结构体)
type slices struct{ptr len cap }.第一部分ptr:指向底层数组的指针;第二部分len:切片的大小;第三部分cap:切片的容量。

切片的使用
1.定义一个切片,使用切片引用一个创建好的数组。
2.使用内置函数make创建切片,var 切片名 []type = make([]type,len,cap).cap选写,若明确责需要cap>=len

func make(Type, size IntegerType) Type
内建函数make分配并初始化一个类型为切片、映射、或通道的对象。其第一个实参为类型,而非值。make的返回类型与其参数相同,而非指向它的指针。

	var floatSlice []float64 = make([]float64, 5)
	floatSlice[0] = 15
	fmt.Println(floatSlice)

在这里插入图片描述
noting: 如果没对切片赋值,那么就会使用默认值[值类型int、float系列为0,string为"",bool为false];通过make创建的切片对应的数组是由make底层维护的,对外不可见,即只能通过slice切片访问各个元素。

3.结合匿名数组,定义切片时,直接指定具体的数组,使用原理类似make的方式。
4.利用已有切片创建切片
5.切片的复制,使用内置函数copy,在已有切片的基础上创建新的切片,该切片不会响应已有切片所对应数组。

    // 3、4、5
	//3使用匿名数组定义切片
	strSlice := []string{"tom", "marry", "jack"}
	fmt.Println("strSlice=", strSlice)
	fmt.Println("strSlice len=", len(strSlice)) // 3
	//4利用已有切片创建切片
	strSlice1 := strSlice[1:]
	fmt.Println("strSlice1=", strSlice1) // strSlice1= [marry jack]

	fmt.Println()
	strSlice1[0] = "Alex"
	fmt.Println("strSlice=", strSlice)   //strSlice= [tom Alex jack]
	fmt.Println("strSlice1=", strSlice1) //strSlice1= [Alex jack]
	//5 要求新建切片不改变原来切片对应的数组值,使用内置函数copy,创建新切片的方法,
	fmt.Println()
	strSlice2 := make([]string, len(strSlice))
	copy(strSlice2, strSlice)
	fmt.Println(strSlice2) // [tom Alex jack]
	strSlice2[1] = "Alice"
	fmt.Println("strSlice2=", strSlice2) //strSlice2= [tom Alice jack]
	fmt.Println("strSlice=", strSlice)   //strSlice= [tom Alex jack]

}

切片的遍历
1.传统 for+len方法
2.for-range方法

package main

import "fmt"

func main() {
	var slice []int = []int{1, 2, 3, 4}
	//使用常规的for循环遍历
	for i := 0; i < len(slice); i++ {
		fmt.Printf("slice[%v]=%v\n", i, slice[i])
	}

	//使用for-range循环遍历
	for index, value := range slice {
		fmt.Printf("index=%v,value=%v\n", index, value)
	}
}

Output
slice[0]=1
slice[1]=2
slice[2]=3
slice[3]=4
index=0,value=1
index=1,value=2
index=2,value=3
index=3,value=4

切片的使用细节
1.slice := arr[0:end]可以简写为slice:=arr[:end]
2.slice := arr[start:len(arr)]可以简写为slice:=arr[start:]
3.slice := arr[0:len(arr)]可以简写为slice:=arr[:]
4.切片定义后不能直接使用,因为本身是一个空集,需要引用到数组,或者make一个空间供切片使用
5.切片可以继续切片
6.使用内置函数append,对切片进行动态值追加

//使用内置函数append,对切片进行动态追加
func main() {
	slice := []int{1, 2, 3, 4}
	fmt.Println(slice) // [1 2 3 4]

	slice = append(slice, 5, 6, 7)
	fmt.Println(slice) // [1 2 3  4 5 6 7]
	
	fmt.Println()
	//当slice切片动态增加后,其指向的数组不再是原来的数组,而是一个新建的数组,
	// 对扩容后的切片进行元素修改,不会响应原先的对应数组
	var arr [3]int = [...]int{1, 2, 3}
	slice1 := arr[:]
	fmt.Printf("arr的地址=%p,arr[0]的地址=%p\n", &arr, &arr[0])
	//arr的地址=0xc0000ae090,arr[0]的地址=0xc0000ae090
	fmt.Println(slice1) //[1 2 3]
	fmt.Printf("slice1的地址=%p,slice[0]的地址=%p\n", &slice1, &slice1[0])
	//slice1的地址=0xc000096090,slice[0]的地址=0xc0000ae090

	//数组的地址等于其元素首地址;切片的元素地址为其对应的数组元素的地址;切片地址不等于其元素首地址

	//使用append,切片容量动态增加后,其元素地址不再对应原先数组元素的地址,而是新建的数组地址
	//该数组为底层数组,只能使用slice访问
	slice1 = append(slice1, slice...)
	fmt.Println(slice1) //[1 2 3 1 2 3 4 5 6 7]
	fmt.Printf("slice1的地址=%p,slice[0]的地址=%p\n", &slice1, &slice1[0])
	//slice1的地址=0xc000096090,slice[0]的地址=0xc0000b00f0

	// 对扩容后的切片进行元素修改,不会响应原先的对应数组
	fmt.Println()
	fmt.Println(arr) // [1 2 3]
	slice1[0] = 10
	fmt.Println(arr)    //[1 2 3]
	fmt.Println(slice1) //[10 2 3 1 2 3 4 5 6 7]

}

切片append操作的底层原理分析:

  • append操作本质上是对数组扩容
  • 已建数组实际上无法改变大小,append操作会在底层创建一新的数组newArr(其长度按照扩容的大小)
  • slice原来所包含的元素拷贝到新的数组newArr,新增的元素随着添加到newArr
  • slice再重新引用到数组newArr
  • 需注意newArr是在底层维护的,只能通过slice访问
    7.切片的拷贝,使用copy方法:copy(slice01,slice),将slice拷贝给slice01

func copy(dst, src []Type) int
内建函数copy将元素从来源切片复制到目标切片中,也能将字节从字符串复制到字节切片中。copy返回被复制的元素数量,它会是 len(src) 和 len(dst) 中较小的那个。来源和目标的底层内存可以重叠。

8.slice切片是引用类型,所以在传递时,遵守引用传递机制。
案例:

func test(slice []int) {
	slice[0] = 100 //这里修改形参内容,会改变实参内容
}

func main() {
	var slice = []int{1, 2, 3}
	fmt.Println(slice) //[1,2,3]
	test(slice)
	fmt.Println(slice) //[100,2,3]
}

string和sliced的关系
1.string底层是一个byte数组,因string也可以进行切片处理。(用处:对字符串进行截取)

func main() {
	//string 底层是一个byte数组,因此也可以进行切片处理
	str := "hello@mmd.com"
	//使用切片类似方法获取到mmd.com
	slice := str[6:]
	fmt.Println(slice) //mmd.com
	// fmt.Printf("slice的容量=%v", cap(slice))  //报错,slice在此时实际上是string类型,没有cap值
}

依据str="abcd",画出使用类似slice 切片法所得新变量slice的内存分布情况如下:
在这里插入图片描述
noting:
1)slice := str[6:]实际上该变量slicestring类型,只有指针ptr和长度len内容。具备常规string类型的一些使用方法,例如通过下标访问元素;不具备slice切片改变形参同时改变实参的能力,string是不可变的
2)string类型本身可以看成一种特殊的切片,它具有指向底层[...]bytebyte类型数组的指针部分,和长度内容部分。常规的切片类型能够读取引用的底层数组元素地址;但string类型由于引用的[...]bytebyte类型数组位于底层维护,故无法读取string类型引用的byte类型数组的元素地址。

2.已知string类型是不可变的,若想改变字符串的内容,使用类似slice切片的方法,则需要先将string类型转换为[]byte类型切片或 []rune切片(针对有汉字时)->修改内容->重写成string。(不如直接使用系统提供的strings.func…方法)

	slice2 := []rune(str)
	slice2[0] = '北'
	str = string(slice2)
	fmt.Println("str=", str)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值