理解Go Interface

理解Go Interface

1 概述

Go語言中的接口很特別,而且提供了難以置信的一系列靈活性和抽象性。接口是一個自定義類型,它是一組方法的集合,要有方法為接口類型就被認為是該接口。從定義上來看,接口有兩個特點:

  • 接口本質是一種自定義類型,因此不要將Go語言中的接口簡單理解為C++/Java中的接口,后者僅用于聲明方法簽名。
  • 接口是一種特殊的自定義類型,其中沒有數據成員,只有方法(也可以為空)。

接口是完全抽象的,因此不能將其實例化。然而,可以創建一個其類型為接口的變量,它可以被賦值為任何滿足該接口類型的實際類型的值。接口的重要特性是:

  1. 只要某個類型實現了接口所有的方法,那么我們就說該類型實現了此接口。該類型的值可以賦給該接口的值。
  2. 作為1的推論,任何類型的值都可以賦值給空接口interface{}。

接口的特性是Go語言支持鴨子類型的基礎,即“如果它走起來像鴨子,叫起來像鴨子(實現了接口要的方法),它就是一只鴨子(可以被賦值給接口的值)”。憑借接口機制和鴨子類型,Go語言提供了一種有利于類、繼承、模板之外的更加靈活強大的選擇。只要類型T的公開方法完全滿足接口I的要求,就可以把類型T的對象用在需要接口I的地方。這種做法的學名叫做”Structural Typing“。

2 方法

Go語言中同時有函數和方法。一個方法就是一個包含了接受者的函數,接受者可以是命名類型或者結構體類型的一個值或者是一個指針。所有給定類型的方法屬于該類型的方法集。

type User struct {Name  stringEmail string
}func (u User) Notify() error// User 類型的值可以調用接受者是值的方法
damon := User{"AriesDevil", "ariesdevil@xxoo.com"}
damon.Notify()// User 類型的指針同樣可以調用接受者是值的方法
alimon := &User{"A-limon", "alimon@ooxx.com"}
alimon.Notify()

