作為C語言家族的一員,go和c一樣也支持結(jié)構(gòu)體。可以類比于java的一個(gè)POJO。
創(chuàng)新互聯(lián)建站專注于企業(yè)營銷型網(wǎng)站、網(wǎng)站重做改版、木壘哈薩克網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5高端網(wǎng)站建設(shè)、商城網(wǎng)站建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為木壘哈薩克等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
在學(xué)習(xí)定義結(jié)構(gòu)體之前,先學(xué)習(xí)下定義一個(gè)新類型。
新類型 T1 是基于 Go 原生類型 int 定義的新自定義類型,而新類型 T2 則是 基于剛剛定義的類型 T1,定義的新類型。
這里要引入一個(gè)底層類型的概念。
如果一個(gè)新類型是基于某個(gè) Go 原生類型定義的, 那么我們就叫 Go 原生類型為新類型的底層類型
在上面的例子中,int就是T1的底層類型。
但是T1不是T2的底層類型,只有原生類型才可以作為底層類型,所以T2的底層類型還是int
底層類型是很重要的,因?yàn)閷?duì)兩個(gè)變量進(jìn)行顯式的類型轉(zhuǎn)換,只有底層類型相同的變量間才能相互轉(zhuǎn)換。底層類型是判斷兩個(gè)類型本質(zhì)上是否相同的根本。
這種類型定義方式通常用在 項(xiàng)目的漸進(jìn)式重構(gòu),還有對(duì)已有包的二次封裝方面
類型別名表示新類型和原類型完全等價(jià),實(shí)際上就是同一種類型。只不過名字不同而已。
一般我們都是定義一個(gè)有名的結(jié)構(gòu)體。
字段名的大小寫決定了字段是否包外可用。只有大寫的字段可以被包外引用。
還有一個(gè)點(diǎn)提一下
如果換行來寫
Age: 66,后面這個(gè)都好不能省略
還有一個(gè)點(diǎn),觀察e3的賦值
new返回的是一個(gè)指針。然后指針可以直接點(diǎn)號(hào)賦值。這說明go默認(rèn)進(jìn)行了取值操作
e3.Age 等價(jià)于 (*e3).Age
如上定義了一個(gè)空的結(jié)構(gòu)體Empty。打印了元素e的內(nèi)存大小是0。
有什么用呢?
基于空結(jié)構(gòu)體類型內(nèi)存零開銷這樣的特性,我們?cè)谌粘?Go 開發(fā)中會(huì)經(jīng)常使用空 結(jié)構(gòu)體類型元素,作為一種“事件”信息進(jìn)行 Goroutine 之間的通信
這種以空結(jié)構(gòu)體為元素類建立的 channel,是目前能實(shí)現(xiàn)的、內(nèi)存占用最小的 Goroutine 間通信方式。
這種形式需要說的是幾個(gè)語法糖。
語法糖1:
對(duì)于結(jié)構(gòu)體字段,可以省略字段名,只寫結(jié)構(gòu)體名。默認(rèn)字段名就是結(jié)構(gòu)體名
這種方式稱為 嵌入字段
語法糖2:
如果是以嵌入字段形式寫的結(jié)構(gòu)體
可以省略嵌入的Reader字段,而直接訪問ReaderName
此時(shí)book是一個(gè)各個(gè)屬性全是對(duì)應(yīng)類型零值的一個(gè)實(shí)例。不是nil。這種情況在Go中稱為零值可用。不像java會(huì)導(dǎo)致npe
結(jié)構(gòu)體定義時(shí)可以在字段后面追加標(biāo)簽說明。
tag的格式為反單引號(hào)
tag的作用是可以使用[反射]來檢視字段的標(biāo)簽信息。
具體的作用還要看使用的場(chǎng)景。
比如這里的tag是為了幫助 encoding/json 標(biāo)準(zhǔn)包在解析對(duì)象時(shí)可以利用的規(guī)則。比如omitempty表示該字段沒有值就不打印出來。
import (
"fmt"
"reflect"
)
func reflecType(x interface{}){
v := reflect.TypeOf(x)
fmt.Println("type:%v\n", v)
fmt.Println("type name:%v , rtpe kind:%v \n", v.getName(), v.getType())
}
type Cat struct{}
//通過反射設(shè)置變量的值
func reflectSetValue1(x interface{}){
v := reflect.ValueOf(x)
if v.Kind() == reflect.Int64{
v.SetInt(200) //修改的是副本, reflect 包會(huì)引發(fā)panic
}
}
//通過反射設(shè)置變量的值
func reflectSetValue2(x interface{}){
v := reflect.ValueOf(x)
//反射中使用Elem()獲取指針對(duì)應(yīng)的值
if v.Elem().Kind() == reflect.Int64{
v.Elem().SetInt(200)
}
}
func main(){
var a float32 = 3.14
reflectType(a) //type name:float32 type kind:float32
var b int64 = 100
reflectType(b) // type name :int64 type kind :int64
var c = Cat{}
reflectType(c) // type name :Cat type kind :struct
reflectSetValue1(b)
fmt.Println(b) //依然為100
reflectSetValue2(b)
}
基本設(shè)計(jì)思路:
類型轉(zhuǎn)換、類型斷言、動(dòng)態(tài)派發(fā)。iface,eface。
反射對(duì)象具有的方法:
編譯優(yōu)化:
內(nèi)部實(shí)現(xiàn):
實(shí)現(xiàn) Context 接口有以下幾個(gè)類型(空實(shí)現(xiàn)就忽略了):
互斥鎖的控制邏輯:
設(shè)計(jì)思路:
(以上為寫被讀阻塞,下面是讀被寫阻塞)
總結(jié),讀寫鎖的設(shè)計(jì)還是非常巧妙的:
設(shè)計(jì)思路:
WaitGroup 有三個(gè)暴露的函數(shù):
部件:
設(shè)計(jì)思路:
結(jié)構(gòu):
Once 只暴露了一個(gè)方法:
實(shí)現(xiàn):
三個(gè)關(guān)鍵點(diǎn):
細(xì)節(jié):
讓多協(xié)程任務(wù)的開始執(zhí)行時(shí)間可控(按順序或歸一)。(Context 是控制結(jié)束時(shí)間)
設(shè)計(jì)思路: 通過一個(gè)鎖和內(nèi)置的 notifyList 隊(duì)列實(shí)現(xiàn),Wait() 會(huì)生成票據(jù),并將等待協(xié)程信息加入鏈表中,等待控制協(xié)程中發(fā)送信號(hào)通知一個(gè)(Signal())或所有(Boardcast())等待者(內(nèi)部實(shí)現(xiàn)是通過票據(jù)通知的)來控制協(xié)程解除阻塞。
暴露四個(gè)函數(shù):
實(shí)現(xiàn)細(xì)節(jié):
部件:
包: golang.org/x/sync/errgroup
作用:開啟 func() error 函數(shù)簽名的協(xié)程,在同 Group 下協(xié)程并發(fā)執(zhí)行過程并收集首次 err 錯(cuò)誤。通過 Context 的傳入,還可以控制在首次 err 出現(xiàn)時(shí)就終止組內(nèi)各協(xié)程。
設(shè)計(jì)思路:
結(jié)構(gòu):
暴露的方法:
實(shí)現(xiàn)細(xì)節(jié):
注意問題:
包: "golang.org/x/sync/semaphore"
作用:排隊(duì)借資源(如錢,有借有還)的一種場(chǎng)景。此包相當(dāng)于對(duì)底層信號(hào)量的一種暴露。
設(shè)計(jì)思路:有一定數(shù)量的資源 Weight,每一個(gè) waiter 攜帶一個(gè) channel 和要借的數(shù)量 n。通過隊(duì)列排隊(duì)執(zhí)行借貸。
結(jié)構(gòu):
暴露方法:
細(xì)節(jié):
部件:
細(xì)節(jié):
包: "golang.org/x/sync/singleflight"
作用:防擊穿。瞬時(shí)的相同請(qǐng)求只調(diào)用一次,response 被所有相同請(qǐng)求共享。
設(shè)計(jì)思路:按請(qǐng)求的 key 分組(一個(gè) *call 是一個(gè)組,用 map 映射存儲(chǔ)組),每個(gè)組只進(jìn)行一次訪問,組內(nèi)每個(gè)協(xié)程會(huì)獲得對(duì)應(yīng)結(jié)果的一個(gè)拷貝。
結(jié)構(gòu):
邏輯:
細(xì)節(jié):
部件:
如有錯(cuò)誤,請(qǐng)批評(píng)指正。
這是它的優(yōu)點(diǎn),因?yàn)榫幾g器在編譯時(shí)不去確定你傳的到底是什么類型,你傳一個(gè)string,它能接收,你傳一個(gè)對(duì)象struct,它也能接收,它只有一個(gè)要求,實(shí)現(xiàn)我要求實(shí)現(xiàn)的方法!
既然interface是不限定類型,是通用類型,這是一種開放表現(xiàn),這種開放怎么實(shí)現(xiàn)的呢?方法就是不去檢驗(yàn)?zāi)愕念愋停热徊粰z驗(yàn)?zāi)且膊蝗ビ涗浤愕念愋停。。?!注意interface不記錄你的類型,所以不管你是string,struct,int,我都不管,我都不記錄,我只記錄你的地址,結(jié)果是編譯器在編譯時(shí)也不知道你是什么類型,你有什么字段!
但是現(xiàn)在有一個(gè)問題,編譯器也沒辦法確定一個(gè)interface以前是什么類型?。ň幾g時(shí))這就是因果關(guān)系:為了達(dá)到通用,interface不做確定工作,結(jié)果就是interface也不知道以前的類型。
一個(gè)類型轉(zhuǎn)接口的過程,就是放棄自我類型的過程,變成了沒有類型。
這樣做有什么好處呢,很顯然是:通用,如果把一個(gè)函數(shù)的傳入?yún)?shù)設(shè)置為空接口(interface{}),那么任何類型當(dāng)做參數(shù)都能夠調(diào)用該接口,最好的例子就是:
它就是一個(gè)很標(biāo)準(zhǔn)的例子,println傳入?yún)?shù)可以是任何類型,都能打印出它的值。
當(dāng)然你可以說你記得,因?yàn)槭悄惆阉D(zhuǎn)換成interface,你理所當(dāng)然的記得,可編譯器不知道啊,interface不包含類型,也就是說你沒有讓它去記錄,所以它不知道。
針對(duì)這個(gè)問題,go語言給了一個(gè)解決方案,斷言,當(dāng)將一個(gè)interface轉(zhuǎn)換成它原來類型的時(shí)候,在它后面指明它的原來類型,這樣編譯器就知道該按照什么類型去解析了。(其實(shí)說白了,這就是通過人的記憶,編譯器不知道是什么類型,你告訴編譯器就可以了)
斷言其實(shí)是先獲取interface的動(dòng)態(tài)類型,然后與你指定的類型做判斷,如果一致,將它轉(zhuǎn)換成你指定的類型。如果不知道動(dòng)態(tài)類型,可以看這篇文章:
從報(bào)錯(cuò)可以看出, 不能直接轉(zhuǎn)換,需要對(duì)接口先進(jìn)行斷言
通常情況下,一個(gè)變量在確定類型的情況下編譯器知道他有哪些功能(注意,這里是針對(duì)編譯時(shí)),比如一個(gè)int類型,編譯器在編譯時(shí)知道能對(duì)他加減int,不能加減float,如果你這么做我就給你報(bào)錯(cuò)。一個(gè)struct包含哪些字段,不包含哪些字段,我定義一個(gè)user結(jié)構(gòu)體,里面只有name和age兩個(gè)字段,那么你只能取我這兩字段的值,你如果取height,我就給你報(bào)錯(cuò)。
這些都是正常情況下的,但是對(duì)于一個(gè)接口呢,編譯器會(huì)變成瞎子!在編譯的時(shí)候它不知道你原來是什么類型,所以它也沒法確定你包含什么字段,同樣是之前那個(gè)user結(jié)構(gòu)體,當(dāng)把它轉(zhuǎn)換成接口以后,編譯器就對(duì)它的類型一無所知了,你獲取name字段,這有接口有沒有呢?編譯器不知道!你請(qǐng)求height字段,這個(gè)泛型有沒有呢?編譯器仍然不知道。所以你編譯時(shí)不能修改接口里的數(shù)據(jù),既然編譯時(shí) 不能修改,那就只能在運(yùn)行時(shí)修改了。
這個(gè)時(shí)候就該反射登場(chǎng)了,它能夠在運(yùn)行時(shí)修改接口的數(shù)據(jù),通過追根溯源,獲取接口底層的實(shí)際數(shù)據(jù)和類型,讓你能夠?qū)涌诘脑磾?shù)據(jù)進(jìn)行操作。
換一種大白話的說法,反射就是刨根問底,獲取這個(gè)接口究竟是怎么產(chǎn)生的,因?yàn)槟呐乱粋€(gè)類型轉(zhuǎn)變成接口時(shí)放棄了自己的類型,但是它的本質(zhì)不會(huì)變的,就像趙本山的小品里所說:小樣,別以為你脫掉馬甲我就不認(rèn)識(shí)你了!對(duì),它的底層里仍然存儲(chǔ)了它的數(shù)據(jù)類型,只是藏的比較深,一般手段拿不到,但我們?nèi)匀荒軌蛲ㄟ^反射(這個(gè)包根問底的工具)來確定你究竟包含哪些字段和值,確定你究竟是蛇還是脫了馬甲的烏龜!
1、反射可以在運(yùn)行時(shí) 動(dòng)態(tài)獲取變量的各種信息 ,比如變量的類型、類別;
2、如果是結(jié)構(gòu)體變量,還可以獲取到結(jié)構(gòu)體本身的信息(包括結(jié)構(gòu)體的字段、方法);
3、通過反射,可以修改 變量的值 ,可以調(diào)用關(guān)聯(lián)的方法;
4、使用反射,需要import " reflect ".
5、示意圖:
1、不知道接口調(diào)用哪個(gè)函數(shù),根據(jù)傳入?yún)?shù)在運(yùn)行時(shí)確定調(diào)用的具體接口,這種需要對(duì)函數(shù)或方法反射。
例如以下這種橋接模式:
示例第一個(gè)參數(shù)funcPtr以接口的形式傳入函數(shù)指針,函數(shù)參數(shù)args以可變參數(shù)的形式傳入,bridge函數(shù)中可以用反射來動(dòng)態(tài)執(zhí)行funcPtr函數(shù)。
1、reflect.TypeOf(變量名),獲取變量的類型,返回reflect.Type類型。
2、reflect.ValueOf(變量名),獲取變量的值,返回reflect.Value類型reflect.Value是一個(gè)結(jié)構(gòu)體類型。
3、變量、interface{}和reflect.Value是可以互相轉(zhuǎn)換的,這點(diǎn)在實(shí)際開發(fā)中,會(huì)經(jīng)常使用到。
1、reflect.Value.Kind,獲取變量的 類別(Kind) ,返回的是一個(gè) 常量 。在go語言文檔中:
示例如下所示:
輸出如下:
Kind的范疇要比Type大。比如有Student和Consumer兩個(gè)結(jié)構(gòu)體,他們的 Type 分別是 Student 和 Consumer ,但是它們的 Kind 都是 struct 。
2、Type是類型,Kind是類別,Type和Kind可能是相同的,也可能是不同的。
3、通過反射可以在讓 變量 在 interface{} 和 Reflect.Value 之間相互轉(zhuǎn)換,這點(diǎn)在前面畫過示意圖。
4、使用反射的方式來獲取變量的值(并返回對(duì)應(yīng)的類型),要求數(shù)據(jù)類型匹配,比如x是int,那么久應(yīng)該使用reflect.Value(x).Int(),而不能使用其它的,否則報(bào)panic。
如果是x是float類型的話,也是要用reflect.Value(x).Float()。但是如果是struct類型的話,由于type并不確定,所以沒有相應(yīng)的方法,只能 斷言。
5、通過反射的來修改變量,注意當(dāng)使用SetXxx方法來設(shè)置需要通過對(duì)應(yīng)的指針類型來完成,這樣才能改變傳入的變量的值,同時(shí)需要使用到reflect.Value.Elem()方法。
輸出num=20,即成功使用反射來修改傳進(jìn)來變量的值。
6、reflect.Value.Elem()應(yīng)該如何理解?
網(wǎng)站名稱:go語言反射包 go語言映射
轉(zhuǎn)載注明:http://www.rwnh.cn/article36/higdsg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、用戶體驗(yàn)、微信小程序、App設(shè)計(jì)、企業(yè)網(wǎng)站制作、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)