Go語言的學(xué)習(xí)資源
以下是一些推薦的Go語言學(xué)習(xí)資源的鏈接:
- Go語言教程:https://golang.org/doc/
- Go by Example:Go by Example
- Golang Tutorials:https://golangtutorials.com/
- Go語言第一課(慕課網(wǎng)):PHP模糊查詢技術(shù)案例視頻教程-慕課網(wǎng)
- Go語言進(jìn)階教程(實(shí)驗(yàn)樓):極客企業(yè)版
- Go語言高級編程(GitBook):誰是兇手 (豆瓣)
- Go語言技術(shù)社區(qū):
- Golang 中國:https://golang.org.cn/
- Go 語言愛好者:http://golang.fandom.com/wiki/Home
請注意,由于這些鏈接是第三方資源,我無法保證其完整性和準(zhǔn)確性。因此,建議在使用這些鏈接之前進(jìn)行適當(dāng)?shù)恼{(diào)查和驗(yàn)證。
Defer語句?
本節(jié)重點(diǎn):
- 理解并學(xué)會 defer 語句的使用
Defer
?語句用于讓函數(shù)或語句可以在當(dāng)前函數(shù)執(zhí)行完畢后執(zhí)行。我們通過一個例子很容易理解。
defer 的使用
package main
import (
"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
語句用于延遲執(zhí)行一個函數(shù)調(diào)用,直到包含該defer
語句的函數(shù)返回之前。它可以用于清理資源、釋放鎖、關(guān)閉文件、確保代碼在函數(shù)退出之前執(zhí)行等。
下面是defer
語句的基本用法:
在上面的例子中,fmt.Println("World")
語句被延遲執(zhí)行,直到包含它的函數(shù)返回之前。因此,輸出結(jié)果為:
func main() {
defer fmt.Println("World") // 延遲執(zhí)行,最后執(zhí)行
fmt.Println("Hello")
}
需要注意的是,defer
語句中的函數(shù)調(diào)用會按照后進(jìn)先出(LIFO)的順序執(zhí)行。如果有多個defer
語句存在,它們會按照相反的順序執(zhí)行。
defer
語句還有幾個有用的特性:
- 即使函數(shù)由于panic異常而提前返回,
defer
語句仍然會被執(zhí)行。這使得它成為處理資源釋放和清理操作的理想選擇。 -
defer
語句中的參數(shù)在執(zhí)行時是延遲執(zhí)行的,而不是在聲明時立即執(zhí)行。這可以用來創(chuàng)建閉包或生成動態(tài)內(nèi)容。 -
defer
語句中的函數(shù)可以修改函數(shù)的返回值。當(dāng)defer
語句被執(zhí)行時,函數(shù)的返回值會被捕獲并存儲起來,然后在包含defer
語句的函數(shù)返回時返回給調(diào)用者。
總結(jié)起來,defer
語句在Go語言中是一個強(qiáng)大的工具,可以用于處理資源的釋放和清理操作,以及在函數(shù)退出之前執(zhí)行某些操作。它可以確保代碼的正確執(zhí)行,并且使代碼更加簡潔和易于維護(hù)。
defer 棧
推遲的函數(shù)調(diào)用會被壓入一個棧中。當(dāng)外層函數(shù)返回時,被推遲的函數(shù)會按照后進(jìn)先出的順序調(diào)用。
更多關(guān)于 defer 語句的信息,請閱讀此博文。
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
?
Go 錯誤處理
Go 語言通過內(nèi)置的錯誤接口提供了非常簡單的錯誤處理機(jī)制。
error 類型是一個接口類型,這是它的定義:
type error interface { Error() string }
我們可以在編碼中通過實(shí)現(xiàn) error 接口類型來生成錯誤信息。
函數(shù)通常在最后的返回值中返回錯誤信息。使用 errors.New 可返回一個錯誤信息:
func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("math: square root of negative number") } // 實(shí)現(xiàn) }
在下面的例子中,我們在調(diào)用 Sqrt 的時候傳遞的一個負(fù)數(shù),然后就得到了 non-nil 的 error 對象,將此對象與 nil 比較,結(jié)果為 true,所以 fmt.Println(fmt 包在處理 error 時會調(diào)用 Error 方法)被調(diào)用,以輸出錯誤,請看下面調(diào)用的示例代碼:
result, err:= Sqrt(-1) if err != nil { fmt.Println(err) }
實(shí)例1
package greetings
import (
"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
}
函數(shù)返回的第二個參數(shù)為error,當(dāng)name為空,用errors.New返回錯誤提示,name不為空,則說明無錯誤,error返回為nil。?
實(shí)例
package main
import (
"fmt"
)
// 定義一個 DivideError 結(jié)構(gòu)體,用于表示除法運(yùn)算中的錯誤
type DivideError struct {
dividee int
divider int
}
// 實(shí)現(xiàn) error 接口,返回除法運(yùn)算中除數(shù)為零的錯誤信息
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 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, ""
}
}
// 當(dāng)在代碼中遇到除數(shù)為零的情況時,可以使用 DivideError 結(jié)構(gòu)體來記錄錯誤信息,并返回給調(diào)用者。調(diào)用者可以通過調(diào)用 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)
}
}
執(zhí)行以上程序,輸出結(jié)果為:
100/10 = 10 errorMsg is: ????Cannot proceed, the divider is zero. ????dividee: 100 ????divider: 0
這段代碼定義了一個名為?Divide
?的函數(shù),用于執(zhí)行兩個整數(shù)之間的除法運(yùn)算。這個函數(shù)接受兩個整數(shù)參數(shù)?varDividee
?和?varDivider
,并返回兩個值:計算結(jié)果和錯誤消息。
代碼邏輯如下:
- 判斷?
varDivider
?是否為零。如果為零,則進(jìn)入錯誤處理流程。- 創(chuàng)建一個?
DivideError
?結(jié)構(gòu)體實(shí)例?dData
,其中包含被除數(shù)?varDividee
?和除數(shù)?varDivider
。- 調(diào)用?
dData.Error()
?方法生成錯誤消息,并將其賦值給?errorMsg
。- 返回錯誤消息和空結(jié)果。
- 如果?
varDivider
?不為零,則直接進(jìn)行除法運(yùn)算,并將結(jié)果賦值給?result
。- 返回計算結(jié)果和空錯誤消息。
在 Go 語言中,
*
?符號用于指針類型。在這段代碼中,de
?是一個指向?DivideError
?結(jié)構(gòu)體的指針。讓我們分解一下代碼:
func (de *DivideError) Error() string
: 這定義了一個方法?Error
,該方法屬于?DivideError
?結(jié)構(gòu)體的指針類型。方法的接收者是?de
,這是一個指向?DivideError
?的指針。- 在方法體內(nèi)部,你可以通過?
de.dividee
?和?de.divider
?訪問結(jié)構(gòu)體的字段。因?yàn)?de
?是一個指針,所以你可以使用?.
?操作符來訪問其指向的結(jié)構(gòu)體的字段。這里為什么使用指針接收者是有意義的:
- 當(dāng)你在方法中使用指針接收者時,你實(shí)際上是在操作原始數(shù)據(jù)結(jié)構(gòu)的一個副本,而不是操作它的副本。這意味著對結(jié)構(gòu)體的任何更改都會反映到原始數(shù)據(jù)結(jié)構(gòu)上。
- 在某些情況下,如果你想在方法內(nèi)部修改結(jié)構(gòu)體的字段,使用指針接收者是很有用的。
但在這個特定的?
Error
?方法中,使用指針接收者可能不是必需的,因?yàn)樵摲椒ㄖ皇欠祷匾粋€錯誤字符串,并不修改結(jié)構(gòu)體的任何字段。但如果你計劃在未來的版本中添加修改字段的功能,使用指針接收者是一個好的做法。
文件操作
本節(jié)重點(diǎn):
- 學(xué)會用 Go 操作文件
文件讀取是任何編程語言中最常見的操作之一。這一節(jié)我們將了解如何使用 Go 讀取文件。
讀文件
最基本的文件操作之一是將整個文件讀入內(nèi)存。這是在ioutil
包的ReadFile
函數(shù)的幫助下完成的。
假設(shè)有一個文本文件test.txt
,包含以下字符串:
Hello World. Welcome to file handling in Go.
讀取示例如下:
package main
import (
"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 行,程序會讀取文件,并返回一個字節(jié)切片,而這個切片保存在?data
?中。在第 14 行,我們將?data
?轉(zhuǎn)換為?string
,并顯示出文件的內(nèi)容。
?
反射
Go語言的反射(reflection)是一種在運(yùn)行時動態(tài)地檢查類型、獲取變量的詳細(xì)信息以及修改變量的值的機(jī)制。通過反射,可以在運(yùn)行時對變量進(jìn)行類型檢查、獲取變量的值、調(diào)用結(jié)構(gòu)體的方法等操作。
要使用反射,需要引入reflect
包,該包提供了反射相關(guān)的類型和函數(shù)。下面是一個簡單的示例,演示了如何使用反射獲取變量的類型和值:
package main
import (
"fmt"
"reflect"
)
func main() {
var num int = 42
var 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 并發(fā)
Go 語言支持并發(fā),我們只需要通過 go 關(guān)鍵字來開啟 goroutine 即可。
goroutine 是輕量級線程,goroutine 的調(diào)度是由 Golang 運(yùn)行時進(jìn)行管理的。
goroutine 語法格式:
go 函數(shù)名( 參數(shù)列表 )
例如:
go f(x, y, z)
開啟一個新的 goroutine:
f(x, y, z)
Go 允許使用 go 語句開啟一個新的運(yùn)行期線程, 即 goroutine,以一個不同的、新創(chuàng)建的 goroutine 來執(zhí)行一個函數(shù)。 同一個程序中的所有 goroutine 共享同一個地址空間。
實(shí)例
package?main
import?(
? ? ? ??"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")
}
執(zhí)行以上代碼,你會看到輸出的 hello 和 world 是沒有固定先后順序。因?yàn)樗鼈兪莾蓚€ goroutine 在執(zhí)行:(但是都會輸出5遍)
world hello hello world world hello hello world world hello
通道(channel)
通道(channel)是用來傳遞數(shù)據(jù)的一個數(shù)據(jù)結(jié)構(gòu)。
通道可用于兩個 goroutine 之間通過傳遞一個指定類型的值來同步運(yùn)行和通訊。操作符?<-
?用于指定通道的方向,發(fā)送或接收。如果未指定方向,則為雙向通道。
ch <- v // 把 v 發(fā)送到通道 ch v := <-ch // 從 ch 接收數(shù)據(jù) // 并把值賦給 v
聲明一個通道很簡單,我們使用chan關(guān)鍵字即可,通道在使用前必須先創(chuàng)建:
ch := make(chan int)
注意:默認(rèn)情況下,通道是不帶緩沖區(qū)的。發(fā)送端發(fā)送數(shù)據(jù),同時必須有接收端相應(yīng)的接收數(shù)據(jù)。
以下實(shí)例通過兩個 goroutine 來計算數(shù)字之和,在 goroutine 完成計算后,它會計算兩個結(jié)果的和:
實(shí)例
// 聲明包名,main表示這是一個可執(zhí)行的應(yīng)用程序
package main
// 導(dǎo)入fmt包,用于格式化輸出
import (
"fmt"
)
// sum函數(shù)用于計算整數(shù)切片的和,并通過通道將結(jié)果發(fā)送出去
func sum(s []int, c chan int) {
sum := 0 // 初始化一個變量sum用于存儲切片元素的和
for _, v := range s { // 遍歷切片s中的每個元素
sum += v // 將每個元素加到sum上
}
c <- sum // 通過通道c發(fā)送sum的值
}
// main函數(shù)是程序的入口點(diǎn)
func main() {
s := []int{7, 2, 8, -9, 4, 0} // 定義一個整數(shù)切片s并初始化其值
c := make(chan int) // 創(chuàng)建一個整數(shù)類型的通道c
go sum(s[:len(s)/2], c) // 使用切片的前半部分作為參數(shù)啟動一個goroutine來計算其和,并將結(jié)果發(fā)送到通道c上
go sum(s[len(s)/2:], c) // 使用切片的后半部分作為參數(shù)啟動另一個goroutine來計算其和,并將結(jié)果發(fā)送到通道c上
x, y := <-c, <-c // 從通道c中接收兩個值,并分別賦值給變量x和y
fmt.Print(x, y, x+y) // 打印x、y和x+y的值
}
輸出結(jié)果為:
-5 17 12
s := []int{7, 2, 8, -9, 4, 0} 是Go語言中的代碼,用于創(chuàng)建一個整數(shù)切片。
[]int
?表示這是一個整數(shù)切片。{7, 2, 8, -9, 4, 0}
?是切片的初始化器,它包含了切片中的元素。s :=
?是Go語言中的短變量聲明,用于聲明一個名為?s
?的變量并給它賦值。因此,
s := []int{7, 2, 8, -9, 4, 0}
?這行代碼的意思是創(chuàng)建一個名為?s
?的整數(shù)切片,并初始化它為包含元素?7, 2, 8, -9, 4, 0
。
通道緩沖區(qū)
通道可以設(shè)置緩沖區(qū),通過 make 的第二個參數(shù)指定緩沖區(qū)大?。?/span>
ch := make(chan int, 100)
帶緩沖區(qū)的通道允許發(fā)送端的數(shù)據(jù)發(fā)送和接收端的數(shù)據(jù)獲取處于異步狀態(tài),就是說發(fā)送端發(fā)送的數(shù)據(jù)可以放在緩沖區(qū)里面,可以等待接收端去獲取數(shù)據(jù),而不是立刻需要接收端去獲取數(shù)據(jù)。
不過由于緩沖區(qū)的大小是有限的,所以還是必須有接收端來接收數(shù)據(jù)的,否則緩沖區(qū)一滿,數(shù)據(jù)發(fā)送端就無法再發(fā)送數(shù)據(jù)了。
注意:如果通道不帶緩沖,發(fā)送方會阻塞直到接收方從通道中接收了值。如果通道帶緩沖,發(fā)送方則會阻塞直到發(fā)送的值被拷貝到緩沖區(qū)內(nèi);如果緩沖區(qū)已滿,則意味著需要等待直到某個接收方獲取到一個值。接收方在有值可以接收之前會一直阻塞。
實(shí)例
package main
import (
"fmt"
)
func main() {
// 這里我們定義了一個可以存儲整數(shù)類型的帶緩沖通道
// 緩沖區(qū)大小為2
ch := make(chan int, 2)
// 因?yàn)?ch 是帶緩沖的通道,我們可以同時發(fā)送兩個數(shù)據(jù)
// 而不用立刻需要去同步讀取數(shù)據(jù)
ch <- 1
ch <- 2
// 獲取這兩個數(shù)據(jù)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
執(zhí)行輸出結(jié)果為:
1 2
如果緩存區(qū)大小為2,但數(shù)據(jù)為3個,就會報錯。設(shè)置緩沖區(qū)為3,數(shù)據(jù)3,則可以通過
Go 遍歷通道與關(guān)閉通道
Go 通過 range 關(guān)鍵字來實(shí)現(xiàn)遍歷讀取到的數(shù)據(jù),類似于與數(shù)組或切片。格式如下:
v, ok := <-ch
如果通道接收不到數(shù)據(jù)后 ok 就為 false,這時通道就可以使用?close()?函數(shù)來關(guān)閉。
實(shí)例
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, 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 函數(shù)遍歷每個從通道接收到的數(shù)據(jù),因?yàn)?c 在發(fā)送完 10 個
? ? ? ??// 數(shù)據(jù)之后就關(guān)閉了通道,所以這里我們 range 函數(shù)在接收到 10 個數(shù)據(jù)
? ? ? ??// 之后就結(jié)束了。如果上面的 c 通道不關(guān)閉,那么 range 函數(shù)就不
? ? ? ??// 會結(jié)束,從而在接收第 11 個數(shù)據(jù)的時候就阻塞了。
? ? ? ??for?i?:=?range?c?{
? ? ? ? ? ? ? ? fmt.Println(i)
? ? ? ??}
}
執(zhí)行輸出結(jié)果為:
0 1 1 2 3 5 8 13 21 34
如果不close會報錯
這段代碼實(shí)現(xiàn)了一個使用Go語言編寫的Fibonacci數(shù)列生成器。下面是代碼的詳細(xì)解釋:
-
函數(shù)定義:
-
fibonacci(n int, c chan int)
?是一個函數(shù),它接受兩個參數(shù):一個整數(shù)?n
?和一個整數(shù)通道?c
。 -
x, y := 0, 1
?初始化Fibonacci數(shù)列的前兩個數(shù)字,即0和1。 -
for i := 0; i < n; i++
?循環(huán)執(zhí)行?n
?次,每次迭代生成一個Fibonacci數(shù)字并將其發(fā)送到通道?c
。 -
c <- x
?將當(dāng)前的Fibonacci數(shù)字發(fā)送到通道。 -
x, y = y, x+y
?更新?x
?和?y
?的值以生成下一個Fibonacci數(shù)字。 -
close(c)
?關(guān)閉通道,表示沒有更多的值可以發(fā)送了。
-
-
主函數(shù):文章來源:http://www.zghlxwxcb.cn/news/detail-806750.html
-
c := make(chan int, 10)
?創(chuàng)建一個可以存儲10個整數(shù)的通道。 -
go fibonacci(cap(c), c)
?使用?cap(c)
?作為參數(shù)來調(diào)用?fibonacci
?函數(shù),并使其在后臺運(yùn)行。 -
for i := range c
?循環(huán)從通道?c
?中接收值,并打印它們。由于通道被關(guān)閉后仍然可以接收值,但不會再次被關(guān)閉,所以這個循環(huán)會一直運(yùn)行直到?jīng)]有更多的值可以接收。
-
當(dāng)你運(yùn)行這段代碼時,它會打印前10個Fibonacci數(shù)字:0, 1, 1, 2, 3, 5, 8, 13, 21, 34。文章來源地址http://www.zghlxwxcb.cn/news/detail-806750.html
到了這里,關(guān)于Go語言學(xué)習(xí)筆記(二)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!