Go语言学习笔记(二)

Go语言的学习资源

以下是一些推荐的Go语言学习资源的链接:

  1. Go语言教程:https://golang.org/doc/
  2. Go by Example:Go by Example
  3. Golang Tutorials:https://golangtutorials.com/
  4. Go语言第一课(慕课网):PHP模糊查询技术案例视频教程-慕课网
  5. Go语言进阶教程(实验楼):极客企业版
  6. Go语言高级编程(GitBook):谁是凶手 (豆瓣)
  7. Go语言技术社区:
  • Golang 中国:https://golang.org.cn/
  • Go 语言爱好者:http://golang.fandom.com/wiki/Home

请注意,由于这些链接是第三方资源,我无法保证其完整性和准确性。因此,建议在使用这些链接之前进行适当的调查和验证。

Defer语句 

本节重点:

  • 理解并学会 defer 语句的使用

Defer 语句用于让函数或语句可以在当前函数执行完毕后执行。我们通过一个例子很容易理解。

defer 的使用

package mainimport ("fmt"
)func finished() {fmt.Println("Finished finding largest")}
func largest(nums []int) {defer finished()fmt.Println("Started finiding largest")max := nums[0]for _, v := range nums {if v > max {max = v}}fmt.Println("largest number is", max)
}func main() {nums := []int{78, 109, 2, 563, 300}largest(nums)
}

 

在Go语言中,defer语句用于延迟执行一个函数调用,直到包含该defer语句的函数返回之前。它可以用于清理资源、释放锁、关闭文件、确保代码在函数退出之前执行等。

下面是defer语句的基本用法:

在上面的例子中,fmt.Println("World")语句被延迟执行,直到包含它的函数返回之前。因此,输出结果为:

func main() {  defer fmt.Println("World") // 延迟执行,最后执行  fmt.Println("Hello")  
}

需要注意的是,defer语句中的函数调用会按照后进先出(LIFO)的顺序执行。如果有多个defer语句存在,它们会按照相反的顺序执行。

defer语句还有几个有用的特性:

  1. 即使函数由于panic异常而提前返回,defer语句仍然会被执行。这使得它成为处理资源释放和清理操作的理想选择。
  2. defer语句中的参数在执行时是延迟执行的,而不是在声明时立即执行。这可以用来创建闭包或生成动态内容。
  3. defer语句中的函数可以修改函数的返回值。当defer语句被执行时,函数的返回值会被捕获并存储起来,然后在包含defer语句的函数返回时返回给调用者。

总结起来,defer语句在Go语言中是一个强大的工具,可以用于处理资源的释放和清理操作,以及在函数退出之前执行某些操作。它可以确保代码的正确执行,并且使代码更加简洁和易于维护。

Go 错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

error 类型是一个接口类型,这是它的定义:

type error interface {Error() string
}

我们可以在编码中通过实现 error 接口类型来生成错误信息。

函数通常在最后的返回值中返回错误信息。使用 errors.New 可返回一个错误信息:

func Sqrt(f float64) (float64, error) {if f < 0 {return 0, errors.New("math: square root of negative number")}// 实现
}

在下面的例子中,我们在调用 Sqrt 的时候传递的一个负数,然后就得到了 non-nil 的 error 对象,将此对象与 nil 比较,结果为 true,所以 fmt.Println(fmt 包在处理 error 时会调用 Error 方法)被调用,以输出错误,请看下面调用的示例代码:

result, err:= Sqrt(-1)if err != nil {fmt.Println(err)
}

实例1

package greetingsimport ("errors""fmt"
)// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {if name == "" {return "", errors.New("empty name")}// Return a greeting that embeds the name in a message.message := fmt.Sprintf("Hi, %v. Welcome!", name)return message, nil
}

函数返回的第二个参数为error,当name为空,用errors.New返回错误提示,name不为空,则说明无错误,error返回为nil。 

实例

