【FAQ】go的十万个为什么?-指针分配|Go主题月

简介: 与 C 系列中的所有语言一样,Go 中的所有内容都按值传递。也就是说,一个函数总是获得所传递事物的副本,就像有一个赋值语句将值分配给参数一样。例如,将int值传递给函数将生成的副本int,而将指针传递将生成指针的副本,但不会复制其指向的数据。

网络异常,图片无法展示
|

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 的大小的多少?

intuint的大小是特定于实现的,但在给定平台上彼此相同。为了实现可移植性,依赖于特定大小的值的代码应使用显式大小的类型,例如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)列。

目录
相关文章
|
16天前
|
存储 安全 Java
Go 指针
该文章深入探讨了Go语言中的指针概念,包括指针的获取、使用、限制以及如何安全地返回局部变量的地址,同时讨论了`unsafe.Pointer`在打破指针限制方面的作用。
12 3
|
1月前
|
存储 安全 Go
Go 中的指针:了解内存引用
Go 中的指针:了解内存引用
|
11天前
|
存储 安全 Go
深入理解 Go 语言中的指针类型
【8月更文挑战第31天】
8 0
|
11天前
|
存储 Go 开发者
掌握Go语言中的指针
【8月更文挑战第31天】
6 0
|
1月前
|
缓存 Java 编译器
Go 中的内存布局和分配原理
Go 中的内存布局和分配原理
|
14天前
|
Go 计算机视觉
Go从入门到放弃之指针
Go从入门到放弃之指针
|
2月前
|
存储 Go
go切片和指针切片
go切片和指针切片
17 2
|
28天前
|
人工智能 Go
go切片参数传递用值还是指针
go切片参数传递用值还是指针
25 0
|
28天前
|
存储 人工智能 Java
深入理解 go reflect - 要不要传指针
深入理解 go reflect - 要不要传指针
12 0
|
2月前
|
Go
GO 指针数据类型的使用
GO 指针数据类型的使用
12 0