成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

第十五章:指針類型

不知名網(wǎng)友 / 2989人閱讀

摘要:指針類型的零值指針類型的零值指針類型的零值都是,也就是說,一個沒有存儲地址的指針等于解除引用解除引用一個指針變量持有另一個變量的地址。

image

本篇翻譯自《Practical Go Lessons》 Chapter 15: Pointer type

1 你將在本章將學(xué)到什么?

  • 什么是指針?
  • 什么時指針類型?
  • 如何去創(chuàng)建并使用一個指針類型的變量。
  • 指正類型變量的零值是什么?
  • 什么是解除引用?
  • slices, maps, 和 channels 有什么特殊的地方?

2 涵蓋的技術(shù)概念

  • 指針
  • 內(nèi)存地址
  • 指針類型
  • 解除引用
  • 引用

3 什么是指針?

指針是“是一個數(shù)據(jù)項,它存儲另外一個數(shù)據(jù)項的位置”。
在程序中,我們不斷地存儲和檢索數(shù)據(jù)。例如,字符串、數(shù)字、復(fù)雜結(jié)構(gòu)…。在物理層面,數(shù)據(jù)存儲在內(nèi)存中的特定地址,而指針存儲的就是這些特定內(nèi)存地址。

image

記住指針變量,就像其他變量一樣,它也有一個內(nèi)存地址。

4 指針類型

Go 中的指針類型不止一種,每一種普通類型就對應(yīng)一個指針類型。相應(yīng)地,指針類型也限定了它自己只能指向?qū)?yīng)類型的普通變量(地址)。

指針類型的語法為:

*BaseType

BaseType指代的是任何普通類型。

我們來看一下例子:

  • *int 表示指向 int 類型的指針
  • *uint8 表示指向 uint8 類型的指針
type User struct {	ID string	Username string}
  • *User 表示指向 User 類型的指針

5 如何去創(chuàng)建一個指針類型變量?

下面的語法可以創(chuàng)建:

var p *int

這里我們創(chuàng)建了一個類型為 *int 的變量 p*int 是指針類型(基礎(chǔ)類型是 int)。

讓我們來創(chuàng)建一個名為 answer 的整型變量。

var answer int = 42

現(xiàn)在我們給變量 p 分配一個值了:

p = &answer

使用 & 符號我們就能得到變 answer地址。來打印出這個地址~

fmt.Println(p)// 0xc000012070

0xc000012070 是一個十六進(jìn)制數(shù)字,因為它的以 0x 為前綴。內(nèi)存地址通常是以十六進(jìn)制格式表示。你也可以使用二進(jìn)制(用 0 和 1)表示,但不易讀。

6 指針類型的零值

指針類型的零值都是 nil,也就是說,一個沒有存儲地址的指針等于 nil

var q *intfmt.Println(q == nil)// true

7 解除引用

一個指針變量持有另一個變量的地址。如果你想通過指針去訪問地址背后的變量值該怎么辦?你可以使用解除引用操作符 *。

來舉個例子,我們定義一個結(jié)構(gòu)體類型 Cart

type Cart struct {	ID string	Paid bool}

然后我們創(chuàng)建一個 Cart 類型的變量 cart,我們可以得到這個變量的地址,也可以通過地址找到這個變量:
image
image

  • 使用 * 操作符,你可以通過地址找到變量值
  • 使用 & 操作符,你可以得到變量的地址

7.1 空指針解引用:運(yùn)行時 panic

每個 Go 程序員都會遇到這個 panic(報錯):

panic: runtime error: invalid memory address or nil pointer dereference[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1091507]

為了更好地理解它,我們來復(fù)現(xiàn)一下:

package mainimport "fmt"func main() {    var myPointerVar *int    fmt.Println(*myPointerVar)}

在程序里,我們的定義了一個指針變量 myPointerVar,這個變量的類型是 *int(指向整型)。

