国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Go 方法介紹,理解“方法”的本質

這篇具有很好參考價值的文章主要介紹了Go 方法介紹,理解“方法”的本質。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

Go 方法介紹,理解“方法”的本質

目錄
  • Go 方法介紹,理解“方法”的本質
    • 一、認識 Go 方法
      • 1.1 基本介紹
      • 1.2 聲明
      • 1.2.1 引入
      • 1.2.2 一般聲明形式
      • 1.2.3 receiver 參數作用域
      • 1.2.4 receiver 參數的基類型約束
      • 1.2.5 方法聲明的位置約束
      • 1.2.6 如何使用方法
    • 二、方法的本質
    • 三、巧解難題

一、認識 Go 方法

1.1 基本介紹

我們知道,Go 語言從設計伊始,就不支持經典的面向對象語法元素,比如類、對象、繼承,等等,但 Go 語言仍保留了名為“方法(method)”的語法元素。當然,Go 語言中的方法和面向對象中的方法并不是一樣的。Go 引入方法這一元素,并不是要支持面向對象編程范式,而是 Go 踐行組合設計哲學的一種實現層面的需要。

在 Go 編程語言中,方法是與特定類型相關聯的函數。它們允許您在自定義類型上定義行為,這個自定義類型可以是結構體(struct)或任何用戶定義的類型。方法本質上是一種函數,但它們具有一個特定的接收者(receiver),也就是方法所附加到的類型。這個接收者可以是指針類型或值類型。方法與函數的區(qū)別是,函數不屬于任何類型,方法屬于特定的類型。

1.2 聲明

1.2.1 引入

首先我們這里以 Go 標準庫 net/http 包中 *Server 類型的方法 ListenAndServeTLS 為例,講解一下 Go 方法的一般形式:

和 Go 函數一樣,Go 的方法也是以 func 關鍵字修飾的,并且和函數一樣,也包含方法名(對應函數名)、參數列表、返回值列表與方法體(對應函數體)。

而且,方法中的這幾個部分和函數聲明中對應的部分,在形式與語義方面都是一致的,比如:方法名字首字母大小寫決定該方法是否是導出方法;方法參數列表支持變長參數;方法的返回值列表也支持具名返回值等。

不過,它們也有不同的地方。從上面這張圖我們可以看到,和由五個部分組成的函數聲明不同,Go 方法的聲明有六個組成部分,多的一個就是圖中的 receiver 部分。在 receiver 部分聲明的參數,Go 稱之為 receiver 參數,這個 receiver 參數也是方法與類型之間的紐帶,也是方法與函數的最大不同。

Go 中的方法必須是歸屬于一個類型的,而 receiver 參數的類型就是這個方法歸屬的類型,或者說這個方法就是這個類型的一個方法。以圖中的 ListenAndServeTLS 為例,這里的 receiver 參數 srv 的類型為 *Server,那么我們可以說,這個方法就是 *Server 類型的方法。

注意!這里說的是 ListenAndServeTLS*Server 類型的方法,而不是 Server 類型的方法。

1.2.2 一般聲明形式

方法的聲明形式如下:

func (t *T或T) MethodName(參數列表) (返回值列表) {
    // 方法體
}

其中各部分的含義如下:

  • (t *T或T):括號中的部分是方法的接收者,用于指定方法將附加到的類型。t 是接收者的名稱,T 是接收者的類型。接收者可以是值類型(T)或指針類型(*T)。如果使用值類型作為接收者,方法操作的是接收者的副本,而指針類型允許方法修改接收者的原始值。無論 receiver 參數的類型為 *T 還是 T,我們都把一般聲明形式中的 T 叫做 receiver 參數 t 的基類型。如果 t 的類型為 T,那么說這個方法是類型 T 的一個方法;如果 t 的類型為 *T,那么就說這個方法是類型 *T 的一個方法。而且,要注意的是,每個方法只能有一個 receiver 參數,Go 不支持在方法的 receiver 部分放置包含多個 receiver 參數的參數列表,或者變長 receiver 參數。
  • MethodName:這是方法的名稱,用于在調用方法時引用它。
  • (參數列表):這是方法的參數列表,定義了方法可以接受的參數。如果方法不需要參數,此部分為空。
  • (返回值列表):這是方法的返回值列表,定義了方法返回的結果。如果方法不返回任何值,此部分為空。
  • 方法體:方法體包含了方法的具體實現,這里可以編寫方法的功能代碼。

