go语言中的数组与切片(二)

简介: go语言中的数组与切片

go语言中的数组与切片(一)https://developer.aliyun.com/article/1391423


动态增减元素

可动态增减元素是数组切片比数组更为强大的功能。与数组相比,数组切片多了一个存储能力(capacity)的概念,即元素个数和分配的空间可以是两个不同的值。合理地设置存储能力的值,可以大幅降低数组切片内部重新分配内存和搬送内存块的频率,从而大大提高程序性能。

假如你明确知道当前创建的数组切片最多可能需要存储的元素个数为50,那么如果你设置的存储能力小于50,比如20,那么在元素超过20时,底层将会发生至少一次这样的动作——重新分配一块“够大”的内存,并且需要把内容从原来的内存块复制到新分配的内存块,这会产生比较明显的开销。

数组切片支持Go语言内置的cap()函数和len()函数,cap()函数返回的是数组切片分配的空间大小,而len()函数返回的是数组切片中当前所存储的元素个数。

package main 
import "fmt" 
func main() { 
 mySlice := make([]int, 5, 10) 
 fmt.Println("len(mySlice):", len(mySlice)) 
 fmt.Println("cap(mySlice):", cap(mySlice)) 
} 

该程序的输出结果为:

len(mySlice): 5

cap(mySlice): 10

如果需要往上例中mySlice已包含的5个元素后面继续新增元素,可以使用append()函数。

下面的代码可以从尾端给mySlice加上3个元素,从而生成一个新的数组切片:

