Go项目指针分配
函数参数什么时候用值传递?
与 C 系列中的所有语言一样,Go 中的所有内容都按值传递。也就是说,一个函数总是获得所传递事物的副本,就像有一个赋值语句将值分配给参数一样。例如,将int
值传递给函数将生成的副本int
,而将指针传递将生成指针的副本,但不会复制其指向的数据。
Map和Slice一般用指针指向值:它们包含指向基础 Map 或 Slice 数据的指针描述符。复制 Map 或 Slice 的值时不会复制其指向的数据。复制接口类型的值将复制该接口值上的事务。如果该接口类型的值持有struct
结构体,则复制该结构体。如果有指针,则复制指针,但是同样不是它指向的数据。
注意,该讨论是关于操作的语义的。实际的实现可以应用优化以避免复制,只要优化不更改语义即可。
什么时候应该使用指向接口的指针?
一般很少使用接口值的指针,这仅在罕见,棘手的情况下才会使用,这些情况涉及伪装接口值的类型以进行延迟评估。
将指向接口值的指针传递给需要接口的函数是一个常见的错误。虽然编译器会报错,但是情况仍然令人困惑,因为有时必须有一个指针才能满足接口的要求。可以看到,尽管指向具体类型的指针可以满足接口,但有一个例外,指向接口的指针永远不能满足接口。
考虑变量声明:
var w io.Writer
打印函数fmt.Fprintf将满足io.Writer的值作为第一个参数,该值实现了规范的Write方法。这样我们可以写:
fmt.Fprintf(w, "hello, world\n")
但是,如果我们传递w的地址,则该程序将无法编译。
fmt.Fprintf(&w, "hello, world\n") // 编译出错
一个例外情况是,任何值,甚至是指向接口的指针,都可以分配给空接口类型(interface {})的变量。即使这样,如果值是指向接口的指针,几乎可以肯定是一个错误。结果可能会令人困惑。
我应该用值还是指针来定义方法?
func (s *MyStruct) pointerMethod() { } // 指针传递方法 func (s MyStruct) valueMethod() { } // 值传递方法
对于不习惯使用指针的程序员,这两个示例之间的区别可能令人困惑,但是情况实际上非常简单。在类型上定义方法时,接收方(在上面的示例中为s)的行为就好像它是该方法的参数一样。将接收器定义为值还是指针是一个同样的问题,就像函数参数应该是值还是指针一样。有几个注意事项。
- 首先,也是最重要的一点,该方法是否需要修改接收器?如果是这样,则接收者必须是一个指针。
- Map 和 Slice 是因为引用类型,因此它们就有些特殊,可以不用指针也能改变内容,但是,要在方法中更改切片的长度,接收者必须仍然是指针。
- 在指针传递方法中,如果在方法里更改了字段,接收器的源数据也会被更改
- 在值传递方法中,如果在方法里更改字段,接收器源数据不受影响
- 顺便说一句,在Java方法中,接收器始终是指针,尽管它们的指针性质有些掩饰了(并且有人建议在语言中添加值接收器)。Go中的价值接收者是不寻常的。
- 其次是对效率的考虑。如果接收器很大,例如一个大的结构,那么使用指针接收器会更节省资源。
- 接下来是一致性。如果类型的某些方法必须具有指针接收器,则其余的方法也应具有指针接收器,因此无论如何使用该类型,方法集都是一致的。有关详细信息,请参见方法集部分。
- 对于诸如基本类型,切片和很小的结构体之类的类型,值接收器非常节约资源,因此,除非该方法的语义要求使用指针,否则值接收器将高效且清晰。
make 和 new 有什么不同?
简而言之:new 分配内存,而 make 初始化 slice,Map 和 Channel 类型。
有关更多详细信息,请参见relevant section of Effective Go。
在64位计算机上,int 的大小的多少?
int
和uint
的大小是特定于实现的,但在给定平台上彼此相同。为了实现可移植性,依赖于特定大小的值的代码应使用显式大小的类型,例如int64
。在32位计算机上,编译器默认情况下使用32位整数,而在64位计算机上,整数具有64位。(从历史上看,这并不总是正确的)
另一方面,浮点变量和复杂类型的大小总是确定的(没有浮点型或复杂的基本类型),因为程序员在使用浮点数时应注意精度。用于(无类型的)浮点常量的默认类型为float64
。因此,foo := 3.0
声明了一个float64
类型的变量foo
。对于由(无类型的)常量初始化的float32
变量,必须在变量声明中显式指定变量类型:
var foo float32 = 3.0
或者,必须为常量提供一个类型,该类型可以像foo := float32(3.0)
中那样进行转换。
我如何知道在堆还是栈上分配了变量?
从正确性的角度来看,您不需要知道。只要有对它的引用,Go中的每个变量都存在。实现选择的存储位置与语言的语义无关。
存储位置确实会影响编写高效的程序。如果可能,Go编译器将在该函数的栈框架中分配该函数本地的变量。但是,如果编译器无法证明函数返回后未引用该变量,则编译器必须在垃圾回收堆上分配该变量,以避免悬空指针错误。另外,如果局部变量很大,则将其存储在堆中而不是栈中可能更有意义。
在当前的编译器中,如果使用了变量的地址,则该变量是在堆上分配的候选对象。然而,基本的逃逸分析可以识别某些情况,其中此类变量不会超出函数的返回范围,而是可以驻留在栈中。
为什么我的Go进程使用了那么多虚拟内存?
Go内存分配器保留了大范围的虚拟内存作为分配的场所。该虚拟内存对于特定的Go进程是本地的;保留不会剥夺其他进程的内存。
要查找分配给Go进程的实际内存量,请使用Unix top
命令并查阅RES
(Linux)或 RSIZE
(macOS)列。