DAY6 GOLANG (六) 数组与切片

· · 505 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

数组的定义

var 数组名 [ 数组大小]数据类型
var a [5]int

数组必须定义长度,且一个数组不能动态改变长度。不要担心这个限制,切片(slice)可以弥补这个不足

四种初始化数组的方式

var numArr01 [3]int =[3]int{1, 2, 3}
var numArr02 = [3]int{5, 6, 7}
//这里的 [...]是规定的写法
var numArr03 = [...]int{8, 9 ,10}
var numArr04 = [...]int{1: 800, 0: 900, 2:999}

数组是值类型

在 Go 中数组是值类型而不是引用类型。这意味着当数组变量被赋值时,将会获得原数组(:也就是等号右面的数组)的拷贝。新数组中元素的改变不会影响原数组中元素的值。

同样的,如果将数组作为参数传递给函数,仍然是值传递,在函数中对(作为参数传入的)数组的修改不会造成原数组的改变。

数组的长度

内置函数 len 用于获取数组的长度:len(a)

数组的遍历

1.普通方式遍历

func main() {
    a := [...]float64{67.7, 89.8, 21, 78}
    for i := 0; i < len(a); i++ { 
        fmt.Printf("%d th element of a is %.2f\n", i, a[i])
    }
}

2.for-range 结构遍历

func main() {
    a := [...]float64{67.7, 89.8, 21, 78}
    for i, v := range a {
        fmt.Printf("%d the element of a is %.2f\n", i, v)
    }
}

range 返回数组的索引和索引对应的值

多维数组

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)
}

上利用速记声明创建了一个二维数组 a,逗号是必须的,这是因为词法分析器会根据一些简单的规则自动插入分号。
在第 23 行声明了另一个二维数组 b,并通过索引的方式给数组 b 中的每一个元素赋值。这是初始化二维数组的另一种方式。声明的函数 printarray 通过两个嵌套的 range for 打印二维数组的内容。输出为:

lion tiger  
cat dog  
pigeon peacock 

apple samsung  
microsoft google  
AT&T T-Mobile

基本语法
for index ,value := range arry01{
}
index 返回的是数组的下标,value是该下标对应的值。

切片

数组的长度是固定的,没办法动态增加数组的长度。而切片却没有这个限制,切片更灵活,实际上在 Go 中,切片比数组更为常见。切片并不存储任何元素而只是对现有数组的引用。切片[ ]中没数字,数组中[ ]内有数字。

方式一

func main() {  
    a := [5]int{76, 77, 78, 79, 80}
    var b []int = a[1:4] //creates a slice from a[1] to a[3]
    fmt.Println(b)
}

通过 a[start:end] 这样的语法创建了一个从 a[start]a[end -1] 的切片。在上面的程序中,第 9 行 a[1:4] 创建了一个从 a[1]a[3] 的切片。因此 b 的值为:[77 78 79]

方式二

func main() {  
    c := []int{6, 7, 8} //creates and array and returns a slice reference
    fmt.Println(c)
}

在上面的程序中,第 9 行 c := []int{6, 7, 8} 创建了一个长度为 3 的 int 数组,并返回一个切片给 c。

修改切片

切片本身不包含任何数据。它仅仅是底层数组的一个上层表示。对切片进行的任何修改都将反映在底层数组中。

func main() {  
    darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
    dslice := darr[2:5]
    fmt.Println("array before",darr)
    for i := range dslice {
        dslice[i]++
    }
    fmt.Println("array after",darr) 
}

上面程序的第 9 行,我们创建了一个从 darr[2]darr[5] 的切片 dslicefor 循环将这些元素值加 1。执行完 for 语句之后打印原数组的值,我们可以看到原数组的值被改变了。程序输出如下:

array before [57 89 90 82 100 78 67 69 59]  
array after [57 89 91 83 101 78 67 69 59]

当若干个切片共享同一个底层数组时,对每一个切片的修改都会反映在底层数组中


