> 文章列表 > Go语言精修(尚硅谷笔记)第七章

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

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

七、数组切片

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

7.1 数组的定义

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

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

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

对上图的总结:

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] = 100fmt.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] = 100fmt.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, 33fmt.Println("slice 的元素个数 =", len(slice)) // 2fmt.Println("slice 的容量 =", cap(slice)) //4 切片的容量是可以动态变化  
}

7.7 切片的内存形式

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

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

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

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

type slice struct{ptr *[ 2 ]intlen intcap int
}

7.8 切片的使用

  • 方式 1

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

  • 方式 2

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

基本语法:

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

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

案例演示:

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

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

对上面代码的小结:

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

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

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

  • 方式 3

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

案例演示:

package mainimport ("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 mainimport ("fmt"
)func main() {//使用常规的for循环遍历切片var arr [5]int = [...]int{10, 20, 30, 40, 50}//slice := arr[1:4] // 20, 30, 40slice := 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追加给slice3slice3 = append(slice3, slice3...) // 100, 200, 300,400, 500, 600 100, 200, 300,400, 500, 600fmt.Println("slice3", slice3)}

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

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

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

5)切片的拷贝操作

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

package mainimport ("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, 5fmt.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 mainimport ("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 mainimport ("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)
}