乐趣区

关于go:兼容并蓄广纳百川Go-lang118入门精炼教程由白丁入鸿儒go-lang复合容器类型的声明和使用EP04

书接上回,容器数据类型是指一种数据结构、或者抽象数据类型,其实例为其余类的对象。或者说得更具体一点,它是以一种遵循特定拜访规定的办法来存储对象。容器的大小取决于其蕴含的根底数据对象(或数据元素)的个数。Go lang 中罕用的容器数据有数组、切片和汇合。

数组

数组是一个由长度固定的特定类型元素组成的序列,一个数组能够由零个或多个元素组成,它是一种线性的数据结构,同时外部元素的内存地址是相连的,没错,Python 中的元祖(tuple)和 Go lang 中的数组就是一类货色,因为定长的个性,所以在系统资源占用层面具备肯定的劣势。

咱们能够应用 [n]Type 来申明一个数组。其中 n 示意数组中元素的数量,Type 示意每个元素的类型:

package main  
  
import "fmt"  
  
func main() {  
    // 申明时没有指定数组元素的值, 默认为零值  
    var arr [5]int  
    fmt.Println(arr)  
  
    arr[0] = 1  
    arr[1] = 2  
    arr[2] = 3  
    fmt.Println(arr)  
}

程序返回:

[0 0 0 0 0]  
[1 2 3 0 0]

除此之外,也可通过海象操作符等形式进行申明:

package main  
  
import "fmt"  
  
func main() {  
    // 间接在申明时对数组进行初始化  
    var arr1 = [5]int{15, 20, 25, 30, 35}  
    fmt.Println(arr1)  
  
    // 应用短申明  
    arr2 := [5]int{15, 20, 25, 30, 35}  
    fmt.Println(arr2)  
  
    // 局部初始化, 未初始化的为零值  
    arr3 := [5]int{15, 20} // [15 20 0 0 0]  
    fmt.Println(arr3)  
  
    // 能够通过指定索引,不便地对数组某几个元素赋值  
    arr4 := [5]int{1: 100, 4: 200}  
    fmt.Println(arr4) // [0 100 0 0 200]  
  
    // 间接应用 ... 让编译器为咱们计算该数组的长度  
    arr5 := [...]int{15, 20, 25, 30, 35, 40}  
    fmt.Println(arr5)  
  
    // 定义多维数组  
    arr := [3][2]string{{"1", "10"},  
        {"2", "3"},  
        {"3", "4"}}  
    fmt.Println(arr) // [[15 20] [25 22] [25 22]]  
  
    // 数组取值  
  
    fmt.Println(arr[0][0])  
  
}

同时数组反对嵌套,也就是多维数组构造,最初通过数组的下标进行取值操作。

通过将数组作为参数传递给 len 函数,能够取得数组的长度:

package main  
  
import "fmt"  
  
func main() {a := [...]float64{67.7, 89.8, 21, 78}  
    fmt.Println("length of a is",len(a))  
  
}

数组是值类型,而不是援用类型。这意味着当它们被调配给一个新变量时,将把原始数组的正本调配给新变量。如果对新变量进行了更改,则不会影响原对象,这有点像 Python 中的可变以及不可变数据类型,原理都是一样的:

package main  
  
import "fmt"  
  
func main() {a := [...]string{"USA", "China", "India", "Germany", "France"}  
    b := a // a copy of a is assigned to b  
    b[0] = "Singapore"  
    fmt.Println("a is", a)  
    fmt.Println("b is", b)  
}

和 Python 中元祖不同的是,数组元素的值能够扭转,然而元素成员不能增减,最初数组能够应用 for 关键字或者 range 关键字进行遍历操作:



package main  
  
import "fmt"  
  
func test() {a := [...]float64{67.7, 89.8, 21, 78}  
    sum := float64(0)  
    for i, v := range a { //range returns both the index and value  
        fmt.Printf("%d the element of a is %.2f\n", i, v)  
        sum += v  
    }  
    fmt.Println("\nsum of all elements of a", sum)  
  
}  
  
func main() {a := [...]float64{67.7, 89.8, 21, 78}  
    for i := 0; i < len(a); i++ { //looping from 0 to the length of the array  
        fmt.Printf("%d th element of a is %.2f\n", i, a[i])  
    }  
  
    test()}  


程序返回:



0 th element of a is 67.70  
1 th element of a is 89.80  
2 th element of a is 21.00  
3 th element of a is 78.00  
0 the element of a is 67.70  
1 the element of a is 89.80  
2 the element of a is 21.00  
3 the element of a is 78.00  
  
sum of all elements of a 256.5

应用内置的 len 办法将返回数组中元素的个数,即数组的长度。

