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.hqwc.cn/news/336116.html

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

相关文章

【教学类-45-04】X-Y之间的“三连减加“题(a-b+c=)

作品展示&#xff1a; 背景需求&#xff1a; 【教学类-45-02】X-Y之间的“三连减“题(a-b-c)-CSDN博客文章浏览阅读465次&#xff0c;点赞15次&#xff0c;收藏7次。【教学类-45-02】X-Y之间的"三连减"题(a-b-c)https://blog.csdn.net/reasonsummer/article/details…

【Proteus仿真】【Arduino单片机】汽车车窗除霜系统设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用LCD1602显示模块、光线传感器、DS18B20温度传感器、PCF8691 ADC模块、继电器加热模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD…

3. SPSS数据文件的基本加工和处理

如何获取SPSS自带的案例数据文件&#xff1f; 首先找到SPSS的安装目录&#xff0c;然后找到Samples文件夹 可以看到有不同语言版本&#xff0c;选择简体中文 就能看到很多.sav文件 数据文件的整理 个案排序 单值排序 例&#xff1a;对于下面的数据集&#xff0c;将工资按…

华为mux vlan+DHCP+单臂路由用法配置案例

最终效果&#xff1a; vlan 2模拟局域网服务器&#xff0c;手动配置地址&#xff0c;也能上公网 vlan 3、4用dhcp分配地址 vlan 4的用户之间不能互通&#xff0c;但可以和其它vlan通&#xff0c;也能上公网 vlan 3的用户不受任何限制可以和任何vlan通&#xff0c;也能上公网 交…

SSL 阿里云证书申请和备案

一、什么是SSL SSL证书是数字证书的一种&#xff0c;类似于驾驶证、护照和营业执照的电子副本。因为配置在服务器上&#xff0c;也称为SSL服务器证书。遵守SSL协议&#xff0c;由受信任的数字证书颁发机构CA&#xff0c;在验证服务器身份后颁发&#xff0c;具有服务器身份验证…

IPC之十五:使用libdbus通过D-Bus请求系统调用实现任意DNS记录解析的实例

关于D-Bus的文章中曾介绍了如何通过D-Bus调用系统服务从而实现解析出一个域名的IP地址的过程&#xff0c;本文我们继续调用系统调用来实现解析任意DNS记录&#xff0c;系统调用的方法与前一篇文章类似&#xff0c;只是方法名称和调用参数以及返回参数不同&#xff0c;本文将详细…

typora导出html添加目录

typora导出html添加目录 使用方法 首先要从typora导出html文件&#xff0c;之后用记事本编辑器html文件 找到文档最后面&#xff0c;如图&#xff1a; 用文字编辑类工具打开sideBar.txt&#xff0c;复制其中所有内容【内容在下面】 在如上图的位置插入所复制的内容 打开修改…

node的下载、安装、配置

下载&#xff1a; 官网下载&#xff1a;Node.js 左右两个都可以&#xff1a; 安装&#xff1a; 打开cmd&#xff1a; 输入以下指令&#xff0c;如果出现版本号说明安装成功 node -v npm -v 如果npm -v报错&#xff0c;就需要自行下载npm CNPM Binaries Mirror ps&#xff1…

如何实现两台Linux虚拟机ssh免密登录

实验开始前 1.准备好两台虚拟机&#xff08;下载好镜像文件的&#xff09; 2.实验步骤 公钥验证&#xff1a;&#xff08;免密登陆验证方式&#xff09; &#xff08;1&#xff09;生成非对称秘钥 [rootclient ~]# ssh-keygen -t rsa Generating public/private rsa key pai…

STM32F4XX的12位ADC采集数值超过4096右对齐模式设置失败

文章目录 一、前言二、问题1&#xff1a;数值超过4096三、问题1的排错过程四、问题2&#xff1a;右对齐模式设置失败五、问题2的解决方法5.1 将ADC_ExternalTrigConv设置为05.2 使用ADC_StructInit()函数 一、前言 最近在学习STM32的ADC功能&#xff0c;遇到了一个奇怪的问题。…

C#PDF转Excel

組件 Spire.Pdf.dll, v7.8.9.0 【注意&#xff1a;版本太低的没有此功能】 在Visual Studio中找到参考&#xff0c;鼠标右键点击“引用”&#xff0c;“添加引用”&#xff0c;将本地路径debug文件夹下的dll文件添加引用至程序。 界面图&#xff1a; 1个label&#xff0c;1…

酷开科技凭借AIGC技术打造从产品到运营到生态的范本

近日&#xff0c;酷开科技成功挑战“全球最多人同时线上和线下开箱”吉尼斯纪录&#xff0c;为中国品牌出海打样。酷开科技&#xff0c;除了硬件上的实力&#xff0c;更有软件上的硬核。酷开科技之所以能够从中国OTT行业独角兽走向海外市场“开疆拓土”&#xff0c;是基于创新的…