Go语言学习记录4——数组、切片和变参函数

一.数组

1.1 数组的声明

1.1.1 常规声明

var a [3]Type
例如:

package main

import (  
    "fmt"
)

func main() {  
    var a [3]int
    fmt.Println(a)
}
/* outputs:
[0 0 0]
*/

数组默认为了[0 0 0]

1.1.2 短变量形式的初始化

刚才那样的初始化可以用该语句代替:
a := [3]int{0, 0, 0}当然你可以填写需要的值
若后面的常量并没有包含全部的Size,那么还是会由0代替:a := [3]int{12}则生成的是[12 0 0]
若初始化数组的常量是简单的,那么可以用...让编译器去推导sizea := [3]int{0, 0, 0},此时a还是一个长度为3的数组。

1.2 数组是一种特殊的类型

目前所用用到的数组都是采用的一个实例化的变量,而并非引用,如同这样:

package main

import "fmt"

func main() {  
    a := [...]int{1, 2, 3, 4}
    b := a
    b[0] = 2
    fmt.Println("a is ", a)
    fmt.Println("b is ", b) 
}
/* outputs:
a is  [1 2 3 4]
b is  [2 2 3 4]
*/

1.3 数组的遍历

1.3.1 常规方法

一般所有数组都通用len获取数组长度的方法,通过遍历下标去遍历数组:

package main

import "fmt"

func main() {  
    a := [...]int{4, 3, 2, 1}
    for i := 0; i < len(a); i++ {
        fmt.Printf("No.%d %d\n", i, a[i])
    }
}

1.3.2 range方法

通过这种类似于迭代器的方式去遍历数组,也可以得到和上例相同的结果:

package main

import "fmt"

func main() {  
    a := [...]int{4, 3, 2, 1}
    for index, value := range a{
        fmt.Printf("No.%d %d\n", index, value)
    }
}