1.2.3 receiver 參數作用域

方法接收器(receiver)參數、函數 / 方法參數,以及返回值變量對應的作用域范圍,都是函數 / 方法體對應的顯式代碼塊。

這就意味著,receiver 部分的參數名不能與方法參數列表中的形參名,以及具名返回值中的變量名存在沖突,必須在這個方法的作用域中具有唯一性。如果不唯一,比如下面的例子中那樣,Go 編譯器就會報錯:

type T struct{}

func (t T) M(t string) { // 編譯器報錯:duplicate argument t (重復聲明參數t)
    ... ...
}

不過,如果在方法體中沒有使用 receiver 參數,我們也可以省略 receiver 的參數名,就像下面這樣:

type T struct{}

func (T) M(t string) { 
    ... ...
}

僅當方法體中的實現不需要 receiver 參數參與時,我們才會省略 receiver 參數名,不過這一情況很少使用,了解一下即可。

1.2.4 receiver 參數的基類型約束

Go 語言對 receiver 參數的基類型也有約束,那就是 receiver 參數的基類型本身不能為指針類型或接口類型。

下面的例子分別演示了基類型為指針類型和接口類型時,Go 編譯器報錯的情況:

type MyInt *int
func (r MyInt) String() string { // r的基類型為MyInt,編譯器報錯:invalid receiver type MyInt (MyInt is a pointer type)
    return fmt.Sprintf("%d", *(*int)(r))
}

type MyReader io.Reader
func (r MyReader) Read(p []byte) (int, error) { // r的基類型為MyReader,編譯器報錯:invalid receiver type MyReader (MyReader is an interface type)
    return r.Read(p)
}

1.2.5 方法聲明的位置約束

Go 要求,方法聲明要與 receiver 參數的基類型聲明放在同一個包內。基于這個約束,我們還可以得到兩個推論。

  • 第一個推論:我們不能為原生類型(例如 int、float64、map 等)添加方法。例如,下面的代碼試圖為 Go 原生類型 int 增加新方法 Foo,這是不允許的,Go 編譯器會報錯:
func (i int) Foo() string { // 編譯器報錯:cannot define new methods on non-local type int
    return fmt.Sprintf("%d", i) 
}
  • 第二個推論:不能跨越 Go 包為其他包的類型聲明新方法。例如,下面的代碼試圖跨越包邊界,為 Go 標準庫中的 http.Server 類型添加新方法 Foo,這是不允許的,Go 編譯器同樣會報錯:
import "net/http"

func (s http.Server) Foo() { // 編譯器報錯:cannot define new methods on non-local type http.Server
}

1.2.6 如何使用方法

我們直接還是通過一個例子理解一下。如果 receiver 參數的基類型為 T,那么我們說 receiver 參數綁定在 T 上,我們可以通過 *T 或 T 的變量實例調用該方法:

type T struct{}

func (t T) M(n int) {
}

func main() {
    var t T
    t.M(1) // 通過類型T的變量實例調用方法M

    p := &T{}
    p.M(2) // 通過類型*T的變量實例調用方法M
}

這段代碼中,方法 M 是類型 T 的方法,通過 *T 類型變量也可以調用 M 方法。

二、方法的本質

通過以上,我們知道了 Go 的方法與 Go 中的類型是通過 receiver 聯系在一起,我們可以為任何非內置原生類型定義方法,比如下面的類型 T:

type T struct { 
    a int
}

func (t T) Get() int {  
    return t.a 
}

