go数组与切片

 定义数组(2种方式):

var arr = [5]int{1,2,3,4,5}                    // 指定数组长度为5

var arr2 = [...]int{1,2,3,4,5,6,7,8,9}     // "..."表示由编译器确定数组大小(9)

可以先定义数组后赋值

var arr [4]int                                       // 也要指定长度

arr[0]=1;   arr[1]=2;   arr[2]=3;  arr[3]=4 

Go 语言数组声明需要指定元素类型及元素个数

不指定也会根据初始化的元素个数自动推算出大小,不可改变

数组需要明确指定大小,切片不指定大小

定义切片:

func main() {
    /*通过数组来初始化切片*/
    var arr = [...]int {2,6,9,8,1}   // 先定义数组, 大小为5
    var s1 = arr[:]                  // 切片s1 -> [2,6,9,8,1]
    var s2 = arr[1:3]                // [6,9]      取不到最后一个, 1<= index <3
    var s3 = arr[1:]                 // [6,9,8,1]
    var s4 = arr[:4]                 // [2,6,9,8]   取不到最后一个
	
    /*通过切片来初始化切片*/
    var s5 = s1[2:]            		// [9,8,1]
	
    /*直接初始化切片, make([]type, len, cap)*/
    var s6 = make([]int , 3, 6)		// [0,0,0]
    /*直接赋值初始化切片, 可以看到如下的中括号里面没有内容*/
    var s7 = []int {1,2,3,4,5}		// [1,2,3,4,5]
}

var s []type 用于定义切片, 通常[]type是省略了的, 所以如下是错误的操作

var s []int = [5]int{1,2,3,4,5}    // 左边是定义切片, 右边是数组

 

数组与切片的打印可使用%v

func main() {
    var array = [3]int{1,2,3}
    var slice = array[:]
    fmt.Printf("%v", array)
    fmt.Printf("%v", slice)
}

数组与切片的关系:

func main() {
    var a [6]int = [...]int{1,2,3,4,5,6}    // 数组
    s1 := a[2:4]                            // 切片s1基于数组a -- > 3,4
    s2 := a[2:]                             // 切片s2基于数组a  --> 3,4,5,6
    fmt.Printf("%p\n", s1)                  // 0xc00006e040
    fmt.Printf("%p\n", s2)                  // 0xc00006e040
}

如上所示, 切片s1与s2都是基于数组a

输出结果: s1与s2相同, 即: 切片就是数组的第一个元素的地址(所以切片是引用类型)

这就是数组与切片的基于关系: 切片指向它基于的数组的第一个元素

所以修改数组元素的值时, 切片对应元素会跟随变化, 反之也是

 

append与切片容量:

append用于向切片末尾追加元素, 参数为可变参函数

追加后, 如果切片容量不够, 则需要增加容量(切片地址改变)

所以用法类似这样: s  = append(s, 10)  (理解: append后并不知道是否超出容量, 因此需要使用赋值符)

如果没有超出容量, 则不会重新分配内存

func main() {
	var a [8]int = [...]int{1,2,3,4,5,6,7,8}
	s := a[6:]

	fmt.Println(cap(s))          // 容量为:2
	fmt.Printf("%p\n", s)        // 地址为:0xc0000780f0

	s = append(s,10)            // 添加一个元素(超出了容量, 扩容)
	fmt.Println(cap(s))         // 容量为:4
	fmt.Printf("%p\n", s)       // 地址为:0xc0000460c0
	
	
	s = append(s,10)            // 再添加一个元素(没有超出容量, 不扩容)
	fmt.Println(cap(s))         // 容量为:4
	fmt.Printf("%p\n", s)       // 地址为:0xc0000460c0
}

如上所示: 切片容量发生变化时, 会重新分配地址

切片与基于底层数组关系断开(不再基于原底层数组)

即: 如果切片与数组是基于关系(切片指向数组), 会相互影响, 如果断开基于, 则不再相互影响

func main() {   
    a := [...]int{10, 20, 30, 40, 50}
    s1 := a[:]               // s1基于a
    s2 := a[:]               // s2基于a
	
    s1[0] = 100
    fmt.Println(a[0])		// 100, 修改切片会同时修改其底层数组
    fmt.Println(s2[0])		// 100, 修改切片会同时修改同一底层数组的其它切片
	
    /*改变切片容量, 则该切片与原底层数组关系断开*/
    s1 = append(s1, 200)	// 切片s1增加一个元素, 达到扩容
    fmt.Println(cap(s1))        // 10, 容量增加到10
	
    s1[0] = 0
    fmt.Println(a[0])		// 100, s1已与底层数组关系断开
    fmt.Println(s2[0])		// 100
}

