Go的变量作用域


1. 变量作用域

1.1. 全局变量

package a

var g int // 本包内可见
var G int // 外部import a后可见

1.2. 局部变量

func Test() {
    var a int // a在Test()内可见
    ...
    for i := 1; i < a; i++ { // i在for循环内部可见
        ...
    }
    ...
    if true {
        var b int // b在if内部可见
    }
}

1.3. 参数变量

func Test(a int) { // a在Test()方法内可见,在Test()外赋值。
    ...
}

局部变量声明后未使用会编译失败;参数变量在function内可以不使用,比如以下情况也是可以编译通过的。

func main() {
    a := 1
    Test(a) // a作为参数调用
}

func Test(a) { // a在Test()内部没有使用
    fmt.Println("test")
}
  • 对按值传参的情况,方法内对参数a的修改不影响传入前的原参数a
  • 对按址传参的情况,方法内对参数a的修改也会影响到传入前的原参数a

2. 循环并发中的变量传参问题

理解了go中的变量作用域后,我们来看看下面这段代码

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println(i)
        }()
    }

    wg.Wait()
}

输出结果如下

10
10
10
10
10
10
10
10
10
10
  • 这是因为变量i作为局部变量,同时在for循环和go协程中被引用,循环递增和打印的是同一个地址的数据
  • 实际上这个输出是无法预期的,这里都输出10是因为后台协程完成创建时,for循环已经完成了对i的递增操作
  • 如果要想让循环中的go协程如我们预期的一样输出1~10的值,要采取以下写法,使用参数变量
package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            fmt.Println(i)
        }(i)
    }

    wg.Wait()
}

3. Go的:=操作符

Go的:=操作符用于声明变量的同时给变量赋值,它也会定义新变量的作用域。以下面这段代码为例

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {
    a := 1
    fmt.Printf("a in main: %p, %d\n", &a, a)
    // a in main: 0xc420016090, 1 
    // 在main中声明了变量a,地址为0xc420016090,并给a赋值为1

    for a := 2; a < 10; a++ {
        fmt.Printf("a in for: %p, %d\n", &a, a)
        // a in for: 0xc420016098, 2
        // 在for循环中通过 := 声明了一个该循环内可见的局部变量a,地址为0xc420016098,并给a赋值为2
        /*     
            a in for: 0xc420016098, 3
            a in for: 0xc420016098, 4
            a in for: 0xc420016098, 5
            a in for: 0xc420016098, 6
            a in for: 0xc420016098, 7
            a in for: 0xc420016098, 8
            a in for: 0xc420016098, 9 
        */
        // 在for循环过程中始终是对这个局部变量a(0xc420016098)的地址做操作,对main中之前声明的a没有影响
    }

    if a == 1 {
        a := 2
        fmt.Printf("a in if: %p, %d\n", &a, a)
        // a in if: 0xc4200160f8, 2
        // 在if中通过 := 声明了一个该条件内可见的局部变量a,地址为0xc4200160f8,并给a赋值为2;对main中的a没有影响
    }

    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Printf("a in go before define: %p, %d\n", &a, a)
        // a in go before define: 0xc420016090, 1
        // main中声明的局部变量a,在go协程中同样可见

        a := 3
        fmt.Printf("a in go after define: %p, %d\n", &a, a)
        // a in go after define: 0xc420016110, 3
        // 在go协程中通过 := 声明了一个该协程内可见的局部变量a,地址为00xc42001611,并给a赋值为3,对main中的a没有影响
    }()
    wg.Wait()

    // a := 4 这种操作是不被允许的(no new variables on left side of :=)
    b, a := -3, 4
    fmt.Printf("a in main after define b %d: %p, %d\n", b, &a, a)
    // a in main after define b -3: 0xc420016090, 4
    // 在main中重新通过 := 来同时声明a, b;b被声明为一个新的变量并赋值,a仍然是原来main中的变量a(0xc420016090),不会被重新声明,只会被赋值
}

results matching ""

    No results matching ""