go语言编程系列(七)

简介: go语言编程系列(七)

Go语言中的数组和切片

复合数据类型

在计算机科学中,复合类型是一种数据类型,它可以原始类型和其他的复合类型所构成,构成一个复合类型的动作,又称为组合。

复合数据类型主要有:

①、数组

②、Slice

③、Map

④、结构体

⑤、JSON

数组和结构体都是有固定内存大小的数据结构。

在复合数据类型中数组是由同构的元素组成--每个数组元素都是完全相同的类型--结构体则是异构的元素组成的。

相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。

数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。在Go中,数组是一种固定长度,类型相同的数据结构,数组可以包含任何类型的元素,但是它们的长度在定义时就必须确定,并且无法动态改变。

以下是一个数组的案例:


package mainimport "fmt"func ArrayDemo() {  // 定义一个包含五个整数的数组  var a [5]int  // 打印数组的值  fmt.Println(a)  // 修改数组中的元素  a[2] = 3  fmt.Println(a)  fmt.Println(a[2])  // 定义并初始化一个数组  b := [3]string{"hello", "world", "!"}  fmt.Println(b)  // 遍历数组并打印每个元素  for i, x := range b {  fmt.Printf("b[%d]: %s\n", i, x) }}

执行结果如下:


[0 0 0 0 0][0 0 3 0 0]3[hello world !]b[0]: hellob[1]: worldb[2]: !

在该示例中,我们首先使用var关键字定义了一个包含五个整数的数组a,由于没有显示初始化数组中的元素,因此它们都被自动初始化零值。

我们通过fmt.println函数打印了这个数组的值,并修改了其中的一个元素,并再次打印这个数组的值和一个特定的元素。

接下来,我们使用短变量声明语法定义了一个包含三个字符串的数组b,并通过花括号进行了初始化。我们遍历了数组b并通过fmt.printf函数打印了每个元素的索引和值。

需要注意的是,在使用数组时,我们需要特别注意数组的长度和元素类型,并尽可能地遵循最佳实践,以确保代码的正确性和可读性。同时,我们也需要注意数组在内存中的分布和访问方式,以便在处理大型数据集合高性能应用程序时保持最佳实践。

数组的定义

数组的每个元素都被初始化为元素类型的零值,对于数字类型来说就是0.


var m [3]int = [3]int{1, 2, 3}var n [3]int = [3]int{1, 2}fmt.Println(n[2]) // "0"

在数组字面值中,如果在数组的长度位置出现的是"..."省略号,则表示住宿的长度是根据初始化值的个数来计算。


m := [...]int{1, 2, 3}fmt.Printf("%T\n", m) // "[3]int"

数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型,数组的长度必须是常量表达式,因此数组的长度需要在编译阶段确定。

数组可以直接进行比较,当数组内的元素都一样的时候表示两个数组相等。


arr1 := [3]int{1, 2, 3}arr2 := [3]int{1, 2, 3}arr3 := [3]int{1, 2, 4}fmt.Println(arr1 == arr2, arr1 == arr3) //true,false

元素的访问

数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。内置的len函数将返回数组中元素的个数。

数组作为参数

数组可以作为函数的参数传入,但由于数组在作为参数的时候,其实是进行了拷贝,这样在函数内部改变数组的值,是不影响到外面数组的值


func ArrIsArgs(arr [4]int) {  arr[0] = 120}m := [...]int{1, 2, 3, 4}ArrIsArgs(m)

如果想要改变外部数组的值,就只能使用指针。

使用指针,在函数内部改变的数组的值,也会改变外面数组的值


func ArrIsArgs(arr *[4]int) {   arr[0] = 20}m:= [...]int{1, 2, 3, 4}ArrIsArgs(&m)

这里的*和&的区别:

①、&是取地址符号,即取得某个变量的地址,如:&a

②、*是指指针类型变量定义,可以表示一个变量是指针类型,也可以表示一个指针变量所指向的存储单元,也就是这个地址所存储的值。

通常这样的情况下都是用切片来解决,而不是用数组。

由于数组的长度是固定的,因而在使用的时候我们用的最多的是slice(切片),它是可以增长和收缩动态序列,slice功能也更灵活。

Slice

Slice(切片)代表变长的序列,序列中的每个元素都有相同的类型

一个Slice类型一般写作[]T,其中T代表Slice中元素的类型

Slice的语法和数组很像,只是没有固定长度而已

数组和Slice关系非常密切,一个Slice可以访问数组的部分或者全部数据,而且Slice的底层本身就是对数组的引用。

从数组或切片生成新的切片

切片默认指向一段连续的内存区域,可以是数组,也可以是切片本身。

从连续内存区域生成切片是常见的操作,格式如下:


// slice:表示目标切片对象// 开始位置:对应目标切片对象的索引// 结束位置:对应目标切片的结束索引slice [开始位置 : 结束位置]

一个slice分为三个部分组成:指针,长度和容量

①、内置的len和cap函数可以分别返回slice的长度和容量

②、指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice第一个元素并不一定就是数组的第一个元素

③、长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice开始位置到底层数组的结尾位置,内置的len和cap函数分别返回slice的长度和容量。

一个slice的案例

在Go中,切片是一个动态长度的,可变长度数据结构,它基于数组实现,但是可以动态增加或减少其长度,并且支持各种强大的操作和函数。

以下是一个基本的切片定义和使用方式:


func SliceDemo() {  // 定义一个包含五个整数的数组  a := [5]int{1, 2, 3, 4, 5}  // 声明一个从数组 a 中获取的切片  s := a[1:4]  // 打印切片的值和长度  fmt.Println(s)  fmt.Println(len(s))  // 修改切片中的元素  s[1] = 10  fmt.Println(s)  fmt.Println(a)  // 使用 make 函数创建一个新的切片  b := make([]int, 3)  fmt.Println(b)  // 向切片中添加新的元素  b = append(b, 4, 5, 6)  fmt.Println(b)  // 遍历切片并打印每个元素  for i, x := range b {  fmt.Printf("b[%d]: %d\n", i, x) }}

执行结果


[2 3 4]3[2 10 4][1 2 10 4 5][0 0 0][0 0 0 4 5 6]b[0]: 0b[1]: 0b[2]: 0b[3]: 4b[4]: 5b[5]: 6

在该示例中,首先定义了一个包含五个整数的数组a,并通过[1:4]的方式声明了一个从数组a中获取切片s,通过fmt.println函数打印了这个切片的值和长度,并修改了其中一个元素,查看了其对原数组的影响。

接下来,使用了make函数创建了一个新的切片b,并通过append函数向其中添加了三个新元素,遍历了切片b并通过fmt.printf函数打印了每个元素的索引和值。

从指定范围中生成切片

切片有点像C语言里的指针,指针可以做运算,但是代价是内存操作越界。

切片在指针的基础上增加了大小,约束了切片对应的内存区域,切片使用中无法对切片内部的地址和大小进行手动调整,因此切片比指针更安全,强大。

从数组生成切片,代码如下:


var a = [3]int{1, 2, 3}fmt.Println(a, a[1:2])// []int

其中a是一个拥有3个整型元素的数组,被初始化为数值1到3,使用a[1:2]可以生成一个新的切片,代码运行结果如下:


[1 2 3] [2]

其中[2]就是a[1:2]切片操作的结果

切片和数组密不可分,如果将数组理解为一栋办公楼,那么切片就是把不同的连续楼层出租给使用者, 出租的过程需要选择开始楼层和结束楼层,这个过程就会生成切片,表示原有的切片。

生成切片的格式中,当开始和结束位置都被忽略时,生成的切片将表示和原切片一致的切片,并且生成的切片与原切片在数据内容上也是一致的,代码如下:


a := []int{1, 2, 3}fmt.Println(a[:])

a是一个拥有3个元素的切片,将a切片使用a[:]进行操作后,得到的切片与a切片一样,,代码输出结果如下:


[1 2 3]

从数组或切片生成新的切片拥有如下特性:

①、取出的元素数量为:结束位置 - 开始位置;

②、取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;

③、当缺少开始位置时,表示从连续区域开头到结束位置;

④、当缺少结束位置时,表示从开始位置到整个连续区域末尾;

⑤、两者同时缺少时,与切片本身等效;

⑥、两者同时为 0 时,等效于空切片,一般用于切片复位。

根据索引位置取切片 slice 元素值时,取值范围是 (0~len(slice)-1) ,超界会报运行时错误,生成切片时,结束位置可以填写 len(slice) 但不会报错。下面通过实例来熟悉切片的特性:

重置切片,清空拥有的元素

把切片的开始和结束位置都设为 0 时,生成的切片将变空,代码如下:


a := []int{1, 2, 3}fmt.Println(a[0:0])

代码输出如下:


[]

直接声明新的切片

除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明,切片类型声明格式如下:


// 其中 name 表示切片的变量名// Type 表示切片对应的元素类型var name []Type

下面代码展示了切片声明的使用过程:


// 声明字符串切片// 声明一个字符串切片,切片中拥有多个字符串var strList []string// 声明整型切片// 声明一个整型切片,切片中拥有多个整型数值var numList []int// 声明一个空切片// 将 numListEmpty 声明为一个整型切片// 本来会在{}中填充切片的初始化元素,这里没有填充,所以切片是空的,但是此时的numListEmpty 已经被分配了内存,只是还没有元素var numListEmpty = []int{}// 输出3个切片// 切片均没有任何元素,3 个切片输出元素内容均为空fmt.Println(strList, numList, numListEmpty)// 输出3个切片大小// 没有对切片进行任何操作,strList 和 numList 没有指向任何数组或者其他切片fmt.Println(len(strList), len(numList), len(numListEmpty))// 切片判定空的结果//声明但未使用的切片的默认值是 nil,strList 和 numList 也是 nil,所以和 nil 比较的结果是 true// numListEmpty 已经被分配到了内存,但没有元素,因此和 nil 比较时是 falsefmt.Println(strList == nil)fmt.Println(numList == nil)fmt.Println(numListEmpty == nil)

代码输出结果:


[] [] []0 0 0truetruefalse
相关文章
|
1天前
|
Go 定位技术 索引
Go 语言Map(集合) | 19
Go 语言Map(集合) | 19
|
1天前
|
Go
go语言注释,标识符 | 17
go语言注释,标识符 | 17
|
2天前
|
存储 缓存 Go
go语言编程系列(五)
go语言编程系列(五)
|
2天前
|
搜索推荐 Java 编译器
go语言编程系列(四)
go语言编程系列(四)
|
1天前
|
存储 缓存 安全
速成班!去繁存简,一文让你学会go语言基础!!
速成班!去繁存简,一文让你学会go语言基础!!
|
2天前
|
存储 安全 编译器
go语言编程系列(六)
go语言编程系列(六)
|
2天前
|
自然语言处理 Java 测试技术
go语言编程系列(二)
go语言编程系列(二)
|
2天前
|
编译器 Go
go语言编程系列(三)
go语言编程系列(三)
|
4月前
|
开发框架 安全 中间件
Go语言开发小技巧&易错点100例(十二)
Go语言开发小技巧&易错点100例(十二)
60 1
|
1月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
go语言后端开发学习(四) —— 在go项目中使用Zap日志库