您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“如何理解Golang 語(yǔ)言 Method 接收者使用值類型和指針類型”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“如何理解Golang 語(yǔ)言 Method 接收者使用值類型和指針類型”吧!
在 Golang 語(yǔ)言中,function 的參數(shù)和 method 的接收者都可以選擇使用值傳遞和指針傳遞(“引用傳遞”),需要注意的是,其中指針傳遞是傳遞的指針值的副本,而不是指針指向的數(shù)據(jù)的副本。也就是說(shuō) Golang 語(yǔ)言和 C 系的所有語(yǔ)言相同,一切傳遞都是值傳遞。本文我們主要介紹 method 的接收者怎么選擇使用值類型和指針類型。
在使用關(guān)鍵字 type 定義的類型上定義 method,method 的接收者也可以作為 method 的參數(shù),類似于 function 的參數(shù),所以 method 的接收者和 function 參數(shù)一樣,我們也需要考慮選擇使用值類型和指針類型。
關(guān)于這個(gè)問題,我們通常會(huì)從兩方面去考慮,一是如果該 method 需要修改接收者,那么接收者必須使用指針類型;二是如果接收者占用的內(nèi)存大小較大,出于性能考慮,我們也會(huì)選擇使用指針類型的接收者。
除此之外,我們還需考慮一致性。也就是說(shuō),如果該類型的某些 method 必須使用指針類型的接收者,其他 method 也應(yīng)該使用指針類型的接收者。因此無(wú)論如何使用該類型,它的方法集都是一致的。
最后,如果接收者是基本類型,切片和小結(jié)構(gòu)體,他們的值類型的內(nèi)存占用較低,并且易讀。所以,該情況下除非 method 的語(yǔ)義需要必須使用指針類型的接收者,否則,我們可以選擇使用值類型的接收者。
type User struct { name string } func (u User) SetNameValueType(str string) { fmt.Printf("SetNameValueType() pointer:%p\n", &u) // SetNameValueType() pointer:0xc000096240 u.name = str } func (u *User) SetNamePointerType(str string) { fmt.Printf("SetNamePointerType() pointer:%p\n", u) // SetNamePointerType() pointer:0xc000096220 u.name = str } func main () { user1 := &User{} fmt.Printf("pointer:%p\n", user1) // pointer:0xc000096220 fmt.Println(user1) // &{} user1.SetNameValueType("lucy") fmt.Println(user1) // &{} user1.SetNamePointerType("lily") fmt.Println(user1) // &{lily} }
閱讀上面這段代碼,我們可以發(fā)現(xiàn)值類型的接收者,調(diào)用方拷貝了副本;指針類型的接收者,調(diào)用方未拷貝副本。
map 和 slice 值類似于指針:它們是包含指向底層 map 或 slice 數(shù)據(jù)的指針的描述符。復(fù)制 map 或 slice 值不會(huì)復(fù)制它指向的數(shù)據(jù)。需要注意的是,如果超過(guò) slice 的容量,運(yùn)行時(shí)會(huì)重新分配一個(gè)新內(nèi)存地址。
map 源碼:
type hmap struct { count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) extra *mapextra // optional fields }
slice 源碼:
type slice struct { array unsafe.Pointer len int cap int }
示例代碼:
func main () { user1 := &User{} fmt.Printf("pointer:%p\n", user1) // pointer:0xc000096220 fmt.Println(user1) // &{} user1.SetNameValueType("lucy") fmt.Println(user1) // &{} user1.SetNamePointerType("lily") fmt.Println(user1) // &{lily} // m := make(map[int]int) m := map[int]int{} fmt.Printf("map pointer:%p\n", m) // map pointer:0xc000100180 m[0] = 1 fmt.Printf("map pointer:%p\n", m) // map pointer:0xc000100180 m[1] = 2 s := make([]int, 0, 1) fmt.Printf("slice pointer:%p\n", s) // slice pointer:0xc00001c0a0 s = append(s, 1) fmt.Printf("slice pointer:%p\n", s) // slice pointer:0xc00001c0a0 s = append(s, 2) fmt.Printf("slice pointer:%p\n", s) // slice pointer:0xc00001c0b0 }
閱讀上面這段代碼,我們可以發(fā)現(xiàn) map 類型未分配新內(nèi)存地址,使用 append 函數(shù)向 slice 中追加元素,當(dāng)元素個(gè)數(shù)未超出其容量之前,slice 也未分配新內(nèi)存地址。
關(guān)于接口類型,復(fù)制接口值將復(fù)制存儲(chǔ)在接口值中的對(duì)象。如果接口值持有一個(gè)結(jié)構(gòu)體,則復(fù)制接口值會(huì)復(fù)制該結(jié)構(gòu)體。如果接口值持有指針,則復(fù)制接口值會(huì)復(fù)制指針,但不會(huì)復(fù)制它指向的數(shù)據(jù)。
閱讀到這里,讀者朋友可能會(huì)簡(jiǎn)單認(rèn)為使用值類型會(huì)拷貝副本,使用指針類型不會(huì)拷貝副本。實(shí)際上,我們可以通過(guò)優(yōu)化代碼,在不改變語(yǔ)義的前提下,實(shí)現(xiàn)使用值類型也不會(huì)拷貝副本。
示例代碼:
type User struct { name string } func (u User) SetNameValueType(str string) { fmt.Printf("SetNameValueType() pointer:%p\n", &u) // SetNameValueType() pointer:0xc000096240 u.name = str } func (u User) ValueSetName(str string) User { u.name = str return u } func main () { user2 := &User{} fmt.Printf("user2 pointer:%p\n", user2) // user2 pointer:0xc000010290 user2.SetNameValueType("tom") // SetNameValueType() pointer:0xc0000102a0 user3 := &User{} fmt.Printf("user3 pointer:%p\n", user3) // user3 pointer:0xc0000102b0 user3.ValueSetName("bob") fmt.Printf("pointer:%p\n", user3) // pointer:0xc0000102b0 }
閱讀上面這段代碼,我們發(fā)現(xiàn) User 的 SetNameValueType 方法和 ValueSetName 方法,二者都是值傳遞,但是 SetNameValueType 方法會(huì)拷貝副本,ValueSetName 方法不會(huì)拷貝副本。原因是我們給 ValueSetName 方法定義了一個(gè) User 類型的返回值,從而避免了 ValueSetName 方法拷貝副本。
本文我們主要介紹了 method 的接收者使用值傳遞和指針傳遞的區(qū)別,并且講述了選擇使用值傳遞和指針傳遞需要考慮的決定因素,也指出了復(fù)合類型與值類型的區(qū)別。
到此,相信大家對(duì)“如何理解Golang 語(yǔ)言 Method 接收者使用值類型和指針類型”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。