【Golang】第六弹----数组与切片

发布于:2025-03-19 ⋅ 阅读:(10) ⋅ 点赞:(0)

 笔上得来终觉浅,绝知此事要躬行

🔥 个人主页:星云爱编程

🔥 所属专栏:Golang

🌷追光的人,终会万丈光芒  

 🎉欢迎大家点赞👍评论📝收藏⭐文章

  

目录

一、数组

1.1 基本介绍

1.2 数组的定义

1.3 数组内存布局

1.4 数组的使用

1.5 数组使用细节

二、切片

2.1 基本介绍

2.2 切片内存机制

2.3 切片的使用

2.4切片的遍历

2.5切片的使用细节

2.6string和slice

结语


一、数组

1.1 基本介绍

数组可以存放多个同一类型的数据。数组也是一种数据类型,在 Go 中,数组是值类型

1.2 数组的定义

var 数组名 [数组大小]数据类型

例如:

var arr [3]int

1.3 数组内存布局

数组在内存中的布局如下,以下列代码为例

func main(){
    var arr [3]int
    //当我们定义完数组后,数组的各个元素有默认值0
    fmt.Printf("数组arr的地址%p\n",&arr)
    for i:=0;i<len(arr);i++{
        fmt.Printf("arr[%d]=%d,地址为%p\n",i,arr[i],&arr[i])
    }
}

 

可以得到的结论:

  • 数组的地址可以通过数组名来获取:&数组名

  • 数组首元素地址等于数组的地址

  • 数组的各个元素的地址间隔是依据数组的类型决定,例如:int64-> 8, int32-> 4

1.4 数组的使用

  1. 访问数组元素

    数组名 [下标]

    例如:arr [0]

  2. 初始化数组

    //数组的四种初始化
    func main(){
        //(1)普通初始化
        var arr01 [3]int=[3]int{1,2,3}
        fmt.Println("arr01=",arr01)
    ​
        //(2)省略声明类型
        var arr02 = [3]int{4,5,7}
        fmt.Println("arr02=",arr02)
    ​
        //(3)[...]不指定数组元素个数,但...为固定格式,不能更改
        var arr03 = [...]int{4,2,6,1}
        fmt.Println("arr03=",arr03)
    ​
        //(4)指定位置初始化,按数组下标来指定初始化,没被初始化的默认为0
        var arr04 = [...]int{ 3:3,2:2,0:10}
        fmt.Println("arr04=",arr04)
    }

3.数组的遍历

方式一:常规遍历

func main(){
    var arr=[...]int{1,4,6,7,8}
    //遍历数组
    for i:=0;i<len(arr);i++{
        fmt.Println(arr[i])
    }
}

方式二:for-range 结构遍历

基本语法:

for index,value:=range arr{
...
}

说明:

  • 第一个返回值 index 是数组的下标

  • 第二个 value 是 index 下标对应的值

  • index 和 value 是在 for 循环内部的局部变量

  • 遍历数组元素的时候,若不想用下标 index,可以直接把下标 index 标位下划线_

  • index 和 value 的名称是用户自定义的,一般命名为 index 和 value

案例

func main(){
    var arr=[...]int{1,4,6,7,8}
    //遍历数组
    for index,value:=range arr{
        fmt.Printf("arr[%d]=%d\n",index,value)
    }
    var arr2=[...]string{"hello","baby","xinxin"}
    //遍历数组
    for i ,v:=range arr2{
        fmt.Printf("arr2[%d]=%v\n",i,v)
    }
​
}

1.5 数组使用细节

  • 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化

  • var arr [] int 这时 arr 就是一个 slice 切片,切片下一节就讲

  • 数组中的元素可以是任何数据,包括值类型和引用类型,但是不能混用

  • 数组的下标是从 0 开始的, 下标必须在指定范围内使用,否则 panic:数组越界

  • 数组创建后,若没有赋值,有默认值,数值类型默认为 0,字符串类型默认为“”,bool 类型默认为 false

  • Go 的数组属于值类型,在默认情况下是值传递,因此会进行值拷贝,数组间不会相互影响

  • 长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度