func (t *T) Set(a int) int { 
    t.a = a 
    return t.a 
}

在Go 中,Go 方法中的原理是將 receiver 參數以第一個參數的身份并入到方法的參數列表中。按照這個原理,我們示例中的類型 T*T 的方法,就可以分別等價轉換為下面的普通函數:

// 類型T的方法Get的等價函數
func Get(t T) int {  
    return t.a 
}

// 類型*T的方法Set的等價函數
func Set(t *T, a int) int { 
    t.a = a 
    return t.a 
}

這種等價轉換后的函數的類型就是方法的類型。只不過在 Go 語言中,這種等價轉換是由 Go 編譯器在編譯和生成代碼時自動完成的。Go 語言規(guī)范中還提供了方法表達式(Method Expression)的概念,可以讓我們更充分地理解上面的等價轉換。

以上面類型 T 以及它的方法為例,結合前面說過的 Go 方法的調用方式,我們可以得到下面代碼:

var t T
t.Get()
(&t).Set(1)

我們可以用另一種方式,把上面的方法調用做一個等價替換:

var t T
T.Get(t)
(*T).Set(&t, 1)

這種直接以類型名 T 調用方法的表達方式,被稱為Method Expression。通過Method Expression這種形式,類型 T 只能調用 T 的方法集合(Method Set)中的方法,同理類型 *T 也只能調用 *T 的方法集合中的方法。

我們看到,Method Expression 有些類似于 C++ 中的靜態(tài)方法(Static Method)。在 C++ 中的靜態(tài)方法使用時,以該 C++ 類的某個對象實例作為第一個參數。而 Go 語言的 Method Expression 在使用時,同樣以 receiver 參數所代表的類型實例作為第一個參數。

這種通過 Method Expression 對方法進行調用的方式,與我們之前所做的方法到函數的等價轉換是如出一轍的。所以,Go 語言中的方法的本質就是,一個以方法的 receiver 參數作為第一個參數的普通函數。

而且,Method Expression 就是 Go 方法本質的最好體現,因為方法自身的類型就是一個普通函數的類型,我們甚至可以將它作為右值,賦值給一個函數類型的變量,比如下面示例:

func main() {
    var t T
    f1 := (*T).Set // f1的類型,也是*T類型Set方法的類型:func (t *T, int)int
    f2 := T.Get    // f2的類型,也是T類型Get方法的類型:func(t T)int
    fmt.Printf("the type of f1 is %T\n", f1) // the type of f1 is func(*main.T, int) int
    fmt.Printf("the type of f2 is %T\n", f2) // the type of f2 is func(main.T) int
    f1(&t, 3)
    fmt.Println(f2(t)) // 3
}

三、巧解難題

我們來看一段代碼:

package main

import (
    "fmt"
    "time"
)

type field struct {
    name string
}

func (p *field) print() {
    fmt.Println(p.name)
}

func main() {
    data1 := []*field{{"one"}, {"two"}, {"three"}}
    for _, v := range data1 {
        go v.print()
    }

    data2 := []field{{"four"}, {"five"}, {"six"}}
    for _, v := range data2 {
        go v.print()
    }

    time.Sleep(3 * time.Second)
}

這段代碼在我的多核 macOS 上的運行結果是這樣(由于 Goroutine 調度順序不同,你自己的運行結果中的行序可能與下面的有差異):

one
two
three
six
six
six

為什么對 data2 迭代輸出的結果是三個“six”,而不是 four、five、six?

我們來分析一下。首先,我們根據 Go 方法的本質,也就是一個以方法的 receiver 參數作為第一個參數的普通函數,對這個程序做個等價變換。這里我們利用 Method Expression 方式,等價變換后的源碼如下:

type field struct {
    name string
}

func (p *field) print() {
    fmt.Println(p.name)
}

