亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

為什么我無法使用引用將值附加到結構的切片?

為什么我無法使用引用將值附加到結構的切片?

Go
精慕HU 2023-08-07 16:38:36
我去,我假設切片是通過引用傳遞的,但這似乎適用于值,但不適用于數組本身。例如,如果我有這個結構:    l := Line{        Points: []Point{            Point{3, 4},        },    }我可以定義一個變量,它傳遞對結構切片的引用slice := l.Points然后,如果我修改它,變量引用的原始結構將反映這些修改。slice[0].X = 1000fmt.Printf(    "This value %d is the same as this %d",     slice[0].X,     l.Points[0].X,)這與數組的行為不同,我認為數組是按值傳遞的。因此,例如,如果我使用數組定義了前面的代碼:l := Line{    Points: [1]Point{        Point{3, 4},    },}arr := l.Pointsarr[0].X = 1000fmt.Println(arr.[0].X != s.Points[0].X) // equals true, original struct is untouched那么,該l結構就不會被修改。現在,如果我想修改切片本身,我顯然不能這樣做:slice = append(slice, Point{99, 100})因為這只會重新定義切片變量,從而丟失原始引用。我知道我可以簡單地這樣做:l.Points = append(l.Points, Point{99, 100})但是,在某些情況下,使用另一個變量比鍵入整個變量更方便。我試過這個:*slice = append(*slice, Point{99, 100})但它不起作用,因為我試圖取消引用顯然不是指針的東西。我終于嘗試了這個:slice := &l.Points*slice = append(l.Points, Point{99, 100})它有效,但我不確定發生了什么。為什么切片的值沒有被覆蓋?這里如何append運作?
查看完整描述

4 回答

?
絕地無雙

TA貢獻1946條經驗 獲得超4個贊

讓我們首先解決術語問題。Go語言規范并不像您所使用的那樣使用引用一詞。然而 Go 確實有指針,而指針是引用的一種形式。此外,切片和映射有點特殊,因為存在一些底層數據(切片下的數組或映射的存儲),這些數據可能已經存在,也可能不存在,或者通過聲明或定義類型為 或 的變量來創建某些類型T或類型對T1T21slice of Tmap[T1]T2

在談論時,我們可以將引用一詞的用法理解為顯式指針,例如:


func f1(p *int) {

? ? // code ...

}

以及談論時隱含的指針:


func f2(m map[T1]T2) { ... }

func f3(s []T) { ... }

在 中f1,preally 是一個指針:因此它指的是某個實際的int或 is nil。在 中f2,m指的是某些底層地圖,或者是nil。在 中f3,s指的是某個底層數組,或者是nil。


但如果你寫:


l := Line{

? ? Points: []Point{

? ? ? ? Point{3, 4},

? ? },

}

那么你一定寫過:


type Line struct {

? ? // ... maybe some fields here ...

? ? Points []Point

? ? // ... maybe more fields here ...

}

這Line是一個結構類型。它不是切片類型;它不是地圖類型。它包含一種切片類型,但它本身不是一種切片類型。


您現在談論傳遞這些切片。如果您傳遞,則您將按值l傳遞整個內容。struct區分它和傳遞 的值非常重要l.Points。接收這些參數之一的函數必須使用正確的類型來聲明它。


因此,在大多數情況下,談論參考文獻只是轉移注意力——分散人們對實際情況的注意力。我們需要知道的是:您使用什么源代碼為哪些變量分配什么值?


完成所有這些后,讓我們討論一下實際的代碼示例:


l.Points = append(l.Points, Point{99, 100})

這正是它所說的:

  • 傳遞l.Pointsto?append,它是一個內置函數,因為它具有神奇的類型靈活性(與 Go 的其余部分相比,類型相當嚴格)。它采用任何類型的值(?T的切片,對于任何有效類型T)加上一個或多個類型的值,并生成相同類型的新值。[]TT[]T

  • 將結果賦給l.Points.

它什么時候append起作用,它可能:

  • 接收nil(給定類型):在這種情況下,它創建底層數組,或者

  • 接收非零切片:在這種情況下,它會寫入底層數組或根據需要丟棄該數組,轉而使用新的更大容量的數組。2

因此,在所有情況下,底層數組實際上可能剛剛被創建或替換。因此,對同一底層數組的任何其他使用進行適當更新非常重要。將結果分配回更新(可能是唯一的)引用底層數組的切片變量。l.Points

然而,我們可以打破這些假設:

s2?:=?l.Points

現在l.Pointss2都引用(單個)底層數組。修改底層數組的操作至少可能會影響s2??l.Points

你的第二個例子本身就可以:

*slice?=?append(*slice,?Point{99,?100})

但您尚未顯示其自身是如何?slice聲明和/或分配給的。

你的第三個例子也很好:

slice?:=?&l.Points
*slice?=?append(l.Points,?Point{99,?100})

這些行中的第一行聲明并初始化slice為指向l.Pointsslice因此該變量的類型為*[]Point。它的值(slice即 中的值,而不是 中的值)*slice是 的地址l.Points,其類型為[]Point。

中的值*slice就是 中的值l.Points。所以你可以寫:

