在我的应用程序中,我会经常传递对静态字符串的引用。我希望避免让Go为每次调用分配内存,但是我没有将地址输入到我的字符串文字中。
为什么不能获取字符串文字的地址(请参阅下面示例中的test1()
)?我是否误解了语法,或者由于Go的内部工作原因而限制它?
如果不可能,那么最佳解决方案是什么?
test2()
有效,但每次都会为var hej
分配内存吗?
test3()
不会分配任何新内存,但我希望避免功能之外的混乱。
package main
import "fmt"
var konnichiwa = `こんにちは世界`
// Gives the compile error `cannot take the address of "Hello world"`
func test1() (*string) { return &`Hello world` }
// Works fine
func test2() (*string) {
hej := `Hej världen`
return &hej
}
func test3() (*string) { return &konnichiwa }
func main(){
fmt.Println(*test1())
fmt.Println(*test2())
fmt.Println(*test3())
}
感谢您的帮助!
答案 0 :(得分:12)
关于传递“静态”字符串的最佳解决方案的问题,
提供建议“不要担心分配字符串”是很诱人的,因为在你描述传递相同字符串的位置时,这实际上是正确的,可能很多次。一般来说,考虑内存使用真的很好。这是非常糟糕的猜测,更糟糕的是根据另一种语言的经验猜测。
这是您的程序的修改版本。您认为内存分配在哪里?
package main
import "fmt"
var konnichiwa = `こんにちは世界`
func test1() *string {
s := `Hello world`
return &s
}
func test2() string {
return `Hej världen`
}
func test3() string {
return konnichiwa
}
func main() {
fmt.Println(*test1())
fmt.Println(test2())
fmt.Println(test3())
}
现在问编译器:
> go tool 6g -S t.go
(我将程序命名为t.go.)在输出中搜索对runtime.new的调用。只有一个!我会为你破坏它,它在test1。
因此,如果没有太多的切线,那么对编译器输出的一点看就表明我们通过使用字符串类型而不是* string来避免分配。
答案 1 :(得分:9)
获取文字的地址(字符串,数字等)是非法的,因为它具有模糊的语义。
你是在拿实际常数的地址吗?哪个允许修改值(并可能导致运行时错误)或者您是否要分配新对象,复制常量并将地址转换为新版本?
这种歧义在test2
的情况下不存在,因为您正在处理一个明确定义了语义的现有变量。如果字符串定义为const
,则同样不起作用。
语言规范通过明确禁止您要求的内容来避免这种歧义。解决方案是test2
。虽然它稍微冗长一点,但它使规则简单而干净。
当然,每个规则都有例外,在Go中这涉及复合文字:以下是合法的,并在规范中定义如下:
func f() interface{} {
return &struct {
A int
B int
}{1, 2}
}
答案 2 :(得分:6)
go中的字符串是不可变的,只是一个指针和一个长度(总长度:2个字)。
所以你不必使用指针来有效地处理它。
只需传递字符串。
答案 3 :(得分:2)
传递一个字符串通常不会在Go中分配内存 - 它是一个值类型(ptr为字节和一个int len)。
仅支持复合文字(如
)获取文字地址 v := &T{1, "foo"}
但不适用于像
这样的简单值 w := &1
x := &"foo"
答案 4 :(得分:0)
我正在使用interface {}来表示我的字符串有时为nil。就我而言,它看起来像:
testCases := []struct {
Values map[string]interface{}
}{
{
Values: map[string]interface{}{"var1": nil},
},
{
Values: map[string]interface{}{"var1": "my_cool_string"},
},
}
以及随后的以下代码(您可能还需要断言检查):
if v != nil {
vv := v.(string)
}