然后我嘗試對它進(jìn)行解引用,myPointerVar 變量持有一個尚未初始化的指針,因此該指針的值為 nil。因為我們嘗試去尋找一個不存在的地址,程序?qū)箦e!我們嘗試找到空地址,而空地址在內(nèi)存中不存在。

8 Maps 和 channels

Maps 和 channels 變量里保存了對內(nèi)部結(jié)構(gòu)的指針。因此,即便向一個函數(shù)或方法傳遞的 map 或 channel 不是指針類型,也開始對這個 map 或 channel 進(jìn)行修改。讓我們看一個例子:

func addElement(cities map[string]string) {    cities["France"] = "Paris"}
  • 這個函數(shù)將一個 map 作為輸入
  • 它向 map 中添加一項數(shù)據(jù)(key = "France", value = "Paris")
package mainimport "log"func main() {    cities := make(map[string]string)    addElement(cities)    log.Println(cities)}
  • 我們初始化一個名為 cities 的 map
  • 然后調(diào)用函數(shù) addElement
  • 程序打印出:
map[France:Paris]

我們將在專門的部分中更廣泛地介紹 channels 和 maps。

9 切片

9.1 切片定義

切片是相同類型元素的集合。在內(nèi)部,切片是一個具有三個字段的結(jié)構(gòu):

  • length:長度
  • capacity:容量
  • pointer:執(zhí)向內(nèi)部數(shù)組的指針
    下面是一個關(guān)于切片 EUcountries 的例子:
package mainimport "log"func main() {    EUcountries := []string{"Austria", "Belgium", "Bulgaria"}    log.Println(EUcountries)}

9.2 函數(shù)或方法將切片作為參數(shù)或接收器:小心

9.2.0.1 Example1: 向切片添加元素

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"}...)}
  • 函數(shù) addCountries 將一個字符串類型切片作為參數(shù)
  • 它通過內(nèi)建函數(shù) 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]

答案:這個函數(shù)實際輸出:

[Austria Belgium Bulgaria]

9.2.0.2 解釋

  • 這個函數(shù)將[]string類型元素作為參數(shù)
  • 當(dāng)函數(shù)被調(diào)用時,Go 會將切片 EUcountries 拷貝一份傳進(jìn)去
  • 函數(shù)將得到一個拷貝的切片數(shù)據(jù):
    • 長度
    • 容量
    • 指向底層數(shù)據(jù)的指針
  • 在函數(shù)內(nèi)部,缺失的國家被添加了進(jìn)去
  • 切片的長度會增加
  • 運(yùn)行時將分配一個新的內(nèi)部數(shù)組

讓我們在函數(shù)中添加一個日志來可視化它:

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]
  • 這里的改變只會影響拷貝的版本

9.2.0.3 Example2:更新元素

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])    }}
  • 我們添加新函數(shù) upper,它將把一個字符串切片的每個元素都轉(zhuǎn)換成大寫

問題:依你看,程序?qū)鬏斚旅婺膫€?

[AUSTRIA BELGIUM BULGARIA][Austria Belgium Bulgaria]

答案:這個函數(shù)將返回:

[AUSTRIA BELGIUM BULGARIA]

9.2.0.4 解釋

  • 函數(shù) upper 獲取切片 EUcountries 的副本(和上面一樣)
  • 在函數(shù)內(nèi)部,我們更改切片元素的值 countries[k] = strings.ToUpper(countries[k])
  • 切片副本仍然有對底層數(shù)組的引用
  • 我們可以修改!
  • .. 但只有已經(jīng)在切片中的切片元素。

9.2.0.5 結(jié)論

  • 當(dāng)你將切片傳遞給函數(shù)時,它會獲取切片的副本。
  • 這并不意味著你不能修改切片。
  • 你只可以修改切片中已經(jīng)存在的元素。

9.3 函數(shù)或方法將切片指針作為參數(shù)或接收器

如果使用切片指針,你就可以在函數(shù)中修改這個切片了:

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"}...)}

這個程序?qū)⑤敵觯?/p>

