slice本来就是引用类型,为什么还需要传入指针
var students []dao.Student
db.Find(students)
这样写会爆错
var students []dao.Student
db.Find(&students)
这也就不会
但是map就可以不传指针
result := map[string]interface{}{}
db.Model(&dao.Student{}).Find(result)
map和slice同为引用类型,为什么find函数中使用方式却不同
Comment From: github-actions[bot]
The issue has been automatically marked as stale as it missing playground pull request link, which is important to help others understand your issue effectively and make sure the issue hasn't been fixed on latest master, checkout https://github.com/go-gorm/playground for details. it will be closed in 30 days if no further activity occurs. if you are asking question, please use the Question template, most likely your question already answered https://github.com/go-gorm/gorm/issues or described in the document https://gorm.io ✨ Search Before Asking ✨
Comment From: ivila
@espoirr 先给个结论,逻辑上(即Golang的设计来说)来说你都要传指针,当前Golang的实现上你的map可以不传指针。
先解答你的问题,为什么slice是个引用类型,却需要传入指针,而同为引用类型的map却不需要。 这是因为实际上在编译后,函数传递时,Slice传递的是个SliceHeader一样的结构体,你可以看这里,而map是传递了变量的地址。 但注意的是,这些都是当前实现上的事情。
然后再说一个概念,从设计上来说,这里不是在传入指针,而是在传入引用(可以看看C#的ref,out等关键字设计,能让你对这些设计上的东西有些了解),也就是约定上这个函数内部可以对传入的变量做修改。假如你传递的并非引用,那实际上约定来说我就不该动你的东西。 所以当你的slice传递的不是引用时,我爱怎么实现都可以(比如当前Golang的实现就是传入一份struct的Copy),也就导致了你的疑问。(对于这些问题有兴趣的可以看看C++的undefined behavior,C++这种问题比较多)
然后当前Golang编译上,map传递变量地址我没有找到设计说明(就是保证一定传递的是地址,可能是没有,也可能是我没看到),我个人觉得当前的编译后传递地址的原因是因为map的实现太大了,传递拷贝会影响性能,当然这只是我无知的猜测,不过如果我猜测是对的话,你用map时没有传递引用,实际上也是各undefined behavior,哪天跑不了了也是可能的。
Comment From: thinkgos
仔细看下slice和map的实现. 实际map持有的是指针,指向实现, 传map可以修改原始数据. 而slice并不是, 它实际是数组指针, 这个切片的长度, 容量. 你不传指针的方式进去, 底层无法修改原始数据. 那么你什么也得不到. 找篇传门介绍slice的文章看看原理就很容易明白
Comment From: github-actions[bot]
The issue has been automatically marked as stale as it missing playground pull request link, which is important to help others understand your issue effectively and make sure the issue hasn't been fixed on latest master, checkout https://github.com/go-gorm/playground for details. it will be closed in 30 days if no further activity occurs. if you are asking question, please use the Question template, most likely your question already answered https://github.com/go-gorm/gorm/issues or described in the document https://gorm.io ✨ Search Before Asking ✨
Comment From: espoirr
@ivila @thinkgos 我的理解是,gorm在find查询到结果,将结果的底层数组重新赋值给传入的引用,在函数内改变引用的指向,就需要传递引用的指针
Comment From: github-actions[bot]
The issue has been automatically marked as stale as it missing playground pull request link, which is important to help others understand your issue effectively and make sure the issue hasn't been fixed on latest master, checkout https://github.com/go-gorm/playground for details. it will be closed in 30 days if no further activity occurs. if you are asking question, please use the Question template, most likely your question already answered https://github.com/go-gorm/gorm/issues or described in the document https://gorm.io ✨ Search Before Asking ✨
Comment From: ivila
@ivila @thinkgos 我的理解是,gorm在find查询到结果,将结果的底层数组重新赋值给传入的引用,在函数内改变引用的指向,就需要传递引用的指针
@espoirr 这个理解是错的,从实现上来说,gorm没有修改引用的指向,而是直接在原值上做了更改(通过反射的方式,根据指针找到了slice这个结构体的地址并操作原值修改,就算底层用了Assign,实际编译结果也是找到原值做修改)。 或者说你以为的slice是这样:
var a []int // 你认为这个是个指针类型,等同于指针
methodCall(a) // 你认为这里应该传入自动传入指指针
实际上slice编译后是这样
var a struct { // 实际上这里是个结构体类型
Data uintptr
Len int
Cap int
}
methodCall(a) // 你传入的是个结构体
这种从“实现”上说明的方式,可能你会好理解一点。
然后对于map来说
var m map[string]interface{} //这里是声明了一个hmap的指针,看makemap函数返回值(https://github.com/golang/go/blob/22344e11f27d9667e7bbb6209df59e9a9e976d91/src/runtime/map.go#L297)
methodCall(m) // 传入的是个 *hmap指针
实际实现长这样
var m *hmap // 这个是个指针类型
methodCall(m)
但是如果我上面说的,不要过多去关注实现上的东西,而是去关注设计上的东西。 毕竟实现与设计不同算Bug,有实现但没设计叫undefine behavior,工程上这两个我们都不想要。
Comment From: github-actions[bot]
The issue has been automatically marked as stale as it missing playground pull request link, which is important to help others understand your issue effectively and make sure the issue hasn't been fixed on latest master, checkout https://github.com/go-gorm/playground for details. it will be closed in 30 days if no further activity occurs. if you are asking question, please use the Question template, most likely your question already answered https://github.com/go-gorm/gorm/issues or described in the document https://gorm.io ✨ Search Before Asking ✨
Comment From: yaokuku123
在Go语言中,虽然slice和map都是引用类型,但它们在函数参数传递和修改数据时的行为有所不同。让我们详细探讨一下为什么在db.Find函数中需要传入slice的指针,而map可以直接传值。
Slice和Map的引用类型 首先,我们确认一下slice和map作为引用类型的行为:
Slice: 一个slice包含指向底层数组的指针、长度和容量。当你传递一个slice到函数中时,实际上是复制了这个slice结构体(包含指针、长度和容量),而不是底层数组。如果在函数中直接修改这个slice(如:追加新元素),外部的slice不会受到影响,除非你传递的是slice的指针。
Map: 一个map是一个指向runtime.hmap结构的指针。当你传递一个map到函数中时,实际上是复制了这个指针。所以在函数内部对map的修改(如:添加或删除键值对)会直接影响到原始的map,因为它们指向的是同一个底层数据结构。
db.Find函数的行为 在Go的ORM(如GORM)中,db.Find函数用于查询数据库并将结果填充到提供的变量中。由于不同的数据结构需要不同的处理方式,因此需要注意传递的参数类型。
Slice: db.Find需要能够修改传入的slice,增加查询到的元素。这就要求传入的slice必须是可修改的。如果只传入一个slice副本(即传值),db.Find无法修改原slice的内容,所以必须传入slice的指针来允许函数修改底层数据。
var students []dao.Student
db.Find(&students) // 传入slice的指针
通过传递slice的指针,db.Find函数可以直接修改底层数组的内容并调整slice的长度。
Map: 由于map本身就是一个指针,传递map时只复制了这个指针,因此函数内部的修改会直接反映在原始map上。
result := map[string]interface{}{}
db.Model(&dao.Student{}).Find(result) // 直接传递map
在这种情况下,db.Find可以直接修改map的内容,而无需传递map的指针。
例子:
package main
import "fmt"
func modify_slice(s []int) {
s[0] = 99
}
func main() {
s := []int{1, 2, 3, 4, 5}
modify_slice(s)
fmt.Println(s) // [99 2 3 4 5] 可以修改当前切片中指针指向的数组中的数值
}
package main
import "fmt"
func modify_slice(s []int) {
s = append(s, 100)
}
func main() {
s := []int{1, 2, 3, 4, 5}
modify_slice(s)
fmt.Println(s) // [1 2 3 4 5] 无法给原切片添加新的数据
}
package main
import "fmt"
func modify_slice(s *[]int) {
*s = append(*s, 100)
}
func main() {
s := []int{1, 2, 3, 4, 5}
modify_slice(&s) // [1 2 3 4 5 100] 可以成功给数组添加值
fmt.Println(s)
}
总结 Slice: 需要传入指针,以便函数可以修改底层数组和slice的长度。 Map: 由于map本身就是指针,直接传递map即可,因为修改map的内容会影响到原始map。