DAY6 GOLANG (六) 数组与切片

数组的定义

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}

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

arr := [ ] [ ] int {{123}{356}}

数组是值类型

在 Go 中数组是值类型而不是引用类型。这意味着当数组变量被赋值时,将会获得原数组(:也就是等号右面的数组)的拷贝。新数组中元素的改变不会影响原数组中元素的值。
值类型就是现金,要用直接用;引用类型是存折,要用还得先去银行取现。
值类型与引用类型的区别在于值类型的变量直接包含其数据,而引用类型的变量则存储对象引用。

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

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

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

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

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

数组的长度

内置函数 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是该下标对应的值。

  • 二维数组的遍历

    • 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++{

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

      }

      }

切片

数组的长度是固定的,没办法动态增加数组的长度。而切片却没有这个限制,切片更灵活,实际上在 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 不再被引用。

系列文章来源于网络和本人总结,只作学习备忘使用。
本文参考:https://blog.csdn.net/u011304970/article/details/74938457

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容