[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]
  • 函數(shù) addCountries2 將字符串切片的指針([]string)作為參數(shù)
  • 函數(shù) append 調(diào)用時的第一個參數(shù)是 *countriesPtr(即我們通過指針 countriesPtr 去找到原值)
  • append 的第二個參數(shù)沒有改變
  • 函數(shù) addCountries2 的結(jié)果會影響到外部的變量

10 指向結(jié)構(gòu)體的指針

有一個快捷方式可以讓你直接修改 struct 類型的變量而無需使用*運(yùn)算符:

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,我們可以直接修改變量 cartItem 字段
  • 這是因為運(yùn)行時自動通過結(jié)構(gòu)體指針找到了原值進(jìn)行了修改,以下是等價的寫法
(*carPtr).Items = []Item{    {SKU: "154550", Quantity: 12},    {SKU: "DTY8755", Quantity: 1},}

(這也有效,但更冗長)

11 使用指針作為方法的接收器

指針通常用作方法的接收器,讓我們以 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 結(jié)構(gòu)體的一個字段。

當(dāng)然,如果你不想使用指針作為接收器,你也可以:

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}

在主函數(shù)的第一行,我們創(chuàng)建了一個 Cat 類型的變量 cat,它的 Name 是 "Millow"
當(dāng)我們調(diào)用具有值接收器RenameV2 方法時,函數(shù)外部變量 cat 的 Name 沒有發(fā)生改變。
當(dāng)我們調(diào)用 Rename 方法時,cat 的 Name 字段值會發(fā)生變化。
image

11.1 何時使用指針接收器,何時使用值接收器

  • 以下情況使用指針接收器:
    • 你的結(jié)構(gòu)體很大(如果使用值接收器,Go 會復(fù)制它)
    • 你想修改接收器(例如,你想更改結(jié)構(gòu)變量的名稱字段)
    • 你的結(jié)構(gòu)包含一個同步原語(如sync.Mutex)字段。如果你使用值接收器,它還會復(fù)制互斥鎖,使其無用并導(dǎo)致同步錯誤。
    • 當(dāng)接收器是一個 map、func、chan、slice、string 或 interface值時(因為在內(nèi)部它已經(jīng)是一個指針)
    • 當(dāng)你的接收器是持有指針時

12 隨堂測試

12.1 問題

  1. 如何去表示一個持有指向 Product 指針的變量?
  2. 指針類型的零值是多少?
  3. "解引用(dereferencing)" 是什么意思?
  4. 如何解引用一個指針?
  5. 填空: ____ 在內(nèi)部是一個指向 ____ 的指針。
  6. 判斷正誤:當(dāng)我想函數(shù)中修改 map 時,我的函數(shù)需要接收一個指向 map 的指針作為參數(shù),我還需要返回修改后的 map?

12.2 答案

  1. 如何去表示一個持有指向 Product 指針的變量?
    *Product
  2. 指針類型的零值是多少?
    nil
  3. "解引用(dereferencing)" 是什么意思?
    • 指針是指向存儲數(shù)據(jù)的內(nèi)存位置的地址。
    • 當(dāng)我們解引用一個指針時,我們可以訪問存儲在該地址的內(nèi)存中的數(shù)據(jù)。
  4. 如何解引用一個指針?
    使用解引用操作符 *
  5. 填空: ____ 在內(nèi)部是一個指向 ____ 的指針。
    slice 在內(nèi)部是一個指向 array 的指針。
  6. 判斷正誤:當(dāng)我想函數(shù)中修改 map 時,我的函數(shù)需要接收一個指向 map 的指針作為參數(shù),我還需要返回修改后的 map
    錯, 函數(shù)中只要接收一個 map 類型參數(shù)就行,也不需要返回更改后的map,因為 map 變量內(nèi)部存儲了指向底層數(shù)據(jù)的指針

關(guān)鍵要點(diǎn)

  • 指針是指向數(shù)據(jù)的地址
  • 類型 *T 表示所有指向 T 類型變量的指針集合
  • 創(chuàng)建指針變量,可以使用運(yùn)算符&。它將獲取一個變量的地址
