返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

5.6 指针

发布于 2024-10-12 19:15:48 字数 3937 浏览 0 评论 0 收藏 0

不能将内存 地址指针 混为一谈。

地址是内存中每个字节单元的唯一编号,而指针则是实体。指针需分配内存空间,相当于一个专门用来保存地址的整型变量。

            x: int          p: *int
   -------+---------------+--------------+-------
     ...  | 100           | 0xc000000000 |  ...    memory
   -------+---------------+--------------+-------
          0xc000000000    0xc000000008             address


   提示:为便于阅读,地址假定。
func main() {
	var x int
	var p *int = &x

	*p = 100

	println(p, *p)
}
  • 取址运算符 & 用于获取目标地址。
  • 指针运算符 * 间接引用目标对象。
  • 二级指针 **T ,含包名则写成 **package.T
  • 指针默认值 nil ,支持 ==!= 操作。
  • 不支持指针运算,可借助 unsafe 变相实现。
func main() {

	// 空指针也会分配内存。
	var p *int
	println(unsafe.Sizeof(p))  // 8

/*

   p: *int
   +--------------+
   | 0            |
   +--------------+
   0xc000000008

*/
    
	var x int

/*

   p: *int                x: int
   +---------------+      +---------------+
   | 0             |      | 0             |
   +---------------+      +---------------+
   0xc000000008           0xc000000000

*/
    
    
	// 二级指针,指针的指针。
	var pp **int = &p
	*pp = &x

/*

                               +--------- *p -----------+
                               |                        | 
   pp: **int              p    |                 x      v 
   +---------------+      +---------------+      +---------------+
   | 0xc000000008 -|----->| 0xc000000000 -|----->| 0             |
   +---------------+      +---------------+      +---------------+
   0xc000000010           0xc000000008   ^       0xc000000000   ^
         |                               |                      |
         +-------- *pp ------------------+                      |
         |                                                      |
         +-------- **pp ----------------------------------------+
*/
    
    
	*p = 100
    **pp += 1
    
	println(**pp, *p, x)   // 101, 101, 101
}

并非所有对象都能进行取址操作。

func main() {
	m := map[string]int{
		"a": 1,
	}

	// _ = &m["a"]
	//     ~~~~~~~ invalid: cannot take address of m["a"]
}

支持相等运算符,但不能做加减法运算,不能做类型转换。
如两个指针指向同一地址,或都为 nil ,那么它们相等。

func main() {
	var b byte
	var p *byte = &b

	// p++
	// ~~~ invalid: p++ (non-numeric type *byte) 

	// n := (*int)(p)
	//      ~~~~~~ cannot convert *byte to *int
}
func main() {
	var p1, p2 *int
	println(p1 == p2)   // true

	var x int
	p1, p2 = &x, &x
	println(p1 == p2)   // true

	var y int
	p2 = &y
	println(p1 == p2)   // false
}

指针没有专门指向成员的 -> 运算符,统一使用 . 选择表达式。

func main() {
	a := struct {
		x int
	}{ 100 }
    
	p := &a
	p.x += 100
    
	println(p.x) // 200
}

零长度(0 byte)对象指针是否相等,与版本及编译优化有关,不过肯定不等于 nil

func main() {
	var a, b struct{}
	var c [0]int

	pa, pb, pc := &a, &b, &c

	println(pa, pb, pc)
	println(pa == nil || pc == nil)
	println(pa == pb)              
}

/*

$ go build -gcflags "-N -l" && ./test

0xc00009cf56 0xc00009cf56 0xc00009cf50
false
true

$ go build && ./test

0xc000088f70 0xc000088f70 0xc000088f70
false
false

*/

借助 unsafe 实现指针转换和运算,须自行确保内存安全。

  • 普通指针: *T ,包含类型信息。
  • 通用指针: Pointer ,只有地址,没有类型。
  • 指针整数: uintptr ,足以存储地址的整数。
func main() {
	d := [...]int{1, 2, 3}
	p := &d

	// *[3]int --> *int
    p2 := (*int)(unsafe.Pointer(p))

	// p2++
	p2 = (*int)(unsafe.Add(unsafe.Pointer(p2), unsafe.Sizeof(p[0])))
	*p2 += 100

	fmt.Println(d)  // [1 102 3]
}

普通指针( *T )和通用指针( Pointer )都能构成引用,影响垃圾回收。
uintptr 只是整数,不构成引用关系,无法阻止垃圾回收器清理目标对象。

有关 unsafe 更多信息,请阅读《中卷:标准库,语言》章节。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文