容器

数组和切片

在Go语言中,数组和切片是两个基本的数据结构,用于存储和操作一组元素。它们有一些相似之处,但也有许多不同之处。下面我们详细介绍数组和切片的特点、用法以及它们之间的区别。

数组

数组是固定长度的序列,存储相同类型的元素。数组的长度在定义时就固定下来,不能改变。

package main

import "fmt"

func main() {
// 定义一个长度为5的整型数组
var arr [5]int
fmt.Println(arr) // 输出: [0 0 0 0 0] // 定义并初始化一个长度为5的整型数组
arr2 := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr2) // 输出: [1 2 3 4 5] // 让编译器推断数组长度
arr3 := [...]int{1, 2, 3}
fmt.Println(arr3) // 输出: [1 2 3]
}

可以使用索引来访问和修改数组中的元素:

package main

import "fmt"

func main() {
arr := [3]int{1, 2, 3}
fmt.Println(arr[0]) // 输出: 1 arr[1] = 10
fmt.Println(arr) // 输出: [1 10 3]
}

可以使用for循环来遍历数组:

package main

import "fmt"

func main() {
arr := [3]int{1, 2, 3}
for i, v := range arr {
fmt.Println(i, v)
}
}

切片

切片是动态数组,可以按需增长。切片由三个部分组成:指针、长度和容量。指针指向数组中切片的起始位置,长度是切片中的元素个数,容量是从切片起始位置到数组末尾的元素个数。

package main

import "fmt"

func main() {
// 创建一个长度和容量为3的整型切片
slice := make([]int, 3)
fmt.Println(slice) // 输出: [0 0 0] // 定义并初始化一个切片
slice2 := []int{1, 2, 3, 4, 5}
fmt.Println(slice2) // 输出: [1 2 3 4 5]
}

切片可以通过数组或另一个切片生成:

package main

import "fmt"

func main() {
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]
fmt.Println(slice) // 输出: [2 3 4]
}

可以使用内置的append函数向切片追加元素:

package main

import "fmt"

func main() {
slice := []int{1, 2, 3}
slice = append(slice, 4, 5)
fmt.Println(slice) // 输出: [1 2 3 4 5]
}

其他操作和数组基本一样,下面再说下数组和切片的区别:

  1. 长度

    • 数组的长度是固定的,定义后不能改变。
    • 切片的长度是动态的,可以通过append函数增加元素。
  2. 灵活性
    • 数组在使用上较为僵化,因为长度固定,适用于元素数量已知且固定的场景。
    • 切片更加灵活,适用于需要动态添加或删除元素的场景。
  3. 性能
    • 数组的访问速度通常比切片快,因为它们是固定大小的,编译器可以进行更多的优化。
    • 切片在性能上稍逊,但由于其灵活性,使用更加广泛。

container包

在Go语言的标准库中,container包提供了三种常见的数据结构:(heap)、双向链表(list)和环形队列(ring)。这些数据结构为开发者提供了高效的插入、删除和访问操作。下面我们详细介绍这三个数据结构及其用法。

head

heap 包实现了堆数据结构。堆是一种特殊的树状结构,可以用于实现优先队列。

要使用 container/heap 包,必须定义一个实现 heap.Interface 接口的类型。该接口包含以下方法:

  • Len() int:返回元素数量。
  • Less(i, j int) bool:报告索引 i 处的元素是否小于索引 j 处的元素。
  • Swap(i, j int):交换索引 i 和 j 处的元素。
  • Push(x interface{}):将元素 x 添加到堆中。
  • Pop() interface{}:移除并返回堆中的最小元素。

这些方法要求用户明确实现堆的各种操作,增加了使用的复杂度。

package main

import (
"container/heap"
"fmt"
) // 定义一个实现 heap.Interface 的类型
type IntHeap []int func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *IntHeap) Push(x interface{}) {
*h = append(*h, x.(int))
} func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
} func main() {
h := &IntHeap{2, 1, 5}
heap.Init(h)
heap.Push(h, 3)
fmt.Printf("最小元素: %d\n", (*h)[0])
for h.Len() > 0 {
fmt.Printf("%d ", heap.Pop(h))
}
// 输出: 最小元素: 1
// 1 2 3 5
}

list

list 包实现了双向链表(doubly linked list)。双向链表允许高效的插入和删除操作。

package main

import (
"container/list"
"fmt"
) func main() {
l := list.New() // 在链表前插入元素
l.PushFront(1)
l.PushFront(2) // 在链表后插入元素
l.PushBack(3) // 遍历链表
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
// 输出:
// 2
// 1
// 3
}

ring

