大家好,今天將梳理出的 Go語言基礎語法內容,分享給大家。 請多多指教,謝謝。
本次《Go語言基礎語法內容》共分為三個章節,本文為第三章節
- Golang 基礎之基礎語法梳理 (一)
- Golang 基礎之基礎語法梳理 (二)
- Golang 基礎之基礎語法梳理 (三)
本章節內容
- interface
- 反射
- 泛型
interface
介紹
在Go語言中介面 (interface) 是一種型別, 一種抽象的型別。
介面 (interface) 定義了一個物件的行為規範, 只定義規範不實現,由具體的物件來實現規範的細節。
介面做的事情就像是定義一個協議(規則)。
Interface 是一組method的集合, 是duck-type programming 的一種體現。
介面的定義
- 介面是一個或多個方法簽名的集合
- 介面只有方法宣告,沒有實現,沒有資料欄位
- 介面可以匿名嵌入其他介面,或嵌入到結構中
- 介面呼叫不會做receiver的自動轉換
- 介面同樣支援匿名欄位方法
- 介面也可實現類似OOP中的多型
- 任何型別的方法集中只要擁有該介面'對應的全部方法'簽名
- 只有當介面儲存的型別和物件都為nil時,介面才等於nil
- 用 interface{} 傳遞任意型別資料是Go語言的慣例用法,而且 interface{} 是型別安全的
- 空介面可以作為任何型別資料的容器
- 一個型別可實現多個介面
- 介面命名習慣以 er 結尾
使用
每個介面由數個方法組成,介面的定義如下
type 介面型別 interface {
方法名1 (引數列表1) 返回值列表1
方法名2 (引數列表2) 返回值列表2
...
}
注意
介面名:使用type將介面定義為自定義的型別名。Go語言的介面在命名時,一般會在單詞後面新增er,如有寫操作的介面叫Writer,有字串功能的介面叫Stringer等。介面名最好要能突出該介面的型別含義。
方法名:當方法名首字母是大寫且這個介面型別名首字母也是大寫時,這個方法可以被介面所在的包(package)之外的程式碼訪問。
引數列表、返回值列表:引數列表和返回值列表中的引數變數名可以省略。
例子
type writer interface {
Write([]byte) error
}
值接收者和指標接收介面
type Mover interface {
move()
}
type dog struct {}
func (d dog) move() {
fmt.Println("狗狗")
}
func main() {
var x Mover
var wangcai = dog{}
x = wangcai // x 可以接收dog型別
var fugui = &dog{} // fugui是 *dog 型別
x = fugui // x可以接收*dog型別 指標接收
x.move()
}
多個型別實現同一介面
// Mover 介面
type Mover interface {
move()
}
type dog struct {
name string
}
type car struct {
brand string
}
// dog 型別實現 Mover 介面
func (d dog) move() {
fmt.Printf("%s: mmmm", d.name)
}
// car 型別實現 Mover 介面
func (c car) move() {
fmt.Printf("%s: mmmm", c.brand)
}
func main() {
var x Mover
var a = dog{name: "旺財"}
var b = car{brand: "蝦米"}
x = a
x.move()
x = b
x.move()
}
一個介面的方法,不一定需要由一個型別完全實現,介面的方法可以通過在型別中嵌入其他型別或者結構體來實現。
介面巢狀
介面與介面間可以通過巢狀創造出新的介面。
type Sayer interface {
say()
}
type Mover interface {
move()
}
// 介面巢狀
type animal interface {
Sayer
Mover
}
// 巢狀得到的介面的使用與普通介面一樣
type cat struct {
name string
}
func (c cat) say() {
fmt.Println("ssss")
}
func (c cat) move() {
fmt.Println("mmmm")
}
func main() {
var x animal
x = cat{name: "花花"}
x.move()
x.say()
}
空介面
空介面是指沒有定義任何方法的介面,因此任何型別都實現了空介面。
空介面型別的變數可以儲存任意型別的變數。
func main() {
// 定義一個空介面 x
var x interface{}
s := "test data"
x = s
fmt.Printf("type:%T value: %v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value: %v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value: %v\n", x, x)
}
空介面作為函式的引數
使用空介面實現可以接收任意型別的函式物件。
func show(a interface{}){
fmt.Printf("type:%T value: %v\n", a, a)
}
空介面作為map的引數
使用空介面實現可以儲存任意值的字典
var Info = make(map[string]interface{})
Info["id"] = 1
Info["name"] = "帽兒山的槍手"
fmt.Println(Info)
獲取空介面值
判斷空介面中值,可以使用型別斷言,語法如下
x.(T)
x
表示型別為 interface{} 的變數
T
表示斷言 x 可能是的型別
該語法返回兩個引數,第一個引數是 x 轉化為 T 型別後的變數, 第二個值是一個布林值, 若為 true 則表示斷言成功, false 則表示失敗。
func main() {
var x interface{}
x = "data"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("型別斷言失敗")
}
}
如果要斷言多次,可以寫 if
判斷, 也可以用 switch
語句實現。
反射
介紹
什麼是反射?
例如:有時候我們需要知道某個值是什麼型別,才能用對等邏輯去處理它。
以下是常用的處理方法:
// 虛擬碼
switch value := value.(type){
case string:
// 處理操作
case int:
// 處理操作
...
}
這樣處理,會寫的非常長,而且還可能存在自定的型別,也就是說這個判斷日後可能還要一直改,因為無法知道未知值到底屬於什麼型別。
如果使用反射來處理,使用標準庫 reflect
中的 TypeOf 和 ValueOf 函式從介面中獲取目標物件的資訊,就可以輕鬆處理這個問題。
更多介紹,可參考reflect 官方地址
Go語言提供了一種機制,在編譯時不知道型別的情況下,可更新變數、執行時檢視值、呼叫方法以及直接對他們的佈局進行操作的機制,稱為反射。
使用
使用反射檢視物件型別
package main
import (
"fmt"
"reflect"
)
func main() {
var name string = "帽兒山的槍手"
nameType := reflect.TypeOf(name)
nameValue := reflect.ValueOf(name)
fmt.Println("name type: ", nameType)
fmt.Println("name value: ", nameValue)
}
輸出
name type: string
name value: 帽兒山的槍手
struct 型別反射用法
package main
import (
"fmt"
"reflect"
)
type Info struct {
Name string
Desc string
}
func (i Info) Detail() {
fmt.Println("detail info")
}
func main() {
i := Info{Name: "帽兒山的槍手", Desc: "技術分享"}
t := reflect.TypeOf(i) // 獲取目標物件
v := reflect.ValueOf(i) // 獲取value值
for i := 0; i < v.NumField(); i++ { // NumField()獲取欄位總數
key := t.Field(i) // 根據下標,獲取包含的key
value := v.Field(i).Interface() // 獲取key對應的值
fmt.Printf("key=%s value=%v type=%v\n", key.Name, value, key.Type)
}
// 獲取Info的方法
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("方法 Name=%s Type=%v\n", m.Name, m.Type)
}
}
輸出
key=Name value=帽兒山的槍手 type=string
key=Desc value=技術分享 type=string
方法 Name=Detail Type=func(main.Info)
通過反射判斷型別用法
package main
import (
"fmt"
"reflect"
)
type Info struct {
Name string
Desc string
}
func main() {
i := Info{Name: "帽兒山的槍手", Desc: "技術分享"}
t := reflect.TypeOf(i)
// Kind()函式判斷值的型別
if k := t.Kind(); k == reflect.Struct {
fmt.Println("struct type")
}
num := 100
switch v := reflect.ValueOf(num); v.Kind() {
case reflect.String:
fmt.Println("string type")
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Println("int type")
default:
fmt.Printf("unhandled kind %s", v.Kind())
}
}
輸出
struct type
int type
通過反射修改內容
package main
import (
"fmt"
"reflect"
)
type Info struct {
Name string
Desc string
}
func main() {
i := &Info{Name: "帽兒山的槍手", Desc: "技術分享"}
v := reflect.ValueOf(i)
// 修改值必須是指標型別
if v.Kind() != reflect.Ptr {
fmt.Println("不是指標型別")
return
}
v = v.Elem() // 獲取指標指向的元素
name := v.FieldByName("Desc") // 獲取目標key的值
name.SetString("好好工作")
fmt.Printf("修改後資料: %v\n", *i)
}
輸出
修改後資料: {帽兒山的槍手 好好工作}
通過反射呼叫方法
package main
import (
"fmt"
"reflect"
)
type Info struct {
Name string
Desc string
}
func (i Info) Detail() {
fmt.Println("detail info")
}
func main() {
i := Info{Name: "帽兒山的槍手", Desc: "技術分享"}
v := reflect.ValueOf(i)
// 獲取方法控制權
mv := v.MethodByName("Detail")
mv.Call([]reflect.Value{}) // 這裡是無呼叫引數 []reflect.Value{}
}
輸出
detail info
泛型
介紹
泛型的概念,可以從多型看起,多型是同一形式表現出不同行為的一種特性,在程式語言中被分為兩類,臨時性多型和引數化多型。
根據實參生成不同的版本,支援任意數量的呼叫,即泛型,簡言之,就是把元素型別變成了引數。
golang版本需要在 1.17版本或以上,才支援泛型使用。
(1.17版本泛型是golang推出的嚐鮮版,1.18是正式版本)
舉例:
func Add(a, b int) int{}
func AddFloat(a, b float64) float64{}
在泛型的幫助下,上面程式碼就可以簡化成為:
func Add[T any](a, b T) T
Add後面的[T any],T表示型別的標識,any表示T可以是任意型別。
a、b和返回值的型別T和前面的T是同一個型別。
為什麼用[],而不是其他語言中的<>,官方有過解釋,大概就是<>會有歧義。曾經計劃使用() ,因為太容易混淆,最後使用了[]。
泛型3大概念
- 型別引數
- 型別約束
- 型別推導
特性
- 函式可以通過type關鍵字引入額外的型別引數
(type parameters)列表:func F(type T)(p T) { ... }
- 這些型別引數可以像一般的引數一樣在函式體中使用
- 型別也可以擁有型別引數列表:
type M(type T) []T
- 每個型別引數可以擁有一個約束:
func F(type T Constraint)(p T) { ... }
- 使用interface來描述型別的約束
- 被用作型別約束的interface可以擁有一個預宣告型別列表,限制了實現此介面的型別的基礎型別
- 使用泛型函式或型別時需要傳入型別實參
- 型別推斷允許使用者在呼叫泛型函式時省略型別實參
- 泛型函式只允許進行型別約束所規定的操作
使用
對泛型進行輸出
如果Go當前版本是1.17版本,執行時需要加引數 -gcflags=-G=3
# 完整命令
go run -gcflags=-G=3 example.go
示例
package main
import (
"fmt"
)
func print[T any](s []T) {
for _, v := range s {
fmt.Printf("%v ", v)
}
fmt.Printf("\n")
}
func main() {
print[int]([]int{1,2,3,4})
print[float64]([]float64{1.01, 2.02, 3.03, 4.04})
print[string]([]string{"a", "b", "c", "d"})
}
輸出
1 2 3 4
1.01 2.02 3.03 4.04
a b c d
Go1.18 中,any
是 interface{} 的別名
使用泛型約束,控制型別的使用範圍
原先的語法中,型別約束會用逗號分隔的方式來展示
type int, int8, int16, int32, int64
在新語法中,結合定義為 union element(聯合元素),寫成一系列由豎線 ”|“ 分隔的型別或近似元素。
int | int8 | int16 | int32 | int64
示例
package main
import (
"fmt"
)
type CustomType interface {
int | int8 | int16 | int32 | int64 | string
}
func add[T CustomType] (a, b T) T{
return a + b
}
func main() {
fmt.Println(add(1, 2))
fmt.Println(add("帽兒山的槍手", "技術分享"))
}
輸出
3
帽兒山的槍手技術分享
上述 CustomType
介面型別也可以寫成以下格式
type CustomType interface {
~int | ~string
}
上述宣告的型別集是 ~int,也就是所有型別為 int 的型別(如:int、int8、int16、int32、int64)都能夠滿足這個型別約束的條件。
泛型中自帶 comparable 約束
因為不是所有的型別都可以==比較,所以Golang內建提供了一個comparable約束,表示可比較的。
官方說明
comparable是由所有可比較型別(布林、數字、字串、指標、通道、可比較型別的陣列、欄位均為可比較型別的結構)實現的介面。可比較介面只能用作型別引數約束,不能用作變數的型別。
package main
import (
"fmt"
)
func diff[T comparable](a []T, v T) {
for _, e := range a {
if e == v {
fmt.Println(e)
}
}
}
func main() {
diff([]int{1, 2, 3, 4}, 3)
}
輸出
3
泛型中操作指標
package main
import (
"fmt"
)
func pointerOf[T any](v T) *T {
return &v
}
func main() {
name := pointerOf("帽兒山的槍手")
fmt.Println(*name)
id := pointerOf(100)
fmt.Println(*id)
}
輸出
帽兒山的槍手
100
技術文章持續更新,請大家多多關注呀~~
搜尋微信公眾號【 帽兒山的槍手 】,關注我