摘要:指針類型的零值指針類型的零值指針類型的零值都是,也就是說,一個沒有存儲地址的指針等于解除引用解除引用一個指針變量持有另一個變量的地址。
本篇翻譯自《Practical Go Lessons》 Chapter 15: Pointer type
指針是“是一個數據項,它存儲另外一個數據項的位置”。
在程序中,我們不斷地存儲和檢索數據。例如,字符串、數字、復雜結構…。在物理層面,數據存儲在內存中的特定地址,而指針存儲的就是這些特定內存地址。
記住指針變量,就像其他變量一樣,它也有一個內存地址。
Go 中的指針類型不止一種,每一種普通類型就對應一個指針類型。相應地,指針類型也限定了它自己只能指向對應類型的普通變量(地址)。
指針類型的語法為:
*BaseType
BaseType
指代的是任何普通類型。
我們來看一下例子:
*int
表示指向 int
類型的指針*uint8
表示指向 uint8
類型的指針type User struct { ID string Username string}
*User
表示指向 User
類型的指針下面的語法可以創建:
var p *int
這里我們創建了一個類型為 *int
的變量 p
。*int
是指針類型(基礎類型是 int
)。
讓我們來創建一個名為 answer
的整型變量。
var answer int = 42
現在我們給變量 p
分配一個值了:
p = &answer
使用 &
符號我們就能得到變 answer
的地址。來打印出這個地址~
fmt.Println(p)// 0xc000012070
0xc000012070
是一個十六進制數字,因為它的以 0x
為前綴。內存地址通常是以十六進制格式表示。你也可以使用二進制(用 0 和 1)表示,但不易讀。
指針類型的零值都是 nil
,也就是說,一個沒有存儲地址的指針等于 nil
var q *intfmt.Println(q == nil)// true
一個指針變量持有另一個變量的地址。如果你想通過指針去訪問地址背后的變量值該怎么辦?你可以使用解除引用操作符 *
。
來舉個例子,我們定義一個結構體類型 Cart
:
type Cart struct { ID string Paid bool}
然后我們創建一個 Cart
類型的變量 cart
,我們可以得到這個變量的地址,也可以通過地址找到這個變量:
*
操作符,你可以通過地址找到變量值&
操作符,你可以得到變量的地址每個 Go 程序員都會遇到這個 panic(報錯):
panic: runtime error: invalid memory address or nil pointer dereference[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1091507]
為了更好地理解它,我們來復現一下:
package mainimport "fmt"func main() { var myPointerVar *int fmt.Println(*myPointerVar)}
在程序里,我們的定義了一個指針變量 myPointerVar
,這個變量的類型是 *int
(指向整型)。
然后我嘗試對它進行解引用,myPointerVar
變量持有一個尚未初始化的指針,因此該指針的值為 nil
。因為我們嘗試去尋找一個不存在的地址,程序將會報錯!我們嘗試找到空地址,而空地址在內存中不存在。
Maps 和 channels 變量里保存了對內部結構的指針。因此,即便向一個函數或方法傳遞的 map 或 channel 不是指針類型,也開始對這個 map 或 channel 進行修改。讓我們看一個例子:
func addElement(cities map[string]string) { cities["France"] = "Paris"}
package mainimport "log"func main() { cities := make(map[string]string) addElement(cities) log.Println(cities)}
cities
的 mapaddElement
map[France:Paris]
我們將在專門的部分中更廣泛地介紹 channels 和 maps。
切片是相同類型元素的集合。在內部,切片是一個具有三個字段的結構:
EUcountries
的例子:package mainimport "log"func main() { EUcountries := []string{"Austria", "Belgium", "Bulgaria"} log.Println(EUcountries)}
package mainimport "log"func main() { EUcountries := []string{"Austria", "Belgium", "Bulgaria"} addCountries(EUcountries) log.Println(EUcountries)}func addCountries(countries []string) { countries = append(countries, []string{"Croatia", "Republic of Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden"}...)}
addCountries
將一個字符串類型切片作為參數append
向切片添加字符串來修改切片[Austria Belgium Bulgaria Croatia Republic of Cyprus Czech Republic Denmark Estonia Finland France Germany Greece Hungary Ireland Italy Latvia Lithuania Luxembourg Malta Netherlands Poland Portugal Romania Slovakia Slovenia Spain Sweden][Austria Belgium Bulgaria]
答案:這個函數實際輸出:
[Austria Belgium Bulgaria]
[]string
類型元素作為參數EUcountries
拷貝一份傳進去讓我們在函數中添加一個日志來可視化它:
func addCountries(countries []string) { countries = append(countries, []string{"Croatia", "Republic of Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden"}...) log.Println(countries)}
日志打印出:
[Austria Belgium Bulgaria Croatia Republic of Cyprus Czech Republic Denmark Estonia Finland France Germany Greece Hungary Ireland Italy Latvia Lithuania Luxembourg Malta Netherlands Poland Portugal Romania Slovakia Slovenia Spain Sweden]
package mainimport ( "log" "strings")func main() { EUcountries := []string{"Austria", "Belgium", "Bulgaria"} upper(EUcountries) log.Println(EUcountries)}func upper(countries []string) { for k, _ := range countries { countries[k] = strings.ToUpper(countries[k]) }}
upper
,它將把一個字符串切片的每個元素都轉換成大寫問題:依你看,程序將傳輸下面哪個?
[AUSTRIA BELGIUM BULGARIA][Austria Belgium Bulgaria]
答案:這個函數將返回:
[AUSTRIA BELGIUM BULGARIA]
upper
獲取切片 EUcountries 的副本(和上面一樣)countries[k] = strings.ToUpper(countries[k])
如果使用切片指針,你就可以在函數中修改這個切片了:
package mainimport ( "log")func main() { EUcountries := []string{"Austria", "Belgium", "Bulgaria"} addCountries2(&EUcountries) log.Println(EUcountries)}func addCountries2(countriesPtr *[]string) { *countriesPtr = append(*countriesPtr, []string{"Croatia", "Republic of Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden"}...)}
這個程序將輸出:
[Austria Belgium Bulgaria Croatia Republic of Cyprus Czech Republic Denmark Estonia Finland France Germany Greece Hungary Ireland Italy Latvia Lithuania Luxembourg Malta Netherlands Poland Portugal Romania Slovakia Slovenia Spain Sweden]
addCountries2
將字符串切片的指針([]string
)作為參數append
調用時的第一個參數是 *countriesPtr
(即我們通過指針 countriesPtr
去找到原值)append
的第二個參數沒有改變addCountries2
的結果會影響到外部的變量有一個快捷方式可以讓你直接修改 struct 類型的變量而無需使用*
運算符:
type Item struct { SKU string Quantity int}type Cart struct { ID string CreatedDate time.Time Items Item}cart := Cart{ ID: "115552221", CreatedDate: time.Now(),}cartPtr := &cartcartPtr.Items = []Item{ {SKU: "154550", Quantity: 12}, {SKU: "DTY8755", Quantity: 1},}log.Println(cart.Items)// [{154550 12} {DTY8755 1}]
cart
是一個 Cart
類型變量cartPtr := &cart
會獲取變量 cart 的地址然后將其存儲到 cartPtr
中cartPtr
,我們可以直接修改變量 cart
的 Item
字段(*carPtr).Items = []Item{ {SKU: "154550", Quantity: 12}, {SKU: "DTY8755", Quantity: 1},}
(這也有效,但更冗長)
指針通常用作方法的接收器,讓我們以 Cat
類型為例:
type Cat struct { Color string Age uint8 Name string}
你可以定義一個方法,使用指向 Cat
的指針作為方法的接收器(*Cat
):
func (cat *Cat) Meow(){ fmt.Println("Meooooow")}
Meow
方法沒有做任何有實際意義的事嗎;它只是打印了字符串"Meooooow"
。我們沒有修改比變量的值。我們來看另一個方法,它修改了 cat 的 Name
:
func (cat *Cat) Rename(newName string){ cat.Name = newName}
此方法將更改貓的名稱。通過指針,我們修改了 Cat 結構體的一個字段。
當然,如果你不想使用指針作為接收器,你也可以:
func (cat Cat) RenameV2(newName string){ cat.Name = newName}
在這個例子中,變量 cat
是一個副本。接收器被命名為“值接收器”。因此,你對 cat 變量所做的任何修改都將在 cat 副本上完成:
package mainimport "fmt"type Cat struct { Color string Age uint8 Name string}func (cat *Cat) Meow() { fmt.Println("Meooooow")}func (cat *Cat) Rename(newName string) { cat.Name = newName}func (cat Cat) RenameV2(newName string) { cat.Name = newName}func main() { cat := Cat{Color: "blue", Age: 8, Name: "Milow"} cat.Rename("Bob") fmt.Println(cat.Name) // Bob cat.RenameV2("Ben") fmt.Println(cat.Name) // Bob}
在主函數的第一行,我們創建了一個 Cat
類型的變量 cat,它的 Name 是 "Millow"
。
當我們調用具有值接收器的 RenameV2
方法時,函數外部變量 cat 的 Name 沒有發生改變。
當我們調用 Rename
方法時,cat 的 Name 字段值會發生變化。
Product
指針的變量?Product
指針的變量?*Product
*
*T
表示所有指向 T
類型變量的指針集合&
。它將獲取一個變量的地址userId := 12546584p := &userId
`userId` 是 `int` 類型的變量`p` 是 `*int` 類型變量`*int` 表示所有指向 `int` 類型變量的指針
*[]string
)*
userId := 12546584p := &userId*p = 4log.Println(userId)
p
是一個指針
- 我們使用
*p
來對指針p
進行解引用- 我們用指令
*p = 4
修改userId
的值- 在代碼片段的末尾,userId 的值為 4(不再是 12546584)
type Cart struct { ID string}var cart CartcartPtr := &cart
- 不需要這樣寫:
(*cartPtr).ID = "1234"
- 你可直接這樣寫:
cartPtr.Items = "1234"
- 變量
cart
就會被修改
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/125672.html
摘要:在包下主要包括輸入輸出兩種流,每種輸入輸出流又可分為字節流和字符流兩大類。輸入輸出是從程序運行所在的內存的角度而言的。的輸入流主要由和作為基類,而輸出流主要由和作為基類。 本章主要參考和摘自瘋狂java講義上面的(java編程思想的后面看過后有新的內容再補充進去吧)。 輸入輸出是所有程序都必需的部分————使用輸入機制允許程序讀取外部數據(包括磁盤、光盤等存儲設備上的數據和用戶輸入的...
摘要:基于版本基于版本。由于中英行文差異,完全的逐字逐句翻譯會很冗余啰嗦。譯者在翻譯中同時參考了谷歌百度有道翻譯的譯文以及編程思想第四版中文版的部分內容對其翻譯死板,生造名詞,語言精煉度差問題進行規避和改正。 來源:LingCoder/OnJava8 主譯: LingCoder 參譯: LortSir 校對:nickChenyx E-mail: 本書原作者為 [美] Bru...
摘要:是目前最熱門的一種前端開發框架。對于前端工程師來說,掌握這門炙手可熱的技術是完全有必要的。雖然目前已出,但是官方并不會放棄版本,還會持續維護更新,而且掌握的基本知識能更快的幫助我們邁入。 AngularJS是目前最熱門的一種前端開發框架。對于前端工程師來說,掌握這門炙手可熱的技術是完全有必要的。本書會將作者掌握的AngularJS知識傾囊相授,并從學以致用的角度出發,用實例詳細地講解各...
閱讀 3735·2023-01-11 11:02
閱讀 4244·2023-01-11 11:02
閱讀 3050·2023-01-11 11:02
閱讀 5180·2023-01-11 11:02
閱讀 4737·2023-01-11 11:02
閱讀 5534·2023-01-11 11:02
閱讀 5313·2023-01-11 11:02
閱讀 3990·2023-01-11 11:02