Go语言精修(尚硅谷笔记)第七章

文章详细介绍了Go语言中数组和切片的概念及使用,包括数组的定义、初始化、内存布局、遍历以及注意事项。切片作为数组的引用类型,具有动态调整长度的特性,文中通过不同方式展示了切片的创建和使用,包括通过数组引用和`make`函数。此外,还提到了字符串与切片的关系,强调了字符串的不可变性以及如何通过转换进行修改。
摘要由CSDN通过智能技术生成

七、数组和切片

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

7.1 数组的定义

var 数组名 [数组大小]数据类型
var a [5]int// 数组名 [长度]数据类型
赋初值 a[0]= 1 a[1]= 30 .

7.2 数组在内存布局(*)数组地址连续

image-20210114135801066

对上图的总结:

1 ) 数组的地址可以通过数组名来获取 &intArr

2 ) 数组的第一个元素的地址,就是数组的首地址

3 ) 数组的各个元素的地址间隔是依据数组的类型决定,比如int 64 - > 8 int 32 - > 4

7.3 初始化数组的方式

package main
import (
	"fmt"
)

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

	var numArr02 = [3]int{5, 6, 7}
	fmt.Println("numArr02=", numArr02)
	//这里的 [...] 是规定的写法由go推导数组大小
	var numArr03 = [...]int{8, 9, 10}
	fmt.Println("numArr03=", numArr03)

	var numArr04 = [...]int{1: 800, 0: 900, 2:999}
	fmt.Println("numArr04=", numArr04)
    
    f := [...] int{0: 1, 4: 1, 9: 1} // [1 0 0 0 1 0 0 0 0 1]
  	fmt.Println(f)

    e := [5] int{4: 100} // [0 0 0 0 100]
    fmt.Println(e)
    
	//类型推导
	strArr05 := [...]string{1: "tom", 0: "jack", 2:"mary"}
	fmt.Println("strArr05=", strArr05)
}

7.4 数组遍历

1)方式1:for(;;;)遍历数组

2)方式2:for-range结构遍历

for index,value :=range array01{

}

1.index数组的下标

2.value该下标对应的值

3.他们都是for循环内可见的局部变量

4.如果不想使用下标index,可以替换为"_"

5.index和value的名称不是固定的。可以自己改变

for-range的案例

package main
import (
	"fmt"
)

func main() {

	//演示for-range遍历数组
	 heroes  := [...]string{"宋江", "吴用", "卢俊义"}
	//使用常规的方式遍历,我不写了..

	for i, v := range heroes {
		fmt.Printf("i=%v v=%v\n", i , v)
		fmt.Printf("heroes[%d]=%v\n", i, heroes[i])
	}

	for _, v := range heroes {
		fmt.Printf("元素的值=%v\n", v)
	}
}

7.5 数组使用注意事项

1 ) 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化。否则报越界

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

3 ) 数组创建后,如果没有赋值,有默认值(零值)

  • 数值类型数组:默认值为 0
  • 字符串数组: 默认值为 “”
  • bool数组: 默认值为 false

5 ) 使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值(默认零值) 3 使用数组

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

7 ) 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)

package main
import (
	"fmt"
)

//函数
func test02(arr *[3]int) {
	fmt.Printf("arr指针的地址=%p", &arr)
	(*arr)[0] = 88 //!!
} 


func main() {	
	arr := [3]int{11, 22, 33}
	fmt.Printf("arr 的地址=%p", &arr)
	test02(&arr)
	fmt.Println("main arr=", arr)
}	

10 ) 长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度,看下面案例

//题1
package main
import (
	"fmt"
)

//默认值拷贝
func modify(arr []int) {
	arr[0] = 100
    fmt.Println("modify的arr",arr)
} 

func main() {	
	var arr = [...]int{1,2,3}
	modify(arr)
}	
//编译错误,因为不能把[3]int 传递给[]int
//题2
package main
import (
	"fmt"
)

//默认值拷贝
func modify(arr [4]int) {
	arr[0] = 100
    fmt.Println("modify的arr",arr)
} 

func main() {	
	var arr = [...]int{1,2,3}
	modify(arr)
}	
//编译错误,因为不能把[3]int 传递给[4]int

7.6 切片的定义

1 ) 切片的英文是slice

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

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

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

5 ) 切片定义的基本语法:

var 切片名 []类型
//比如:vara[]int
package main
import (
	"fmt"
)

func main() {
	//演示切片的基本使用
	var intArr [5]int = [...]int{1, 22, 33, 66, 99}
	//声明/定义一个切片
	//slice := intArr[1:3]
	//1. slice 就是切片名
	//2. intArr[1:3] 表示 slice 引用到intArr这个数组 
	//3. 引用intArr数组的起始下标为 1 , 最后的下标为3(但是不包含3)    
	slice := intArr[1:3] 
	fmt.Println("intArr=", intArr) //[1 22 33 66 99]
	fmt.Println("slice 的元素是 =", slice) //  22, 33
	fmt.Println("slice 的元素个数 =", len(slice)) // 2
	fmt.Println("slice 的容量 =", cap(slice)) //4 切片的容量是可以动态变化  
}

7.7 切片的内存形式

我们画图分析一下切片在内存中是如何布局的,这个是一个非常重要的知识点:(以前面的案例来分析)

image-20210114144926465

1 .slice的确是一个引用类型

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

type slice struct{
 ptr *[ 2 ]int
 len int
 cap int
}

7.8 切片的使用

  • 方式 1

第一种方式:定义一个切片,然后让切片去引用一个已经创建好的数组,比如前面的案例就是这样的。

  • 方式 2

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

