Golang中如何通用地将基本类型转换为指针?
Golang中如何通用地将基本类型转换为指针?
我知道在Golang中,使用&可以获取变量的指针,但不能用于常量:
// 错误
a := &"test"
对于这种情况,我们需要显式地定义一个额外的变量:
// 正确
str := "test"
a := &str
我尝试定义一个函数来为每种类型进行转换:
func PStr(s string) *string { return &s }
func PInt(s int) *int { return &s }
a := &PStr("test")
我考虑过使用泛型将所有基本类型转换为指针:
type BasicType interface {
~string | ~bool | ~int | ~int64
}
func P[T BasicType](t T) *T { return &t }
a := P("test")
b := P(20)
这个方法可行,但我想知道标准库中是否已经存在这样的函数。
更多关于Golang中如何通用地将基本类型转换为指针?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
您可以使用内置的 new() 函数,通过一条命令为变量分配内存并获取其指针。
谢谢,Leo,但在我这里,new 是用于类型而不是字面量的。
这不是Go 1.22版本中添加到database/sql包里的内容吗?Null值,你可以无需检查默认值就能判断它是否存在。
你打算如何使用这些指针?你只能获取一个变量的指针,因为这允许你修改其底层值。字面量是常量表达式,因此编译器不允许你获取它的指针。
请注意,在你的最后一个例子中,a 和 b 是指向 "test" 和 20 的副本的指针。这是因为你首先通过值传递这些字面量给 P。该值被复制到 t,然后返回的是指向 t 的指针,而不是实际的函数参数。
感谢您的回答。我只是想在一行中创建一个新的结构体,如下所示:
type UserQuery struct {
Age *int
MemoNull *bool
}
query := UserQuery{Age: P(30), MemoNull: P(false)}
无论如何,我在这里找到了答案:
它提供了一种类似的方法:
func Ptr[T any](v T) *T {
return &v
}
但我仍然想知道为什么 Go 团队没有在 SDK 中提供这样的函数或语法糖来获取字面量的指针。
但我仍然想知道为什么 Go 团队没有在 SDK 中提供这样的函数或语法糖来获取字面量的指针。
因为你无论如何都不是在获取字面量的指针。函数 Ptr 首先从字面量实例化一个(局部)变量 v,然后返回指向该变量的指针(顺便说一句,这可能因此导致它逃逸到堆上)。
这个操作根本没有收益。在 Go 中,指针应为其声明的目的而使用:当你需要在函数中修改某些内容时通过引用传递,或者(在少数情况下)为了降低复制大型数据结构的成本。
func main() {
fmt.Println("hello world")
}
首先,我只想在一行代码中获取一个字面量字符串或数字的指针,而不是像下面这样:
age := 30
memoNull := false
query := UserQuery{Age: &age, MemoNull: &memoNull}
我知道编译器不会为数字 30 分配内存,所以我必须显式声明 age 变量来获取其指针。因此,在这种情况下,从字面量实例化一个(局部)变量 v 是可以接受的。
其次,UserQuery 结构体中的每个字段都有一些逻辑,只有在它们未被赋值时才需要执行。所以我必须在结构体中使用指针,以便检查值是否为 nil,而不是 0 或空字符串。
感谢Leo提供的信息。我正在基于Go 1.18版本开发一个框架,这是首个支持泛型的版本,我之前并不了解1.22版本中的新特性。
我查看了这个新特性,我认为它们能提供类似的效果,但并不完全相同。使用
type Null[T any] struct {
V T
Valid bool
}
那么UserQuery就会是
type UserQuery struct {
Age sql.Null[int]
MemoNull sql.Null[bool]
}
然后通过检查sql.Null.Valid来判断字段是否被赋值了,对吗?这通过检查指针是否为nil达到了与之前代码相同的效果。
但我认为这会带来一些副作用。首先创建这样的结构体会占用更多内存,并且对于JSON包进行(反)序列化以及反射创建来说也会比较麻烦。
你怎么看?
f0rb: 并通过检查 sql.Null.Valid 来判断字段是否被赋值,对吗?
是的。
f0rb: 首先创建这样的结构体会占用更多内存。
这是不对的。你使用指针的方案实际上比这个方案需要更多内存。在讨论中,你的一些回答看起来像是把指针当作值来对待。但它是一个指针,你实际的值仍然在内存中的某个地方。因此,由于结构体不会产生内存开销,其大小是各字段大小的总和。所以,例如,Null[string] 将需要 8+1 个字节。同时,指针有固定的大小,在 32 位系统上是 4 字节,在 64 位系统上是 8 字节。由于 x64 更常见,你使用 *string 的方案将需要 8+8 个字节。
f0rb: 对于 JSON 包来说,(反)序列化也很麻烦。
Marshal/Unmarshal 是接口。你总是可以为这些值实现自己的解决方案,但这并不是什么独特的事情。有很多现成的包可以帮你完成。例如,可以看看这个。
f0rb: 反射式创建也是如此。
嗯,在我学习 Go 语言的时候,一个非常聪明的人告诉我:
如果你在使用反射,那么你可能做错了什么。
在什么情况下会需要用 reflect 包来创建一个带指针的结构体以用于 SQL 呢?!不过好吧,如果这确实是需求,你可以获取字符串的值,然后自己创建 Null。在 Go 语言中,大部分时候并没有超级简单的一行代码解决方案。我上面提到的那个包,已经包含了创建其类型的函数。
在标准库中,确实没有提供通用的基本类型转指针函数。你的泛型实现是目前最简洁和类型安全的方式。
不过,标准库的 sync.Pool 和 atomic.Value 等包中,有一些类似模式的实现可以参考。例如:
// 类似标准库中的模式
func newString(s string) *string { return &s }
func newInt(i int) *int { return &i }
你的泛型版本已经相当完善,可以进一步扩展支持更多类型:
type BasicType interface {
~string | ~bool | ~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 | ~complex64 | ~complex128
}
func P[T BasicType](t T) *T { return &t }
// 使用示例
func main() {
strPtr := P("hello")
intPtr := P(42)
boolPtr := P(true)
floatPtr := P(3.14)
fmt.Println(*strPtr, *intPtr, *boolPtr, *floatPtr)
}
如果需要处理 nil 情况,可以添加一个辅助函数:
func POrNil[T BasicType](t T) *T {
var zero T
if t == zero {
return nil
}
return &t
}
对于结构体类型,也可以类似处理:
type Person struct {
Name string
Age int
}
func PStruct[T any](t T) *T { return &t }
func main() {
p := PStruct(Person{Name: "Alice", Age: 30})
fmt.Println(p.Name, p.Age)
}
虽然标准库没有直接提供这个功能,但你的实现方式符合Go的惯用法,并且在很多开源项目中都有类似实现。
感谢你分享关于指针的知识,Leo,但我想你忘记了 sql.Null 中布尔值的内存对齐。Null[string] 将需要 8+8 字节,而不是 8+1。而且我知道分配给指针的值需要额外的字节。
这里我做了一个测试:
type UserQuery3 struct {
Name Null[string]
Age Null[int]
}
type UserQuery4 struct {
Name *string
Age *int
}
func main() {
fmt.Println(unsafe.Sizeof("s")) // 16
fmt.Println(unsafe.Sizeof(true)) // 1
fmt.Println(unsafe.Sizeof(5)) // 8
fmt.Println(unsafe.Sizeof(Null[bool]{})) // 2
fmt.Println(unsafe.Sizeof(Null[int]{})) // 16
fmt.Println(unsafe.Sizeof(Null[string]{})) // 24
fmt.Println(unsafe.Sizeof(UserQuery3{})) // 40
fmt.Println(unsafe.Sizeof(UserQuery3{Name: Null[string]{V: "s"}})) // 40
fmt.Println(unsafe.Sizeof(UserQuery4{})) // 16
fmt.Println(unsafe.Sizeof(UserQuery4{Name: P("s")}), unsafe.Sizeof("s")) // 16 16
fmt.Println(unsafe.Sizeof(UserQuery4{Age: P(5), Name: P("s")}), unsafe.Sizeof("s"), unsafe.Sizeof(5)) // 16 16 8
}
在我的电脑上,string 需要 16 字节。
因此,UserQuery3 将始终需要 40 字节,而 UserQuery4 在所有指针都为 nil 时需要 16 字节,在所有字段都被赋值时最多需要 40 字节。
在大多数情况下,结构体中只有不到一半的字段会被赋值,这就是我说创建 UserQuery3 会占用更多内存的意思。
对于 JSON 数据,它将需要额外的实现或包,从而引入复杂性。
对于反射,我构建的是一个框架,用于处理用户定义的、具有任意字段的结构体。据我所知,除了反射之外,没有其他方法可以获取字段名。