二、切片

2.1 基本介绍

  1. 切片的英文是 slice

  2. 切片是数组的一个引用类型,在进行值传递时,遵守引用传递的机制

  3. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度都一样

  4. 切片的长度是可以变化的,因为切片是一个可以动态变化的数组

切片定义的基本语法:

var name []int

例子

func main(){
	var arr=[...]int{1,3,5,7,9}
	//声明定义一个切片
	slice:=arr[1:3]
	//slice:=arr[1:3]
	//解读:
	//(1)slice就是切片名
	// (2)arr[1:3]表示slice引用到arr这个数组
	// (3)引用arr数组的起始下标为1,最后的下标为3,但是不包含3,即[1:3)
	fmt.Println("arr=",arr)
	fmt.Println("slice的元素=",slice)
	fmt.Println("slice的元素个数=",len(slice))
	fmt.Println("slice的容量=",cap(slice))//切片的容量是可以动态变化的,大小一般为元素个数的两倍
}

2.2 切片内存机制

  • slice 是一个引用类型

  • slice 从底层来说,其实就是一个数据结构(struct 结构体)

    type slice struct{
    	ptr*[2]int
    	len int
    	cap int
    }
  1. 总结

    代码示例

func main(){
	var arr=[...]int{1,3,5,7,9}
	//声明定义一个切片
	slice:=arr[1:3]
	//slice:=arr[1:3]
	//解读:
	//(1)slice就是切片名
	// (2)arr[1:3]表示slice引用到arr这个数组
	// (3)引用arr数组的起始下标为1,最后的下标为3,但是不包含3,即[1:3)
	fmt.Println("arr=",arr)
	fmt.Println("slice的元素=",slice)
	fmt.Println("slice的元素个数=",len(slice))
	fmt.Println("slice的容量=",cap(slice))//切片的容量是可以动态变化的,大小一般为元素个数的两倍

	fmt.Println("arr[1]的地址",&arr[1])
	fmt.Println("slice的地址",&slice[0])
	
}

2.3 切片的使用

方式一:定义一个切片,然后让切片去引用一个已经创建好的数组

方式二:通过 make 来创建切片

基本语法:

var 切片名 []type=make([]type,len,[cap])

说明:type 为数据类型

len 为切片大小

cap 指定切片容量

案例:

//演示make创建切片
func main(){
	var slice []int=make([]int,10,20)
	slice[0]=1
	slice[2]=3
	slice[4]=5
	slice[6]=7
	slice[8]=9
	for index,value:=range slice{
		fmt.Printf("slice[%d]=%d\n",index,value)
	}
}

小结:

  • 通过 make 方式创建切片可以指定切片的大小和容量

  • 如果没有给切片的各个元素赋值,那么就会使用默认值(机制同数组)

  • 通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去访问各个元素

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

案例演示:

func main(){
	var slice []string=[]string{"fake","make","right","evote"}
	fmt.Println("slice=",slice)
	fmt.Println("slice size=",len(slice))
	fmt.Println("slice cap=",cap(slice))
}

面试题:方式一和方式二的区别

方式一是直接引用数组,这个数组是事先存在的,程序员可见的

方式二是通过make来创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员是看不见的

2.4切片的遍历

切片的遍历与数组一样,有2种方式

(1)for循环常规遍历

func main(){
	var slice []int=make([]int,5,10)
	slice[0]=1
	slice[2]=3
	slice[4]=5
	for i:=0;i<len(slice);i++{
		fmt.Printf("slice[%d]=%d\n",i,slice[i])
	}
}

(2)for-range 结构遍历

func main(){
	var slice []int=make([]int,5,10)
	slice[0]=1
	slice[2]=3
	slice[4]=5
	for index,value:=range slice{
		fmt.Printf("slice[%d]=%d\n",index,value)
	}
}

