4.Go语言-数组切片

1.数组

  • 数组是同一种数据类型元素的集合。 在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。 基本语法:

    // 数组长度必须是常量,且是类型的组成部分,一旦定义,长度不变。
    var a [3]int
    
  • 数组可以通过下标进行访问,下标从0开始,最后一个元素下标是:len-1。

    // 遍历方式
    for i:=0;i<len(Arr);i++ {
    	
    }
    
    for index,v := range Arr {
        
    }
    
  • 如果下标在数组的合法范围之外,则触发访问越界,会panic

var arr = [4]string{"1","2","3","4"}
fmt.Println(arr[5])
// 错误信息: invalid array index 5 (out of bounds for 4-element array)
  • 数组的初始化:

    package main
    
    import "fmt"
    
    //数组相关内容
    func main() {
    	var a [3]int
    	var b [4]int
    	//注意a与b不能互相赋值,长度不同
    	fmt.Println(a, b)
    	//数组的初始化
    	//1定义时使用初始值列表的方式初始化
    	var cityArray = [4]string{"北京", "上海", "广州", "深圳"}
    	fmt.Println(cityArray)
    	fmt.Println(cityArray[3])
    	//2.编译器推导数组的长度
    	var boolArray = [...]bool{true, false, true, false, false}
    	fmt.Println(boolArray)
    	//3.使用索引值方式初始化
    	var langeArray = [...]string{1: "Golang", 3: "Python", 7: "Java"}
    	fmt.Println(langeArray)
    	fmt.Printf("%T
    ", langeArray)
    
    }
    
  • 数组内定义结构体

    d := [...]struct {
        name string
        age uint16
    }{
        // 注意最后一行要加逗号,否则报错:syntax error: unexpected newline, expecting comma or {
        {"u1",123},
        {"u2",456},
        {"u3",789},
    }
    // [{u1 123} {u2 456} {u3 789}]
    fmt.Println(d)
    
  • 数组的遍历:

    • for 循环遍历
    var cityArray = [4]string{"北京", "上海", "广州", "深圳"}
    	//1.for 循环遍历
    	 for i := 0; i < len(cityArray); i++ {
    	 	fmt.Println(cityArray[i])
    	}
    
    
    • for range循环遍历
    var cityArray = [4]string{"北京", "上海", "广州", "深圳"}
    	for index, value := range cityArray {
    		fmt.Println(index, value)
    	}
    
  • 二维数组:

    • 注意:多维数组,只有最外层可以用[...]让编译器自己识别编译,内层不能使用。
    //定义一个多维数组
    cityArray := [...][2]string{
    		{"北京", "西安"},
    		{"上海", "重庆"},
    		{"杭州", "成都"},
    		{"广州", "深圳"},
    	}
    	//打印杭州
    	fmt.Println(cityArray[2][0])
    	//遍历循环二维数组,
    	for _, value1 := range cityArray {
    		for _, value2 := range value1 {
    			fmt.Println(value2)
    		}
    	}
    // demo2
    package main
    import "fmt"
    
    func main() {
        // 这里需要注意,第二个维度的不能使用"..."
    	var arr1 [2][3]int = [...][3]int{{1,2,3},{4,5,6}}
    	// [[1 2 3] [4 5 6]]
    	fmt.Println(arr1)
    }
    
  • 数组类型

    • 数组是值类型,赋值和传参会赋值整个数组,而不是指针。所以改变副本的值,不会改变本身的值。
    var arr = [4]string{"1","2","3","4"}
    arr2 := arr
    arr2[3] = "10"
    fmt.Println(arr,arr2)
    // [1 2 3 4] [1 2 3 10]
    
    • demo
    package main
    
    import "fmt"
    
    //数组相关内容
    func main() {
    	//数组 是 值类型
    	x := [3]int{1, 2, 3}
    	fmt.Println(x)//[1 2 3]
    	f1(x)
        
        y:= x
        y[0] = 1000
        
    	fmt.Println(x)//[1 2 3]
    }
    
    func f1(a [3]int) {
    	a[0] = 100
    }
    //前后打印结果一样,数组是值类型,无论是把它当参数传到函数里,还是做一个变量赋值,它都是把数组的值完整拷贝一份再复制给变量
    //无论是一维数组,还是二维数组,还是给数组里的值赋值。它都不会改变
    
  • 因数组是值拷贝,会造成性能问题,通常会建议使用slice,或数组指针

    package main
    import "fmt"
    
    func test(x [2]int) {
        // &x 
    	fmt.Printf("x: %p
    ",&x)
    	x[1] = 1000
    }
    
    
    func main() {
    	a := [2]int{}
    	fmt.Printf("a:%p
    ",&a)
    	test(a)
    	// a:0x1104a058
    	// x: 0x1104a078
    	// [0 0]
    }
    
    func printArr(arr *[5]int) {
    	arr[0] = 10
    }
    func main() {
    	var arr1 [5]int
    	printArr(&arr1)
    	// [10 0 0 0 0]
    	fmt.Println(arr1)
    }
    
  • 通过len和cap返回数组长度

    a := [2]int{}
    println(len(a),cap(a))
    // 2 2
    
    • 练习题1:求数组和
    求数组[1,3,5,7,8]的所有元素和
    package main
    
    import "fmt"
    
    func main() {
    	// 练习题:
    	var result int
    	var sumList = [5]int{1, 3, 5, 7, 8}
    	for i := 0; i < len(sumList); i++ {
    		result += sumList[i]
    	}
    	fmt.Println(result)
    
    }
    找出数组中和为指定两个元素的下标,比如[1,3,5,7,8]和为8的两个元素下标分别为(0,3)和(1,2)
    package main
    
    import "fmt"
    
    func main() {
    	var sumList = [5]int{1, 3, 5, 7, 8}
    	for index, v1 := range sumList {
    		for temp, v2 := range sumList {
    			if index < temp && v1+v2 == 8 {
    				fmt.Println(index, temp)
    			}
    		}
    	}
    
    }
    
    • 求数组内随机数的和
    package main
    import (
    	"fmt"
    	"math/rand"
    	"time"
    )
    
    
    
    func sumArr(a [10]int) int {
    	var sum int = 0
    	for _,value := range a {
    		sum += value
    	}
    	return sum
    }
    
    
    func main() {
    	// 设置随机数种子, 可以保证每次随机都是随机的
    	rand.Seed(time.Now().Unix())
    	var b [10]int
    	for i:=0;i<len(b);i++ {
    		// 生成一个0-1000随机数
    		b[i] = rand.Intn(1000)
    	}
    	sum := sumArr(b)
    	fmt.Printf("sum=%d
    ",sum)
    }
    
  • 找出数组中和为给定值的两个元素的下标,例如数组[1,3,5,8,7],找出两个元素之和等于8的下标分别是(0,4)和(1,2)

    func testArr(arr [5]int, num int) {
    	for i:=0;i<len(arr);i++ {
    		for j:=i+1;j<len(arr);j++ {
    			if arr[i]+arr[j] == num {
    				fmt.Printf("(%d,%d)
    ",i,j)
    			}
    		}
    	}
    }
    
    func main() {
    	b := [5]int{1,3,5,8,7}
    	testArr(b,8)
    }
    

2.切片

  • 首先slice切片并不是数组或数组指针,它用过内部指针和相关属性引用数组片段,从而实现变长方案。

    1.切片:切片是数组的一个引用,所以切片是引用类型,但自身是结构体,值拷贝传递
    2.切片的长度可以改变,因此切片是一个可变数组
    3.切片遍历方式和数组一样,可以用len求长度,表示可用元素数量,读写操作不能超过该限制。
    4.cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。。
    5.切片的定义:var 变量名 []类型,比如 var str []string  var arr []int。
    6.如果 slice == nil,那么 len、cap 结果都等于 0。
    
    最终:
    切片是一个拥有相同类型元素的可变长度的序列,它基于数组类型做的一层封装,灵活度高,支持自动扩容,切片是一个引用类型,它的内部结构包含地址,长度和容量。切片一般用于快速地操作一块数据集合。
    
  • 定义一个切片

    package main
    
    import "fmt"
    
    //生成切片(slice)
    func main() {
    	var a []string
    	var b []int
    	var c = []bool{false, true}
    	fmt.Println(a)
    	fmt.Println(b)
    	fmt.Println(c)
    }
    
  • 切片的长度和容量

    • 切片拥有自己长度和容量,我们可以通过使用内置len函数求长度,使用内置cap()函数求切片容量。
  • 基于数组切片

    package main
    
    import "fmt"
    
    //生成切片(slice)
    func main() {
    	//基于数组得到切片
    	a := [5]int{22, 23, 24, 25, 26}
    	b := a[1:4]
    	fmt.Println(b)
    	//打印类型
    	fmt.Printf("%T
    ", b)
    }
    
  • make函数构造切片

    • len长度,cap计算切片容量
    package main
    
    import "fmt"
    
    //生成切片(slice)
    func main() {
    	//make函数构造函数
    	//d为一个元素个数为5,容量为10的切片
    	d := make([]int, 5, 10)
    	fmt.Println(d)
    	fmt.Printf("%T
    ", d)
        //通过len()函数获取切片的长度
    	fmt.Println(len(d))
    	//通过cap()函数获取切片的容量
    	fmt.Println(cap(d))
    //[0 0 0 0 0]
    //[]int
    //5
    //10
    
  • 面试题:

    func main() {
    	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    	s1 := arr[2:]
    	fmt.Println(s1)//[2 3 4 5 6 7]
    	//修改
    	s1[0] = 100
    	//是view的操作(切片映射数组),不同于python,python切片相当于单复制一份表
    	fmt.Println(s1)//[100 3 4 5 6 7] 
    	fmt.Println(arr)//[0 1 100 3 4 5 6 7]
    
    	s2 := arr[2:6]
    	fmt.Println(s2) //[100 3 4 5]
    	//前面不能取定死,可以从原数组往后面取(通过映射),超过了原数组长度会报错。但append不会报错
        // 错误信息:invalid slice index 10 (out of bounds for 5-element array)
    	s3 := s2[3:5]
    	fmt.Println(s3) //[5 6]
    	}
    
  • 切片初始化

    全局:
    var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    var slice0 []int = arr[start:end] 
    var slice1 []int = arr[:end]        
    var slice2 []int = arr[start:]        
    var slice3 []int = arr[:] 
    var slice4 = arr[:len(arr)-1]      //去掉切片的最后一个元素
    局部:
    arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    slice5 := arr[start:end]
    slice6 := arr[:end]        
    slice7 := arr[start:]     
    slice8 := arr[:]  
    slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
    
  • 切片原理:

    • 切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)

    • 举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。

    1564726138636

    • 切片s2 := a[3:6],相应示意图如下:

    1564726157473

  • nil

    • 切片之间不能直接比较,我们不能使用 == 操作符来判断两个切片是否含有全部相等元素,切片唯一合法的比较操作是和nil比较,一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0,但是我们不能说一个长度和容量为都是0的切片一定是nil.
    package main
    
    import "fmt"
    
    //生成切片(slice)
    func main() {
    	var a []int     //声明int类型切片
    	var b = []int{} //声明并且初始化,会再底层创建数组与其对应
    	c := make([]int, 0)
    	if a == nil {
    		fmt.Println("a是一个nil")
    	}
    	fmt.Println(a, len(a), cap(a))
    	if b == nil {
    		fmt.Println("b是一个nil")
    	}
    	fmt.Println(b, len(b), cap(b))
    	if c == nil {
    		fmt.Println("c是一个nil")
    	}
    	fmt.Println(c, len(c), cap(c))
    
    }
    
    
    • 切片的赋值拷贝

      • 下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别
      	s1 := make([]int, 3)
      	s2 := s1
      	s2[0] = 100
      	fmt.Println(s1)//[100 0 0]
      	fmt.Println(s2)//[100 0 0]
      
      • a赋值给了b,a和b共用一个底层数组,当我们把b[0]设置为100,打印出来a,b都更改
package main

import "fmt"

//生成切片(slice)
func main() {
	//切片的赋值拷贝
	a := make([]int, 3)
	b := a
	b[0] = 100
	fmt.Println(a)
	fmt.Println(b)

}
//[100,0,0]
//[100,0,0]


// 访问底层数组元素指针
s:= []int{0,1,2,3}
p := &[2]
*p += 100
fmt.Println(s)	// 102
  • [][]T,元素类型为[]T
func main() {
	data := [][]int {
		[]int{1,2,3},
		[]int{100,200},
		[]int{11,22,33,44},
	}
	// [[1 2 3] [100 200] [11 22 33 44]]
	fmt.Println(data)
}
  • 可直接修改struct array /slice 成员
package main

import "fmt"
func main() {
	d := [5]struct{
		x int
	}{}
	d[1].x = 10
	d[2].x = 20
	// [{0} {10} {20} {0} {0}]
    // 
	// 0x11010320,0x11010320
	fmt.Println(d)
	fmt.Printf("%p,%p
",&d,&d[0])
}
  • 切片的遍历
package main

import "fmt"

func main() {
	//切片的遍历
	//索引遍历
	a := []int{1, 2, 3, 4, 5}
	for i := 0; i < len(a); i++ {
		fmt.Println(i, a[i])
	}
	//for...range遍历
	for index, value := range a {
		fmt.Println(index, value)
	}

}

  • 切片的扩容
    • append 函数将元素值追加到数组的最后并返回数组
    • 当数组容量不满足继续放入元素,会发生扩容现象。切片numSlice的容量按照1,2,4,8,16这样规则自动进行扩容,每次扩容都是扩容前2倍
package main

import "fmt"

//生成切片(slice)
func main() {
	
	//切片要初始化后才能使用
	var a []int //此时并没有申请内存

	for i := 0; i < 10; i++ {
		a = append(a, i) //需要变量来接收,因为你不知道原来数组是否会扩容
		fmt.Printf("%v len:%d cap:%d ptr:%p
", a, len(a), cap(a), a)
	}

}
// [0] len:1 cap:2 ptr:0x11010080
// [0 1] len:2 cap:2 ptr:0x11010080
// [0 1 2] len:3 cap:4 ptr:0x110100d0
// [0 1 2 3] len:4 cap:4 ptr:0x110100d0
// [0 1 2 3 4] len:5 cap:8 ptr:0x1100e3a0
// [0 1 2 3 4 5] len:6 cap:8 ptr:0x1100e3a0
// [0 1 2 3 4 5 6] len:7 cap:8 ptr:0x1100e3a0
// [0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0x1100e3a0
// [0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0x1100c280
// [0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0x1100c280
  • append支持一次性添加多个元素
package main

import "fmt"

//生成切片(slice)
func main() {
	
	//切片要初始化后才能使用
	var a []int //此时并没有申请内存
	a = append(a,1,2,3,4,5)
    b:=[]int{11,12,13}
    a = append(a,b...)//将切片b加入到里面必须使用...
	fmt.Println(a)
	}
[1 2 3 4 5 11 12 13 14]
  • 切片区别python对比go

    #python
    a = [1,2,3,4,5,6]
    t = a[0:4]
    t[0] = 100
    print(t)#[100, 2, 3, 4]
    print(a)#[1, 2, 3, 4, 5, 6]
    
    p = a[0:5]
    print(p)#[1, 2, 3, 4, 5]
    p.append(1000)
    print(p)#[1, 2, 3, 4, 5, 1000]
    print(a)#[1, 2, 3, 4, 5, 6]
    #python切片后赋值数据,做更改,增添,都不会影响源数据以及以后切片数据,像深拷贝
    
    #go
    func main() {
    	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    	s1 := arr[2:]
    	fmt.Println(s1)//[2 3 4 5 6 7]
    	//修改
    	s1[0] = 100
    	//是view的操作(切片映射数组),不同于python,python切片相当于单复制一份表
    	fmt.Println(s1)//[100 3 4 5 6 7] 
    	fmt.Println(arr)//[0 1 100 3 4 5 6 7]
    
    	s2 := arr[2:6]
    	fmt.Println(s2) //[100 3 4 5]
    	//前面不能不取定死,可以从原数组往后面取(通过映射),超过了原数组长度会报错。
    	s3 := s2[3:5]
    	fmt.Println(s3) //[5 6]
    }
    #go语言,切片相当于映射数组,当切片更改数据,源数组也会更改,后续切片数据也会更改。
    
  • 总结:

    - 首先,如果新申请容量(cap) 大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
    - 否则判断,如果旧切片长度小于1024,则最终容量(newcap)就是旧容量的两倍,即(newcap= doublecap)
    - 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap.for{newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
    - 如果最终容量(cap)计算值溢出,则最终容量(cap) 就是新申请容量(cap)
    
    !需要注意的是:切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样。
    
  • copy函数应用

    package main
    
    import "fmt"
    
    func main() {
    	//切片的copy
    	a := []int{1, 2, 3, 4, 5}
    	b := make([]int, 5, 5)
    	c := b//c为b的赋值,c指向的地址与b一样
    	copy(b, a)//b切片为从a拷贝过来的,指向新的地址
    	b[0] = 100
    	fmt.Println(a)
    	fmt.Println(b)
    	fmt.Println(c)
    }
    //所以当b第0个元素发生变化,c也跟着变化,而a不发生变化
    //[1 2 3 4 5]
    //[100 2 3 4 5]
    //[100 2 3 4 5]
    
  • copy 函数在两个slice间复制数据,复制长度以len小的为准,两个slice可指向同一底层数组,允许元素区间重叠

    func main() {
    
        data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
        fmt.Println("array data : ", data)
        s1 := data[8:]
        s2 := data[:5]
        fmt.Printf("slice s1 : %v
    ", s1)
        fmt.Printf("slice s2 : %v
    ", s2)
        // 将s1元素拷贝到s2上,并覆盖
        copy(s2, s1)
        fmt.Printf("copied slice s1 : %v
    ", s1)
        fmt.Printf("copied slice s2 : %v
    ", s2)
        fmt.Println("last array data : ", data)
    
    }
    // array data :  [0 1 2 3 4 5 6 7 8 9]
    // slice s1 : [8 9]
    // slice s2 : [0 1 2 3 4]
    // copied slice s1 : [8 9]
    // copied slice s2 : [8 9 2 3 4]
    // last array data :  [8 9 2 3 4 5 6 7 8 9]
    
  • 切片resize,调整大小

    var a = []int{1,3,4,5}
    fmt.Printf("%v,%v
    ",a,len(a))
    b := a[1:2]
    fmt.Printf("%v,%v
    ",b,len(b))
    c := b[0:3]
    fmt.Printf("%v,%v
    ",c,len(c))
    
    // [1 3 4 5],4
    // [3],1
    // [3 4 5],3
    
  • 切片元素的删除

    //从切片a中删除"青岛"
    package main
    
    import "fmt"
    
    func main() {
    	//切片删除元素
    	a := []string{"北京", "上海", "青岛", "深圳"}
    	a = append(a[0:2], a[3:]...)
    	fmt.Println(a)
    }
    //[北京 上海 深圳]
    

练习题

//1.请写出下面代码的输出结果
func main() {
	var a = make([]string,5,10)//此时切片容量10,里面已经有5个空格
	for i:=0;i<10;i++{
		a.append(a,fmt.Sprintf("%v",i))//当添加元素0,1,2,3,4此时a容量已经满了,而append恰好能对其进行扩容
	}
	fmt.Println(a)
}
//最后输出结果:
//[     0 1 2 3 4 5 6 7 8 9]
//2.排序题
package main

import (
	"fmt"
	"sort"
)

func main() {
	//切片删除元素
	// a := []string{"北京", "上海", "青岛", "深圳"}
	// a = append(a[0:2], a[3:]...)
	// fmt.Println(a)
	var a = [...]int{3, 7, 8, 1, 9}//a为int类型数组
	sort.Ints(a[:])//接收参数为int类型切片。其实a[:]得到的切片,指向底层数组a,对切片排序,相当于对数组a排序
	fmt.Println(a)
}

原文地址:https://www.cnblogs.com/xujunkai/p/12878447.html