ring 包实现了环形队列(circular list)。环形队列是一种首尾相连的队列结构。

package main

import (
"container/ring"
"fmt"
) func main() {
// 创建一个长度为3的环
r := ring.New(3) // 初始化环中的值
for i := 0; i < r.Len(); i++ {
r.Value = i
r = r.Next()
} // 遍历环中的元素
r.Do(func(p interface{}) {
fmt.Println(p.(int))
})
// 输出:
// 0
// 1
// 2
}

Channel

什么是Channel

在Go语言中,channel是用于在不同的goroutine之间进行通信的机制。它可以让一个goroutine将值发送到一个通道中,另一个goroutine从通道中接收值。channel的设计使得goroutine之间的通信和同步变得简洁而高效。

创建Channel

创建一个channel使用make函数,指定其传递的值的类型:

ch := make(chan int)

可以创建带缓冲的channel,缓冲大小在make时指定:

ch := make(chan int, 100)

发送和接收

发送和接收操作使用箭头符号<-:

ch <- 1   // 发送值1到channel
value := <-ch // 从channel接收值并赋值给变量value

关闭Channel

channel可以被主动关闭,关闭channel使用close函数:

close(ch)

一旦一个channel被关闭,再往该channel发送值会导致panic,从已关闭的channel接收值将立即返回该类型的零值并且不会阻塞(如果通道里还存在未被接收的元素,这些元素也会正常返回,直到所有元素都被接收,才会开始返回零值)。

其他操作

无缓冲通道(缓冲大小为0)

  • 发送操作会阻塞直到有goroutine来接收这个值。
  • 接收操作会阻塞直到有值被发送到channel。

缓冲通道

  • 发送操作会在缓冲区满时阻塞。
  • 接收操作会在缓冲区为空时阻塞。

Select语句

select语句可以用于处理多个channel操作。它会阻塞直到其中一个channel可以进行操作。select语句中的各个分支是随机选择的:

select {
case val := <-ch1:
fmt.Println("Received", val)
case ch2 <- 1:
fmt.Println("Sent 1")
default:
fmt.Println("No communication")
}

示例

基于channel,实现一个简单的生产者-消费者模型:

package main

import (
"fmt"
"time"
) func producer(ch chan int) {
//循环往通道发送5个元素,间隔1秒
for i := 0; i < 5; i++ {
fmt.Println("Producing", i)
ch <- i
time.Sleep(time.Second)
}
//发送完所有消息后关闭通道
close(ch)
} func consumer(ch chan int) {
//可以通过range遍历通道的元素
//因为生产者已经关闭了通道,所以遍历完所有元素后,循环会自己退出
for val := range ch {
fmt.Println("Consuming", val)
time.Sleep(time.Second)
}
} func main() {
ch := make(chan int, 2)
go producer(ch)
consumer(ch)
}

常见问题

  1. 避免在接收端关闭通道:通常由发送方负责关闭channel。
  2. 避免重复关闭通道:多次关闭同一个channel会导致panic。
  3. 避免从未使用的通道发送和接收:未使用的channel操作会导致死锁。比如只接收,没发送,程序会一直阻塞在接收处。

函数

在Go语言中,函数是一等公民(first-class citizen),这意味着函数可以像其他类型(例如整数、字符串等)一样使用和操作。这一特性使得函数的使用非常灵活和强大。具体来说,函数作为一等公民具有以下特点:

函数可以赋值给变量

你可以将一个函数赋值给一个变量,这样就可以通过这个变量来调用函数:

package main

import "fmt"

func main() {
add := func(a, b int) int {
return a + b
}
fmt.Println(add(3, 4)) // 输出: 7
}

函数可以作为参数传递给另一个函数

函数可以作为参数传递给其他函数,这使得可以实现高阶函数:

package main

import "fmt"

func applyOperation(a, b int, op func(int, int) int) int {
return op(a, b)
} func main() {
add := func(a, b int) int {
return a + b
}
result := applyOperation(5, 3, add)
fmt.Println(result) // 输出: 8
}

函数可以作为返回值从另一个函数返回

函数可以从另一个函数返回,这使得可以动态生成函数:

package main

import "fmt"

func createMultiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
} func main() {
double := createMultiplier(2)
triple := createMultiplier(3)
fmt.Println(double(4)) // 输出: 8
fmt.Println(triple(4)) // 输出: 12
}

函数可以嵌套定义

在Go语言中,可以在函数内部定义另一个函数:

package main

import "fmt"

func main() {
outer := func() {
fmt.Println("This is the outer function.") inner := func() {
fmt.Println("This is the inner function.")
} inner()
} outer()
}

函数可以作为匿名函数

