go语言中管道channel和&内存地址和指针的讲解

go语言的设计理念

不要通过共享内存来通信,而是通过通信来共享内存

翻译为大白话就是说:

多个 goroutine 之间通过 channel 来通信,而不是通过共享内存来通信。

通过channel来通信

复制代码
package main

import (
	"fmt"
)
func add(a, b int, ch *chan int) {
	// 通过channel来通信
	*ch <- a + b
}
func main() {
	ch := make(chan int, 1)
	go add(1, 2, &ch)
	// 从channel中获取数据
	fmt.Println(<-ch)
}

问题1: go add(1, 2, &ch) 这里为啥传递的是&ch,而不是ch?

分析:这里其实是没必要的写法 ,Go 里 channel 本身就是指针,传值即传指针,所以不需要再套一层 。

为啥这里没有必须要写成: &ch ? 因为:

Go 的 channel 值内部就是一个指向hchan 结构体的指针,

按值传递时拷贝的只是这个指针 ,底层队列是共享的。所以:

复制代码
func add(a, b int, ch chan int) { ch <- a + b }
go add(1, 2, ch)

效果和现在完全一样,而且更符合 Go 的习惯。

而且标准库里所有接收 channel 的函数

(如context.WithCancel 、time.After 的使用方式)都是直接传 channel 值。

问题2:func add(a, b int, ch *chan int) 这里为啥是 *chan ?

分析:因为调用时传的是&ch ,也就是"channel 变量本身的地址"

所以形参必须声明成*chan int (指向 channel 的指针)

类型和实参必须匹配,这是& 带来的直接结果,不是设计需要。

将上面的代码优化为下面的

复制代码
package main

import (
	"fmt"
)

func add(a, b int, ch chan int) {
  // 通过channel来通信
	ch <- a + b
}

func main() {
	ch := make(chan int, 1)
	go add(1, 2, ch)
  // 从channel中获取数据
	fmt.Println(<-ch)
}

&ch 就是取变量ch 自己的地址,类型为*chan int

&ch 就是取变量ch 自己的地址,类型为*chan int。

复制代码
ch := make(chan int, 2)

变量 ch                     hchan(底层通道结构,堆上)
┌──────────────┐            ┌────────────────────────
│ 0xc000012345 │ ─────────► │ qcount / dataqsiz      │
└──────────────┘            │ buf(环形缓冲区)        │
      ▲                     │ sendq / recvq(等待队列)│
      │                     └────────────────────────┘
      │
   &ch = 这个格子本身的地址(*chan int)
  • ch 的值 =hchan 的地址(一个指针),类型为chan int
  • &ch = 存放这个指针的那块变量内存的地址 → 类型*chan int

区分指针和内存地址

  • 地址 = 某块内存在哪里(一个数字,如0xc000014080 )
  • 指针 = 一个专门用来存地址的变量(或这种类型)
    所以"指针"是容器,"地址"是容器里装的东西。

通过代码来区分指针和内存地址

复制代码
x := 10
p := &x   // p 是指针变量,存的是 x 的地址

fmt.Println(&x)  // 0xc000014080  ← 地址
fmt.Println(p)   // 0xc000014080  ← 和上面一样
fmt.Println(*p)  // 10            ← 顺着地址取到的值
fmt.Println(&p)  // 0xc000014088  ← p 自己也有地址

「谁是指针?」 只有p 。p 是变量,类型*int

换一句话说:&x /&p 是 取地址操作

p 是 指针变量

指针和内存地址的区别

指针是一个专门用来存地址的变量,地址是内存的编号(运行时概念)

指针是能变的那支箭头,地址是箭头指到的坐标

一个常见误区

&ch 不是"ch 指向的对象",而是"ch 这个变量所在的内存地址"。

同理&ch 也有类型,就是*chan int

一句话: & 问"你在哪",* 问"那里装着什么"。