前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >兼容并蓄广纳百川,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang复合容器类型的声明和使用EP04

兼容并蓄广纳百川,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang复合容器类型的声明和使用EP04

作者头像
用户9127725
发布2022-08-08 08:07:53
3280
发布2022-08-08 08:07:53
举报
文章被收录于专栏:刘悦的技术博客

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

    数组

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

    我们可以使用 [n]Type 来声明一个数组。其中 n 表示数组中元素的数量, Type 表示每个元素的类型:

代码语言:javascript
复制
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)
}

    程序返回:

代码语言:javascript
复制
[0 0 0 0 0]
[1 2 3 0 0]

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

代码语言:javascript
复制
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函数,可以获得数组的长度:

代码语言:javascript
复制
package main

import "fmt"

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

}

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

代码语言:javascript
复制
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关键字进行遍历操作:

代码语言:javascript
复制
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()
}

    程序返回:

代码语言:javascript
复制
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 方法将返回数组中元素的个数,即数组的长度。

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

    可以这么理解,go lang的数组是元素值可以改变但长度不变的元祖(tuple)。

    切片(Slice)

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

    使用 []Type 可以创建一个带有 Type 类型元素的切片:

代码语言:javascript
复制
// 声明整型切片
var numList []int

// 声明一个空切片
var numListEmpty = []int{}

    如果愿意,也可以使用 make 方法构造一个切片,格式为 make([]Type, size, cap) :

代码语言:javascript
复制
numList := make([]int, 3, 5)

    切片之所以称之为切片,是因为我们可以对数组进行裁切,从而创建一个切片:

代码语言:javascript
复制
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)

}

    程序返回:

代码语言:javascript
复制
[1 2 3 4 5]
[2 3 4]

    一个切片由三个部分构成:指针 、长度 和 容量 。指针指向第一个切片元素对应的底层数组元素的地址,要注意的是切片的第一个元素并不一定就是数组的第一个元素。长度对应切片中元素的数目;长度不能超过容量,容量一般是从切片的开始位置到底层数据的结尾位置。简单地理解,容量就是从创建切片索引开始的底层数组中的元素个数,而长度是切片中的元素个数:

代码语言:javascript
复制
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中列表的下标越界异常:

代码语言:javascript
复制
s := make([]int, 3, 5)
fmt.Println(s[10]) //panic: runtime error: index out of range [10] with length 3

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

代码语言:javascript
复制
var numList []int
fmt.Println(numList == nil) // true

    切片之间不能比较,因此我们不能使用恒等(== )操作符来判断两个切片是否含有全部相等元素。特别注意,如果你需要测试一个切片是否是空的,使用 len(s) == 0 来判断,而不应该用 s == nil 来判断:

    切片自己不拥有任何数据元素。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中:

代码语言:javascript
复制
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)

}

    程序返回:

代码语言:javascript
复制
[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 的个数是可变的。这些类型的函数被称为可变函数:

代码语言:javascript
复制
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))

}

    程序返回:

代码语言:javascript
复制
[1]
1
[1 2]
2
[1 2 3 4]
4
[1 2 3 4 5 6]
8

    当新的元素被添加到切片时,如果容量不足,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回新的引用。现在新切片的容量是旧切片的两倍。

    切片也可以有多个维度,也就是嵌套的形式:

代码语言:javascript
复制
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) 。

代码语言:javascript
复制
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 :

代码语言:javascript
复制
package main

import "fmt"

func main() {

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

}

    亦或者使用海象操作符:

代码语言:javascript
复制
package main

import "fmt"

func main() {

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

}

    动态地添加新元素:

代码语言:javascript
复制
// 可以使用 `map[key] = value` 向 map 添加元素。
steps3["4"] = "4"

    修改元素:

代码语言:javascript
复制
// 若 key 已存在,使用 map[key] = value 可以直接更新对应 key 的 value 值。
steps3["4"] = "第四步"

    取值:

代码语言:javascript
复制
// 直接使用 map[key] 即可获取对应 key 的 value 值,如果 key不存在,会返回其 value 类型的零值。
fmt.Println(steps3["4"] )

    删除某个key:

代码语言:javascript
复制
//使用 delete(map, key)可以删除 map 中的对应 key 键值对,如果 key 不存在,delete 函数会静默处理,不会报错。
delete(steps3, "4")

    判断 key 是否存在:

代码语言:javascript
复制
// 如果我们想知道 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 。

    集合也可以进行遍历操作:

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

    同样使用len方法来获取集合的长度:

代码语言:javascript
复制
// 使用 len 函数可以获取 map 长度
func createMap() {
  	//...
     fmt.Println(len(steps3))    // 4
}

    当集合被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。所以,集合是引用数据类型:

代码语言:javascript
复制
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)

}

    程序返回:

代码语言:javascript
复制
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中的可变和不可变数据类型,由此可见,天下武功,同归殊途,万法归宗,万变不离其宗。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  •     数组
  •     切片(Slice)
  •     集合(Map)
  •     结语
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档