func main() {
    data1 := []*field{{"one"}, {"two"}, {"three"}}
    for _, v := range data1 {
        go (*field).print(v)
    }

    data2 := []field{{"four"}, {"five"}, {"six"}}
    for _, v := range data2 {
        go (*field).print(&v)
    }

    time.Sleep(3 * time.Second)
}

這段代碼中,我們把對 field 的方法 print 的調用,替換為 Method Expression 形式,替換前后的程序輸出結果是一致的。但變換后,問題是不是豁然開朗了!我們可以很清楚地看到使用 go 關鍵字啟動一個新 Goroutine 時,Method Expression 形式的 print 函數是如何綁定參數的:

  • 迭代 data1 時,由于 data1 中的元素類型是 field 指針 (*field),因此賦值后 v 就是元素地址,與 printreceiver 參數類型相同,每次調用 (*field).print 函數時直接傳入的 v 即可,實際上傳入的也是各個 field 元素的地址。
  • 迭代 data2 時,由于 data2 中的元素類型是 field(非指針),與 printreceiver 參數類型不同,因此需要將其取地址后再傳入 (*field).print 函數。這樣每次傳入的 &v 實際上是變量 v 的地址,而不是切片 data2 中各元素的地址。

在《Go 的 for 循環(huán),僅此一種》中,我們學習過 for range 使用時應注意的幾個問題,其中循環(huán)變量復用是關鍵的一個。這里的 v 在整個 for range 過程中只有一個,因此 data2 迭代完成之后,v 是元素 "six" 的拷貝

這樣,一旦啟動的各個子 goroutine 在 main goroutine 執(zhí)行到 Sleep 時才被調度執(zhí)行,那么最后的三個 goroutine 在打印 &v 時,實際打印的也就是在 v 中存放的值 "six"。而前三個子 goroutine 各自傳入的是元素 "one"、"two" 和 "three" 的地址,所以打印的就是 "one"、"two" 和 "three" 了。

那么原程序要如何修改,才能讓它按我們期望,輸出“one”、“two”、“three”、“four”、 “five”、“six”呢?

其實,我們只需要將 field 類型 print 方法的 receiver 類型由 *field 改為 field 就可以了。我們直接來看一下修改后的代碼:

type field struct {
    name string
}

func (p field) print() {
    fmt.Println(p.name)
}

func main() {
    data1 := []*field{{"one"}, {"two"}, {"three"}}
    for _, v := range data1 {
        go v.print()
    }

    data2 := []field{{"four"}, {"five"}, {"six"}}
    for _, v := range data2 {
        go v.print()
    }

    time.Sleep(3 * time.Second)
}

修改后的程序的輸出結果是這樣的(因 Goroutine 調度順序不同,在你的機器上的結果輸出順序可能會有不同):文章來源地址http://www.zghlxwxcb.cn/news/detail-741775.html

one
two
three
four
five
six

