写点什么

【建议收藏】Go 语言关键知识点总结

  • 2024-07-01
    福建
  • 本文字数:5712 字

    阅读完需:约 19 分钟

容器


数组和切片


在 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到channelvalue := <-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")}
复制代码


文章转载自:LingBrown

原文链接:https://www.cnblogs.com/lbhym/p/18276068

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
【建议收藏】Go语言关键知识点总结_c++_不在线第一只蜗牛_InfoQ写作社区