匿名函数是一种无需命名的函数,可以直接使用:

package main

import "fmt"

func main() {
result := func(a, b int) int {
return a + b
}(3, 5) fmt.Println(result) // 输出: 8
}

闭包(Closures)

Go语言支持闭包,闭包是一个函数,这个函数可以捕获并记住其所在环境的变量:

package main

import "fmt"

func main() {
x := 10 // 定义一个修改外部变量x的闭包
closure := func() int {
x += 1
return x
} fmt.Println(closure()) // 输出: 11
fmt.Println(x) // 输出: 11
}
package main

import "fmt"

func main() {
counter := func() func() int {
count := 0
return func() int {
count++
return count
}
}() fmt.Println(counter()) // 输出: 1
fmt.Println(counter()) // 输出: 2
fmt.Println(counter()) // 输出: 3
}

错误处理

Go语言中的错误处理方式不同于传统的异常处理机制。它采用了明确的、基于值的错误处理方法。每个函数可以返回一个错误值来表示是否出现了问题。

基本错误处理

Go语言中使用内置的error接口类型来表示错误。error接口定义如下:

type error interface {
Error() string
}

函数通常返回一个error类型的值来表示操作是否成功。如果没有错误,返回nil。

package main

import (
"errors"
"fmt"
) // 定义一个函数,返回错误
func divide(a, b int) (int, error) {
if b == 0 {
//如果有问题,通过New方法新建一个错误信息
return 0, errors.New("division by zero")
}
//如果没有错误返回nil
return a / b, nil
} func main() {
result, err := divide(4, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
} result, err = divide(4, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}

自定义错误类型

除了使用errors.New创建简单错误外,Go语言允许我们定义自己的错误类型,实现更丰富的错误信息。

package main

import (
"fmt"
) // 自定义错误类型
type MyError struct {
Code int
Message string
} // 实现error接口的Error方法
func (e *MyError) Error() string {
return fmt.Sprintf("Code: %d, Message: %s", e.Code, e.Message)
} // 定义一个函数,返回自定义错误(只要实现了Error()方法,就可以直接返回error类型)
func doSomething(flag bool) error {
if !flag {
return &MyError{Code: 123, Message: "something went wrong"}
}
return nil
} func main() {
err := doSomething(false)
if err != nil {
fmt.Println("Error:", err) // 类型断言,获取具体的错误类型
if myErr, ok := err.(*MyError); ok {
fmt.Println("Custom Error Code:", myErr.Code)
}
}
}

异常处理机制

Go语言也有类似异常的处理机制,即defer、panic和recover,但它们主要用于处理程序中不可恢复的错误。

  • defer:用于延迟执行一个函数,在函数返回前执行。如果一个函数里面有多个defer语句,写在最后面的defer最先执行。
  • panic:意料之外的错误,也可以手动调用。如果panic没有处理,程序会终止。
  • recover:恢复panic,并停止程序终止的过程。
package main

import "fmt"

func main() {
defer func() {
//使用defer执行一个匿名函数,确保recover一定能执行
if r := recover(); r != nil {
//恢复panic,此处可以进行异常处理,比如打印日志
fmt.Println("Recovered from panic:", r)
}
}() fmt.Println("Starting the program")
//手动触发一个panic
panic("Something went wrong!")
fmt.Println("This line will not be executed")
}

【建议收藏】Go语言关键知识点总结的更多相关文章

  1. C/C++编程笔记:C语言入门知识点(三),请收藏C语言最全笔记!

    今天我们继续来学习C语言的入门知识点,第一课:C/C++编程笔记:C语言入门知识点(二),请收藏C语言最全笔记! 21. 输入 & 输出 当我们提到输入时,这意味着要向程序填充一些数据.输入可 ...

  2. 万字长文肝Git--全流程知识点包您满意【建议收藏】

    您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦. 本文将首先介绍在本地搭建GitLab服务,然后重点介绍Git的常用命令,Git的核心概念以及冲突处理,最后介绍Git与SVN的区别 干货满满,建议 ...

  3. 解密国内BAT等大厂前端技术体系-携程篇(长文建议收藏)

    1 引言 为了了解当前前端的发展趋势,让我们从国内各大互联网大厂开始,了解他们的最新动态和未来规划.这是解密大厂前端技术体系的第四篇,前三篇已经讲述了阿里.腾讯.百度在前端技术这几年的技术发展. 这一 ...

  4. 解密国内BAT等大厂前端技术体系-腾讯篇(长文建议收藏)

    1 引言 为了了解当前前端的发展趋势,让我们从国内各大互联网大厂开始,了解他们的最新动态和未来规划.这是解密大厂前端技术体系的第三篇,前两篇已经讲述了阿里和百度在前端技术这几年的技术发展.这一篇从腾讯 ...

  5. Java异常的10个关键知识点

    前言 总结了Java异常十个关键知识点,面试或者工作中都有用哦,加油. 一. 异常是什么 异常是指阻止当前方法或作用域继续执行的问题.比如你读取的文件不存在,数组越界,进行除法时,除数为0等都会导致异 ...

  6. (转)python资料汇总(建议收藏)零基础必看

    摘要:没料到在悟空问答的回答大受欢迎,为方便朋友,重新整理汇总,内容包括长期必备.入门教程.练手项目.学习视频. 一.长期必备. 1. StackOverflow,是疑难解答.bug排除必备网站,任何 ...

  7. Java程序员必备:异常的十个关键知识点

    前言 总结了Java异常十个关键知识点,面试或者工作中都有用哦,加油. 一. 异常是什么 异常是指阻止当前方法或作用域继续执行的问题.比如你读取的文件不存在,数组越界,进行除法时,除数为0等都会导致异 ...

  8. 解密国内BAT等大厂前端技术体系-美团点评之下篇(长文建议收藏)

    引言 在上篇中,我已经介绍了美团点评的业务情况.大前端的技术体系,其中大前端的技术全景图如下: 上篇重点介绍了工程化和代码质量的部分,工程化涵盖了客户端持续集成平台-MCI.全端监控平台-CAT.移动 ...

  9. 万字超强图文讲解AQS以及ReentrantLock应用(建议收藏)

    | 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it ...

  10. Python 100个样例代码【爆肝整理 建议收藏】

    本教程包括 62 个基础样例,12 个核心样例,26 个习惯用法.如果觉得还不错,欢迎转发.留言. 一. Python 基础 62 例 1 十转二 将十进制转换为二进制: >>> b ...

随机推荐

  1. .Net项目部署到Docker

    .Net项目部署到Docker 环境 linux docker .Net 7 步骤 编写Dockerfile 上传项目文件到linux 运行项目文件到docker 一.设置项目端口 在Program. ...

  2. 一个在线下载地图XYZ瓦片的网站实现

    1. 什么是XYZ瓦片 XYZ瓦片是一种在线地图数据格式,常见的地图底图如Google.OpenStreetMap 等互联网的瓦片地图服务,都是XYZ瓦片,严格来说是ZXY规范的地图瓦片 ZXY规范的 ...

  3. WSL2使用桥接网络,并指定IP

    前言 微软终于解决了宇宙级难题了,一直以来的WSL2每次启动IP都是动态分配的,并且是NAT的网络.当然网上对此也有一些解决方案,编写脚本在启动时修改,但是太麻烦了,这次很完美的解决了这个难题. 检查 ...

  4. Winform项目中纯代码创建WCF服务

    接口: [ServiceContract(CallbackContract = typeof(IViewCallback), SessionMode = SessionMode.Required)] ...

  5. 关于文心一言不能打开F12开发者工具

    直接被JS写死的debugger关键字下了断点.行吧,不让调试就不让调试吧,关闭开发者工具之后,直接跳到了空白页. 开发者工具Ctrl+F8可以禁用断点调试 点击查看代码 <!DOCTYPE h ...

  6. C语言:将有顺序的数组进行逆序排序

    //设计逆向排序之,数字有序排列,进行逆向排序 主要思想就是头和尾进行交换,前提是------数字必须是排好序的才能进行逆序排     /*假设数组为: 7,8,9,10,11     1     N ...

  7. 用 C 语言开发一门编程语言 — 条件分支

    目录 文章目录 目录 前文列表 条件分支 排序函数 等于函数 if 函数 递归函数 源代码 前文列表 <用 C 语言开发一门编程语言 - 交互式解析器> <用 C 语言开发一门编程语 ...

  8. java学习之旅(day.07)

    面向对象编程(oop) 面向过程思想:线性思维 步骤清晰简单,每一步做什么很明确 适合处理较为简单地问题 面向对象思想:总分 抽象 属性+方法=类 分类的思维模式,思考问题首先会解决问题需要哪些分类, ...

  9. C# 使用 NPOI 导出excel 单击单元格背景变黑色的解决办法

    需要手动指定单元格的背景色为一种颜色, 特别注意,我在使用 var color=new XSSFColor(new color...)创建的颜色,即使设置成其他颜色,查看样式属性中,发现color.i ...

  10. HTML——input之单行文本框

    在 HTML 中,把 <input> 标签的 type 属性设置为 text 可以表示单行文本框,又叫做常规文本框.具体语法格式如下: <input type="text& ...