3 回答

TA貢獻1877條經驗 獲得超6個贊
我將簡化問題,以便更容易理解。那里正在做的事情與此非常相似,這也不起作用(您可以在此處運行):
type myInt int
func (a myInt) increment() { a = a + 1 }
func increment(b myInt) { b.increment() }
func main() {
var c myInt = 42
increment(c)
fmt.Println(c) // => 42
}
這不起作用的原因是因為 Go 按值傳遞參數,如文檔所述:
在函數調用中,函數值和參數按通常的順序計算。在對它們求值后,調用的參數按值傳遞給函數,被調用的函數開始執行。
實際上,這意味著上面示例中的每個a、b、 和c都指向不同的 int 變量,a并且b是初始c值的副本。
要修復它,我們必須使用指針,以便我們可以引用相同的內存區域(此處可運行):
type myInt int
func (a *myInt) increment() { *a = *a + 1 }
func increment(b *myInt) { b.increment() }
func main() {
var c myInt = 42
increment(&c)
fmt.Println(c) // => 43
}
Nowa和b都是包含變量地址的指針c,允許它們各自的邏輯改變原始值。注意記錄的行為仍持有此:a和b仍然是原始值的副本,但作為一個參數提供的原始值increment函數的地址的c。
切片的情況與此沒有什么不同。它們是引用,但引用本身是按值作為參數提供的,因此如果您更改引用,調用站點將不會觀察到更改,因為它們是不同的變量。
不過,還有一種不同的方法可以使其工作:實現一個類似于標準append函數的 API 。再次使用更簡單的示例,我們可以在increment不改變原始值且不使用指針的情況下通過返回更改后的值來實現:
func increment(i int) int { return i+1 }
您可以在標準庫中的許多地方看到該技術,例如strconv.AppendInt函數。

TA貢獻1906條經驗 獲得超10個贊
Go 是按值傳遞的。對于參數和接收器都是如此。如果需要給切片賦值,則需要使用指針。
然后我在某處讀到你不應該將指針傳遞給切片,因為它們已經是引用
這并不完全正確,并且缺少故事的一部分。
當我們說某物是“引用類型”時,包括映射類型、通道類型等,我們的意思是它實際上是一個指向內部數據結構的指針。例如,您可以將地圖類型視為基本定義為:
// pseudocode
type map *SomeInternalMapStructure
所以要修改關聯數組的“內容”,不需要賦值給map變量;您可以按值傳遞映射變量,該函數可以更改映射變量指向的關聯數組的內容,并且它對調用者可見。當您意識到它是指向某個內部數據結構的指針時,這是有道理的。如果你想改變你只會分配給一個變量地圖這你想讓它指向內部的關聯數組。
但是,切片更復雜。它是一個指針(指向內部數組),加上長度和容量,兩個整數。所以基本上,你可以把它想象成:
// pseudocode
type slice struct {
underlyingArray uintptr
length int
capacity int
}
所以它不“只是”一個指針。它是一個相對于底層數組的指針。但是長度和容量是切片類型的“值”部分。
因此,如果您只需要更改切片的元素,那么是的,它就像引用類型一樣,因為您可以按值傳遞切片并讓函數更改元素并且它對調用者可見。
但是,當您append()(這就是您在問題中所做的)時,情況就不一樣了。首先,append 影響切片的長度,而長度是切片的直接部分之一,而不是在指針后面。其次,append 可能會產生不同的底層數組(如果原始底層數組的容量不夠,則分配一個新的);因此切片的數組指針部分也可能會更改。因此有必要改變切片值。(這就是為什么要append()返回一些東西。)從這個意義上說,它不能被視為引用類型,因為我們不僅僅是“改變它指向的東西”;我們直接改變切片。
- 3 回答
- 0 關注
- 229 瀏覽
添加回答
舉報