7 数组与切片
7.1 声明和初始化
var identifier [len]type
//e.g.
var array [10]int
注意:Go语言中的数组是一种值类型,可以通过new()来创建:var arr1 = new([5]int) 这种方式和var arr2 [5]int的区别是:
arr1类型是*[5]int,arr2的类型是[5]int。
在函数中数组作为参数传入时,如func1(arr2),会产生一次数组拷贝,func1方法不会修改原始的数组arr2。如果想修改原数组,那么arr2必须通过&操作符以引用方式传过来,例如func(&arr2)
func main(){
var ar [3]int
ffff(ar)
fp(&ar)
}
func ffff(a [3]int) {
fmt.Println(a)
}
func fp(a *[3]int) {
fmt.Println(a)
}
//[0 0 0]
//&[0 0 0]
例子:使用数组来实现返回前50个斐波那契数列的值
package main
import (
"fmt"
)
const NUM int = 49
var arr [NUM]int
func main() {
for i := 0; i < NUM; i++ {
result := llll4(i)
fmt.Printf("%d\n", result)
}
}
func llll4(a int) (res int) {
if arr[a] != 0 {
return arr[a]
} else {
if a <= 1 {
res = 1
} else {
res = llll4(a-1) + llll4(a-2)
}
arr[a] = res
return
}
}
7.1.1 数组常量
定义并初始化数组时 三种方式
//数组长度为5 初始化数组前3个元素的值 后2个元素的值为默认值0
var arr = [5]int{1,2,3}
//使用...表示数组大小时 初始化元素值的个数 即为数组大小
var arr1 = [...]int{1,2,34,453}
fmt.Println("%d\n",len(arr1))//5
//数组长度为5 初始化第3个元素为'a' 第4个元素为'b' 其他元素为默认值''
var arr2 = [5]byte{3:'a',4:'b'}
可以取任意数组常量的地址来作为指向新实例的指针
func main(){
for i := 0; i < 3; i++ {
qqq(&[3]int{i, i * i, i * i * i})
}
}
func qqq(a *[3]int) {
fmt.Println(a)
}
&[0 0 0]
&[1 1 1]
&[2 4 8]
7.1.2 多维数组
const WIDTH int = 1920
const HEIGHT int = 1080
type pixel int
var screen [WIDTH][HEIGHT]pixel
func main(){
for y := 0; y < HEIGHT; y++ {
for x := 0; x < WIDTH; x++ {
screen[x][y] = 0
}
}
}
7.2 切片
切片(slice)是对数组(该数组我们称之为相关数组,通常是匿名的)一个连续片段的引用,是引用类型。切片提供了一个相关数组的动态窗口。
切片的长度可以在运行时修改,最小为0,最大为数组的长度,是一个长度可变的数组。
可以通过len()获取切片长度,通过cap()获取切片的容量。切片容量是从切片的第一个元素开始,到相关数组最后一个元素之间的元素个数。
0 <= len(slice) <= cap(slice)
声明切片的格式:
var identifier []type
//e.g.
var slice []int
一个切片在未初始化之前默认为nil,长度为0
切片的初始化格式:
//表示slice是由数组arr1从下标start到end-1之间的元素构成的子集
var slice []type = arra[start:end]
例子:
arr1 := [10]int{1, 3, 43, 4534, 2}
//定义一个切片 范围是[0,5) 是int类型
var slice1 []int = arr1[0:5]
//定义一个切片 范围是[0,数组长度)
var slice2 []int = arr1[:]
//通过获取数组arr1的地址 定义一个切片 范围[0,数组长度)
var slice3 = &arr1
//相当于切片范围为 [0,3)
//var slice4 = arr1[:3]
//切片范围 [2,数组长度)
//var slice5 = arr1[2:]
//用类似数组的方式初始化 这样创建了长度为5的数组并且创建了一个相关切片
var x = []int{2,3,5,7,11}
切片的优点:切片是引用,不需要使用额外的内存且比使用数组更有效率。
注意:多个切片如果表示同一个数组片段,它们可以共享数据
改变切片大小:
1.扩大切片
arr1 := [10]int{1,2,3,4,5}
//此时 切片范围[0,5]
var slice []int = arr1[0:5]
//扩容
//切片扩大上限 原本slice = arr1[0:5] cap(arr1)计算数组arr1容量 arr1容量为10
//所以扩大后的切片范围[0,数组长度)
slice = slice[:cap(arr1)]
//缩小切片
slice = slice[:len(slice)-1]
切片结构:
切片在内存中的组织方式是一个有3个域的结构体:指向相关数组的指针,切片长度,切片容量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UgkoaXXo-1651290481284)(D:\markdown文件\go\photo\Snipaste_2022-04-27_15-00-49.png)]
例子:
func meme() {
var arr1 [6]int
var slice []int = arr1[2:5]
//arr1元素初始化[0,1,2,3,4,5]
for i := 0; i < len(arr1); i++ {
arr1[i] = i
}
//遍历并输出切片元素
for i := 0; i < len(slice); i++ {
fmt.Printf("slice No.%d Num is : %d\n", i, slice[i])
}
fmt.Printf("arr len is %d\n", len(arr1))
fmt.Printf("slice len is %d\n", len(slice))
fmt.Printf("slice cap is %d\n", cap(slice))
//切片扩容
slice = slice[0:4]
for i := 0; i < len(slice); i++ {
fmt.Printf("slice No.%d Num is : %d\n", i, slice[i])
}
fmt.Printf("slice len is %d\n", len(slice))
fmt.Printf("slice cap is %d\n", cap(slice))
}
slice No.0 Num is : 2
slice No.1 Num is : 3
slice No.2 Num is : 4
arr len is 6
slice len is 3
slice cap is 4
slice No.0 Num is : 2
slice No.1 Num is : 3
slice No.2 Num is : 4
slice No.3 Num is : 5
slice len is 4
slice cap is 4
7.2.1 将切片传递给函数
有一个函数需要对数组进行操作,把函数的需要传入的参数的类型声明为切片,调用函数时,给数组创建一个切片,把切片作为参数传递给函数
package main
import (
"fmt"
)
func main() {
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s := arr[0:5]
xx1(s)
}
func xx1(s []int) {
for _, i := range s {
fmt.Printf("%d\n", i)
}
}
7.2.2 用make()创建一个切片
当相关数组没还有定义时,使用make()来创建一个切片同时创建好相关数组
make 的使用方式是:func make([]T, len, cap)
,其中 cap 是可选参数。
//make([]T,len) 数组类型,数组长度
//创建一个切片同时创建好相关数组 数组类型int 长度为10 切片长度为10 容量也为10
slice1 := make([]int, 10)
fmt.Printf("slice1 len is %d,cap is %d\n", len(slice1), cap(slice1))
//make([]T,len,cap) 数组类型,切片长度,数组长度
slice2 := make([]int, 5, 10)
fmt.Printf("slice2 len is %d,cap is %d\n", len(slice2), cap(slice2))
//slice1 len is 10,cap is 10
//slice2 len is 5,cap is 10
7.2.3 new()和make()的区别
new(T)为每个新的类型T分配一片内存,初始化为0并且返回类型为*T的内存地址。换种说法:该方法返回一个指向类型为T,值为0的地址的指针。
适用于值类型,如数组,结构体。它相当于&T{}
make(T)返回一个类型为T的初始值,只适用于3种内建的引用类型:切片,map和channel
总结:new函数分配内存,make函数初始化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6pzQ2ig-1651290481285)(D:\markdown文件\go\photo\Snipaste_2022-04-27_15-29-48.png)]
//new方法
var p *[]int = new([]int) // *p == nil; with len and cap 0
p := new([]int)
//make方法
var v []int = make([]int, 10, 50)
v := make([]int, 10, 50)
7.2.4 bytes包
类型为[]byte的切片十分常见,Go中的bytes包专门用来处理这种类型
bytes包和strings包很相似,里面包含一个有用的类型Buffer
import "bytes"
type Buffer struct {
...
}
这是一个长度可变的 bytes 的 buffer,提供 Read 和 Write 方法,因为读写长度未知的 bytes 最好使用 buffer。
定义
var buffer bytes.Buffer
或使用new获得一个指针
var r *bytes.Buffer = new(bytes.Buffer)
或通过函数,创建一个Buffer对象并用buf初始化
func NewBuffer(buf []byte) *Buffer
作用:通过buffer串联字符串
类似于Java的StringBuilder类
创建一个buffer,通过buffer.WriteString(s)方法将字符串s追加到后面,再通过buffer.String方法转成string
此方法比使用+=更节省内存和CPU。
7.2.4 切片的复制与追加
//将类型为T的切片从源地址src 拷贝到 目标地址dst,覆盖dst的相关元素,并且返回拷贝的元素个数。拷贝个数是src和dst的长度最小值(例如切片1长度为3 切片2长度为2 将切片1拷贝到切片2中 切片2只能放2个元素 则返回的拷贝元素个数为2)
func copy(dst, src []T) int
//将0个或多个相同类型T的元素追加到切片s后面并返回新的切片 如果切片s的容量不足以存储新增元素,append会分配新的切片来保证已有切片元素和新增元素的存储。此时返回的切片已经指向一个不同的相关数组了。
func append(s []T,x ...T) []T
//如果要把切片y追加到切片x后面 x = append(x,y ...)
例子:
package main
import (
"fmt"
)
func main() {
ccc1()
}
func ccc1() {
s1 := []int{1, 2, 3}
s2 := make([]int, 2)
fmt.Println(s1)
fmt.Printf("%p\n", &s1)
fmt.Println(s2)
fmt.Printf("%p\n", &s2)
n := copy(s2, s1)
fmt.Println(s2)
fmt.Printf("%p\n", &s2)
fmt.Printf("copy num is : %d\n", n)
s3 := []int{1, 2, 3}
s3 = append(s3, 4, 5, 6)
fmt.Println(s3)
//s1加到s3的后面
s3 = append(s3, s1...)
fmt.Println(s3)
}
[1 2 3]
0xc000004078
[0 0]
0xc000004090
[1 2]
0xc000004090
copy num is : 2
[1 2 3 4 5 6]
[1 2 3 4 5 6 1 2 3]
当切片不足以放下要添加的元素时,定义一个新数组来存储原切片元素和要加入的元素 创建一个新切片指向该数组 修改原切片地址 为 新切片地址
func AppendByte(slice []byte, data ...byte) []byte {
//原切片长度
m := len(slice)
//添加元素后 原切片需要的长度
n := m + len(data)
//当需要的长度 > 原切片的容量时
if n > cap(slice) { // if necessary, reallocate
// allocate double what's needed, for future growth.
//创建相关数组和新切片 相同数组长度为(n+1)*2 同时创建的切片长度和容量均为(n+1)*2 此时相关数组元素均为默认值0
newSlice := make([]byte, (n+1)*2)
//将原切片元素 复制到新切片
copy(newSlice, slice)
//修改原切片地址 为新切片地址
slice = newSlice
}
//修改切片长度为[0,n)
slice = slice[0:n]
//将添加的元素放入切片 由于[0,m-1]放的是切片本身的元素 所以[m,n)的下标放新添加的元素
copy(slice[m:n], data)
return slice
}
例题:
写一个函数 InsertStringSlice 将切片插入到另一个切片的指定位置。
//old,new分别是插入切片,被插入切片 location是插入的下标
func InsertStringSlice(old, new []int, location int) []int {
n := len(new)
m := cap(new)
oldLen := len(old)
if location < 0 || location >= n {
fmt.Printf("输入的插入下标有误\n")
return nil
}
//切片扩容
if m < n+oldLen {
newSlice := make([]int, n+oldLen)
copy(newSlice, new)
new = newSlice
}
//new长度增加
new = new[0 : n+oldLen]
x := new[n-location-1 : n-location-1+oldLen]
y := new[n-location-1+oldLen : n+oldLen]
copy(y, x)
copy(x, old)
return new
}
func main() {
arr0 := [5]int{10, 9, 8, 7, 6}
slice1 := arr0[0:5]
arr1 := [3]int{1, 2, 3}
slice2 := arr1[0:3]
fmt.Println(slice1)
slice1 = InsertStringSlice(slice2, slice1, 2)
fmt.Println(slice1)
arr2 := [6]int{1, 2, 3, 4, 5, 6}
slice3 := arr2[0:6]
fmt.Println(slice3)
//fmt.Printf("%p\n", &slice3)
slice3 = RemoveStringSlice(slice3, 1, 2)
fmt.Println(slice3)
}
[10 9 8 7 6]
[10 9 1 2 3 8 7 6]
写一个函数 RemoveStringSlice 将从 start 到 end 索引的元素从切片 中移除。
func RemoveStringSlice(slice []int, start, end int) []int {
//fmt.Printf("inner1 %p\n", &slice)
length := len(slice)
if start > length || start < 0 || start > end {
fmt.Printf("start范围有误\n")
return nil
}
if end > length || end < 0 || end < start {
fmt.Printf("end范围有误\n")
return nil
}
y := slice[end+1 : length]
slice = slice[0 : length-((end-start)+1)]
l := len(slice)
copy(slice[start:l], y)
//fmt.Printf("inner2 %p\n", &slice)
return slice
}
func main() {
arr0 := [5]int{10, 9, 8, 7, 6}
slice1 := arr0[0:5]
arr1 := [3]int{1, 2, 3}
slice2 := arr1[0:3]
fmt.Println(slice1)
slice1 = InsertStringSlice(slice2, slice1, 2)
fmt.Println(slice1)
arr2 := [6]int{1, 2, 3, 4, 5, 6}
slice3 := arr2[0:6]
fmt.Println(slice3)
//fmt.Printf("%p\n", &slice3)
slice3 = RemoveStringSlice(slice3, 1, 2)
fmt.Println(slice3)
}
[1 2 3 4 5 6]
[1 4 5 6]
7.2.5 字符串、数组和切片的应用
7.2.5.1 从字符串生成字节切片
c := []bytes(s)
copy(dst []byte, src string)
7.2.5.2 获取字符串的一部分
//获取str中[start,end)的字符串
substr := str[start,end]
str := "abcdef"
//获取到下标在[1,3)的字符串
s := str[1:3]
//bc
7.2.5.3 字符串和切片的内存结构
在内存中,一个字符串是一个双字结构,即一个指向实际数据的指针和记录字符串长度的整数。因为指针对用户完全不可见,因此我们仍然把字符串看成一个值类型,也就是一个字符数组。
字符串string s = “hello” 和子字符串 t = s[2:3] 在内存中结构如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tq0kELUK-1651290481285)(D:\markdown文件\go\photo\Snipaste_2022-04-28_16-45-17.png)]
7,2,5,4 修改字符串中某个字符
字符串不可变,直接修改字符串中某个字符的值,str[i]不能出现在等号左侧,编译直接报错
str := "abc"
str[0] = 'z'//Cannot assign to str[0]
正确修改方式,将字符串转成字符数组,修改某个元素的值 再将字符数组转成字符串 让变量指向新的字符串
s := "hello"
c := []byte(s)
c[0] = 'c'
s = string(c) //s == "cello"
7.2.5.5 append函数常见操作
-
将切片 b 的元素追加到切片 a 之后:
a = append(a, b...)
-
复制切片 a 的元素到新的切片 b 上:
b = make([]T, len(a)) copy(b, a)
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
slice := arr[0:len(arr)]
fmt.Println(slice)
//1.删除切片中第i个元素
//slice = append(slice[:i],slice[i+1:])
//删除下标为3的元素
slice = append(slice[:3], slice[4:]...)
fmt.Println(slice) //[1 2 3 5 6 7 8 9]
//2.删除切片a中索引i到j之间的元素
//slice = append(slice[:i],slice[j+1:]...)
//删除下标在2-4之间的元素
slice = append(slice[:2], slice[5:]...)
fmt.Println(slice) //[1 2 7 8 9]
//3.为切片a扩展j个元素长度
//slice = append(slice,make([]int,j)...)
//给slice切片扩展5个元素
slice = append(slice, make([]int, 5)...)
fmt.Println(slice) //[1 2 7 8 9 0 0 0 0 0]
//4.在索引i的位置插入元素x
//slice = append(slice[:i],append([]int{x},slice[i+1:]...))
//在下标4的位置插入元素99
slice = append(slice[:4], append([]int{99}, slice[4:]...)...)
fmt.Println(slice) //[1 2 7 8 99 9 0 0 0 0 0]
//5.在索引 i 的位置插入长度为 j 的新切片
//slice = append(slice[:i],append(make([]int,j),slice[5:]...)...)
slice = append(slice[:5], append(make([]int, 5), slice[5:]...)...)
fmt.Println(slice) //[1 2 7 8 99 0 0 0 0 0 9 0 0 0 0 0]
//6.在索引 i 的位置插入切片 b 的所有元素
//slice = append(slice[:i],append(sliceB,slice[i:]...)...)
b := []int{90, 91, 92}
sliceB := b[:]
slice = append(slice[:3], append(sliceB, slice[3:]...)...)
fmt.Println(slice) //[1 2 7 90 91 92 8 99 0 0 0 0 0 9 0 0 0 0 0]
//7.取出位于切片 a 最末尾的元素 y
y := slice[len(slice)-1]
fmt.Println(y)//0
//8.将元素 x 追加到切片 a
x := 100
slice = append(slice, x)
fmt.Println(slice) //[1 2 7 90 91 92 8 99 0 0 0 0 0 9 0 0 0 0 0 100]
例题
func main() {
a, b := s1("abcde", 2)
fmt.Println(a)
fmt.Println(b)
fmt.Println(s2("abcdef"))
bb := []byte{'a', 'b', 'b', 'c', 'c'}
bb = s3(bb)
for _, i := range bb {
fmt.Printf("%c\n", i)
}
}
//编写一个函数,要求其接受两个参数,原始字符串 str 和分割索引 i,然后返回两个分割后的字符串。
func s1(str string, i int) (a, b string) {
slice := []byte(str)
slice1 := slice[:i+1]
slice2 := slice[i+1:]
a = string(slice1)
b = string(slice2)
return
}
//编写一个程序,要求能够反转字符串,即将 “Google” 转换成 “elgooG”
func s2(str string) string {
slice := []byte(str)
for i := 0; i < len(slice)/2; i++ {
j := len(slice) - i - 1
c := slice[j]
slice[j] = slice[i]
slice[i] = c
}
return string(slice)
}
//编写一个程序,要求能够遍历一个数组的字符,并将当前字符和前一个字符不相同的字符拷贝至另一个数组。
func s3(b []byte) []byte {
slice1 := make([]byte, 0)
for i, _ := range b {
if i > 0 {
if b[i-1] != b[i] {
slice1 = append(slice1, b[i])
}
} else {
slice1 = append(slice1, b[i])
}
}
return slice1
}