2.5切片的使用细节

  1. 切片初始化时 var slice=arr[startIndex:endIndex]

    说明:从arr数组下标为startIndex,取到下标为endIndex的元素(不含arr[ednIndex]),即取arr数组中[startIndex,endIndex)的元素

  2. 切片初始化时,仍然不能越界,范围在[0~len(arr)]之间,但是可以动态增长

    一些引用的简写:

    (1)var slice =arr[0:end]可以简写为var slice=arr[:end]

    (2)var slice =arr[start:len(arr)]可以简写为 var slice= arr [start:]

    (3)var slice =arr[0,len(arr)]可以简写为 var slice =arr[:]

  3. cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素

  4. 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组或make一个空间供切片来使用

  5. 切片可以继续切片

  6. 用append内置函数,可以对切片进行动态追加

    (1)切片append操作的本质就是对数组扩容

    (2)Go底层会创建新的数组newarr(安装扩容后大小)

    (3)将slice原来包含的元素拷贝到新的数组newarr

    (4)slice重新引用newarr

    (5)newarr是在底层维护的,程序员不可见

    案例:

    func main(){
    	var slice []int=[]int{23,44,55,77,88}
    	//用append内置函数,对切片进行动态追加
    	slice=append(slice,100,200)
    	fmt.Println("slice=",slice)
    
    	//通过append将切片slice追加给slice,相当于复制一遍
    	slice=append(slice,slice...)
    	fmt.Println("slice=",slice)
    
    }

  7. 切片的拷贝操作

    切片使用copy内置函数完成拷贝,例如:

//拷贝操作
func main(){
	var slice []int=[]int{23,44,55,77,88}
	var slice2 =make([]int,10)
	copy(slice2,slice)//将slice拷贝给slice2
	fmt.Println("slice=",slice)
	fmt.Println("slice2=",slice2)

}

注意事项:

  • copy(para1,para2)参数的数据类型是切片

  • 上例中,slice和slice2的数据空间是独立的,互不影响

思考题:下面的代码有没有错误?

func main(){
	var a[]int =[]int{1,2,3,4,5}
	var slice =make([]int,1)
	fmt.Println(slice)//1
	copy(slice,a)
	fmt.Println(slice)//1
}

不会报错

2.6string和slice

  1. string底层是一个byte数组,因此string也可以进行切片处理

  2. string是不可变的,也就是说不能通过str[0]='a'方式来修改字符串

  3. 若要修改字符串,可以先将string转为[]byte或[]rune,修改后,重写转成string

案例

func main(){
	str:="helloworld"
	//string底层是一个byte数组,因此string也可以进行切片处理str := "helloworld"
	//使用切片得到world
	slice:=str[5:]
	fmt.Println("slice=",slice)
	//string是不可变的,也就说不能通过 str[0]='z’方式来修改字符串
	//str[0]='z'[编译不会通过,报错,原因是string是不可变]

	
	//如果需要修改字符串,可以先将string->[]byte /或者[]rune ->修改->重写转成string
	//将helloworld改为aelloworld
	arr:=[]byte(str)
	arr[0]='a'
	str=string(arr)
	fmt.Println("str=",str)
	//若是我们想把helloworld改为 哇elloworld,则不能用[]byte
	//我们转成[]byte后,可以处理英文和数字,但是不能处理中文
	//原因是[]byte 字节来处理 ,而一个汉字,是3个字节,因此就会出现乱码

	// 解决方法是 将 string 转成[]rune 即可, 因为[]rune是按字符处理,兼容汉字
	arr2:=[]rune(str)
	arr2[0]='哇'
	str=string(arr2)
	fmt.Println("str=",str)

}

结语

感谢您的耐心阅读,希望这篇博客能够为您带来新的视角和启发。如果您觉得内容有价值,不妨动动手指,给个赞👍,让更多的朋友看到。同时,点击关注🔔,不错过我们的每一次精彩分享。若想随时回顾这些知识点,别忘了收藏⭐,让知识触手可及。您的支持是我们前进的动力,期待与您在下一次分享中相遇!

路漫漫其修远兮,吾将上下而求索。


网站公告

今日签到

点亮在社区的每一天
去签到