基本语法:

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

参数说明:type: 就是数据类型 len: 大小 cap :指定切片容量,可选,如果你分配了 cap, 则要求 cap>=len.

案例演示:

package main

import (
	"fmt"
)

func main() {
	var slice []float64 = make([]float64, 5, 10)
	slice[1] = 10
	slice[3] = 20
	fmt.Println(slice)
	fmt.Println("slice的size=", len(slice))
	fmt.Println("slice的cap=", cap(slice))
}

image-20210114150445181

对上面代码的小结:

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

2 ) 如果没有给切片的各个元素赋值,那么就会使用默认值[int,float=> 0 string=>”” bool=> false]

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

  • 方式 3

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

案例演示:

package main

import (
	"fmt"
)

func main() {	
	var strSlice []string = []string{"tom", "jack", "mary"}
	fmt.Println("strSlice=", strSlice)
	fmt.Println("strSlice的size=", len(strSlice))
	fmt.Println("strSlice的cap=", cap(strSlice))
}

方式 1 和方式 2 的区别**(面试)**

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

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

7.9 切片使用注意事项

1)从数组引用切片规则左闭合右开,即

切片初始化时 varslice=arr[startIndex:endIndex]

从arr数组下标为startIndex,取到 下标为endIndex的元素(不含arr[endIndex])。

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

3 ) 切片可以继续切片

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

package main

import (
	"fmt"
)

func main() {

	//使用常规的for循环遍历切片
	var arr [5]int = [...]int{10, 20, 30, 40, 50}
	//slice := arr[1:4] // 20, 30, 40
	slice := arr[1:4]
	for i := 0; i < len(slice); i++ {
		fmt.Printf("slice[%v]=%v ", i, slice[i])
	}

	fmt.Println()
	//使用for--range 方式遍历切片
	for i, v := range slice {
		fmt.Printf("i=%v v=%v \n", i, v)
	}

	slice2 := slice[1:2] //  slice [ 20, 30, 40]    [30]
	slice2[0] = 100      // 因为arr , slice 和slice2 指向的数据空间是同一个,因此slice2[0]=100,其它的都变化

	fmt.Println("slice2=", slice2)
	fmt.Println("slice=", slice)
	fmt.Println("arr=", arr)

	fmt.Println()

	//用append内置函数,可以对切片进行动态追加
	var slice3 []int = []int{100, 200, 300}
	//通过append直接给slice3追加具体的元素
	slice3 = append(slice3, 400, 500, 600)
	fmt.Println("slice3", slice3) //100, 200, 300,400, 500, 600

	//通过append将切片slice3追加给slice3
	slice3 = append(slice3, slice3...) // 100, 200, 300,400, 500, 600 100, 200, 300,400, 500, 600
	fmt.Println("slice3", slice3)

}

image-20210114152259991

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

  • 切片append操作的本质就是对数组扩容
  • go底层会创建一下新的数组newArr(安装扩容后大小)
  • 将slice原来包含的元素拷贝到新的数组newArr
  • slice 重新引用到newArr
  • 注意newArr是在底层来维护的,程序员不可见.

5)切片的拷贝操作

切片使用copy内置函数完成拷贝,举例说明

package main

import (
	"fmt"
)

func main() {

	//切片的拷贝操作
	//切片使用copy内置函数完成拷贝,举例说明
	fmt.Println()
	var slice4 []int = []int{1, 2, 3, 4, 5}
	var slice5 = make([]int, 10)
	copy(slice5, slice4)
	fmt.Println("slice4=", slice4) // 1, 2, 3, 4, 5
	fmt.Println("slice5=", slice5) // 1, 2, 3, 4, 5, 0 , 0 ,0,0,0
}
  • ( 1 ) copy(para 1 ,para 2 ) 参数的数据类型是切片
  • ( 2 ) 按照上面的代码来看,slice 4 和slice 5 的数据空间是独立,相互不影响,也就是说 slice 4 [ 0 ]= 999 ,slice 5 [ 0 ] 仍然是 1,所以是值复制

7.10 string和slice

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

3 ) string是不可变的,也就说不能通过 str[ 0 ]=‘z’ 方式来修改字符串

//string是不可变的,也就说不能通过 str[0] = 'z' 方式来修改字符串 
str[0] = 'z' [编译不会通过,报错,原因是string是不可变]

4 ) 如果需要修改字符串,可以先将string->[]byte/ 或者 []rune-> 修改 -> 重写转成string

package main

import (
	"fmt"
)

func main() {

	//如果需要修改字符串,可以先将string -> []byte / 或者 []rune -> 修改 -> 重写转成string
	//"hello@atguigu" =>改成 "zello@atguigu"
	str := "hello@atguigu"
	arr1 := []byte(str)
	arr1[0] = 'z'
	str = string(arr1)
	fmt.Println("str=", str)
}
package main

import (
	"fmt"
)

func main() {

	//如果需要修改字符串,可以先将string -> []byte / 或者 []rune -> 修改 -> 重写转成string
	//"hello@atguigu" =>改成 "zello@atguigu"
	str := "hello@atguigu"

	// 细节,我们转成[]byte后,可以处理英文和数字,但是不能处理中文
	// 原因是 []byte 字节来处理 ,而一个汉字,是3个字节,因此就会出现乱码
	// 解决方法是 将  string 转成 []rune 即可, 因为 []rune是按字符处理,兼容汉字

	arr1 := []rune(str)
	arr1[0] = '北'
	str = string(arr1)
	fmt.Println("str=", str)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值