go 语言进阶之数组切片

数组 

数组是一组由固定长度的特定类型的元素组成的序列,一个数组可以由零个或多个元素组成。数组长度是数组类型的组成部分。因为数组的长度是数组类型的一部分,不同长度或不同类型的数据组成的数组都是不同的类型,因此go语言很少直接使用数组(不同长度的数组因为类型不同而无法直接赋值)。和数组对应的是切片,切片是可以动态增长收缩的序列,切片的功能更灵活,但理解切片的工作原理还是先理解数组;数组是值类型

示例

package main
import (
	"fmt"
)
func main(){

	var foarr [6]float64
	foarr[0]= 3.0
	foarr[1]= 5.0
	foarr[2]= 1.0
	foarr[3]= 3.4
	foarr[4]= 2.0
	foarr[5]= 50.0
	var t float64
	for i:=0;i<len(foarr);i++{
		t = t+foarr[i]

	}
	//fmt.Sprintf("%.2f",t / 6) 将这个结果四舍五入保留到小数点两位返回值
	a := fmt.Sprintf("%.2f",t / 6) 
	fmt.Println("平均体重",a)
}
// 执行结果
// 平均体重 10.73

  为什么不能直接写成fmt.Sprintf("%.2f",t /len(foarr)) Go 语言 不允许不同类型直接计算。 写成6 Go 会自动推断因为 tfloat64,所以 6 会被当成 float64 常量

package main
import (
	"fmt"
)
func main(){
	var foarr [6]float64
	foarr[0]= 3.0
	foarr[1]= 5.0
	foarr[2]= 1.0
	foarr[3]= 3.4
	foarr[4]= 2.0
	foarr[5]= 50.0
	var t float64
	for i:=0;i<len(foarr);i++{
		t = t+foarr[i]

	}
	//fmt.Sprintf("%.2f",t / 6) 将这个结果四舍五入保留到小数点两位返回值
	a := fmt.Sprintf("%.2f",t / float64(len(foarr))) // 6 具体的数是常量可以类型推导;所以必须强制转换
	fmt.Println("平均体重",a)
}
// 执行结果
// 平均体重 10.73

1) 使用数组解决问题,程序可维护性增加了

2)方法代码可维护性增加了,也容易扩展

数组的定义

var  数组名 [数组长度]数据类型

var a [5]int

赋初始值a[0]=1 a[1]=30 ....

数组内存图

image

 示例

package main
import (
	"fmt"
)
func main(){
	var intArr [3]int// 定义好数组就有默认值各个数组
	intArr[0]=6
	intArr[1]=2
	intArr[2]=3
	fmt.Println(intArr)
	fmt.Printf("intArr数组的地址值=%p,intArr数组的intArr[0] 地址=%p,intArr数组的intArr[1] 地址=%p\n",&intArr,&intArr[0],&intArr[1])

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo02\main.go
// [6 2 3]
// intArr数组的地址值=0xc000014198,intArr数组的intArr[0] 地址=0xc000014198,intArr数组的intArr[1] 地址=0xc0000141a0

  

  内存分析

image

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

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

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

初始化数组

package main
import (
	"fmt"
)
func main(){
	var intArr [3]int// 定义好数组就有默认值各个数组
	intArr[0]=6
	intArr[1]=2
	intArr[2]=3
	fmt.Println(intArr)
	fmt.Printf("intArr数组的地址值=%p,intArr数组的intArr[0] 地址=%p,intArr数组的intArr[1] 地址=%p\n",&intArr,&intArr[0],&intArr[1])
	for i,v:= range intArr{
		fmt.Printf("数组下标=%v,数组下标对应的值=%v\n",i,v)// 遍历数组
	}
	var tr = [...]int {2:3,1:2}//定义一个长度为3的int类型数组,元素0,2,3 注:2:3 表示下标为2(下标从0开始)的元素值为3;1:2表示下标为1的元素值为2;0下标没有写默认还是初始值0
	fmt.Println(tr)
	var a = [...]int {2,6,8} //定义一个长度为3的int类型数组,元素2,6,8
	var b = [...]int {1,2,4:6,8} //定义一个长度为6的int类型数组,元素1,2,0,0,6,8
	fmt.Println(a)
	fmt.Println(b)

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo02\main.go
// [6 2 3]
// intArr数组的地址值=0xc000014198,intArr数组的intArr[0] 地址=0xc000014198,intArr数组的intArr[1] 地址=0xc0000141a0
// 数组下标=0,数组下标对应的值=6
// 数组下标=1,数组下标对应的值=2
// 数组下标=2,数组下标对应的值=3
// [0 2 3]
// [2 6 8]
// [1 2 0 0 6 8]

  初始化示例2

package main
import (
	"fmt"
)
func main(){
	var c [3]int = [3]int {1,2,3}
	fmt.Println(c)
	var d = [3]int {1,2,4}
	fmt.Println(d)
	e := [...]int {4:5}
	fmt.Println(e)

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo02\main.go
// [1 2 3]
// [1 2 4]
// [0 0 0 0 5]

  for-range 结构遍历

基本语法

for index,value := range array0{

}

说明

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

2)第二个返回值value 是数组下标位置的值

3)他们都是仅在for循环内部可见的变量

4)遍历数组元素的时候,如果不想使用下标或者值,可以使用下划线_