到了這里,關于Go 方法介紹,理解“方法”的本質的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 線性代數的本質——幾何角度理解

    線性代數的本質——幾何角度理解

    B站網課來自 3Blue1Brown的翻譯版,看完醍醐灌頂,強烈推薦: 線性代數的本質 本課程從幾何的角度翻譯了線代中各種核心的概念及性質,對做題和練習效果有實質性的提高,下面博主來總結一下自己的理解 在物理中的理解是一個有 起點和終點的方向矢量 ,而在計算機科學中

    2024年02月02日
    瀏覽(29)
  • 機器學習基礎(一)理解機器學習的本質

    機器學習基礎(一)理解機器學習的本質

    ????????導讀: 在本文中,將深入探索機器學習的根本原理,包括基本概念、分類及如何通過構建預測模型來應用這些理論。 目錄 機器學習 機器學習概念 相關概念 機器學習根本:模型 數據的語言:特征與標簽 訓練與測試:模型評估 機器學習的分類 監(jiān)督學習:有指導

    2024年02月20日
    瀏覽(21)
  • 本質安全設備標準(IEC60079-11)的理解(四)

    本質安全設備標準(IEC60079-11)的理解(四)

    IEC60079-11使用了較長的篇幅來說明設計中需要考慮到的各種間距, 這也從一定程度上說明了間距比較重要,在設計中是需要認真考慮。 從直覺上來講,間距越大,電路越安全,因為間距比較大,不容易產生電弧。同時間距比較大,也易于散熱,從溫度角度來說,設備也比較安

    2024年02月15日
    瀏覽(64)
  • 【云原生-深入理解Kubernetes-1】容器的本質是進程

    【云原生-深入理解Kubernetes-1】容器的本質是進程

    大家好,我是秋意零。 ?? CSDN作者主頁 ?? 博客主頁 ?? 簡介 ?? 普通本科生在讀 在校期間參與眾多計算機相關比賽,如:?? “省賽”、“國賽” ,斬獲多項獎項榮譽證書 ?? 各個平臺, 秋意零/秋意臨 賬號創(chuàng)作者 ?? 云社區(qū) 創(chuàng)建者 點贊、收藏+關注下次不迷路! 歡迎加

    2024年02月02日
    瀏覽(29)
  • 單點登錄與權限管理本質:權限管理介紹

    前面幾篇文章介紹了單點登錄的本質,包括cookie、session、重定向的基本概念,單點登錄的基本交互流程,cookie的重要性和安全問題。單點登錄能夠確保:必須通過身份驗證后,才能訪問網站,且訪問多個系統時,只需要登錄一次。 該系列的完整寫作計劃,可見:系列概述 系

    2024年02月11日
    瀏覽(15)
  • 串口RS232 RS485最本質的區(qū)別!-!I2C通訊協議 最簡單的總線通訊!-深入理解SPi通訊協議!

    串口RS232 RS485最本質的區(qū)別!-!I2C通訊協議 最簡單的總線通訊!-深入理解SPi通訊協議!

    來自 先講串口通訊,因為不管是R4232還是R485,都是串口通訊的變種。知道了串口通訊,再來看232和485,就很容易理解了。串口通訊非常容易實現,它在兩個芯片之間就可以實現信號的傳輸。在進行串口通訊時,首先要約定好真格式和波特率。這是一幀我們常見的幀格式,一共

    2024年02月04日
    瀏覽(24)
  • Python——迭代器(可迭代、可迭代對象、迭代器、遍歷本質、iter函數、next函數、__iter__方法、__next__方法、自定義可迭代對象與自定義迭代器、for循環(huán)本質)

    迭代(iter) 我們經常聽說過\\\"版本迭代\\\"這個詞,意思是在原來版本的基礎上,再提升一個版本的過程。那么我們僅僅看看\\\"迭代\\\"這個詞,會發(fā)現迭代就是一個根據原來的狀態(tài)決定本次狀態(tài)的過程 迭代應用于Python中,迭代具體是指根據原來的數據輸出(并不一定是要打印,也可

    2024年02月04日
    瀏覽(23)
  • 深入理解 go chan

    深入理解 go chan

    go 里面,在實際程序運行的過程中,往往會有很多協程在執(zhí)行,通過啟動多個協程的方式,我們可以更高效地利用系統資源。 而不同協程之間往往需要進行通信,不同于以往多線程程序的那種通信方式,在 go 里面是通過 channel (也就是 chan 類型)來進行通信的, 實現的方式

    2024年02月01日
    瀏覽(23)
  • 理解Go中的零值

    理解Go中的零值

    在 Go 語言中,零值(Zero Value)是指在聲明變量但沒有顯式賦值的情況下,變量會被自動賦予一個默認值。這個默認值取決于變量的類型,不同類型的變量會有不同的零值。零值是 Go 語言中的一個重要概念,因為它確保了變量在聲明后具有一個可預測的初始狀態(tài),減少了未初

    2024年02月05日
    瀏覽(22)
  • Go的閉包理解

    筆記倉庫:gitee.com/xiaoyinhui 一點點筆記,以便以后翻閱。

    2024年02月22日
    瀏覽(26)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包