User的結構體類型,定義了一個該類型的方法叫做Notify,該方法的接受者是一個User類型的值。要調用Notify方法我們需要一個?User類型的值或者指針。Go調用和解引用指針使得調用可以被執行。注意,當接受者不是一個指針時,該方法操作對應接受者的值的副本(意思就是即使你使用了指針調用函數,但是函數的接受者是值類型,所以函數內部操作還是對副本的操作,而不是指針操作。

我們可以修改Notify方法,讓它的接受者使用指針類型:

func (u *User) Notify() error

再來一次之前的調用(注意:當接受者是指針時,即使用值類型調用那么函數內部也是對指針的操作。

總結:

  • 一個結構體的方法的接收者可能是類型值或指針
  • 如果接收者是值,無論調用者是類型值還是類型指針,修改都是值的副本
  • 如果接收者是指針,則調用者修改的是指針指向的值本身。

3 接口實現

type Notifier interface {Notify() error
}func SendNotification(notify Notifier) error {return notify.Notify()
}unc (u *User) Notify() error {log.Printf("User: Sending User Email To %s<%s>\n",u.Name,u.Email)return nil
}func main() {user := User{Name:  "AriesDevil",Email: "ariesdevil@xxoo.com",}SendNotification(user)
}// Output:
cannot use user (type User) as type Notifier in function argument:
User does not implement Notifier (Notify method has pointer receiver)

上述代碼是編譯不過的,見Output,編譯錯誤關鍵信息Notify method has pointer receiver。 編譯器不考慮我們的是實現該接口的類型,接口的調用規則是建立在這些方法的接受者和接口如何被調用的基礎上。下面的是語言規范里定義的規則,這些規則用來說明是否我們一個類型的值或者指針實現了該接口:

  • 類型?*T?的可調用方法集包含接受者為?*T?或?T?的所有方法集
  • 類型?T?的可調用方法集包含接受者為?T?的所有方法
  • 類型?T?的可調用方法集包含接受者為?*T?的方法

也就是說:

  • 接收者是指針?*T?時,接口的實例必須是指針
  • 接收者是值?T?時,接口的實例可以是指針也可以是

4 空接口與nil

空接口(interface{})不包含任何的method,正因為如此,所有的類型都實現了interface{}interface{}對于描述起不到任何的作用(因為它不包含任何的method),但是interface{}在我們需要存儲任意類型的數值的時候相當有用,因為它可以存儲任意類型的數值。它有點類似于C語言的void*類型。

Go語言中的nil在概念上和其它語言的null、None、nil、NULL一樣,都指代零值或空值。nil是預先說明的標識符,也即通常意義上的關鍵字。nil只能賦值給指針、channel、func、interface、map或slice類型的變量。如果未遵循這個規則,則會引發panic。

在底層,interface作為兩個成員來實現,一個類型(type)和一個值(data)。參考官方文檔翻譯Go中error類型的nil值和nil。

import ("fmt""reflect"
)func main() {var val interface{} = int64(58)fmt.Println(reflect.TypeOf(val))val = 50fmt.Println(reflect.TypeOf(val))
}

type用于存儲變量的動態類型,data用于存儲變量的具體數據。在上面的例子中,第一條打印語句輸出的是:int64。這是因為已經顯示的將類型為int64的數據58賦值給了interface類型的變量val,所以val的底層結構應該是:(int64, 58)。我們暫且用這種二元組的方式來描述,二元組的第一個成員為type,第二個成員為data。第二條打印語句輸出的是:int。這是因為字面量的整數在golang中默認的類型是int,所以這個時候val的底層結構就變成了:(int, 50)。

func main() {var val interface{} = nilif val == nil {fmt.Println("val is nil")} else {fmt.Println("val is not nil")}
}

變量val是interface類型,它的底層結構必然是(type, data)。由于nil是untyped(無類型),而又將nil賦值給了變量val,所以val實際上存儲的是(nil, nil)。因此很容易就知道val和nil的相等比較是為true的。

進一步驗證:

func main() {var val interface{} = (*interface{})(nil)if val == nil {fmt.Println("val is nil")} else {fmt.Println("val is not nil")}
}

(*interface{})(nil)是將nil轉成interface類型的指針,其實得到的結果僅僅是空接口類型指針并且它指向無效的地址。也就是空接口類型指針而不是空指針,這兩者的區別蠻大的。

對于(*int)(nil)(*byte)(nil)等等來說是一樣的。上面的代碼定義了接口指針類型變量val,它指向無效的地址(0x0),因此val持有無效的數據。但它是有類型的(*interface{})。所以val的底層結構應該是:(*interface{}, nil)

有時候您會看到(*interface{})(nil)的應用,比如var ptrIface = (*interface{})(nil),如果您接下來將ptrIface指向其它類型的指針,將通不過編譯。或者您這樣賦值:*ptrIface = 123,那樣的話編譯是通過了,但在運行時還是會panic的,這是因為ptrIface指向的是無效的內存地址。其實聲明類似ptrIface這樣的變量,是因為使用者只是關心指針的類型,而忽略它存儲的值是什么。

小結: 無論該指針的值是什么:(*interface{}, nil),這樣的接口值總是非nil的,即使在該指針的內部為nil。

5 接口變量存儲的類型

接口的變量里面可以存儲任意類型的數值(該類型實現了某interface)。那么我們怎么反向知道這個變量里面實際保存了的是哪個類型的對象呢?目前常用的有兩種方法:

  • comma-ok斷言

    value, ok = element.(T),這里value就是變量的值,ok是一個bool類型,element是interface變量,T是斷言的類型。如果element里面確實存儲了T類型的數值,那么ok返回true,否則返回false。

  • switch測試

    switch value := element.(type) {case int:fmt.Printf("list[%d] is an int and its value is %d\n", index, value)case string:fmt.Printf("list[%d] is a string and its value is %s\n", index, value)...
    

    element.(type)語法不能在switch外的任何邏輯里面使用,如果你要在switch外面判斷一個類型就使用comma-ok。

6 接口與反射

反射是程序運行時檢查其所擁有的結構,尤其是類型的一種能力。Go語言也提供對反射的支持。

在前面的interface{}與nil的底層實現已提到,在reflect包中有兩個類型需要了解:TypeValue。這兩個類型使得可以訪問接口變量的內容,還有兩個簡單的函數,reflect.TypeOfreflect.ValueOf,從接口值中分別獲取reflect.Type?和reflect.Value

如同物理中的反射,在Go語言中的反射也存在它自己的鏡像。從reflect.Value可以使用Interface方法還原接口值:

var x float64 = 3.4
v := reflect.ValueOf(x)// Interface 以 interface{} 返回 v 的值。
// func (v Value) Interface() interface{}// y 將為類型 float64
y := v.Interface().(float64) 
fmt.Println(y)

聲明:本文是收集網上一些關于Go語言中接口(interface)的說明,是一篇學習筆記,文中多處引用,參考文章列表在最后,可直接訪問了解詳情。

參考:
[1]?Go 語言中的方法,接口和嵌入類型
[2]?詳解interface和nil
[3]?Go語言interface詳解

轉載于:https://www.cnblogs.com/zhangboyu/p/7453003.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/282573.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/282573.shtml
英文地址,請注明出處:http://en.pswp.cn/news/282573.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

『中級篇』Dockerfile詳解(17)

一般的&#xff0c;Dockerfile 分為四部分&#xff1a;基礎鏡像信息、維護者信息、鏡像操作指令和容器啟動時執行指令。 ####官網學習 https://docs.docker.com/engine/reference/builder/#usage ####FROM 必須為第一個命令&#xff0c;指定基礎鏡像 FROM <image> FROM &…

洛谷 1165日志分析

題目描述 M 海運公司最近要對旗下倉庫的貨物進出情況進行統計。目前他們所擁有的唯一記錄就是一個記錄集裝箱進出情況的日志。該日志記錄了兩類操作&#xff1a;第一類操作為集裝箱入庫操作&#xff0c;以及該次入庫的集裝箱重量&#xff1b;第二類操作為集裝箱的出庫操作。這些…

KestrelServer詳解[1]:注冊監聽終結點(Endpoint)

具有跨平臺能力的KestrelServer是最重要的服務器類型。針對KestrelServer的設置均體現在KestrelServerOptions配置選項上&#xff0c;注冊的終結點是它承載的最重要的配置選項。這里所謂的終結點&#xff08;Endpoint&#xff09;與“路由”介紹的終結點不是一回事&#xff0c;…

php截取字符串,帶中文,多余的省略號代替

function subtext($text, $length) {if(mb_strlen($text, utf8) > $length) {return mb_substr($text, 0, $length, utf8)....;} else {return $text;}}$str 我們是family happy family; echo subtext($str,5); //我們是fa...

數據庫添加

<body><form action"herozhuce.php" method"post"> <div>賬號<input type"text" name"account"/></div> <div>密碼<input type"text" name"password"/></div> &…

快來加入阿里云大學【云學院】班級助理招募—機會稍縱即逝,錯過遙遙無期!...

2019獨角獸企業重金招聘Python工程師標準>>> 如果你對云計算、大數據、云安全、人工智能領域感興趣~ 如果你想從事與此相關的工作~~ 如果你又喜歡邊交流邊學習的方式~ 那么&#xff0c;加入我們吧&#xff01; 我們將為你提供一個廣闊的平臺&#xff0c;讓你接觸到云…

深入理解ajax系列第五篇——進度事件

前面的話 一般地&#xff0c;使用readystatechange事件探測HTTP請求的完成。XHR2規范草案定義了進度事件Progress Events規范&#xff0c;XMLHttpRequest對象在請求的不同階段觸發不同類型的事件&#xff0c;所以它不再需要檢査readyState屬性。這個草案定義了與客戶端服務器通…

對象(poco)深度克隆

提供深度克隆對象功能,基于編譯表達式實現&#xff0c;性能與原生代碼幾無差別&#xff0c;遠超 json/binary 序列化實現。1. 簡單示例class Person {public int Id { get; set; }public string Name { get; set; }public int Age { get; set; }public DateTime Birth { get; s…

php將數字轉化為中文大寫人民幣格式

<?phpfunction cny($ns) {static $cnums array("零","壹","貳","叁","肆","伍","陸","柒","捌","玖"),$cnyunits array("圓","角","分&…

BZOJ1787 [Ahoi2008]Meet 緊急集合 LCA

歡迎訪問~原文出處——博客園-zhouzhendong 去博客園看該題解 題目傳送門 - BZOJ1787 題意概括 有一棵節點為n個(n≤500000)的樹。接下來m次詢問(m≤500000)&#xff0c;每次給出3個點 a,b,c &#xff0c;現在讓你求一個點 p &#xff0c;使得 dis(p,a) dis(p,b) dis(p,c) 最…

Linux之ACL權限控制

ACL權限控制主要目的是提供傳統的owner,group,other的read,wirte,execute權限之外的具體權限設置&#xff0c;可以針對單一用戶或組來設置特定的權限 設置ACL權限&#xff1a;setfacl查看ACL權限&#xff1a;getfacl 比如&#xff1a;某一目錄權限為 drwx------ 2 root root 40…

WIX、Squarespace、WordPress 三者的優劣分別是什么?

層出不窮的智能建站&#xff0c;模板建站&#xff0c;源碼建站&#xff0c;云建站&#xff0c;仿站&#xff0c;各種建站概念都拋灑于紅海之中。到底什么樣的網站適合自己&#xff0c;什么樣的網站值得我們去消費&#xff0c;什么樣的網站能長久&#xff0c;是個非常值得思考的…

平滑的加權輪詢均衡算法

前言在反向代理、路由、分布式應用調度等場景中通常都需要用到負載均衡算法&#xff0c;負載均衡的關鍵要點是“均衡”&#xff0c;即確保調用請求能均衡地落到多個處理節點上&#xff0c;負載均衡算法一般使用隨機或輪詢都可以保證均衡性。現實中由于服務器性能或資源分配的差…

php類精確驗證身份證號碼

<?php class check_IdCard {// $num為身份證號碼&#xff0c;$checkSex&#xff1a;1為男&#xff0c;2為女&#xff0c;不輸入為不驗證public function checkIdentity($num, $checkSex ) { // 不是15位或不是18位都是無效身份證號if (strlen($num) ! 15 && strl…

請說說接口和抽象類的區別?

1.從使用目的來看&#xff1a; 接口只是一個類間的協議&#xff0c;它并沒有規定怎么去實現&#xff1b; 抽象類可以重用你代碼使你的代碼更加簡潔&#xff1b;2.從行為來看&#xff1a; 接口可以多繼承,multi-implement 抽象類不能實例化&#xff0c;必須子類化才能實例化…

GitHub 使用

Git 是由 Linux 之父 Linus Tovalds 為了更好的管理 linux 內核開發而創立的分布是版本控制/軟件管理配置軟件. 簡單來說, Git 管理你的 代碼的歷史記錄 的工具. 首先注冊賬戶 (已經完成, moveofgod) 然后, 下載一個 GitHub Desktop(mac), msisgit 客戶端 (可以用命令行實現, …

LinkedHashMap 與 HashMap區別

2019獨角獸企業重金招聘Python工程師標準>>> LinkedHashMap 與 HashMap區別 &#xff08;非原創&#xff09; HashMap,LinkedHashMap,TreeMap都屬于Map Map 主要用于存儲鍵(key)值(value)對&#xff0c;根據鍵得到值&#xff0c;因此鍵不允許鍵重復,但允許值重復。 …

C# 11 中的 file local type

C# 11 中的 file local typeIntro在之前的版本中&#xff0c;我們想要一個類型只在當前的類型中生效&#xff0c;通常我們會在一個類的內部聲明一個 private 的類型以此來控制這個類型的訪問權限&#xff0c;在 C# 11 中引入了一個 file local type&#xff0c;僅在聲明類型的這…

PHP實現類似百度搜索自動完成(代碼簡單)

一、效果圖: 二、HTML代碼 <html lang"en"> <head><meta charset"utf-8"><title>jQuery UI 自動完成&#xff08;Autocomplete&#xff09; - 默認功能</title><link rel"stylesheet" href"/public/Auto…

Mysql讀寫分離php腳本

<?php/*php如何連接mysql*/ /*$link mysql_connect(‘localhost‘, ‘root‘, ‘‘);if (!$link) {die(‘Could not connect: ‘ . mysql_error());}echo ‘Connected successfully‘;mysql_close($link);*/ /*php如何選擇數據庫*//*$link mysql_connect(‘localhost‘, …