zoukankan      html  css  js  c++  java
  • Go 数组&切片

    数组相关

       在Go语言中,数组是一种容器相关的数据类型,用于存放多种相同类型的数据。

    数组定义

       在定义数组时,必须定义数组的类型以及长度,数组一经定义不可进行改变。

       同时,数组的长度是按照元素个数进行统计的,并且数组长度是数组的一部分。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr [10]string  // 定义一个长度为10byte的数组
    	fmt.Println(len(arr)) // 10 数组长度
    	fmt.Println(cap(arr)) // 10 数组容量
    // 定义完成后进行赋值 arr = [10]string{"①","②","③"} }

    定义赋值

       数组可以在一经定义的时候就进行赋值,赋值的长度不可大于数组的长度。

       数组也可以不定义长度,而是使用...语法来动态计算填充的元素长度。

       数组也可以在定义的时候通过索引对其进行赋值。

       以下是定长的数组定义赋值

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr = [10]string{"①", "②", "③", "④","⑤","⑥","⑦","⑧","⑨","⑩"}
    	fmt.Printf("数组长度:%d
    ", len(arr)) // 数组长度:10
    	fmt.Printf("数组容量:%d
    ", cap(arr)) // 数组容量:10
    	fmt.Printf("第一个元素:%v
    ", arr[0])  // 第一个元素:①
    }
    

       以下是不定长的数组定义赋值

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr = [...]string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩", "拾壹"}
    	fmt.Printf("数组长度:%d
    ", len(arr)) // 数组长度:11
    	fmt.Printf("数组容量:%d
    ", cap(arr)) // 数组容量:11
    	fmt.Printf("最后一个元素:%v
    ", arr[len(arr)-1])  // 最后一个元素:拾壹
    }
    

       以下是定长的数组索引赋值:

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr = [10]string{1: "②", 8: "⑨"}
    	fmt.Printf("数组长度:%d
    ", len(arr)) // 数组长度:11
    	fmt.Printf("数组容量:%d
    ", cap(arr)) // 数组容量:11
    	fmt.Printf("数组元素:%v
    ", arr) // 数组元素:[ ②       ⑨ ]
    }
    

    默认填充

       当定义了一个数组并未填充数据时,会进行默认的数据填充:

       布尔值是false

       字符串是""

       整形和浮点型都是0

    多维数组

       多维数组只有第一层可以使用...来让编译器推导数组长度,其他层都需要手动指定长度。

       如下定义了一个二维数组:

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	// 一维数组:不定长  二维数组:定长,2个 在一维数组中创建了3个二维数组
    	var arr = [...][2]string{
    		{"1-1", "1-2"},
    		{"2-1", "2-2"},
    		{"3-1", "3-2"},
    	}
    	fmt.Println(arr) // [[1-1 1-2] [2-1 2-2] [3-1 3-2]]
    }
    
    

    值类型

       数组本身是值类型的,所以当重新复制一份数组时,不会受到前数组的影响。

       这相当于深拷贝。

       如下示例,单一的数组中存入值类型,互不影响。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr = [...]int{1, 2, 3}
    	// 数组是值类型
    	newArr := arr
    	arr[0] = 10
    	fmt.Println(arr) // [10 2 3]
    	fmt.Println(newArr) // [1 2 3]
    }
    
    

      

       示例二,二维数组的改变,也不会引发另一个数组的改变,故此说是深拷贝。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr = [...][1]int{
    		{10},
    	}
    	// 数组是值类型
    	newArr := arr
    	arr[0][0] = 100
    	fmt.Println(arr) // [[100]]
    	fmt.Println(newArr) // [[10]]
    }
    
    

    循环遍历

       可以使用for的索引循环,也可以使用range来进行数组遍历。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr = [10]string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"}
    	// 索引循环
    	for index := 0; index < len(arr); index++ {
    		fmt.Println(index)
    	}
    	// range循环
    	for index,ele := range arr{
    		fmt.Println(index)
    		fmt.Println(ele)
    	}
    }
    
    

    切片相关

       切片是基于数组的一层封装,非常灵活且支持扩容。

       你可以将它当作一个更加高级的数组。

       注意:切片是引用类型,不能直接做比较,只能和nil做比较。

       nil则相当于null,是指没有分配内存的一个变量。只要被分配了内存,就非nil

    切片声明

       声明切片类型的基本语法如下:

    var 变量名 []切片类型
    

       以下是一些示例,对于单纯的创建切片来说,它没有像数组一样的容量限制,但是具有类型限制:

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var a []string
    	var b = []int{}
    	var c = []bool{true,false}
    	fmt.Println(a)  // []
    	fmt.Println(b)  // []
    	fmt.Println(c)  // [true false]
    	fmt.Println(a == nil) // true
    }
    

      可以如同上面示例中,在定义阶段进行赋值,也可以在定义完成后再进行赋值

    package main
    import (
    	"fmt"
    )
    
    func main(){
    	var a []string
    	a = []string{"①","②","③","④","⑤"}
    
    }
    

    切片引用

       如果对一个数组进行切片取值,那么切片的元素引用于原数组中的元素。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr = [...]string{"①", "②", "③", "④", "⑤"}
    	var arrScile = arr[1:3] // 引用数组的②③
    	arr[1] = "二"
    	arr[2] = "三" 
    	fmt.Println(arrScile) // [二 三] 跟随发生改变
    }
    
    

       这里是常见的一些切片引用的操作,需要注意的是切片引用顾头不顾尾。

    a[2:]  // 等同于 a[2:len(a)]
    a[:3]  // 等同于 a[0:3]
    a[:]   // 等同于 a[0:len(a)]
    

    长度容量

       引用切片的len()指的是切出来的元素数量。

       而cap()则是被切片的数组中,从第一个位置切的地方往后算还剩多少个元素。

       长度就是当前切片或者数组中存在的元素个数

       容量就是最多可以容纳的元素个数

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr = [...]string{"①", "②", "③", "④", "⑤"}
    	var arrSlice = arr[1:3] 
    	fmt.Println(len(arrSlice)) // 2
    	fmt.Println(cap(arrSlice)) // 4
    }
    
    

       如何理解?这里有一幅图,因为有指针的关系。所以才是引用:

       image-20200929181049361

    make切片

       上面的切片都是经过数组创建出来的,切片的容量不能由我们自己进行控制。

       但是使用make()的话就可以动态的创建出一个切片。

    make([]类型, size切片中元素数量, cap切片容量)
    

       如下示例:

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	// 创建一个string类型的切片 默认存放3个元素 最大可存放5个
    	var slice = make([]string,3,5)
    	fmt.Println(slice) // [  ]
    }
    

    append

       使用内置函数append(),可以为切片添加元素。

       可以添加一个元素,也可以添加多个元素,甚至可以利用解构语法进行切片合并。

       需要注意的是,当使用append()方法后,应该用一个变量来接收它。

       以下是添加多元素的示例

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var slice []string
    	slice = append(slice, "一", "二")
    	fmt.Println(slice) // [一 二]
    }
    

       以下是合并两个切片的示例

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var (
    		slice1   []string
    		slice2   []string
    	)
    	slice1 = append(slice1, "一", "二","三")
    	slice2 = append(slice2, "四", "五")
    	slice1 = append(slice1,slice2...)
    	fmt.Println(slice1)  // [一 二 三 四 五]
    
    }
    

    copy

       切片是引用类型,如果想将其作为值类型的传递可以用copy

       它有两个参数,分别是destSlicesrcSlice

       destSlice:目标切片

       srcSlice:数据来源切片

       注意:目标切片必须是make创建的切片,否则将会失败

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	slice1 := []int{1, 2, 3, 4, 5}
    	slice2 := make([]int, 5, 5)
    	// 目标切片  数据来源切片
    	copy(slice2, slice1)
    	slice1[0] = 100
    	fmt.Println(slice1) // [100 2 3 4 5]
    	fmt.Println(slice2) // [1 2 3 4 5]
    }
    
    

    删除元素

       切片中没有提供任何删除元素的方法,但是可以利用append()返回新切片的特性,来达到删除元素的目的。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	slice1 := []int{1, 2, 3, 4, 5}
    	// 删除3
    	slice1 = append(slice1[:2],slice1[3:]...)
    	fmt.Print(slice1) // [1 2 4 5]
    }
    

    扩容行为

       切片不需要指定容量,它会自动进行扩容。如下示例:

    func main() {
    	var a []string
    
    	fmt.Println(cap(a)) // 0 初始值
    	a = append(a,"1")
    	fmt.Println(cap(a)) // 1  
    	a = append(a,"2")
    	fmt.Println(cap(a)) // 2
    	a = append(a,"3")
    	fmt.Println(cap(a)) // 4
    	a = append(a,"4")
    	fmt.Println(cap(a)) // 4
    	a = append(a,"5")
    	fmt.Println(cap(a)) // 8
    	a = append(a,"6")
    	fmt.Println(cap(a)) // 8
    	a = append(a,"7")
    	fmt.Println(cap(a)) // 8
    	a = append(a,"8")
    	fmt.Println(cap(a)) // 8
    }
    

       可以通过查看$GOROOT/src/runtime/slice.go源码,其中扩容相关代码如下:

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
    	newcap = cap
    } else {
    	if old.len < 1024 {
    		newcap = doublecap
    	} else {
    		// Check 0 < newcap to detect overflow
    		// and prevent an infinite loop.
    		for 0 < newcap && newcap < cap {
    			newcap += newcap / 4
    		}
    		// Set newcap to the requested cap when
    		// the newcap calculation overflowed.
    		if newcap <= 0 {
    			newcap = cap
    		}
    	}
    }
    

       从上面的代码可以看出以下内容:

    • 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
    • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
    • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
    • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

       需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如intstring类型的处理方式就不一样。

       关于上面的示例,其实只看前两个判断就够了。因为旧的容量没有超过1024

    循环遍历

       可以使用for的索引循环,也可以使用range来进行切片遍历。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var arr = []string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"}
    	// 索引循环
    	for index := 0; index < len(arr); index++ {
    		fmt.Println(index)
    	}
    	// range循环
    	for index,ele := range arr{
    		fmt.Println(index)
    		fmt.Println(ele)
    	}
    }
    
    
  • 相关阅读:
    Django rest_framework实现增删改查接口
    文件的三种打开方式知识点回顾
    Django中基表的创建、外键字段属性简介、脏数据概念、子序列化
    drf序列化与反序列化作业1
    rest_framework序列化与反序列化1
    APIview的请求生命周期源码分析
    sql
    正则上面的一个坑
    多线程与多进程---方法对比与使用
    网络编程----踩坑篇
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13751637.html
Copyright © 2011-2022 走看看