func main() {  
    numa := [3]int{78, 79 ,80}
    nums1 := numa[:] //creates a slice which contains all elements of the array
    nums2 := numa[:]
    fmt.Println("array before change 1",numa)
    nums1[0] = 100
    fmt.Println("array after modification to slice nums1", numa)
    nums2[1] = 101
    fmt.Println("array after modification to slice nums2", numa)
}

可以看到,在第 9 行, numa[:] 中缺少了开始和结束的索引值,这种情况下开始和结束的索引值默认为 0len(numa)。这里 nums1nums2 共享了同一个数组。程序的输出为:

array before change 1 [78 79 80]  
array after modification to slice nums1 [100 79 80]  
array after modification to slice nums2 [100 101 80]

从输出结果可以看出,当多个切片共享同一个数组时,对每一个切片的修改都将会反映到这个数组中。

切片的长度和容量

切片的长度是指切片中元素的个数。切片的容量是指从切片的起始元素开始到其底层数组中的最后一个元素的个数。

func main() {  
    fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
    fruitslice := fruitarray[1:3]
    fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
}

译者注:使用内置函数 cap 返回切片的容量。

func main() {  
    fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
    fruitslice := fruitarray[1:3]
    fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
}

在上面的程序中,创建了一个以 fruitarray 为底层数组,索引从 13 的切片 fruitslice。因此 fruitslice 长度为2

fruitarray 的长度是 7。fruiteslice 是从 fruitarray 的索引 1 开始的。因此 fruiteslice 的容量是从 fruitarray 的第 1 个元素开始算起的数组中的元素个数,这个值是 6。因此 fruitslice 的容量是 6。程序的输出为:length of slice 2 capacity 6。

切片的长度可以动态的改变(最大为其容量)。任何超出最大容量的操作都会发生运行时错误。

用make创建切片

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

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

make 创建的切片的元素值默认为 0 值。上面的程序输出为:[0 0 0 0 0]

追加元素到切片

我们已经知道数组是固定长度的,它们的长度不能动态增加。而切片是动态的,可以使用内置函数 append 添加元素到切片。append 的函数原型为:append(s []T, x ...T) []T

x …T 表示 append 函数可以接受的参数个数是可变的。这种函数叫做变参函数

你可能会问一个问题:如果切片是建立在数组之上的,而数组本身不能改变长度,那么切片是如何动态改变长度的呢?实际发生的情况是,当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组。并将原有数组的元素被拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍(译者注:当超出切片的容量时,append 将会在其内部创建新的数组,该数组的大小是原切片容量的 2 倍。最后 append 返回这个数组的全切片,即从 0 到 length - 1 的切片)。下面的程序使事情变得明朗:

func main() {  
    cars := []string{"Ferrari", "Honda", "Ford"}
    fmt.Println("cars:", cars, "has old length", len(cars), "and capacity", cap(cars)) //capacity of cars is 3
    cars = append(cars, "Toyota")
    fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) //capacity of cars is doubled to 6
}

在上面的程序中,cars 的容量开始时为 3。在第 10 行我们追加了一个新的元素给 cars,并将 append(cars, "Toyota")的返回值重新复制给 cars。现在 cars 的容量翻倍,变为 6。上面的程序输出为:

cars: [Ferrari Honda Ford] has old length 3 and capacity 3  
cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6  

切片的 0 值为 nil。一个 nil 切片的长度和容量都为 0。可以利用 append 函数给一个 nil 切片追加值。

func main() {  
    var names []string //zero value of a slice is nil
    if names == nil {
        fmt.Println("slice is nil going to append")
        names = append(names, "John", "Sebastian", "Vinay")
        fmt.Println("names contents:",names)
    }
}

在上面的程序中 namesnil,并且我们把 3 个字符串追加给 names程序的输出为:

slice is nil going to append  
names contents: [John Sebastian Vinay]

可以使用 ... 操作符将一个切片追加到另一个切片末尾:

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

上面的程序中,在第10行将 fruits 追加到 veggies 并赋值给 food...操作符用来展开切片。程序的输出为:food: [potatoes tomatoes brinjal oranges apples]

切片作为函数参数

