Golang中如何处理包含嵌入引用类型结构体的拷贝与引用问题
Golang中如何处理包含嵌入引用类型结构体的拷贝与引用问题 你好,
我想知道下面哪种使用结构体的方式是正确的,以及为什么?
谢谢
type UserStorage struct {
database *sql.DB
timeout time.Duration
}
db1 := UserStorage{ /* 如上所示传递参数 */ } // 这个?
db2 := &UserStorage{ /* 如上所示传递参数 */ } // 还是这个?
// 然后将 `db1` 或 `db2` 注入到你需要的地方。
4 回复
这两种方式没有绝对的正确与否,只是使用方式不同。
如果一个函数 updateStorage 期望接收一个 *UserStorage 指针,那么你必须这样传递 db1:updateStorage(&db1),而传递 db2 时只需 updateStorage(db2)。如果有一个 readStorage 函数期望接收一个 UserStorage 值,那么你必须分别这样传递 db1 和 db2:readStorage(db1) 和 readStorage(*db2)。
在 Go 语言中,函数参数总是按值传递,所以这个假设的 readStorage 函数会得到一个 UserStorage 的副本,并且无法修改传入的原始值。接收指针的函数则可以修改这些指针所指向的值。你可以根据这些细节来决定使用 UserStorage 还是 *UserStorage。
在Go中,选择使用值类型还是指针类型主要取决于结构体的使用场景和语义。对于你提供的UserStorage结构体,由于它包含一个*sql.DB引用类型字段,通常建议使用指针。
原因分析
database字段已经是引用类型:*sql.DB本身就是一个指针,无论结构体是值类型还是指针类型,这个字段都是共享的- 避免不必要的拷贝:即使结构体不大,使用指针可以确保传递的是引用而不是副本
- 语义一致性:
UserStorage通常表示一个资源持有者,使用指针更符合其角色
推荐使用指针方式
// 推荐:使用指针
storage := &UserStorage{
database: db, // *sql.DB
timeout: 30 * time.Second,
}
// 在函数间传递时也是指针
func NewService(storage *UserStorage) *Service {
return &Service{storage: storage}
}
两种方式的区别示例
type UserStorage struct {
database *sql.DB
timeout time.Duration
}
func main() {
// 方式1:值类型
db1 := UserStorage{
database: initDB(),
timeout: 30 * time.Second,
}
// 方式2:指针类型
db2 := &UserStorage{
database: initDB(),
timeout: 30 * time.Second,
}
// 修改测试
modifyStorage(db1) // 传递副本,原db1不变
modifyStoragePtr(db2) // 传递引用,原db2会被修改
}
func modifyStorage(s UserStorage) {
s.timeout = 60 * time.Second // 只修改副本
}
func modifyStoragePtr(s *UserStorage) {
s.timeout = 60 * time.Second // 修改原对象
}
实际应用中的建议
// 工厂函数通常返回指针
func NewUserStorage(db *sql.DB, timeout time.Duration) *UserStorage {
return &UserStorage{
database: db,
timeout: timeout,
}
}
// 方法接收器也使用指针
func (s *UserStorage) QueryUser(id int) (*User, error) {
// 需要访问database字段
// 如果使用值接收器,每次调用都会创建副本
}
// 接口实现
type Storage interface {
QueryUser(id int) (*User, error)
}
// UserStorage实现Storage接口
var _ Storage = (*UserStorage)(nil) // 编译时检查
对于包含引用类型字段且需要共享状态的结构体,使用指针(&UserStorage{})是更合适的选择。这避免了不必要的拷贝,确保了语义的正确性,并且与Go语言中资源管理器的常见模式保持一致。