数组遍历

package main
import (
	"fmt"
)
func main(){
	var c [3]int = [3]int {1,2,3}
	fmt.Println(c)
	var d = [3]int {1,2,4}
	fmt.Println(d)
	e := [...]int {4:5}
	fmt.Println(e)
	for i,v := range e{
		fmt.Printf("数组下标=%v,数组下标对应的值=%v\n",i,v)
	}

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo02\main.go
// [1 2 3]
// [1 2 4]
// [0 0 0 0 5]
// 数组下标=0,数组下标对应的值=0
// 数组下标=1,数组下标对应的值=0
// 数组下标=2,数组下标对应的值=0
// 数组下标=3,数组下标对应的值=0
// 数组下标=4,数组下标对应的值=5

  细节

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

package main
import (
	"fmt"
)
func main(){
	var arr01 [3] int
	arr01[0]=1
	arr01[1]=30
    // arr01[2]=1.1 cannot use 1.1 (untyped float constant) as int value in assignment (truncated)数据类型不同报错
	arr01[2]=9
	// arr01[3]=8   invalid argument: index 3 out of bounds [0:3]  数组下标超出范围报错;不能动态变化
	
	fmt.Println(arr01)
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo03\main.go
// [1 30 9]

  

2)var arr []int 这时arr 就是一个slice切片  

3)数组中的元素可以是任何数据类型,包括值类型,引用数据类型

4)数组创建后,如果没有赋值,有默认值

     数值型数组:默认值0

     字符串数组:  默认值""

      bool数组: 默认为false

package main
import (
	"fmt"
)
func main(){
	var arr02 [3] int
	var arr03 [3] string
	var arr04 [2] bool
	fmt.Println(arr02)
	fmt.Println(arr03)
	fmt.Println(arr04)
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo03\main.go
// [0 0 0]
// [  ]
// [false false]

  

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

6)数组下标从0开始的

7)数组下标必须在指定范围内使用,否则报panic:数组越界,比如 var arr [5] int 则有效下标为0-4

8)go的数组属值类型,在默认情况下是值传递,因此会进行值拷贝,数组之间不会影响

package main
import (
	"fmt"
)
func test (arr [3]int){
	arr[0] = 88
	fmt.Println("test",arr)
}
func main(){

	test(arr02)
	fmt.Println("man",arr02)
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo03\main.go
// test [88 0 0]
// man [0 0 0]

  内存分析(值不同的内存分析)

image

 

9)如果想要在其他函数中,修改原来的数组,可以用引用值传递(指针方式)

package main
import (
	"fmt"
)