可以认为切片在内部表示为如下的结构体:

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

可以看到切片包含长度、容量、以及一个指向首元素的指针。当将一个切片作为参数传递给一个函数时,虽然是值传递,但是指针始终指向同一个数组。因此将切片作为参数传给函数时,函数对该切片的修改在函数外部也可以看到。让我们写一个程序来验证这一点。

func subtactOne(numbers []int) {  
    for i := range numbers {
        numbers[i] -= 2
    }

}
func main() {

    nos := []int{8, 7, 6}
    fmt.Println("slice before function call", nos)
    subtactOne(nos)                               //function modifies the slice
    fmt.Println("slice after function call", nos) //modifications are visible outside

}

在上面的程序中,第 17 行将切片中的每个元素的值减2。在函数调用之后打印切片的的内容,发现切片内容发生了改变。你可以回想一下,这不同于一个数组,对函数内部的数组所做的更改在函数外不可见。上面的程序输出如下:

array before function call [8 7 6]  
array after function call [6 5 4] 

多维切片

同数组一样,切片也可以有多个维度。

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

上面程序的输出如下:

C C++  
JavaScript  
Go Rust

内存优化

切片保留对底层数组的引用。只要切片存在于内存中,数组就不能被垃圾回收。这在内存管理方便可能是值得关注的。假设我们有一个非常大的数组,而我们只需要处理它的一小部分,为此我们创建这个数组的一个切片,并处理这个切片。这里要注意的事情是,数组仍然存在于内存中,因为切片正在引用它。

解决该问题的一个方法是使用 copy 函数 func copy(dst, src []T) int 来创建该切片的一个拷贝。这样我们就可以使用这个新的切片,原来的数组可以被垃圾回收。

func countries() []string {  
    countries := []string{"USA", "Singapore", "Germany", "India", "Australia"}
    neededCountries := countries[:len(countries)-2]
    countriesCpy := make([]string, len(neededCountries))
    copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
    return countriesCpy
}
func main() {  
    countriesNeeded := countries()
    fmt.Println(countriesNeeded)
}

在上面程序中,第 9 行 neededCountries := countries[:len(countries)-2] 创建一个底层数组为 countries 并排除最后两个元素的切片。第 11 行将 neededCountries 拷贝到 countriesCpy 并在下一行返回 countriesCpy。现在数组countries 可以被垃圾回收,因为 neededCountries 不再被引用。

值类型就是现金,要用直接用;引用类型是存折,要用还得先去银行取现。

值类型与引用类型的区别在于值类型的变量直接包含其数据,而引用类型的变量则存储对象引用。

对于引用类型,两个变量可能引用同一个对象,因此对一个变量的操作可能影响另一个变量所引用的对象。
对于值类型,每个变量都有自己的数据副本,对一个变量的操作不可能影响另一个变量。

值类型 : 基本数据类型int、float、bool、string以及数组和struct。
引用类型:指针、slice、map、chan等都是引用类型

值类型:变量直接存储,内存通常在栈中分配。
引用类型:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配。通过GC回收

通常在函数中转入指针效率比较高,因为方法中的参数是需要进行拷贝的,拷贝指针的效率比较高,要是一个大的接口体的话拷贝的效率就比较低

  • 定义:var arr [4] [6]int ---------------------[[0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0 0]]

    赋值 : arr [2] [9]= 3

    遍历一次的话就是遍历出里面的数组

    要是想遍历出所有的元素要进行嵌套循环

    直接赋值: var 数组名 【大小】 【大小】类型=【大小】 【大小】类型{{初值。。。}{初值。。。。}}

    arr := 【】【】int {{123}{356}}

  • 二维数组的遍历

    • for ---- range

      for i , v := range arr{----------- v 是一个一维数组

      for j , v1 := range v{

      ..............................................

      }

      }

    • 双重for循环:

      for i:=0;i<len(arr);i++{

      for j:=0;j<len (arr[ i ]);j++{

      ................................................

      }

      }


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:

查看原文:DAY6 GOLANG (六) 数组与切片

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

505 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传