如果你只需要indexvalue中的一个,那么用空标识符_代替不需要的那个元素:
例如,我只需要值:for _, value := range a { 或者 for index := range a {,且这个indexvalue均为拷贝

1.4 二维数组

二维数组类似于其他语言,那么看这个example即可:

package main

import (  
    "fmt"
)

func printarray(a [3][2]string) {  
    for _, v1 := range a {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}

func main() {  
    a := [3][2]string{
        {"lion", "tiger"},
        {"cat", "dog"},
        {"pigeon", "peacock"}, //this comma is necessary. The compiler will complain if you omit this comma
    }
    printarray(a)
    var b [3][2]string
    b[0][0] = "apple"
    b[0][1] = "samsung"
    b[1][0] = "microsoft"
    b[1][1] = "google"
    b[2][0] = "AT&T"
    b[2][1] = "T-Mobile"
    fmt.Printf("\n")
    printarray(b)
}

二.切片

这里的切片又不是Python或者·matlab那样的切片,Go语言的切片是对原数组的部分的引用!!!
引用!!!

2.1 创建切片

2.1.1 已知数组创建切片

切片是左闭右开,实际引用的范围应该是[start, end - 1]

package main

import (  
    "fmt"
)

func main() {  
    a := [5]int{76, 77, 78, 79, 80}
    var b []int = a[1:4]
    fmt.Println(b)
}
/* outputs:
[77 78 79]
*/

2.1.2 隐形创建一个数组并返回其切片

当我们省略掉初始化数组的长度时:

veggies := []string{"potatoes","tomatoes","brinjal"}

编译器会给我们创建一个数组,并返回其切片

2.2 修改切片

因为是引用,所以对切片的修改会作用到原数组:

package main

import (  
    "fmt"
)

func main() {  
    a := [5]int{76, 77, 78, 79, 80}
	var b []int = a[1:4]
	fmt.Println("A_before is ", a)
	b[1]++
    fmt.Println("A_after  is ", a)
}
/* outputs
A_before is  [76 77 78 79 80]
A_after  is  [76 77 79 79 80]
*/

2.3 切片的长度与容量

package main

import (  
    "fmt"
)

func main() {  
    a := [5]int{76, 77, 78, 79, 80}
	var b []int = a[1:3]
	fmt.Printf("length of slice %d capacity %d\n", len(b), cap(b))
}
/* outputs
length of slice 2 capacity 4
*/

然后我们把var b []int = a[1:3]改为var b []int = a[1:4],此时的输出则是:length of slice 3 capacity 4
继续我们把var b []int = a[1:4]改为var b []int = a[2:4],此时的输出则是:length of slice 2 capacity 3
我们可以看到:

  • 切片的长度是由切片引用的长度决定的
  • 切片的容量是原数组的len减去切片起始的下标,代表切片的最大长度,这样就比较好理解了。

2.4 修改切片长度

通过再次对切片赋值,我们可以实现切片的长度修改,但是永远不能访问到第一次创建切片之外的元素

package main

import (  
    "fmt"
)

func main() {  
    a := [5]int{76, 77, 78, 79, 80}
	var b []int = a[1:3]
	fmt.Println("B is ", b)
	b = b[0:1]
	fmt.Println("B is ", b)
	b = b[0:4]
	fmt.Println("B is ", b)
}
/* outputs
B is  [77 78]
B is  [77]
B is  [77 78 79 80]
*/

例如这个例子,我们可以访问到77(包括77)后面的所有元素,但是并不能访问到76

2.5 使用make创建切片

func make([]T, len, cap) []T可以用来创建切片,该函数接受长度和容量作为参数,返回切片。容量是可选的,默认与长度相同。使用 make 函数将会创建一个数组并返回它的切片。

package main

import (  
    "fmt"
)

func main() {  
    i := make([]int, 5, 5)
    fmt.Println(i)
}

2.6 追加元素到切片

2.6.1 追加一个元素

使用append追加元素到切片,其用法为:append(s []T, x ...T) []T
先看这段代码:

package main

import (  
    "fmt"
)

func main() {  
	a := [5]int{76, 77, 78, 79, 80}
	var b []int= a[:]
	b[0] = 1 
	fmt.Println("A is ", a)
	fmt.Println("B is ", b, "cap is", cap(b))
	b = append(b, 81)
	b[0] = 0 
	fmt.Println("B is ", b, "cap is", cap(b))
	fmt.Println("A is ", a)
}
/* outputs
A is  [1 77 78 79 80]
B is  [1 77 78 79 80] cap is 5
B is  [0 77 78 79 80 81] cap is 10
A is  [1 77 78 79 80]
*/

好奇怪,为什么此时的B又不是A的引用了?
因为:
append超出切片的容量时,append 将会在其内部创建新的数组,该数组的大小是原切片容量的 2 倍。最后 append 返回这个数组的全切片,即从 0 到 length - 1 的切片)
真是好奇怪的特性,对其他语言的使用者非常不友好

2.6.2 追加一个切片

如果我们要合并两个切片,我们可以使用...且鉴于go的特性,我们就不创建数组了:

package main

import (  
    "fmt"
)

func main() {  
    veggies := []string{"potatoes","tomatoes","brinjal"}
    fruits := []string{"oranges","apples"}
    food := append(veggies, fruits...)
    fmt.Println("food:",food)
}
/* outputs
food: [potatoes tomatoes brinjal oranges apples]
*/

2.7 切片作为参数

切片的结构可以看做:

type slice struct {  
    Length        int
    Capacity      int
    ZerothElement *byte
}

可以看到切片包含长度、容量、以及一个指向首元素的指针。所以我们可以通过这种方式传递实参

package main

import (  
    "fmt"
)
func add (vals []int) {
	for index := range vals {
		vals[index]++
	}
}
func main() {  
	a := [5]int{76, 77, 78, 79, 80}
	var b []int= a[:]
	fmt.Println("A is ", a)
	add(b)
	fmt.Println("A is ", a)
}
/* outputs
A is  [76 77 78 79 80]
A is  [77 78 79 80 81]
*/

2.8 多维切片

还是用一个例子表示:

package main

import (  
    "fmt"
)


func main() {  
     pls := [][]string {
            {"C", "C++"},
            {"JavaScript"},
            {"Go", "Rust"},
            }
    for _, v1 := range pls {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}
/* outputs
C C++  
JavaScript  
Go Rust  
*/

2.9 内存优化

因为数组只是引用,不管你怎么引用,数组一直都在内存里。如果我们只需要这个数组的一部分,那么我们可以使用copy(dst, src []T) int来拷贝一个数组,然后触发回收机制,把原来那个长数组回收。

package main

import (  
    "fmt"
)
func add (vals []int) {
	for index := range vals {
		vals[index]++
	}
}
func main() {  
	a := [5]int{76, 77, 78, 79, 80}
	var b []int= a[1:3]
	c := make([]int, len(b))
	copy(c, b)
	c[1] = 2
	fmt.Println("C is ", c)
	fmt.Println("A is ", a)
}
/* outputs
C is  [77 2]
A is  [76 77 78 79 80]
*/

理论上,如果不输出,此时的a所使用的内存已经会被回收。

三.可变参数

变参函数是指可以接收可变数量的参数的函数。
如果一个函数的最后一个参数由 ...T 表示,则表示该函数可以接收任意数量的类型为 T 的参数。
例如:

package main

import (  
    "fmt"
)

func find(num int, nums ...int) {  
    fmt.Printf("type of nums is %T\n", nums)
    found := false
    for i, v := range nums {
        if v == num {
            fmt.Println(num, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {  
    find(89, 89, 90, 95)
    find(45, 56, 67, 45, 90, 109)
    find(78, 38, 56, 98)
    find(87)
}
/* outputs
type of nums is []int
89 found at index 0 in [89 90 95]

type of nums is []int
45 found at index 2 in [56 67 45 90 109]

type of nums is []int
78 not found in  [38 56 98]

type of nums is []int
87 not found in  []
*/
上一篇:Go语言编程从入门到精通(包的使用、导入、安装、更新、创建)


下一篇:go1.18beta1 泛型demo: hashmap