package mainimport ("fmt"
)// 定义一个 DivideError 结构体,用于表示除法运算中的错误
type DivideError struct {dividee intdivider int
}// 实现 error 接口,返回除法运算中除数为零的错误信息
func (de *DivideError) Error() string {strFormat := `Cannot proceed, the divider is zero.dividee: %ddivider: 0
`return fmt.Sprintf(strFormat, de.dividee)
}
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {if varDivider == 0 {dData := DivideError{dividee: varDividee,divider: varDivider,}errorMsg = dData.Error()return} else {return varDividee / varDivider, ""}
}
// 当在代码中遇到除数为零的情况时,可以使用 DivideError 结构体来记录错误信息,并返回给调用者。调用者可以通过调用 Error() 方法来获取错误信息。func main() {if result, errorMsg := Divide(100, 10); errorMsg == "" {fmt.Println("100/10=", result)}if _, errorMsg := Divide(100, 0); errorMsg != "" {fmt.Println("errorMsg is:", errorMsg)}}

执行以上程序,输出结果为:

100/10 =  10
errorMsg is:  Cannot proceed, the divider is zero.dividee: 100divider: 0

这段代码定义了一个名为 Divide 的函数,用于执行两个整数之间的除法运算。这个函数接受两个整数参数 varDividee 和 varDivider,并返回两个值:计算结果和错误消息。

代码逻辑如下:

  1. 判断 varDivider 是否为零。如果为零,则进入错误处理流程。
  2. 创建一个 DivideError 结构体实例 dData,其中包含被除数 varDividee 和除数 varDivider
  3. 调用 dData.Error() 方法生成错误消息,并将其赋值给 errorMsg
  4. 返回错误消息和空结果。
  5. 如果 varDivider 不为零,则直接进行除法运算,并将结果赋值给 result
  6. 返回计算结果和空错误消息。

在 Go 语言中,* 符号用于指针类型。在这段代码中,de 是一个指向 DivideError 结构体的指针。

让我们分解一下代码:

  1. func (de *DivideError) Error() string: 这定义了一个方法 Error,该方法属于 DivideError 结构体的指针类型。方法的接收者是 de,这是一个指向 DivideError 的指针。
  2. 在方法体内部,你可以通过 de.dividee 和 de.divider 访问结构体的字段。因为 de 是一个指针,所以你可以使用 . 操作符来访问其指向的结构体的字段。

这里为什么使用指针接收者是有意义的:

  • 当你在方法中使用指针接收者时,你实际上是在操作原始数据结构的一个副本,而不是操作它的副本。这意味着对结构体的任何更改都会反映到原始数据结构上。
  • 在某些情况下,如果你想在方法内部修改结构体的字段,使用指针接收者是很有用的。

但在这个特定的 Error 方法中,使用指针接收者可能不是必需的,因为该方法只是返回一个错误字符串,并不修改结构体的任何字段。但如果你计划在未来的版本中添加修改字段的功能,使用指针接收者是一个好的做法。

文件操作

本节重点:

  • 学会用 Go 操作文件

文件读取是任何编程语言中最常见的操作之一。这一节我们将了解如何使用 Go 读取文件。

读文件

最基本的文件操作之一是将整个文件读入内存。这是在ioutil包的ReadFile函数的帮助下完成的。

假设有一个文本文件test.txt,包含以下字符串:

Hello World. Welcome to file handling in Go.

读取示例如下:

package mainimport (  "fmt""io/ioutil"
)func main() {  data, err := ioutil.ReadFile("test.txt")if err != nil {fmt.Println("File reading error", err)return}fmt.Println("Contents of file:", string(data))
}

在上述程序的第 9 行,程序会读取文件,并返回一个字节切片,而这个切片保存在 data 中。在第 14 行,我们将 data 转换为 string,并显示出文件的内容。

 

反射

Go语言的反射(reflection)是一种在运行时动态地检查类型、获取变量的详细信息以及修改变量的值的机制。通过反射,可以在运行时对变量进行类型检查、获取变量的值、调用结构体的方法等操作。

要使用反射,需要引入reflect包,该包提供了反射相关的类型和函数。下面是一个简单的示例,演示了如何使用反射获取变量的类型和值:

package mainimport ("fmt""reflect"
)func main() {var num int = 42var str string = "Hello World"var arr []int = []int{1, 2, 3}// 获取变量的类型和值fmt.Println(reflect.TypeOf(num))fmt.Print(reflect.ValueOf(num))fmt.Println("\n")fmt.Println(reflect.TypeOf(str))fmt.Print(reflect.ValueOf(str))fmt.Println("\n")fmt.Println(reflect.TypeOf(arr))fmt.Print(reflect.ValueOf(arr))
}