func arrLength() {arr := [...]string{"Go 语言极简一本通", "Go 语言微服务架构外围 22 讲", "从 0 到 Go 语言微服务架构师"}  
    fmt.Println("数组的长度是:", len(arr)) // 数组的长度是: 3  
}

能够这么了解,go lang 的数组是元素值能够扭转但长度不变的元祖(tuple)。

切片(Slice)

切片是对数组的一个间断片段的援用,所以切片是一个援用类型。切片自身不领有任何数据,它们只是对现有数组的援用,每个切片值都会将数组作为其底层的数据结构。slice 的语法和数组很像,只是没有固定长度而已。

应用 []Type 能够创立一个带有 Type 类型元素的切片:

// 申明整型切片  
var numList []int  
  
// 申明一个空切片  
var numListEmpty = []int{}

如果违心,也能够应用 make 办法结构一个切片,格局为 make([]Type, size, cap):

numList := make([]int, 3, 5)

切片之所以称之为切片,是因为咱们能够对数组进行裁切,从而创立一个切片:

package main  
  
import "fmt"  
  
func main() {arr := [5]int{1, 2, 3, 4, 5}  
    var s1 = arr[1:4]  
    fmt.Println(arr)  
    fmt.Println(s1)  
  
}

程序返回:

[1 2 3 4 5]  
[2 3 4]

一个切片由三个局部形成:指针、长度 和 容量。指针指向第一个切片元素对应的底层数组元素的地址,要留神的是切片的第一个元素并不一定就是数组的第一个元素。长度对应切片中元素的数目;长度不能超过容量,容量个别是从切片的开始地位到底层数据的结尾地位。简略地了解,容量就是从创立切片索引开始的底层数组中的元素个数,而长度是切片中的元素个数:

package main  
  
import "fmt"  
  
func main() {s := make([]string, 3, 5)  
    fmt.Println(len(s)) // 3  
    fmt.Println(cap(s)) // 5  
  
}

由上可知,切片内置的 len 办法和 cap 办法别离返回切片 的长度和容量。

如果切片操作超出下限将导致一个 panic 异样,有点像 Python 中列表的下标越界异样:

s := make([]int, 3, 5)  
fmt.Println(s[10]) //panic: runtime error: index out of range [10] with length 3

切片是援用类型,所以申明之后不对它进行赋值的话,它的默认值是 nil

var numList []int  
fmt.Println(numList == nil) // true

切片之间不能比拟,因而咱们不能应用恒等(==)操作符来判断两个切片是否含有全副相等元素。特地留神,如果你须要测试一个切片是否是空的,应用 len(s) == 0 来判断,而不应该用 s == nil 来判断:

切片本人不领有任何数据元素。它只是底层数组的一种示意。对切片所做的任何批改都会反映在底层数组中:

package main  
  
import "fmt"  
  
func main() {var arr = [...]int{1, 2, 3}  
    s := arr[:]  
    fmt.Println(arr)  
    fmt.Println(s)  
  
    s[0] = 4  
    fmt.Println(arr)  
    fmt.Println(s)  
  
}

程序返回:

[1 2 3]  
[1 2 3]  
[4 2 3]  
[4 2 3]

下面的 arr[:] 没有填入起始值和完结值,默认就是 0 和 len(arr)。

应用 append 办法能够将新元素追加到切片上,这和 Python 中的列表办法一模一样,append 办法的定义是 func append(slice []Type, elems …Type) []Type。其中 elems …Type 在函数定义中示意该函数承受参数 elems 的个数是可变的。这些类型的函数被称为可变函数:

package main  
  
import "fmt"  
  
func main() {s := []int{1}  
    fmt.Println(s)  
    fmt.Println(cap(s))  
  
    s = append(s, 2)  
    fmt.Println(s)  
    fmt.Println(cap(s))  
  
    s = append(s, 3, 4)  
    fmt.Println(s)  
    fmt.Println(cap(s))  
  
    s = append(s, []int{5, 6}...)  
    fmt.Println(s)  
    fmt.Println(cap(s))  
  
}

程序返回:

[1]  
1  
[1 2]  
2  
[1 2 3 4]  
4  
[1 2 3 4 5 6]  
8

当新的元素被增加到切片时,如果容量有余,会创立一个新的数组。现有数组的元素被复制到这个新数组中,并返回新的援用。当初新切片的容量是旧切片的两倍。

切片也能够有多个维度,也就是嵌套的模式:

package main  
  
import "fmt"  
  
func main() {numList := [][]string{{"1", "1"},  
        {"2", "2"},  
        {"3", "3"},  
    }  
    fmt.Println(numList)  
  
}

汇合(Map)