func test1 (arr *[3]int){
	(*arr)[0]=90 //(*arr[])
	fmt.Println("test1",*arr)

}
func main(){

	var arr02 [3] int

	fmt.Println("man",arr02)
	test1(&arr02)
	fmt.Println("man",arr02)

	
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo03\main.go
// man [0 0 0]
// test1 [90 0 0]
// man [90 0 0]

  内存分析(值不同的内存分析)

image

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

示例

package main

import (
	//"bytes"
	"fmt"
)
func test (arr [3]int){
	arr[0] = 88
	fmt.Println("test",arr)
}
func test1 (arr *[3]int){
	(*arr)[0]=90 //(*arr[])
	fmt.Println("test1",*arr)

}
func main(){
	var mychars [26]byte
	for i,_ := range mychars{
		mychars[i] = 'A' + byte(i)
	}
	for i,_:= range mychars{
		fmt.Printf("%c ",mychars[i])
	}
	fmt.Println()
	fmt.Println("------------")
	//
	var arr1  = [9]int  {9,86,75,56,156,785,567,877,678}
	var x int
	var t int 
	for i,_ := range arr1{
		if arr1[i] >= x {
			x =arr1[i]
			t = i
		}


	}
	fmt.Printf("最大值=%v;最大值对应的下标是=%v\n",arr1[t],t)
	//求平均值跟和
	var arr2 [6]int = [6]int {56,78,98,45,56,89}
	var s int
	//var g int 
	for _,v := range arr2{
		s += v
	}
	//如何让平均值保留小数
	fmt.Printf("arr2 数组的和=%v,数组的平均值是=%v\n",s,float64(s)/float64(len(arr2)))
	
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo04\main.go
// A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 
// ------------
// 最大值=877;最大值对应的下标是=7
// arr2 数组的和=422,数组的平均值是=70.33333333333333

  随机生成数组,并翻转数组

package main

import (
	//"bytes"
	"fmt"
	"math/rand"
)
func test (arr [3]int){
	arr[0] = 88
	fmt.Println("test",arr)
}
func test1 (arr *[3]int){
	(*arr)[0]=90 //(*arr[])
	fmt.Println("test1",*arr)

}
func main(){
	var t [6] int //初始化数组
	for i,_ :=range t{
		t[i]=rand.Intn(100)+1 //数组下标赋值
	}
	fmt.Println("交换前",t) //赋值后打印
	y :=0
	for i :=len(t)-1;i>=len(t)/2;i--{
		t[i],t[y] = t[y],t[i] //交换两个值
		y++
	}
	fmt.Println("交换后",t) //交换后打印
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo05\main.go
// 交换前 [18 85 29 93 13 5]
// 交换后 [5 13 93 29 85 18]

  切片

切片就是一种简化版的动态数组。因为动态数组长度是不固定,切片的长度也就不能是类型的组成部分。虽然数组有适用的地方,但是数组类型和操作不够灵活,因此在go代码中数组使用并不多。切片则使用得相等广泛。理解切片原理

1)切片的英语单词slice

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

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

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

5)切片定义的基本语法

var 变量名 [] 类型

	var(
	    a []int //nil切片,和nil相等,表示一个不存在的切片
		b = []int {} // 空切片。和nil不相等,一般用来表示一个空集合
		c = []int{1,2,3}//有3个元素的切片,len为2,cap为3
	    d = c[:2] //有两个元素的切片,len为2,cap为3
		e = c[0:2:cap(c)] //有2个元素的切片。len为2,cap为3
		f = c[:0] //有0个元素,len为0,cap为3
		g = make([]int,3) //有3个元素的切片。len和cap都为3
		h = make([]int,2,3) //有2个元素的切片。len为2,cap为3
		i = make([]int,0,3)//有0个元素的切片。len为0,cap为3
	)

  示例

package main

import (
	//"bytes"
	"fmt"
	_ "math/rand"
)
func test (arr [3]int){
	arr[0] = 88
	fmt.Println("test",arr)
}
func test1 (arr *[3]int){
	(*arr)[0]=90 //(*arr[])
	fmt.Println("test1",*arr)

}
func main(){
	var in [5]int = [5] int {1,2,3,9,7}
	s := in[1:3] //s:是切片的名字in[1:3]表示从下标为1的元素开始到下标为3的元素前一个,包左不包右
	fmt.Println("数组",in)
	fmt.Println("切片=",s)
	fmt.Println("切片 长度=",len(s))
	fmt.Println("切片 容量=",cap(s))//切片的容量是可以动态变化的;一般而言长度的两倍
	fmt.Println("in len=",len(in))
	fmt.Printf("in[1]的地址值%p\n",&in[1]) //打印数组下标为1的元素内存地址值
	fmt.Printf("s[0]的地址值%p\n",&s[0]) //打印切片的内存地址值
	fmt.Println("in 数组",in)
	s[0]= 4 //修改切片第0个元素
	fmt.Println("in 数组",in)

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo06\main.go
// in [1 2 3 9 7]
// slice= [2 3]
// slice len= 2
// slice cap= 4
// in len= 5
// in[1]的地址值0xc00000e338
// s[0]的地址值0xc00000e338
// in 数组 [1 2 3 9 7]
// in 数组 [1 4 3 9 7]

  

  内存分析

image

总结

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

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

type slice struct {

    data uintpte

    len int

    cap int

)

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

定义方式2:var 切片名[] type = make([],len,[cap])

参数说明:type :就是数据类型;len:大小,cap:指定容量(可选)

func make(Type, size IntegerType) Type
内建函数make分配并初始化一个类型为切片、映射、或通道的对象。其第一个实参为类型,而非值。make的返回类型与其参数相同,而非指向它的指针。其具体结果取决于具体的类型:

切片:size指定了其长度。该切片的容量等于其长度。切片支持第二个整数实参可用来指定不同的容量;
     它必须不小于其长度,因此 make([]int, 0, 10) 会分配一个长度为0,容量为10的切片。
映射:初始分配的创建取决于size,但产生的映射长度为0。size可以省略,这种情况下就会分配一个
     小的起始大小。
通道:通道的缓存根据指定的缓存容量初始化。若 size为零或被省略,该信道即为无缓存的。


func cap(v Type) int
内建函数cap返回 v 的容量,这取决于具体类型:

数组:v中元素的数量,与 len(v) 相同
数组指针:*v中元素的数量,与len(v) 相同
切片:切片的容量(底层数组的长度);若 v为nil,cap(v) 即为零
信道:按照元素的单元,相应信道缓存的容量;若v为nil,cap(v)即

  示例

package main

import (

	"fmt"

)

func main(){
	var(
		// a []int //nil切片,和nil相等,表示一个不存在的切片
		// b = []int {} // 空切片。和nil不相等,一般用来表示一个空集合
		//c = []int{1,2,3}//有3个元素的切片,len为2,cap为3
		// d = c[:2] //有两个元素的切片,len为3,cap为3;c开头到小标为2 
		// e = c[0:2:cap(c)] //有2个元素的切片。len为2,cap为3
		// f = c[:0] //有0个元素,len为0,cap为3
		g = make([]int,3) //有3个元素的切片。len和cap都为3;cap 省略,表示与
		// h = make([]int,2,3) //有2个元素的切片。len为2,cap为3
		// i = make([]int,0,3)//有0个元素的切片。len为0,cap为3
		s []float64 =make([]float64, 4,10)
	)
	var k []int = make([]int, 4,8)
	fmt.Printf("g的值:")
	for _,v := range g{
		fmt.Printf("%v ",v)
	}
	fmt.Println()
	fmt.Println("g的容量:",cap(g)) 
	fmt.Printf("s的值:")
	for _,v:= range s{
		fmt.Printf("%v ",v)
	}
	fmt.Println()
	fmt.Println("s的容量:",cap(s))
	fmt.Printf("k的值:")
	for _,v:= range k{
		fmt.Printf("%v ",v)
	}
	fmt.Println()
	fmt.Println("k的容量:",cap(k))


	

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// g的值:0 0 0 
// g的容量: 3
// s的值:0 0 0 0
// s的容量: 10
// k的值:0 0 0 0
// k的容量: 8

  make 内存分析图

image

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

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

3)通过make 方式创建的切片对应的数组是由make底层维护,对外不可见,只能通过切片访问

方式三

package main

import (
	//"bytes"
	"fmt"
	_ "math/rand"
)
func main(){

	var j []int = []int {1,3,4}
	fmt.Println("j的长度",len(j))
	fmt.Println("j的容量",cap(j))


	

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// j的长度 3
// j的容量 3

  方式1与方式2的区别

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

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

image

 遍历切片

package main

import (

	"fmt"
	_ "math/rand"
)
func main(){
	var j []int = []int {1,3,4}
	fmt.Println("j的长度",len(j))
	fmt.Println("j的容量",cap(j))
	for i:=0;i<len(j);i++{
		fmt.Printf("方式1:j的%v元素的值%v\n",i,j[i])
	}
	for i,v := range j{
		fmt.Printf("方式2:j的%v元素的值%v\n",i,v)
	}


	

}
// 执行结果

// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// j的长度 3
// j的容量 3
// 方式1:j的0元素的值1
// 方式1:j的1元素的值3
// 方式1:j的2元素的值4
// 方式2:j的0元素的值1
// 方式2:j的1元素的值3
// 方式2:j的2元素的值4

  追加元素append

内置的泛型函数append 可以在切片的尾部追加N个元素;不过需要注意的是,在容量不足的情况下,append的操作会导致重新分配内存,可能导致巨大的内存分配和复制数据的代价。即使容量够,依然需要用append函数的返回值来更新切片本身,因为新的切片长度已经发生变化

package main

import (
	//"bytes"
	"fmt"
	_ "math/rand"
	_"net"
)

func main(){

    var a []int //声明一个切片元素为空
	fmt.Println(a) 
	a = append(a, 7) //追加1个元素
	fmt.Println(a)
	a = append(a, 2,7,8,9)//追加多个元素,手写解包方式
	fmt.Println(a)
    a = append(a, []int{1,2,3}...) //追加一个切片,切片需要解包
	fmt.Println(a)

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// []
// [7]
// [7 2 7 8 9]
// [7 2 7 8 9 1 2 3]

  除了在切片尾部追加,还可以在切片的开头添加元素

package main

import (
	//"bytes"
	"fmt" 
func main(){

	var b []int = []int {1,2,3}
	fmt.Println("b=",b)
	b= append([]int{0},b...)//在开头添加一个元素
	fmt.Println("b=",b)
	b= append([]int{-3,-2,-1},b...) //在开头添加一个切片
	fmt.Println("b=",b)
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// b= [1 2 3]
// b= [0 1 2 3]
// b= [-3 -2 -1 0 1 2 3]

  在开头一般都会导致内存的重新分配,而且会导致已有的元素全部赋值。因此,从切片的开头添加元素的性能一般比从尾部追加元素的性能差很多;

由于append函数返回新的切片,也就是它支持链式操作。可以将多个append操作组合起来,实现切片中间插入元素

package main

import (
	"fmt"

}
func main(){

	var c []int = []int {1,2,4}
	fmt.Println("c=",c)
	c = append(c[:1],append([]int{10},c[1:]...)...)//在下标1的位置插入10
	fmt.Println("c=",c)
	c = append(c[:1],append([]int{23,45,89},c[1:]...)...) //在下标1的位置插入切片
	fmt.Println("c=",c)
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// c= [1 2 4]
// c= [1 10 2 4]
// c= [1 23 45 89 10 2 4]

  每个添加操作中的第二个append调用都会创键一个临时切片,并将c[1:]的内容复制到新创建的切片里,然后将临时创建的切片再追加到a[:i];可以用copy和append组合可以避免创建中间临时切片,同样完成元素添加;copy (c[3+1:],c[3:])//c[3:]向后移动一个位置 因为从第四个元素到后面所有元素拷贝。从第四(下标是3下标从0开始)个元素开始所有元素整体向后移,因为是拷贝下标是3的四个元素此时还是原来放入值;因此在下一步就是c[3]=8,就完成了添加元素

package main

import (
	//"bytes"
	"fmt"
)

func main(){
	var c []int = []int {1,2,4}
	fmt.Println("c=",c)
	c = append(c[:1],append([]int{10},c[1:]...)...)//在下标1的位置插入10
	fmt.Println("c=",c)
	c = append(c[:1],append([]int{23,45,89},c[1:]...)...) //在下标1的位置插入切片
	fmt.Println("c=",c)
	c = append(c, 0) //切片扩展1个空间
	copy (c[3+1:],c[3:])//c[3:]向后移动一个位置;
	fmt.Println("copy操作后,c=",c)

	c[3]=8// 重新设置下标为3的值
	fmt.Println("copy操作并重新赋值后,c=",c)

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// c= [1 2 4]
// c= [1 10 2 4]
// c= [1 23 45 89 10 2 4]
// copy操作后,c= [1 23 45 89 89 10 2 4]
// copy操作并重新赋值后,c= [1 23 45 8 89 10 2 4]

  c = append(c, 0) //扩展切片的长度,为要插入的元素留出空间。第二句copy操作将要插入的位置开始之后的元素向后挪东一个位置。第三句真实地将新添加的元素赋值到对应的位置。操作语句虽然冗长一些。但相比前面方法可以减少临时切片

删除切片元素

根据要删除元素的位置有三种情况:从开头删除,从中间删除,从尾部删除,其中从尾部删除最快

package main

import (
	"fmt"
)

func main(){
	var a = [] int {1,2,3,4,5,6,7,8,9,10,12}
	fmt.Println("开始删除前a",a)
	a = a[:len(a)-1] //删除尾部一个元素
	fmt.Println("删除尾部一个元素a",a)
	a = a[:len(a)-3] //删除尾部3个元素
	fmt.Println("删除尾部三个元素a",a)
	a = a[1:] //删除开头1个元素
	fmt.Println("删除开头1个元素a",a)
	a = a[2:] //删除开头2个元素
	fmt.Println("删除开头2个元素a",a)
	a = append(a[:0],a[1:]...) //删除开头1个元素
	fmt.Println("删除开头1个元素append*a",a)
	a = append(a[:0],a[2:]...)   //删除开头2个元素
	fmt.Println("删除开头2个元素append*a",a)
	var b = [] int {1,2,3,4,5,6,7,8,9,10,12,5}
	fmt.Println("开始删除前b",b)
	b = append(b[:2],b[2+1:]...) //删除中间一个元素
	fmt.Println("删除中间一个元素b",b)
	b = append(b[:2],b[2+2:]...) //删除中间两个元素
	fmt.Println("删除中间二个元素b",b)
	b = b[:1+copy(b[1:],b[1+1:])] //删除中间一个元素
	fmt.Println("删除中间一个元素b(copy)",b)
	b = b[:1+copy(b[1:],b[1+2:])] //删除中间两个元素
	fmt.Println("删除中间两个元素b(copy)",b)
	 
	
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo08\main.go
// 开始删除前a [1 2 3 4 5 6 7 8 9 10 12]
// 删除尾部一个元素a [1 2 3 4 5 6 7 8 9 10]
// 删除尾部三个元素a [1 2 3 4 5 6 7]
// 删除开头1个元素a [2 3 4 5 6 7]
// 删除开头2个元素a [4 5 6 7]
// 删除开头1个元素append*a [5 6 7]
// 删除开头2个元素append*a [7]
// 开始删除前b [1 2 3 4 5 6 7 8 9 10 12 5]
// 删除中间一个元素b [1 2 4 5 6 7 8 9 10 12 5]
// 删除中间二个元素b [1 2 6 7 8 9 10 12 5]
// 删除中间一个元素b(copy) [1 6 7 8 9 10 12 5]
// 删除中间两个元素b(copy) [1 8 9 10 12 5]

  

 

细节

切片初始化时 var slice = arr[startIndex:end:index];说明 从arr数组下标为startindex,取到下标为endindex的元素(不含arr[endindex])

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

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

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

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

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

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

切片可以继续切片

示例

package main

import (
	//"bytes"
	"fmt"
	_ "math/rand"
	_"net"
)
func main(){

	var c []int = []int {1,2,4}
	fmt.Println("c=",c)
	c = append(c[:1],append([]int{10},c[1:]...)...)//在下标1的位置插入10
	fmt.Println("c=",c)
	c = append(c[:1],append([]int{23,45,89},c[1:]...)...) //在下标1的位置插入切片
	fmt.Println("c=",c)
	c = append(c, 0) //切片扩展1个空间
	copy (c[3+1:],c[3:])//c[3:]向后移动一个位置;
	fmt.Println("copy操作后,c=",c)

	c[3]=8// 重新设置下标为3的值
	fmt.Println("copy操作并重新赋值后,c=",c)
	d := c[1:4]
	fmt.Println(d)
	fmt.Println(&c[1])
	fmt.Println(&d[0])
	fmt.Println(cap(d))
	// d = append([]int{0},d...)
	d =append(d, 1,2,3)
	fmt.Println(&c[1])
	fmt.Println(&d[0])
	var t [5]int = [5]int {3,7,8,8,5}

	e := t[:5]
	fmt.Println("数组1",&t[0])
	fmt.Println("e1",&e[0])
	e = append(e,1,2,3 )
	fmt.Println("数组",&t[0])
	fmt.Println("e",&e[0])

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// c= [1 2 4]
// c= [1 10 2 4]
// c= [1 23 45 89 10 2 4]
// copy操作后,c= [1 23 45 89 89 10 2 4]
// copy操作并重新赋值后,c= [1 23 45 8 89 10 2 4]
// [23 45 8]
// 0xc00006a0c8
// 0xc00006a0c8
// 11
// 0xc00006a0c8
// 0xc00006a0c8
// 数组1 0xc00000e420
// e1 0xc00000e420
// 数组 0xc00000e420
// e 0xc0000102d0

  

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

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

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

2)go底层会创建一下新的数组Newarr(扩容后的大小)

3)将slice原因元素拷贝新数组

4)slice 重新引用Newarr

5)注意Newarr 是底层来维护的

示例

package main

import (
	//"bytes"
	"fmt"
	_ "math/rand"
	_"net"
)
func test (arr [3]int){
	arr[0] = 88
	fmt.Println("test",arr)
}
func test1 (arr *[3]int){
	(*arr)[0]=90 //(*arr[])
	fmt.Println("test1",*arr)

}
func main(){
	var g []int = []int {1,2,3}
	fmt.Println(g)
	g = append(g, 4)
	fmt.Println(g)

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// [1 2 3]
// [1 2 3 4]

  内存分析

image

 拷贝操作

切片的使用copy内置函数完成拷贝

说明copy(para1,para2):para1和para2都是切片

示例

package main

import (
	//"bytes"
	"fmt"
	_ "math/rand"
	_"net"
)
func test (arr [3]int){
	arr[0] = 88
	fmt.Println("test",arr)
}
func test1 (arr *[3]int){
	(*arr)[0]=90 //(*arr[])
	fmt.Println("test1",*arr)

}
func main(){

	var g []int = []int {1,2,3}
	fmt.Println(g)
	g = append(g, 4)
	fmt.Println(g)
	var b []int = []int {1,2,3,4,5}
	var c = make([]int,10)
	fmt.Println(c)
	copy(c,b)
	fmt.Println(c)

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo07\main.go
// [1 2 3]
// [1 2 3 4]
// [0 0 0 0 0 0 0 0 0 0]
// [1 2 3 4 5 0 0 0 0 0]

  1)copy 参数必须是切片

        2)  按照拷贝一个切片给另一个切片 修改一个切片的值不会影响另一个切片

string 和slice 

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

示例

import (
	"fmt"
	//"slices"
)

func main(){
	str := "[email protected]"
	slice := str[6:]
	for _,v := range slice{
		fmt.Printf("%q",v)
	} 

	fmt.Println("\n",slice)
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo09\main.go
// 'c''h''e''n''x''i''.''c''o''m'
//  chenxi.com

  

2)string 和切片在内存上的形式,以“abcd” 画出内存示意图

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

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

package main

import (
	"fmt"
	//"slices"
)

func main(){
	str := "[email protected]"
	slice := str[6:]
	for _,v := range slice{
		fmt.Printf("%q",v)
	} 

	fmt.Println("\n",slice)
	

	//修改string
	//slice2 := str[:]
	fmt.Println("打印初始数组",str)
	arr := []byte(str) //转为切片
	arr[0] = 'z' //修改下标为0 的元素
	str = string(arr) //重新转成string 并赋值给原来变量
	fmt.Println("打印修改后数组",str)
	//细节,我们转成byte后,可以处理英文和数字,无法处理中文;原因是byte是按字节编码的
	//解决方法:[]rune 是按字符处理
	arr1 := []rune(str)
	arr1[0]= '陈'
	str = string(arr1)
	fmt.Println("rune 修改汉字",str)
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo09\main.go
// 'c''h''e''n''x''i''.''c''o''m'
//  chenxi.com
// 打印初始数组 [email protected]
// 打印修改后数组 [email protected]
// 陈[email protected]

 示例

package main

import (
	"fmt"
	//"slices"
)
func fbn (n int) ([]uint64){
	fbnSlice := make([]uint64,n)
	for i,_ := range fbnSlice{
		if i == 0 || i==1{
			fbnSlice[i]=1
		}else {
			fbnSlice[i]= fbnSlice[i-1] +fbnSlice[i-2] 
		}
	}
	return fbnSlice

}

func main(){
	fmt.Println("值=",fbn(30))

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter06\demo10\main.go
// 值= [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040]

  

 

posted @ 2026-03-13 16:52  烟雨楼台,行云流水  阅读(2)  评论(0)    收藏  举报