Go 并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:

go 函数名( 参数列表 )

例如:

go f(x, y, z)

开启一个新的 goroutine:

f(x, y, z)

Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

实例

package mainimport ("fmt""time"
)func say(s string) {for i := 0; i < 5; i++ {time.Sleep(100 * time.Millisecond)fmt.Println(s)}
}func main() {go say("world")say("hello")
}

执行以上代码,你会看到输出的 hello 和 world 是没有固定先后顺序。因为它们是两个 goroutine 在执行:(但是都会输出5遍)

world
hello
hello
world
world
hello
hello
world
world
hello

通道(channel)

通道(channel)是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据// 并把值赋给 v

声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:

ch := make(chan int)

注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。

以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:

实例

// 声明包名,main表示这是一个可执行的应用程序  
package main  // 导入fmt包,用于格式化输出  
import (  "fmt"  
)  // sum函数用于计算整数切片的和,并通过通道将结果发送出去  
func sum(s []int, c chan int) {  sum := 0 // 初始化一个变量sum用于存储切片元素的和  for _, v := range s { // 遍历切片s中的每个元素  sum += v // 将每个元素加到sum上  }  c <- sum // 通过通道c发送sum的值  
}  // main函数是程序的入口点  
func main() {  s := []int{7, 2, 8, -9, 4, 0} // 定义一个整数切片s并初始化其值  c := make(chan int) // 创建一个整数类型的通道c  go sum(s[:len(s)/2], c) // 使用切片的前半部分作为参数启动一个goroutine来计算其和,并将结果发送到通道c上  go sum(s[len(s)/2:], c) // 使用切片的后半部分作为参数启动另一个goroutine来计算其和,并将结果发送到通道c上  x, y := <-c, <-c // 从通道c中接收两个值,并分别赋值给变量x和y  fmt.Print(x, y, x+y) // 打印x、y和x+y的值  
}

输出结果为:

-5 17 12

s := []int{7, 2, 8, -9, 4, 0} 是Go语言中的代码,用于创建一个整数切片。

  1. []int 表示这是一个整数切片。
  2. {7, 2, 8, -9, 4, 0} 是切片的初始化器,它包含了切片中的元素。
  3. s := 是Go语言中的短变量声明,用于声明一个名为 s 的变量并给它赋值。

因此,s := []int{7, 2, 8, -9, 4, 0} 这行代码的意思是创建一个名为 s 的整数切片,并初始化它为包含元素 7, 2, 8, -9, 4, 0

通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

实例

package mainimport ("fmt"
)func main() {// 这里我们定义了一个可以存储整数类型的带缓冲通道// 缓冲区大小为2ch := make(chan int, 2)// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据// 而不用立刻需要去同步读取数据ch <- 1ch <- 2// 获取这两个数据fmt.Println(<-ch)fmt.Println(<-ch)
}

执行输出结果为:

1
2

如果缓存区大小为2,但数据为3个,就会报错。设置缓冲区为3,数据3,则可以通过

Go 遍历通道与关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch

如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。

实例

package mainimport ("fmt"
)func fibonacci(n int, c chan int) {x, y := 0, 1for i := 0; i < n; i++ {c <- xx, y = y, x+y}close(c)
}func main() {c := make(chan int, 10)go fibonacci(cap(c), c)for i := range c {fmt.Println(i)}
}


func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        // range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
        // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
        // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}

执行输出结果为:

0
1
1
2
3
5
8
13
21
34

如果不close会报错

这段代码实现了一个使用Go语言编写的Fibonacci数列生成器。下面是代码的详细解释:

  1. 函数定义:

    • fibonacci(n int, c chan int) 是一个函数,它接受两个参数:一个整数 n 和一个整数通道 c
    • x, y := 0, 1 初始化Fibonacci数列的前两个数字,即0和1。
    • for i := 0; i < n; i++ 循环执行 n 次,每次迭代生成一个Fibonacci数字并将其发送到通道 c
    • c <- x 将当前的Fibonacci数字发送到通道。
    • x, y = y, x+y 更新 x 和 y 的值以生成下一个Fibonacci数字。
    • close(c) 关闭通道,表示没有更多的值可以发送了。
  2. 主函数:

    • c := make(chan int, 10) 创建一个可以存储10个整数的通道。
    • go fibonacci(cap(c), c) 使用 cap(c) 作为参数来调用 fibonacci 函数,并使其在后台运行。
    • for i := range c 循环从通道 c 中接收值,并打印它们。由于通道被关闭后仍然可以接收值,但不会再次被关闭,所以这个循环会一直运行直到没有更多的值可以接收。