*slice?=?append(*slice,?Point{99,?100})

這里。由于*slice是 的另一個名稱l.Points,您還可以編寫:

l.Points?=?append(*slice,?Point{99,?100})

僅當由于某種原因無法使用時才需要使用,?3但如果更方便的話也可以使用。讀讀讀,更新更新。*slicel.Points*slice*slicel.Points*slicel.Points


1要了解我所說的可能或可能不會在此處創建的意思,請考慮:

var?s?[]int

對比:

var?s?=?[]int{42}

第一個離開,s == nil而第二個創建一個底層數組,該數組能夠保存一個int42,保存一個int值 42,因此s != nil。

2我不清楚是否有承諾永遠不會在容量大于其當前長度但不足以保存最終結果的現有切片數組上寫入。也就是說,可以append先將10個對象追加到現有的底層數組中,然后發現需要更大的數組并擴展底層數組嗎?如果有其他切片值引用現有的底層數組,則可以觀察到差異。

3如果您有理由將l.Pointsor傳遞&l.Points給某些現有(預先編寫的)函數,則會出現一個經典示例:

  • 如果您需要將l.Points切片值傳遞給某個現有函數,則該現有函數無法更改切片值,但可以更改底層數組。這可能是一個糟糕的計劃,所以如果確實這樣,請確保這是可以的!如果它只讀取切片和底層數組,那就安全得多。

  • 如果您需要將&l.Points指向切片值的值傳遞給某個現有函數,則該現有函數可以更改切片和底層數組。

如果您正在編寫一個新函數,則由您決定以最合適的方式編寫它。如果您只想讀取切片和底層數組,則可以采用 類型的值[]Point。如果您打算就地更新切片,則應該采用類型為“*[]Point指向切片的指針”的值Point。


查看完整回答
反對 回復 2023-08-07
?
收到一只叮咚

TA貢獻1821條經驗 獲得超5個贊

Append 返回一個新切片,該切片可能會修改初始切片的原始支持數組。原始切片仍將指向原始后備數組,而不是新數組(新切片可能位于內存中的同一位置,也可能不位于內存中的同一位置)

例如(操場

slice := []int{1,2,3}

fmt.Println(len(slice))

// Output: 3

newSlice := append(slice, 4)

fmt.Println(len(newSlice))

// Output: 4

fmt.Println(len(slice))

// Output: 3?

雖然切片可以被描述為“指向數組的胖指針”,但它不是指針,因此您無法取消引用它,這就是您收到錯誤的原因。

通過創建一個指向切片的指針,并append按照上面的操作使用,您可以將指針指向的切片設置為 . 返回的“新”切片append。


查看完整回答
反對 回復 2023-08-07
?
梵蒂岡之花

TA貢獻1900條經驗 獲得超5個贊

我知道這可能是褻瀆的,但是對我來說,將切片視為結構很有用。


type Slice struct {

    len int

    cap int

    Array *[n]T // Pointer to array of type T

由于在像 C 這樣的語言中,該[]運算符也是一個解引用運算符,因此我們可以認為每次訪問切片時,我們實際上都在解引用底層數組并為其分配一些值。那是:


var s []int

s[0] = 1

可能被認為等同于(在偽代碼中):


var s Slice

*s.Array[0] = 1 

這就是為什么我們可以說切片是“指針”。因此,它可以像這樣修改其底層數組:


myArray := [3]int{1,1,1}

mySlice := myArray[0:1]

mySlice = append(mySlice, 2, 3) // myArray == mySlice

修改mySlice也會修改myArray,因為切片存儲了指向數組的指針,并且在追加時,我們取消引用該指針。


然而,這種行為并不總是這樣。如果超出原始數組的容量,則會創建一個新數組,并且原始數組保持不變。


myArray := [3]int{1,1,1}

mySlice := myArray[0:1]

mySlice = append(mySlice, 2, 3, 4, 5) // myArray != mySlice

當我們嘗試將切片本身視為實際指針時,就會出現混亂。由于我們可以通過附加到底層數組來修改它,因此我們相信在這種情況下:


sliceCopy := mySlice

sliceCopy = append(sliceCopy, 6)

兩個切片slice和sliceCopy是相同的,但它們不是。我們必須顯式傳遞對切片內存地址的引用(使用&運算符)才能修改它。那是:


sliceAddress := &mySlice

*sliceAddress = append(mySlice, 6) // or append(*sliceAddress, 6)


查看完整回答
反對 回復 2023-08-07
?
ibeautiful

TA貢獻1993條經驗 獲得超6個贊

您的第一次嘗試沒有成功,因為切片不是指針,它們可以被視為引用類型。如果底層數組有足夠的容量,Append 將修改它,否則返回一個新切片。

通過結合這兩種嘗試,您可以實現您想要的目標。

操場

l := Line{

    Points: []Point{

        Point{3, 4},

    },

}


slice := &l.Points


for i := 0; i < 100; i++ {

    *slice = append(*slice, Point{99 + i, 100 + i})

}


fmt.Println(l.Points)


查看完整回答
反對 回復 2023-08-07
  • 4 回答
  • 0 關注
  • 190 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號