背景
本篇主要介绍 Go 语言的指针&通道的用法。
相关资料
指针
指针变量
func basicPtr() { a := 10 fmt.Printf("变量a的内存地址十六进制:%p\n", &a) var ptr *int fmt.Println("空的指针变量:", ptr) fmt.Printf("空的指针变量内存地址十六进制: %p\n", ptr) if ptr == nil { fmt.Println("ptr is nil") } // 将变量a的内存地址赋值给指针变量ptr ptr = &a fmt.Println("ptr:", ptr) fmt.Println("*ptr:", *ptr) var pptr **int pptr = &ptr fmt.Println("pptr:", pptr) fmt.Println("*pptr:", *pptr) fmt.Println("**pptr:", **pptr) }
知识点:
- 将 & 放到一个变量前,就会返回变量的内存地址
- 将 * 放到一个变量类型前,表示变量为一个指针变量
- 当一个指针变量被定义后,没有赋值前,它的值为 nil,内存地址为0
- 想要获取指针变量指向的值,则在指针变量前加一个 * ,指向指针的指针变量,需要加两个*
指针数组
func arrPtr() { arr := []int{2, 4, 6} for index, value := range arr { fmt.Printf("arr[%d]: %d, %p\n", index, value, &arr[index]) } // ptr是指针数组 var ptrArr [3]*int for index := range arr { ptrArr[index] = &arr[index] } fmt.Println("ptrArr:", ptrArr) for index := range ptrArr { fmt.Printf("ptrArr[%d] : %p -> %d\n", index, ptrArr[index], *ptrArr[index]) } }
指针作为函数参数
基础版:
func ptrFunc() { x := 3 fmt.Println("x:", x) x1 := add(x) fmt.Println("x1:", x1) fmt.Println("x:", x) fmt.Println("-----------------------") y := 3 fmt.Println("y:", y) y1 := addPtr(&y) fmt.Println("y1:", y1) fmt.Println("y:", y) } /** * 参数为变量的值 */ func add(a int) int { a = a + 1 return a } /** * 参数为变量的内存地址 */ func addPtr(a *int) int { *a = *a + 1 return *a }
知识点:
- add函数的入参,传递的是变量x的值的copy,当add函数执行时,变量x不会有任何变化
- addPtr函数的入参,传递的是变量x的内存地址,当addPtr函数执行时,会修改变量x的值
传递指针的好处:
- 传指针使得多个函数能操作同一个对象
- 传指针比较轻量级(8bytes),只是传递内存地址,可以用指针传递体积大的结构体
- channel、slice、map这三种类型实现机制类似指针,所以可以直接传递
测试map参数:
func testMap() { myMap := make(map[string]int) myMap["zhangsan"] = 10 myMap["lisi"] = 11 myMap["wangwu"] = 12 fmt.Println("myMap:", myMap) fmt.Printf("myMap:%p\n", myMap) fmt.Println("-----------------------") myMap2 := modifyMap(myMap) fmt.Println("myMap2:", myMap2) fmt.Printf("myMap2:%p\n", myMap2) fmt.Println("myMap:", myMap) fmt.Printf("myMap:%p\n", myMap) } func modifyMap(myMap map[string]int) map[string]int { myMap["lisi"] = 100 return myMap }
测试channel传参:
func testChannel() { ch := make(chan *int, 1) a := 1 ptr := &a modifyChannel(ptr, ch) ptr2 := <-ch fmt.Printf("a: %d, %p\n", a, &a) fmt.Printf("ptr: %d, %p\n", *ptr, ptr) fmt.Printf("ptr2: %d, %p\n", *ptr2, ptr2) } func modifyChannel(ptr *int, ch chan *int) { *ptr = 100 ch <- ptr }
通道
通道基础
func basic() { ch := make(chan int) defer close(ch) go send(1, ch) fmt.Println(<-ch) //fmt.Println(<-ch) go receive(ch) } func send(a int, ch chan int) { ch <- a }
知识点:
- 操作符 <- 用于指定通道的方向,发送或接收
- 如果通道不带缓冲:
- 发送方会阻塞直到接收方从通道中接收了值,所以如果不使用 goroutines 去执行发送,那整个程序就会发生死锁,最终导致panic
- 当通道中无数据,如果不使用 goroutines 去接收,那整个程序也会发生死锁,最终导致panic
通道关闭
func testClose() { ch := make(chan int) go send(1, ch) fmt.Println(<-ch) close(ch) // 判断通道是否被关闭 v, ok := <-ch if ok { fmt.Println("channel is open, value:", v) } else { fmt.Println("channel is closed") //close(ch) //go send(2, ch) //fmt.Println(<-ch) } }
知识点:
- 给已关闭的通道发送或者再次关闭都会导致运行时的panic
向通道中多次发送
func testMultiSend() { arr := []int{1, 2, 3, 4, 5, 6} ch := make(chan int) for _, value := range arr { go add(value, ch) } i := 0 for i < len(arr) { fmt.Println("receive -> ", <-ch) i++ } } /** * 将结果发送到通道ch */ func add(a int, ch chan int) { fmt.Println("send -> ", a) ch <- a }
知识点:
- 一个通道相当于是一个先进先出的队列,也就是说通道中的元素都是严格的按照发送的顺序排序的,接收的顺序也完全是按照发送的顺序接收的
无缓冲区实例
func calc() { arr := []int{1, 2, 3, 4, 5, 6} ch := make(chan int) arrLen := len(arr) / 2 go sum(arr[:arrLen], ch) go sum(arr[arrLen:], ch) // 从通道 ch 中接收 x, y := <-ch, <-ch fmt.Println(x, y) } /** * 计算数组arr的累计值,并将结果发送到通道ch */ func sum(arr []int, ch chan int) { sum := 0 for _, v := range arr { sum += v } fmt.Println("send -> ", sum) ch <- sum // 把 sum 发送到通道 ch }
知识点
- channel 接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得 Goroutines 同步变的更加的简单,而不需要显式的 lock
缓冲区通道
func testChannelBuffer() { ch := make(chan int, 3) ch <- 1 ch <- 2 ch <- 3 //ch <- 4 fmt.Println("cap(ch):", cap(ch)) fmt.Println("len(ch):", len(ch)) fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println("len(ch):", len(ch)) ch <- 5 fmt.Println(<-ch) }
知识点:
- 带缓冲区的通道,允许发送端发送的数据放在缓冲区里面,当缓冲区未满时,不会阻塞,当缓冲区满了,发送端就无法再发送数据了
- cap(ch):查询一个通道的容量(无缓冲区通道返回0)
- len(ch):查询当前有多少个已被发送到此通道但还未被接收出去的元素(无缓冲区通道返回0)
缓冲区通道实例
var wg sync.WaitGroup /** * 测试通道-复杂例子(带缓冲区) */ func channelBufferExample() { arr := []int{1, 2, 3, 4, 5, 6} ch := make(chan int, 10) wg.Add(len(arr)) for _, value := range arr { go addWhiteWg(value, ch) } // 当协程未全部执行完时,一直阻塞(类似于java的CountDownLatch) wg.Wait() close(ch) for { v, ok := <-ch if ok { fmt.Println("channel is open, receive value:", v) } else { fmt.Println("channel is closed") break } } } /** * 将结果发送到通道ch */ func addWhiteWg(a int, ch chan int) { fmt.Println("send -> ", a) ch <- a wg.Done() }
知识点:
- 带缓冲的通道相当于是一个先进先出的队列,也就是说通道中的元素都是严格的按照发送的顺序排序的,接收的顺序也完全是按照发送的顺序接收的
- 带缓冲的通道关闭后,如果缓冲区有数据,接收方仍然可以从缓冲区中获取数据
测试select
func testSelect() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) } func fibonacci(c, quit chan int) { x, y := 1, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } }
知识点:
- select 默认是阻塞的,只有当监听的 channel 中有发送或接收可以进行时才会运行,当多个 channel 都准备好的时候, select 是随机的选择一个执行的
标签:arr,ch,int,fmt,Println,Go,指针,通道 From: https://www.cnblogs.com/xuwenjin/p/16849899.html