国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

第十五章:指針類型

不知名網友 / 2984人閱讀

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

image

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

1 你將在本章將學到什么?

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

2 涵蓋的技術概念

  • 指針
  • 內存地址
  • 指針類型
  • 解除引用
  • 引用

3 什么是指針?

指針是“是一個數據項,它存儲另外一個數據項的位置”。
在程序中,我們不斷地存儲和檢索數據。例如,字符串、數字、復雜結構…。在物理層面,數據存儲在內存中的特定地址,而指針存儲的就是這些特定內存地址。

image

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

4 指針類型

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

指針類型的語法為:

*BaseType

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

我們來看一下例子:

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

5 如何去創建一個指針類型變量?

下面的語法可以創建:

var p *int

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

讓我們來創建一個名為 answer 的整型變量。

var answer int = 42

現在我們給變量 p 分配一個值了:

p = &answer

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

fmt.Println(p)// 0xc000012070

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

6 指針類型的零值

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

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

7 解除引用

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

來舉個例子,我們定義一個結構體類型 Cart

type Cart struct {	ID string	Paid bool}

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

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

7.1 空指針解引用:運行時 panic

每個 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。因為我們嘗試去尋找一個不存在的地址,程序將會報錯!我們嘗試找到空地址,而空地址在內存中不存在。

8 Maps 和 channels

Maps 和 channels 變量里保存了對內部結構的指針。因此,即便向一個函數或方法傳遞的 map 或 channel 不是指針類型,也開始對這個 map 或 channel 進行修改。讓我們看一個例子:

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

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

9 切片

9.1 切片定義

切片是相同類型元素的集合。在內部,切片是一個具有三個字段的結構:

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

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

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"}...)}
  • 函數 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]

9.2.0.2 解釋

  • 這個函數將[]string類型元素作為參數
  • 當函數被調用時,Go 會將切片 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]
  • 這里的改變只會影響拷貝的版本

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

問題:依你看,程序將傳輸下面哪個?

[AUSTRIA BELGIUM BULGARIA][Austria Belgium Bulgaria]

答案:這個函數將返回:

[AUSTRIA BELGIUM BULGARIA]

9.2.0.4 解釋

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

9.2.0.5 結論

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

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

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

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 的結果會影響到外部的變量

10 指向結構體的指針

有一個快捷方式可以讓你直接修改 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,我們可以直接修改變量 cartItem 字段
  • 這是因為運行時自動通過結構體指針找到了原值進行了修改,以下是等價的寫法
(*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 結構體的一個字段。

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

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 字段值會發生變化。
image

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

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

12 隨堂測試

12.1 問題

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

12.2 答案

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

關鍵要點

  • 指針是指向數據的地址
  • 類型 *T 表示所有指向 T 類型變量的指針集合
  • 創建指針變量,可以使用運算符&。它將獲取一個變量的地址
userId := 12546584p := &userId
`userId` 是 `int` 類型的變量`p` 是 `*int` 類型變量`*int` 表示所有指向 `int` 類型變量的指針
  • 具有指針類型的參數/接收器的函數可以修改指針指向的值。
  • map 和 channel 是“引用類型”
  • 接收 map 或 channel 的函數/方法可以修改內部存儲在這兩個數據結構中的值(無需傳遞指向 map 的指針或指向 channel 的指針)
  • 切片在內部保存對數組的引用;任何接收切片的函數/方法都可以修改切片元素。
  • 當你想在函數中修改切片長度和容量時,你應該向該函數傳遞一個指向切片的指針 (*[]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編程思想的后面看過后有新的內容再補充進去吧)。  輸入輸出是所有程序都必需的部分————使用輸入機制允許程序讀取外部數據(包括磁盤、光盤等存儲設備上的數據和用戶輸入的...

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

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

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

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

    ymyang 評論0 收藏0

發表評論

0條評論

不知名網友

|高級講師

TA的文章

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