Golang入门笔记(15)—— 数组和切片
创始人
2024-02-01 13:31:40
0

        编程的世界中,或许是因为一次一次的定义变量,维护管理起来都太费劲了,所以推出了数组,将数据用数组的形式管理起来。

        Go的数组和Java的实现机制是不同的,Go语言的数组是作为基本数据类型存在的。所以数组是开辟在栈帧中开辟内存的;在函数参数传递的时候是,也是当作基本数据类型,进行了值传递。

  •        数组

        先不讲那么多七七八八的理论,来段代码直观感受一下Go语言的数组:

package mainimport "fmt"func main() {var scores [5]intscores[0] = 90scores[1] = 91scores[2] = 92scores[3] = 93scores[4] = 94//求和,求平均sum := 0for i := 0; i < len(scores); i++ {sum += scores[i]}fmt.Println(sum)fmt.Println(sum / len(scores))fmt.Printf("scores 的地址,即第一个空间对应的地址 , %p \n", &scores)println("-------------- 16进制,每次大小间隔为8 ------------------")fmt.Printf("scores 第0个空间对应的地址 , %p \n", &scores[0])//scores 第0个空间对应的地址 , 0xc000010450fmt.Printf("scores 第1个空间对应的地址 , %p \n", &scores[1])//scores 第1个空间对应的地址 , 0xc000010458fmt.Printf("scores 第2个空间对应的地址 , %p \n", &scores[2])//scores 第2个空间对应的地址 , 0xc000010460fmt.Printf("scores 第3个空间对应的地址 , %p \n", &scores[3])//scores 第3个空间对应的地址 , 0xc000010468fmt.Printf("scores 第4个空间对应的地址 , %p \n", &scores[4])//scores 第4个空间对应的地址 , 0xc000010470
}

具体分析:

        16进制数,数组位置,每次大小偏移都是间隔为8,这个8就是每个元素的空间大小。         

关于更多关于数组的认知,可参考之前写的一篇帖子:CSDN

        下面做一个小demo:

package mainimport "fmt"func main() {//已知,班级中有10位同学,录入各同学成绩,计算得出总分和平均分。var scores [10]int//求和,求平均sum := 0for i := 0; i < len(scores); i++ {fmt.Printf("\n 请录入第%v个学生的成绩 \n", i+1)fmt.Scanln(&scores[i]) // 录入,改变数组中元素i的值,通过 & 绑定到哪个地址。sum += scores[i]}fmt.Println("总分:", sum)fmt.Println("平均分", sum/len(scores))}

        数据遍历方式,除了使用上面代码中的以索引下标进行普通遍历外,还可以使用for range 遍历,for range 可以遍历数组,切片,字符串,map 以及通道,for range语法上类似于其他编程语法的foreach语句,形式如下:

for key,val:= range collection{...
}

           代码如下:

package mainfunc main() {var scores [5]intscores[0] = 90scores[1] = 91scores[2] = 92for key, value := range scores {//key,value 属于当次循环的局部变量。println("[", key, "] ==> ", value)}for key, value := range scores {//key,value 属于当次循环的局部变量。println("[", key, "] ==> ", value)}// 如果用不到key,使用 下划线 “_” 可以忽略。for _, value := range scores {println( "==> ", value)}
}

     key,value 属于当次循环中的局部变量。

数组的初始化方式:

第一种:var 类型自己推断。

	var scores = [5] int {1,3,5,7,9}

第二种:事先不知道开辟多大空间。

	var scores = [...]int{1, 3, 5, 7, 9}

第三种:根据下标逐一赋值

	var scores = [...]int{0: 70, 2: 30, 3: 99, 4: 100}

数组的注意事项:

1.数组的长度属于类型的一部分,相当于动态数组的类属性。

	var a = [...]int{0: 70, 2: 30, 3: 99, 4: 100}fmt.Printf(" 数组类型:%T", a) // [5]int

2. Java中数组属于引用类型,但是在Go语言中,数组属于值类型的,默认情况下,按值传递,因此会进行值拷贝。如果想在其他函数中,去修改原来的数组,可以通过指针的方式进行引用传递。


import "fmt"func main() {var a = [...]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4}modify(a)fmt.Println(a)modifyPlus(&a) //传入地址fmt.Println(a)}func modify(arr [5]int) {  arr[0] = -999
}func modifyPlus(arr *[5]int) { // star在类型Type上,means This‘s pointer the address (ptr)(*arr)[0] = -999 // 
}

        分析:当goroutine(轻量的go线程) 的 active-frame活动栈帧  从main函数 运行 到modifyPlus 函数的 栈帧时,通过 *pointerTheAddress变量ptr的方式,进行引用传递的方式,完成了对该内存数据的重新赋值;而modify 是默认的值传递,是值拷贝,操作不是同一块内存。

二维数组 Two Dimensional Array (注音:dimensional大爱闷声捞 恶ray )

        定义二维数组  var twoDimensionalArr  [2][3] int16  

        2个3长度的数组,默认初始值 [0,0,0],[0,0,0] ,内存分配,如下图:

var arr [2][3] int16 内存分配 示意图

        Anyhow无论 它是二维也好,N维也罢,在内存中都是一个连续的内存空间,就是在连续的数组中不断的拆分内存。 二维 [2][3] 即 开辟2*3 =6个类型大小。三维[2][3][4],即2*3*4=24 个类型大小。

package mainimport ("fmt"
)func main() {var a [2][3]inta[0][1] = 22fmt.Printf("\n\t a ↓ \n  %v \n", a) //  [ [0 22 0]  [0 0 0] ]var init2dArray [2][3]int = [2][3]int{{1, 2, 3}, {2, 3, 4}}fmt.Printf("二维数组的初始化操作 ==>  %v \n", init2dArray)fmt.Println("--------- 遍历方式1 普通for循环 ----------")for i := 0; i < len(init2dArray); i++ {for j := 0; j < len(init2dArray[i]); j++ {fmt.Println(init2dArray[i][j])}}fmt.Println("--------- 遍历方式2 for range循环 ----------")for outerKey, outerValue := range init2dArray {//fmt.Println(outerKey, outerValue)for innerKey, innerValue := range outerValue {fmt.Printf("arr[%v][%v]=[%v] \t", outerKey, innerKey, innerValue)}println()}}

        同样,Go和Java一样,都支持多维数组:

package mainimport ("fmt"
)func main() {var a [2][3][4][5]intfmt.Printf("a ==>\n  %v ", a)
}
  •         切片

        切片可以算是对数组的扩展,数组的长度是固定的,所以在Go语言的代码的出现频率不高,而切片是一种建立在数组的基础上做了一层封装,提供了更加强大的功能。正因为如此,所以切片是一种引用类型。切片slice 是对数组一个连续片段的引用,所以切片是一个引用类型,对切片索引的操作,可以改变原来的数组索引上的元素;具体形式:[start:end],这个片段是由起始切点偏移索引和终止切点偏移索引构成,可以是整个数组,或者是部分数组,但是注意一点:切点偏移索引不能越界,切片感觉类似于Java的封装的ArrayList动态数组。

        切片三个要素之我的理解:切片的地址(首元素的地址?),实际元素大小size 和 capacity总的容量;

内存中 原数组 intArr 和切片 示意图

         

        切片定义的三种方式:

方式1:定义一个切片,然后让切片去 切(引用)一个以及创建好的数组。

	var strings []string = []string{"a", "b", "c", "d", "e", "f", "g", "h"}var stringSlice []stringstringSlice = strings[2:6]

方式2:通过内置的make函数来创建切片。

基本语法:var 切片变量名 [] T类型 = make([]T类型,size,capacity);

         make([]T, size, cap)  底层数组的指针、切片的大小( len )和切片的容量(cap)。

	fmt.Println("-----------  make([]T, size, cap) --------------")var bridgeSlice []int = make([]int, 2, 10) fmt.Println(len(bridgeSlice))              // len表示切片存储元素的长度fmt.Println(cap(bridgeSlice))              // cap表示最大可以存储的容量//ps 底层数组对外不可见,不能直接操作数组,通过操作slice切片变量桥接到数组。bridgeSlice[0] = 0bridgeSlice[1] = 1

        点评:底层数组对外不可见,不能直接操作数组,通过操作slice切片变量桥接到数组。

方式3:定义一个切片,直接就指定具体的数组,使用原理类似make方式。

	sliceDefine3 := []string{"a", "b", "c"}fmt.Println(len(sliceDefine3)) // len表示切片存储元素的长度fmt.Println(cap(sliceDefine3)) // cap表示最大可以存储的容量

        点评:质疑一下,我感觉和数组没啥区别呀,连实际元素len和capacity 都一样。不过,学习阶段,被动接受吧,别人说是啥,那就是啥。就算错了,以后再改了这段文字就好了。不追求完美,追求完美本身就是不完美。

参考代码:

	fmt.Println("----------- make函数  make([]T, size, cap) --------------")var bridgeSlice []int = make([]int, 2, 10) //底层数组的指针、切片的大小( len )和切片的容量(cap)。fmt.Println(len(bridgeSlice))              // len表示切片存储元素的长度fmt.Println(cap(bridgeSlice))              // cap表示最大可以存储的容量//ps 对外不可见,不能直接操作数组,通过操作slice切片变量桥接到数组。bridgeSlice[0] = 0bridgeSlice[1] = 1

切片的遍历:和数组遍历方式一样,普通for循环和for...range循环都可以。代码略,参考上面数组遍历。

切片的注意事项:

1,切片定义后,不能直接使用,需要让其引用到一个数组 或 make一个空间供切片使用。

2.切片不能越界。

3.切片可以继续切片。

4.切片的capacity 可以动态增长,容量不够,底层会创建一个新数组,然后指向新数组。该新数组不能直接维护,通过切片间接维护。如果你要想给原来切片append函数追加给原来的slice,并赋值给slice。

slice = append(slice,1,2,3)

5.切片的拷贝。copy函数,第一个参数: destination目标,第二个参数:source 源。

fmt.Println("----------- copy函数 --------------")var x1 []int = []int{1, 2, 3, 4, 5}var x2 []int = make([]int, 10)copy(x2, x1)fmt.Println(x2)

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...