mySlice = append(mySlice, 1, 2, 3) 
``
函数append()的第二个参数其实是一个不定参数,我们可以按自己需求添加若干个元素,甚至直接将一个数组切片追加到另一个数组切片的末尾:
```go
mySlice2 := []int{8, 9, 10} 
// 给mySlice后面添加另一个数组切片
mySlice = append(mySlice, mySlice2...) 

需要注意的是,我们在第二个参数mySlice2后面加了三个点,即一个省略号,如果没有这个省略号的话,会有编译错误,因为按append()的语义,从第二个参数起的所有参数都是待附加的元素。因为mySlice中的元素类型为int,所以直接传递mySlice2是行不通的。加上省略号相当于把mySlice2包含的所有元素打散后传入。

上述调用等同于:

mySlice = append(mySlice, 8, 9, 10) 

数组切片会自动处理存储空间不足的问题。如果追加的内容长度超过当前已分配的存储空间(即cap()调用返回的信息),数组切片会自动分配一块足够大的内存。

基于数组切片创建数组切片

类似于数组切片可以基于一个数组创建,数组切片也可以基于另一个数组切片创建。下面的

例子基于一个已有数组切片创建新数组切片:

oldSlice := []int{1, 2, 3, 4, 5} 
newSlice := oldSlice[:3] // 基于oldSlice的前3个元素构建新数组切片

有意思的是,选择的oldSlicef元素范围甚至可以超过所包含的元素个数,比如newSlice

可以基于oldSlice的前6个元素创建,虽然oldSlice只包含5个元素。只要这个选择的范围不超过oldSlice存储能力(即cap()返回的值),那么这个创建程序就是合法的。newSlice中超出oldSlice元素的部分都会填上0。

复制

数组切片支持Go语言的另一个内置函数copy(),用于将内容从一个数组切片复制到另一个数组切片。如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行复制。下面的示例展示了copy()函数的行为:

slice1 := []int{1, 2, 3, 4, 5} 
slice2 := []int{5, 4, 3} 
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置

map

在Go中,使用map不需要引入任何库,并且用起来也更加方便。map是一堆键值对的未排序集合。比如以身份证号作为唯一键来标识一个人的信息,则这个

package main 
import "fmt" 
// PersonInfo是一个包含个人详细信息的类型
type PersonInfo struct { 
 ID string
 Name string
 Address string
} 
func main() { 
var personDB map[string] PersonInfo 
 personDB = make(map[string] PersonInfo) 
 // 往这个map里插入几条数据
 personDB["12345"] = PersonInfo{"12345", "Tom", "Room 203,..."} 
 personDB["1"] = PersonInfo{"1", "Jack", "Room 101,..."} 
 // 从这个map查找键为"1234"的信息
 person, ok := personDB["1234"] 
 // ok是一个返回的bool型,返回true表示找到了对应的数据
 if ok { 
 fmt.Println("Found person", person.Name, "with ID 1234.") 
 } else { 
 fmt.Println("Did not find person with ID 1234.") 
 } 
}

上面这个简单的例子基本上已经覆盖了map的主要用法,下面对其中的关键点进行细述。

变量声明

map的声明基本上没有多余的元素,比如:

var myMap map[string] PersonInfo 其中,myMap是声明的map变量名,string是键的类型,PersonInfo则是其中所存放的值类型。

创建

我们可以使用Go语言内置的函数make()来创建一个新map。下面的这个例子创建了一个键

类型为string、值类型为PersonInfo的map:

myMap = make(map[string] PersonInfo) 

也可以选择是否在创建时指定该map的初始存储能力,下面的例子创建了一个初始存储能力为100的map:

myMap = make(map[string] PersonInfo, 100) 

创建并初始化map的代码如下:

myMap = map[string] PersonInfo{ 
 "1234": PersonInfo{"1", "Jack", "Room 101,..."}, 
} 

元素赋值

赋值过程非常简单明了,就是将键和值用下面的方式对应起来即可:

myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,..."} 

元素删除

Go语言提供了一个内置函数delete(),用于删除容器内的元素。下面我们简单介绍一下如

何用delete()函数删除map内的元素:

delete(myMap, "1234") 

上面的代码将从myMap中删除键为“1234”的键值对。如果“1234”这个键不存在,那么这个调用将什么都不发生,也不会有什么副作用。但是如果传入的map变量的值是nil,该调用将导致程序抛出异常(panic)。

元素查找

在Go语言中,map的查找功能设计得比较精巧。在Go语言中,要从map中查找一个特定的键,可以通过下面的代码来实现:

value, ok := myMap["1234"] 
if ok { // 找到了
 // 处理找到的value 
} 

判断是否成功找到特定的键,不需要检查取到的值是否为nil,只需查看第二个返回值ok,这让表意清晰很多。配合:=操作符,让你的代码没有多余成分,看起来非常清晰易懂。

new() 和 make() 的区别

看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。

  • new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为*T的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体;它相当
    于 &T{} 。
  • make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:切片、map 和 channel。
相关文章
|
2天前
|
人工智能 Go 调度
掌握Go并发:Go语言并发编程深度解析
掌握Go并发:Go语言并发编程深度解析
|
6天前
|
数据采集 存储 Go
使用Go语言和chromedp库下载Instagram图片:简易指南
Go语言爬虫示例使用chromedp库下载Instagram图片,关键步骤包括设置代理IP、创建带代理的浏览器上下文及执行任务,如导航至用户页面、截图并存储图片。代码中新增`analyzeAndStoreImage`函数对图片进行分析和分类后存储。注意Instagram的反爬策略可能需要代码适时调整。
使用Go语言和chromedp库下载Instagram图片:简易指南
|
1天前
|
Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第23天】Go语言的`context`包提供`Context`接口用于处理任务取消、超时和截止日期。通过传递`Context`对象,开发者能轻松实现复杂控制流。本文解析`context`包特性,讨论常见问题和解决方案,并给出代码示例。关键点包括:1) 确保将`Context`传递给所有相关任务;2) 根据需求选择适当的`Context`创建函数;3) 定期检查`Done()`通道以响应取消请求。正确使用`context`包能提升Go程序的控制流管理效率。
7 1
|
2天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello("Alice")`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
13 1
|
2天前
|
SQL 关系型数据库 MySQL
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
|
3天前
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
11 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
|
3天前
|
Go
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
【4月更文挑战第21天】Go语言函数是代码组织的基本单元,用于封装可重用逻辑。本文介绍了函数定义(包括基本形式、命名、参数列表和多返回值)、调用以及匿名函数与闭包。在函数定义时,注意参数命名和注释,避免参数顺序混淆。在调用时,要检查并处理多返回值中的错误。理解闭包原理,小心处理外部变量引用,以提升代码质量和可维护性。通过实践和示例,能更好地掌握Go语言函数。
18 1
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
|
4天前
|
程序员 Go API
【Go语言快速上手(二)】 分支与循环&函数讲解
【Go语言快速上手(二)】 分支与循环&函数讲解
|
4天前
|
Go
Golang深入浅出之-Go语言基础语法:变量声明与赋值
【4月更文挑战第20天】本文介绍了Go语言中变量声明与赋值的基础知识,包括使用`var`关键字和简短声明`:=`的方式,以及多变量声明与赋值。强调了变量作用域、遮蔽、初始化与零值的重要性,并提醒读者注意类型推断时的一致性。了解这些概念有助于避免常见错误,提高编程技能和面试表现。
19 0
|
4天前
|
编译器 Go 开发者
Go语言入门|包、关键字和标识符
Go语言入门|包、关键字和标识符
22 0