数组
介绍
数组可以存放多个同一类型数据。数值本身也是一种数据类型,在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]
- 若想在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)
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,新增的元素随着添加到newArrslice
再重新引用到数组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:]
实际上该变量slice
是string
类型,只有指针ptr
和长度len
内容。具备常规string
类型的一些使用方法,例如通过下标访问元素;不具备slice
切片改变形参同时改变实参的能力,故string
是不可变的。
2)string
类型本身可以看成一种特殊的切片,它具有指向底层[...]byte
byte类型数组的指针部分,和长度内容部分。常规的切片类型能够读取引用的底层数组元素地址;但string类型由于引用的[...]byte
byte类型数组位于底层维护,故无法读取string
类型引用的byte类型数组的元素地址。
2.已知string
类型是不可变的,若想改变字符串的内容,使用类似slice
切片的方法,则需要先将string
类型转换为[]byte
类型切片或 []rune
切片(针对有汉字时)->修改内容->重写成string
。(不如直接使用系统提供的strings.func…方法)
slice2 := []rune(str)
slice2[0] = '北'
str = string(slice2)
fmt.Println("str=", str)