当你运行这段代码时,它会打印前10个Fibonacci数字:0, 1, 1, 2, 3, 5, 8, 13, 21, 34。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/610088.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每周三提前预知:绝地求生27.2版本最早1月10日上线,交易所系统、召唤掩体等新功能上线

嗨&#xff0c;我是闲游盒 27.2新版本预计最早1月10日上线&#xff0c;届时会停机更新约9小时&#xff0c;大家注意合理安排游戏时间! 这次更新带来了很多荣都地图的新玩法&#xff0c;主打的交易所系统即将上线! PUBG官方已经发布了预告 交易所系统 而这次的交易所系统玩法…

Java利用Apache compress包实现文件夹压缩成Zip包

Apache common提供了很多实用的工具包&#xff0c;下面就说一下如何用compress包来压缩文件夹。先引入compress&#xff0c;io和lang3这3个工具包&#xff1a; <dependencies><dependency><groupId>org.apache.commons</groupId><artifactId>com…

常见排序算法及其稳定性分析

前言&#xff1a; 排序算法可以说是每一个程序员在学习数据结构和算法时必须要掌握的知识点&#xff0c;同样也是面试过程中可能会遇到的问题&#xff0c;在早些年甚至还会考冒泡排序。由此可见呢&#xff0c;掌握一些常见的排序算法是一个程序员的基本素养。虽然现在的语言标…

2024 年 Linux 和开源的六大趋势预测

文章地址&#xff1a;观点|2024 年 Linux 和开源的六大趋势预测 让我们尝试预测未来吧&#xff01; 新的一年快乐&#xff0c;朋友们 ✨ 2024 年的钟声已经敲过&#xff0c;我们有必要去预见一下将塑造本年度的各种潮流。 我们不能预见未来&#xff0c;所以无法精确预知将会发…

libzmq使用zmq_poller就出现 was not declared in this scope

问题描述: 最近在使用zmq的 pub/sup模型的时候,使用zmq_poller就出现 was not declared in this scope 问题分析 关于这个问题,见zmq.h 619行左右: 619: #ifdef ZMQ_BUILD_DRAFT_API poller相关的api接口都在里面,如果这里没有定义ZMQ_BUILD_DRAFT_API宏,poller相关的API是无…

k8s的node亲和性和pod亲和性和反亲和性 污点 cordon drain

node亲和性和pod亲和性和反亲和性 污点 cordon drain 集群调度: schedule的调度算法 预算策略 过滤出合适的节点 优先策略 选择部署的节点 nodeName:硬匹配&#xff0c;不走调度策略&#xff0c;node01 nodeSelector:根据节点的标签选择&#xff0c;会走调度的算法 只…

PSoc62™开发板之PWM呼吸灯

实验目的 利用PWM动态调节输出功率达到控制LED呼吸变化的效果 实验准备 PSoc62™开发板&#xff08;开发板已经板载LED&#xff09; 板载资源 板载有多少pwm 创建工程例程&#xff0c;在libraries/HAL_Drivers/drv_pwm.h中查看BSP支持的pwm数量及对应的GPIO&#xff0c;可…

pgsql中epoch用法

问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 昨天又被叫回来加班,説是数据问题,又回来加班搞,到了以后发现数据没问题,那就是查询接口的事了,写查询接口的人用时间戳去查询,明明直接可以直接用日期查询,非得改成时间戳查询,结果还是有问题,接下来复盘一下…

【华为OD】系统需要提供人民币(CNY)、美元(USD)、英镑(GBP)、港币(HKD)价值 转换功能。

题目描述: 系统需要提供人民币(CNY)、美元(USD)、英镑(GBP)、港币(HKD)价值 转换功能。为了简单处理,题目的说明和考生调试可以用下面的默认汇 率:2 3 1 CNY = 2 HKD 1 USD = 8 CNY 1 GBP = 2 USD请按照汇率实现货

「 网络安全术语解读 」内容安全策略CSP详解

