Go基础 7数组与切片

本文详细介绍了Go语言中的数组和切片。数组是一种值类型,可以通过new()创建,但作为参数传递时会产生拷贝。切片是引用类型,提供了动态数组的功能,可以改变长度和容量。切片可通过make()创建,也可由数组切片得到。文中还讨论了切片的拷贝、追加、复制等操作,以及字符串、数组和切片的相互转换和内存结构。此外,还展示了如何在函数中传递切片以及使用bytes包处理字节切片的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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函数常见操作
  1. 将切片 b 的元素追加到切片 a 之后:a = append(a, b...)

  2. 复制切片 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
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值