• 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)
    }
    
    
  • 相关阅读:
    mybatis :xml文件中传入参数和if标签结合使用时要点
    mysql:查询数据库版本的几种方式
    http post 方法传递参数的2种方式
    深入理解mybatis参数
    Mybatis:动态sql
    Mybatis:传入参数方式以及#{}与${}的区别
    [GLSL]着色器周记02——火焰特效 【转】
    OpenGL ES入门09-GLSL实现常见特效 [转]
    RenderMonkey 练习 第五天 【OpenGL NormalMapping】
    反射向量 及 向量投影
  • 原文地址:https://www.cnblogs.com/xujunkai/p/12878447.html
Copyright © 2020-2023  润新知