for range 作用域

FreeMason發表於2021-07-20
// 借用例子
package main

import (  
    "fmt"
    "time"
)

type field struct {  
    name string
}

func (p *field) print() {  
    fmt.Println(p.name)
}

func main() {  
    data := []field{{"one"},{"two"},{"three"}}
    // 例一
    for _,v := range data {
        // 解決辦法:新增如下語句
        // v := v 
        go v.print()
    }
     // goroutines print: three, three, three
     // for 裡面的 v := v 是解決這問題的一種方案
    time.Sleep(3 * time.Second)    
    // 注意data2是指標陣列
    data2 := []*field{{"one"}, {"two"}, {"three"}}  
    // 例二
    for _, v := range data2 {
        // go執行是函式,函式執行之前,函式的接受物件已經傳過來
        go v.print()                
    }
    // goroutines print: one, two, three
    time.Sleep(3 * time.Second)   
}

上面的程式碼複製於網路後並在註釋上進行了新增,關於例一為什麼會出來這種現象,也看了下網上的答案,答案不少,大部分都只是提到了類似 for 是 copy 方式或者 for 裡面賦值給另一個變數等,這兩種類似的解釋都不完善或者說只講到了一部分

例一原因解析:
1、賦值:for _,v := range data {...} 變數 v 是通過賦值的方式得到
2、條件作用域:for _,v := range data {...} for 到 { 之間的變數為條件作用域,條件作用域也可叫隱式作用域
3、區域性作用域:for {},花括號裡面的變數為區域性作用域

    // 例一
    data := []*field{{"one"},{"two"},{"three"}}
    for _,v := range data {
        // 解決辦法:新增如下語句
        go v.print()
    }
    // goroutines print: three, three, three

    // 核心知識點:變數作用域、變數地址與值的關係
    // go v.print() 這裡是呼叫一個協程,非阻塞。因程式 go v.print() 變數 v 使用的是條件作用域的變數(區域性變數呼叫父級變數,迴圈第一次的v與迴圈n次後的v都是同一個變數同一地址),結合原因 1,後賦值覆蓋前面賦值,所以 go v.print() 結果是取決於 go 這個協程與 for-range 之間的執行速度,如果協程的執行速度遠快於 for-range 一次的速度,那麼 for-range 每次迴圈 go v.print() 的結果都是當前 for-range v 的值,如果 for-range 一次的速度遠快於一次協程的速度,那麼 for-range 每次迴圈 go v.print() 的值有可能是當前 for-range 的值也可能會是後面 for-range v 的值,例子:假設 for-range 的速度是 go v.print() 的 10 倍,data 裡面有 1-20 個 int, len 為 20 的切片,go v.print() 的結果是前面 10 個為 10,後面 10 個為 20

回到家以後,給還在公司的女朋友發個微信,給你準備了個禮物(家就是個地址),剛開始準備的玫瑰,想到女朋友對荷花也非常喜歡,又換成了荷花,想到她也非常喜歡月季,又換成了月季,送那麼比較好呢?換了不知多少次,到換成了項鍊時,她屁顛屁顛的回來了,這時她收到的禮物是項鍊。在這個過程中,地址始終未變,禮物一直在變化,女朋友收到的禮物解決於她到家時間(禮物是變數,家為禮物的地址,月季或玫瑰或項鍊為禮物的值)

本作品採用《CC 協議》,轉載必須註明作者和本文連結
光年之外

相關文章