在 Go lang 中,汇合是散列表 (哈希表) 的援用。它是一个领有键值对元素的无序汇合,在这个汇合中,键是惟一的,能够通过键来获取、更新或移除操作。无论这个散列表有多大,这些操作基本上是通过常量工夫实现的。所有可比拟的类型,如整型,字符串等,都能够作为 key。

应用 make 办法传入键和值的类型,能够创立汇合。具体语法为 make(map[KeyType]ValueType)。

package main  
  
import "fmt"  
  
func main() {  
  
    // 创立一个键类型为 string 值类型为 int 名为 scores 的 map  
    scores := make(map[string]int)  
    steps := make(map[string]string)  
  
    fmt.Println(scores)  
    fmt.Println(steps)  
  
}

也能够用汇合字面值的语法创立汇合,同时还能够指定一些最后的 key/value:

package main  
  
import "fmt"  
  
func main() {var steps2 map[string]string = map[string]string{  
        "1": "1",  
        "2": "2",  
        "3": "3",  
    }  
    fmt.Println(steps2)  
  
}

亦或者应用海象操作符:

package main  
  
import "fmt"  
  
func main() {steps3 := map[string]string{  
        "1": "1",  
        "2": "2",  
        "3": "3",  
    }  
    fmt.Println(steps3)  
  
}

动静地增加新元素:

// 能够应用 `map[key] = value` 向 map 增加元素。steps3["4"] = "4"

批改元素:

// 若 key 已存在,应用 map[key] = value 能够间接更新对应 key 的 value 值。steps3["4"] = "第四步"

取值:

// 间接应用 map[key] 即可获取对应 key 的 value 值, 如果 key 不存在, 会返回其 value 类型的零值。fmt.Println(steps3["4"] )

删除某个 key:

// 应用 delete(map, key)能够删除 map 中的对应 key 键值对, 如果 key 不存在,delete 函数会静默解决,不会报错。delete(steps3, "4")

判断 key 是否存在:

// 如果咱们想晓得 map 中的某个 key 是否存在,能够应用上面的语法:value, ok := map[key]  
v3, ok := steps3["3"]  
fmt.Println(ok)  
fmt.Println(v3)  
  
v4, ok := steps3["4"]  
fmt.Println(ok)  
fmt.Println(v4)

这个逻辑阐明汇合的下标读取能够返回两个值,第一个值为以后 key 的 value 值,第二个值示意对应的 key 是否存在,若存在 ok 为 true,若不存在,则 ok 为 false。

汇合也能够进行遍历操作:

// 遍历 map 中所有的元素须要用 for range 循环。for key, value := range steps3 {fmt.Printf("key: %s, value: %d\n", key, value)  
}

同样应用 len 办法来获取汇合的长度:

// 应用 len 函数能够获取 map 长度  
func createMap() {  
      //...  
     fmt.Println(len(steps3))    // 4  
}

当汇合被赋值为一个新变量的时候,它们指向同一个外部数据结构。因而,扭转其中一个变量,就会影响到另一变量。所以,汇合是援用数据类型:

package main  
  
import "fmt"  
  
func main() {steps4 := map[string]string{  
        "1": "1",  
        "2": "2",  
        "3": "3",  
    }  
    fmt.Println("steps4:", steps4)  
  
    newSteps4 := steps4  
    newSteps4["1"] = "1.1-222"  
    newSteps4["2"] = "2.2-222"  
    newSteps4["3"] = "3.3-222"  
    fmt.Println("steps4:", steps4)  
  
    fmt.Println("newSteps4:", newSteps4)  
  
}

程序返回:

steps4:  map[1:1 2:2 3:3]  
steps4:  map[1:1.1-222 2:2.2-222 3:3.3-222]  
newSteps4:  map[1:1.1-222 2:2.2-222 3:3.3-222]

所以,当须要保留原对象数据做其余操作时,最好不要应用赋值,而是提前申明新容器。同理,当汇合作为参数传递到办法内时,办法对其做了批改操作,也会影响原汇合对象。

结语

在业务代码的编写上,咱们常常会查问来自数据库的源数据,再把它们插入到对应的复合数据类型构造中去,再进行下一步的业务聚合、裁剪、封装、解决,而后返回到前端,进行渲染操作。大体上,咱们会抉择数组、切片还有汇合,个别状况下最内部是切片或者是数组,而后内嵌汇合的数据集,汇合内 key 作为字段,value 作为字段的值。在操作上,须要留神值类型(数组)和援用类型(切片、汇合)的区别:值类型的特点是:变量间接存储值,内存通常在栈中调配;援用类型的特点是:变量存储的是一个地址,这个地址对应的空间里才是真正存储的值,内存通常在堆中调配,说白了就是值类型赋值后批改不会影响原对象,而援用类型反之,有点像 Python 中的可变和不可变数据类型,由此可见,天下文治,同归殊途,万法归宗,万变不离其宗。

退出移动版