切片长度容量获取:

获取切片长度: len(slice)

获取切片容量: cap(slice)

func main() {   
    a := [...]int{10, 20, 30, 40, 50}
    s1 := a[:]
    s2 := a[:]
    s1 = append(s2, 1, 2, 3)         	// 10,20,30,40,50,1,2,3
    var s3 = append(s1, s2...)	        // 10,20,30,40,50,1,2,3,10,20,30,40,50
    println(cap(s3))                    // 20
}

切片复制:copy(t_silice, s_slice)

复制切片要保证目标切片有足够的长度(和容量无关)

func main() {   
    s1 := []int{10, 20, 30, 40, 50}
    s2 := make([]int, 6, 10)
    copy(s2,s1)
    println(s2[4])
}

切片没有插入和删除元素的方法, 可通过子切片操作

例: 去掉切片中值为30的元素

func main() {   
    /*假定目标切片*/
    s1 := []int{10, 20, 30, 40, 50, 15, 30, 77}
	
    /*先循环一遍, 得到有几个值为30的元素*/
    var count = 0;
    for i:= 0; i

数组和切片都可作为函数参数传递:

由于数组是值类型, 切片是引用类型

所以函数接收切片可直接修改内容, 接收数组则不能(需要接收地址才行)

通常函数的参数都会是切片, 而不是数组

func main() {
    // 定义切片
    var array = []int{}
    setArray(array)
 
    // 传入数组
    var array2 = [5]int{1, 2}
    setArray2(array2)
}

func setArray(params []int) {             // 切片作为形参, 不能传入数组
    fmt.Println(len(params))
}
func setArray2(params [5]int) {            // 数组作为形参, 不能传入切片
    fmt.Println(len(params))
}

切片作为函数参数时

如果函数内部修改了此切片某些索引的值,会影响到原切片

但如果在函数内部删除或添加了切片长度,则不会影响到原切片

(我猜想: 删除或添加元素的操作, 可能会使切片容量变化

如果容量变化, 那么地址变化, 则操作的就是新切片

所以删除或添加的操作就干脆不让它影响原切片)

func main() {
    var sli = []int{1,2,3}
    setSlice(sli)
	
    fmt.Println(sli[0])		      // 100, 这个值改变了
    fmt.Println(len(sli))             // 3, 长度还是不变
}

func setSlice(params []int) {
    params[0] = 100                       // 修改第一个元素的值
    fmt.Println(len(params))		  // 长度为3
    params = append(params, 1)   	  // 添加一个元素
    fmt.Println(len(params))		  // 长度变4
}

字符串与切片

字符串本质是字节数组

func main() {   
    var str = "hello world"
    s := str[:5]			
    fmt.Println(s)        // hello, 这样就很方便得到子串
    s[0] = 'A'            // error,不能这样操作, 因为字符串是不能改变内容的
}

深入分析:

字符串底层是一个指针, 指向字节数组

切片是底层也是指针, 指向数组

它们都有长度, 区别就在于一个指向的是byte数组, 一个指向任何数组

str := "hello"        // 长度为5, 指向数组: ['h'   'e'   'l'   'l'   'o']

slice := str[2:]      // 长度为3, 指向数组: ['l'  'l'  'o']

更改字符串中的字符:

func main() {   
    var str = "中ello"
    c := []rune(str)	// 不要使用byte, rune表示字符, 如果是非英文字母使用byte会有问题
    c[0] = 'h'
    fmt.Println(string(c))    // hello
}

因为一个字母表示一个字节, 但是一个非字母就不一定表示一个字节

而rune是可变的, 如果表示英文时是一个字节, 表示中文时是三个字节

 

排序和查找:

导入sort包, 其中有对整数, 字符串, 浮点数排序的函数

func main() {   
   arr := [...]int{1,2,80,-7, 0,2}
   sort.Ints(arr[:])
   fmt.Println(arr)

   arr2 := [...]string{"a", "A", "abc", "fg"}
   sort.Strings(arr2[:])
   fmt.Println(arr2)

   arr3 := [...]float64{2.5, 3.3, 0.6, 9}
   sort.Float64s(arr3[:])
   fmt.Println(arr3)

   a := [...]int{1,2,4,7,0,9,11,2}
   sort.Ints(a[:])				// 需要先排序, 排序后: 0,1,2,2,4,7,9,11
   index := sort.SearchInts(a[:], 11)           // 返回下标值, 从0开始	
   fmt.Println(index)				// 7
}

 

你可能感兴趣的:(go数组与切片)