userId := 12546584p := &userId
`userId` 是 `int` 類型的變量`p` 是 `*int` 類型變量`*int` 表示所有指向 `int` 類型變量的指針
  • 具有指針類型的參數(shù)/接收器的函數(shù)可以修改指針指向的值。
  • map 和 channel 是“引用類型”
  • 接收 map 或 channel 的函數(shù)/方法可以修改內(nèi)部存儲在這兩個數(shù)據(jù)結(jié)構(gòu)中的值(無需傳遞指向 map 的指針或指向 channel 的指針)
  • 切片在內(nèi)部保存對數(shù)組的引用;任何接收切片的函數(shù)/方法都可以修改切片元素。
  • 當(dāng)你想在函數(shù)中修改切片長度和容量時,你應(yīng)該向該函數(shù)傳遞一個指向切片的指針 (*[]string)
  • 解引用允許你訪問和修改存儲在指針地址處的值。
  • 要對指針進(jìn)行解引用操作,請使用運(yùn)算符 *
userId := 12546584p := &userId*p = 4log.Println(userId)

p 是一個指針

  • 我們使用 *p 來對指針 p 進(jìn)行解引用
  • 我們用指令 *p = 4 修改 userId 的值
  • 在代碼片段的末尾,userId 的值為 4(不再是 12546584)
  • 當(dāng)你有一個指向結(jié)構(gòu)的指針時,你可以直接使用你的指針變量訪問一個字段(不需要使用解引用運(yùn)算符)
    • 例子:
type Cart struct {    ID string}var cart CartcartPtr := &cart
  • 不需要這樣寫:(*cartPtr).ID = "1234"
  • 你可直接這樣寫:cartPtr.Items = "1234"
  • 變量 cart 就會被修改

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/125672.html

相關(guān)文章

  • 第十五章 輸入輸出系統(tǒng)

    摘要:在包下主要包括輸入輸出兩種流,每種輸入輸出流又可分為字節(jié)流和字符流兩大類。輸入輸出是從程序運(yùn)行所在的內(nèi)存的角度而言的。的輸入流主要由和作為基類,而輸出流主要由和作為基類。 本章主要參考和摘自瘋狂java講義上面的(java編程思想的后面看過后有新的內(nèi)容再補(bǔ)充進(jìn)去吧)?! ≥斎胼敵鍪撬谐绦蚨急匦璧牟糠帧褂幂斎霗C(jī)制允許程序讀取外部數(shù)據(jù)(包括磁盤、光盤等存儲設(shè)備上的數(shù)據(jù)和用戶輸入的...

    hankkin 評論0 收藏0
  • 《On Java 8》中文版,又名《Java 編程思想》中文第五版

    摘要:基于版本基于版本。由于中英行文差異,完全的逐字逐句翻譯會很冗余啰嗦。譯者在翻譯中同時參考了谷歌百度有道翻譯的譯文以及編程思想第四版中文版的部分內(nèi)容對其翻譯死板,生造名詞,語言精煉度差問題進(jìn)行規(guī)避和改正。 來源:LingCoder/OnJava8 主譯: LingCoder 參譯: LortSir 校對:nickChenyx E-mail: 本書原作者為 [美] Bru...

    BingqiChen 評論0 收藏0
  • 新書《AngularJS半知半解》預(yù)熱!

    摘要:是目前最熱門的一種前端開發(fā)框架。對于前端工程師來說,掌握這門炙手可熱的技術(shù)是完全有必要的。雖然目前已出,但是官方并不會放棄版本,還會持續(xù)維護(hù)更新,而且掌握的基本知識能更快的幫助我們邁入。 AngularJS是目前最熱門的一種前端開發(fā)框架。對于前端工程師來說,掌握這門炙手可熱的技術(shù)是完全有必要的。本書會將作者掌握的AngularJS知識傾囊相授,并從學(xué)以致用的角度出發(fā),用實例詳細(xì)地講解各...

    ymyang 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<