局部变量与全局变量

  1. 局部变量:函数体内、代码块内、参数(函数参数和接收器参数)和返回值变量。
    • 在函数体或代码块内声明的变量称为局部变量,它们的作用域只在代码块内。
    • 参数和返回值变量也是局部变量(接收器参数也是局部变量)。
  2. 全局变量:函数体外声明的变量。
    • 在函数体外声明的变量称为全局变量,它们的作用域是全局的。(在本包范围内)
    • 全局变量可以在整个包甚至外部包(被导出后)使用,全局变量可以在任何函数中使用。
  3. 简式变量
    • 使用 := 声明的变量,一般也是局部变量。(变量可能逃逸到堆里面)
    • 如果新局部变量Ga与同名已定义变量(全局变量Ga)不在同一个作用域中,Go语言会在此作用域新定义局部变量Ga,遮住全局变量Ga,因此尽量全局变量与局部变量不同名。

显式与隐式代码块

  1. Go语言中的标识符作用域是基于代码块的,代码块是包裹在一对花括号{}内部的声明和语句,并且是可嵌套的。
  2. 代码块如:函数的函数体、for循环的循环体等。
  3. 还有隐式的(implicit)代码块。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 使用最多的if语句类型只有if而没有else分支
if simplestms; expression {
    ... ...
}

// 在这种类型的if语句中,有两个代码块:一个隐式的代码块和一个显示的代码块
// 上面代码等价于
{ // 隐式的代码块
    simplestms
    if expression { // 显式代码块
        ... ...
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import "fmt"

func main() {
    // 这里需要注意变量声明的先后顺序
    if a := 1; false {
        fmt.Println(11)
    } else if b := 2; false {
        fmt.Println(22)
    } else if c := 3; false {
        fmt.Println(33)
    } else {
        fmt.Println(a, b, c)
    }

    // 上面代码 等价于如下代码 因此不同位置定义的变量作用域不同
    {
        aa := 1
        if false {
            fmt.Println(11)
        } else {
             bb := 2
            if false {
                fmt.Println(22)
            } else {
                 cc := 3
                if false {
                    fmt.Println(33)
                } else {
                    fmt.Println(aa, bb, cc)
                }
            }
        }
    }

    // Output:
    // 1 2 3
    // 1 2 3
}

变量被遮盖

  1. 相同名字的标识符会遮住前面的标识符,这种情况只能是在不同的块中。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package main

import (
    "fmt"
    "math/rand"
)

// Ga 全局变量
var (
    Ga int = 99
)

// v 全局常量
const (
    v int = 199
)

func main()  {
    // 覆盖外部声明变量Ga
    Ga := "string"
    fmt.Println("main函数中:", Ga)  // main函数中: string

    b := GetGa()
    fmt.Println("main函数中:", b(), b(), b(), b())

    printGVariable()

    fmt.Println(v)

    // 注意这里屏蔽了全局常量v
    v := 1
    {
        v := 2
        fmt.Println(v)
        {
            v := 3
            fmt.Println(v)
        }
    }

    fmt.Println(v)

    // Output:
    // main函数中: string
    // GetGa if中: 55
    // GetGa 循环中: 2
    // GetGa函数中: 99
    // main函数中: 100 101 102 103
    // 103
    // 199
    // 2
    // 3
    // 1
}

// GetGa 返回值为函数 func() int
func GetGa() func() int {
    rand1 := rand.Intn(10)  // 返回随机数 [0,10] 之间
    // 局部变量 Ga
    if Ga := 55; Ga + rand1 < 60 {
        fmt.Println("GetGa if中:", Ga)
    }

    // 局部变量 Ga
    for Ga := 2; ;  {
        fmt.Println("GetGa 循环中:", Ga)
        break
    }

    // 全局变量 Ga
    fmt.Println("GetGa函数中:", Ga)

    // 关于闭包的情况参看函数章节 
    return func() int {
        // 此处Ga的作用域认定上下文是99
        Ga += 1 // 全局变量Ga
        return Ga
    }
}

func printGVariable() {
    // 全局变量Ga
    fmt.Println(Ga)
}

总结

  1. 有花括号一般都存在作用域。
  2. :=标识新声明一个变量,在不同的块中可能会屏蔽所有上层代码块中的相同名称的变量(常量)。
  3. if等语句中存在隐式代码块,需要注意。
  4. 闭包函数可以理解为一个代码块,并且可以使用包含它的函数内的变量。
    • 简单声明(:=)变量只能在函数内部出现,它会覆盖函数外的同名变量。
    • 简单声明(:=)左侧只有一个变量的情况下不能重复声明一个变量,有多个变量时允许的,但这些变量中至少要有一个新的变量,经常是情况是file, err := os.Open("txt.txt")这里的err变量接收错误在相同块的多个地方被使用。

约定和惯例

  1. 可见性规则:
    • Go语言中标识符必须以一个大写字母开头(参看Unicode编码集定义的大写字母集),才能被外部包的代码使用,这称为导出。
    • 标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的。
  2. 命名规范以及语法惯例:
    • 当某个函数需要被外部包调用的时候需要以大写字母开头,并遵守Pascal命名法。(大驼峰命名法)
    • 否则就遵守(小驼峰命名法),即第一个单词的首字母小写,其余单词的首字母大写。
    • 单词之间不以空格断开或连接号(-)、下划线(_)连接,第一个单词首字母采用大写字母,后续单词的首字母也用大写字母(FirstNameLastName)。
    • 左花括号{不能独占一行,这是编辑器的强制规定,右花括号}需要独占一行。(这与Go没有;相关,编译器会加上默认的;)
    • 在定义接口名时也有惯例,一个单方法接口由方法名称加上-er后缀或者-able后缀来命名或者I开头。

注释

  1. 行注释:// 注释。
  2. 块注释:/* 注释 */