引言&#xff1a;什么是CSP&#xff0c;它为什么可以防御一些常见的网络攻击&#xff0c;比如XSS攻击&#xff0c;具体原理是什么&#xff1f;以及如何绕过CSP&#xff1f; 1. CSP定义 CSP&#xff08;Content Security Policy&#xff0c;内容安全策略&#xff09;是一种网络…

LeetCode 32. 最长有效括号

最长有效括号 给你一个只包含 ‘(’ 和 ‘)’ 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 方法一、栈 由于要存在配对&#xff0c;必然有)存在&#xff0c;配对的长度为上一个)到当前)之间的距离&#xff0c;即为最长配对子串&…

centos安装gradle

1.将gradle.zip拷到centos 解压 2.配置环境变量 vim /etc/profile 在最后添加 export GRADLE_HOME/zx/gradle-8.5 export PATH$PATH:$GRADLE_HOME/bin:${PATH} 之后source /etc/profile gradle -version 安装成功

RK3566环境搭建

环境&#xff1a;vmware16&#xff0c;ubuntu 18.04 获取SDK前需要安装 sudo apt update sudo apt install -y repo git python 下载完成后先验证一下MD5码 md5sum rk356x_linux_release_v1.3.0b_20221213_split_dir/*firefly_split* 解压 rk3566ubuntu:/path/to$ mkdir ~…

【7-zip密码】7-Zip如何取消文件加密的密码

7z压缩包设置了密码&#xff0c;解压的时候就需要输入正确的密码才能顺利解压出文件&#xff0c;正常当我们解压文件或者删除密码的时候&#xff0c;虽然方法多&#xff0c;但是都需要输入正确的密码才能完成。忘记密码就无法进行操作。 那么&#xff0c;忘记了7z压缩包的密码…

Linux网络命令

文章目录 Linux网络网络配置命令1、ifconfig&#xff1a;查看网络接口信息&#xff08;显示所有活动网卡&#xff09;1.1 常用命令格式1.2 命令格式&#xff08;图文详解&#xff09;1.2.1 临时修改网卡名称1.2.2 永久修改网卡名称1.2.3 永久修改单个网卡 2、hostname&#xff…

JAVA面向对象基础-容器

一、泛型 我们可以在类的声明处增加泛型列表&#xff0c;如&#xff1a;<T,E,V>。 此处&#xff0c;字符可以是任何标识符&#xff0c;一般采用这3个字母。 【示例9-1】泛型类的声明 1 2 3 4 5 6 7 8 9 10 class MyCollection<E> {// E:表示泛型; Object[] o…

ESP32_ADC(Arduino)

ADC模数转换 ESP32集成了12位的逐次逼近式ADC&#xff0c;分别为ADC1模块ADC2模块&#xff0c;共支持18个模拟输入通道: ADC1模块&#xff1a;8个通道&#xff0c;32~39ADC2模块&#xff1a;10个通道&#xff0c;0&#xff0c;2&#xff0c;4&#xff0c;12 ~ 15&#xff0c;…

调试器加载错误,从任务栏打开可能会导致该问题 2024/1/8

&#x1f9e7;喜欢将常用软件固定在任务栏的用户肯定很苦恼这个问题 &#x1f9e7;问题复现 &#x1f9e7;这里先查找一下原因 &#x1f9e7;查看一下固定在任务栏的微信小程序开发工具的属性 如果不会打开任务栏图标属性界面的小伙伴请先翻到文章最后 &#x1f9e7;再使用同样…

HJ2 计算某字符出现次数

题目描述&#xff1a; 写出一个程序&#xff0c;接受一个由字母、数字和空格组成的字符串&#xff0c;和一个字符&#xff0c;然后输出输入字符串中该字符的出现次数。&#xff08;不区分大小写字母&#xff09; 数据范围&#xff1a; 1≤n≤1000 输入描述&#xff1a; 第一…

第十六章 正则的介绍和使用

文章目录 一、正则的概念和意义二、正则的创建三、正则的写四、正则的使用五、读正则六、练习 一、正则的概念和意义 正则&#xff1a;正确的规则&#xff0c;用来约束字符串的规则&#xff0c;正则的数据类型是对象&#xff0c;正则对象&#xff0c;正则表达式正则的意义&…