• 变量名让你能够把程序中准备使用的每一段数据都赋值给一个简短、易于记忆的名字。

变量声明及使用

变量的定义

1
2
// 关键字 标识符 [类型] = 值
var identifier [type] = value 
  1. 显示声明变量:在声明变量时指定变量的类型。
  2. 隐式声明变量:在声明变量时并未指定变量的类型,而是在编译阶段编译器根据变量值自动判断类型。
    • 整数类型(正数或负数,十进制、十六进制、八进制、二进制):编译器会全部识别为int类型,如:var a = 123
    • 字符串:编译器识别为string类型,如:var b = "a1"
    • 浮点数:编译器识别为float64类型,如:var c = -1.0
    • 复数:编译器识别为complecx128类型,如:var d = 1i
    • 字符类型:编译器识别为rune (int32) 类型,如:var e = 'a'
    • 其他类型根据情况判断,如:var f = map[string]int{}这一定是map,其他类似。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 显式声明 设置值
// 结构 data="hello world!" len=12
var name string = "hello world!"	
var sex uint8
sex = 1

// 显式声明 未设置值 默认取值 "" 
var name string	

// 隐式声明 编辑器会自动推断变量类型,而常量则是在上下文中转换
var name = "hello"	// 系统默认推导为string类型

简单声明

  1. 简单申明使用:=,当声明一个变量时当前变量在当前作用域内未被声明过时。(只能用于函数内)
1
name := "hello" // 系统默认推导string

多变量赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 1. 不同数据类型
// name和sex变量,编辑器默认推导为string类型
// age变量,编辑器默认推断为int类型
var name, sex, age = "hello", "男", 1

// 2. 相同数据类型,a、b、c变量都为bool类型
var a, b, c bool
a, b, c = false, true, false

// 3. 一起声明
var (
    aa uint     // 数值类型 默认值 0
    bb string   // 字符串类型 默认值 ""
    cc [3]int   // 数组类型 默认值 [0,0,0]
)

// 4. 或者在函数内
//  这种形式比较特殊,当前name或sex或age中只要有一个变量在【当前作用域】中未被声明过时才能使用
//  其余的全部退还为赋值操作,比较常用的是f, err := os.Open("./t.txt")这里的err被多次使用退化为赋值操作
name, sex, age := "hello", "男", 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
// 在if的作用域中时(这里的bb和err在该作用域中都没有声明过,因此默认为内部变量)
if bb, err := tt(); err != nil { // bb declared and not used
    log.Println(err, bb)
}

// 等价于

{   // if的作用域内,声明bb和err【局部】变量   
    bb, err := tt()
    if err != nil {
        log.Println(err, bb)
    }
}

// > ---------------------------------------------------------------

var bb byte = 'a' // 作用域外的 bb 变量
// 这里bb在if外,而err在if内作用域,因此if内的bb会生成if内的变量覆盖外层bb变量
if bb, err := tt(); err != nil { // bb declared and not used
    log.Println(err, bb) // 作用域内的 bb 变量
}

// 等价于

var bb byte = 'a'
{   // if的作用域内,声明bb和err【局部】变量,因为在该作用域内都没有bb和err变量
    bb, err := tt()
    if err != nil {
        log.Println(err, bb)
    }
}
  • 总结,:=会在当前作用域寻找变量,如果找到则使用它,如果未找到则声明一个新变量。
  • 注意,:=必须要求左边存在至少一个未定义的变量。:=只能在函数内使用。

变量交换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var a, b string = "world", "hello"
fmt.Println(a, b)   // world hello
// 1. 交换变量值
a, b = b, a
fmt.Println(a, b)   // hello world

// 2. 变量交换等价于
temp := a
a = b
b = temp

标识符

  1. 由字母、数字、下划线(_)组成,其中首字符不能为数字,区分大小写(同一字母的大小写代表不同的标识)。
  2. Go语言规范:
    • 标识符:命名程序实体,如变量名和类型名。
    • 标识符是一个或多个Unicode字母和数字的序列。
    • 标识符中的一个字符必须是Unicode字母(下划线_也被认为是字母)。
    • identifiter = letter {letter | unicode_digit}
      • letter = unicode_letter | _:letter 包含字母(包括除英文字母以外的字母)和下划线。
      • unicode_digit = 0 1 2 3 4 5 6 7 8 9:数字(这里也包括除阿拉伯数字以外的数字)。
  3. Go语言中,命名标识符时,通常选择英文的52个大小写字母以及数字0~9和下划线来组合成合适的标识符。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Go语言变量声明使用关键字 var 
// 下面这种形式 多用于定义全局变量,通常在函数外被定义
var (
    a int
    b bool
    str string
    浮点 float32	// 中文也可以作为变量标识符,在Unicode里的字符都能
)

var a, b int

关键字

  1. Go语言中关键字是保留字,不能作为变量标识符,关键字一共有25个。

Go 关键字
  • break:用于跳出for循环,跳出switchselect块。
    • switchselect都默认自带break如果写了break默认跳出当前块)。
  • continue:用于for循环,表示结束本次循环继续下次循环
  • default:用于switchselect结构的默认分支,当所有case都不满足时执行default分支。
  • func:用于定义函数或方法或函数类型变量。
  • interface:用于定义接口类型。
  • select:选择结构,主要用于chan类型在,注意在select不能使用关键字fallthrough
  • case:用于selectswitch关键字中,表示一个分支块。
  • defer:主要用于func关键字定义的函数和方法体中,表示当前语句在函数退出时执行。
  • go:主要用于创建一个goroutine放入P中等待被线程调用,这也是创建协程的关键。
  • map:用于定义字典类型。
  • struct:用于定义结构体类型。
  • chan:用于定义通道类型,chan的主要作用是在各个goroutine间通信的通道。
  • else:与if关键字配合使用,当上面所有条件都不满足时默认执行else分支的代码块。
  • goto:跳转语句,配合标签能任意跳转到指定代码处,多在函数内使用。
  • package:用于.go文件的包声明,多用于.go文件的第一行代码。
  • switch:选择分支结构,常与casedefaultfallthrough一起使用。
  • const:用于定义常量。
  • fallthrough:用于switchcase块中,表示继续运行下一个case块代码而不检查是否满足条件。
    • 该关键字多用于迁移其他语言代码兼用使用,正常开发中不建议使用。
  • if:分支选择结构多与elseelse if一起使用。
  • range:与for关键字一起使用,从slicemapstringarraychan等中迭代元素。
    • 注意range会拷贝slicearray需要迭代的集合副本,以便于原集合区分。
  • type:多用于定义类型关键字。
  • for:用于开始一个循环。
  • import:导入其他包文件关键字。
  • return:用于函数或方法中,表示结束当前函数并返回给定值。
    • 在没有返回值的函数中也可以使用return来返回函数。
  • var:用于定义变量的关键字。

注意事项

  1. 变量必须先定义才能使用,变量的类型和赋值的类型必须一致。
  2. 变量名不能冲突(同一个作用域内不能冲突)。
  3. 简短定义方式,只能在函数内被定义。
    • 同一个作用域中,已存在同名的变量,则之后的声明初始化,则退化为赋值操作。
    • 前提是,最少要有一个新的变量被定义,且在同一作用域。
  4. 变量定义了如不使用编译通不过。
1
2
3
4
5
6
x := 12             // 定义变量x默认推导为int类型
x, y = 1, "hello"   // 变量x重新赋值为1, 定义变量y默认推导为string类型

// 这种情况经常出现在接收错误情况下,第二个err变量退化为赋值操作
file1, err := os.Open("a.txt") // 在当前作用域声明file1和err变量
file2, err := os.Open("b.txt") // 当前作用域已有err变量直接使用,声明file2变量

作用域

  1. 顶层声明的常量(如const a int = 1),类型(如type a int),变量(如var a = 1)函数的标识符(如func name() int)的范围是包块。
  2. 导入包名称范围是包含导入声明的文件的文件块。
  3. 导入包名称范围是包含导入声明的文件的文件块。
  4. 函数内声明的常量或变量标识符的范围从声明语句的末尾开始,到最内层包含块的末尾结束。
  5. 函数内声明的类型标识符的范围从标识符开始,到最内层包含块的末尾结束。
  6. 块中声明的标识符可以在内部块中重新声明。
  7. 作用域就类似一棵树结构,而每个树枝相当于块,在树干定义的变量能被这个树干分成出的树枝或树枝的树枝使用,而在树枝中重复定义树干的变量则会屏蔽调树干定义的变量。
 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
package main

import (
    "fmt"
)

// 全局变量
var x int = 10              // ---------- 假设全局作用域就是树干编号SU01

func main() {
    fmt.Println(x)  // 10			
    
    // 局部变量
    x := 1                  // ---------- 而在main函数中的局部变量就SU01的树枝SZ01
    fmt.Println(x)  // 1

    // 局部块
    {
        fmt.Println(x)  // 1
         // 局部变量
        x := 2              // ---------- 而在SZ01定义的块及是SZ01的树枝SZ02
        fmt.Println(x)  // 2
    }

    fmt.Println(x)      // 1
    
    // Output:
    // 10
    // 1
    // 1
    // 2
    // 1
}

未使用的变量

  1. 未使用的全局变量编译不会报错。
  2. 函数内未使用定义的变量编译会报错,import导入的包,未使用编译会报错。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
    //"fmt"         // 1. 未使用的import包,编译会报错
)

var x int = 10      // 未使用的全局变量编译不会报错

func main() {
    // 2. 函数内未使用的变量编译会报错
    x := 1	// 10:2: x declared and not used
}

下划线

  1. _ :是特殊标识符,用来忽略结果。该变量是只写,并且是系统定义好的变量,可以随意使用不分作用域。

下划线在import中

  1. import:导入其他package包文件。
  2. 当前 _ 用于 import 中,仅仅是为了调用 init() 函数,所以无法通过包名来调用包中的其他函数和全局变量。
项目目录结构如下:
src目录
|
+--- main.go            
|
+--- hello目录
       |
       +--- hello.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ./src/main.go 文件
package main

import _ "./hello"      // 这里导入使用下划线

func main() {
    // hello.Print()    // 编译报错:./main.go:6:5: undefined: hello
    
    // Output:
    // imp-init() come here.
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ./src/hello/hello.go 文件
package hello

import "fmt"

func init() {
    fmt.Println("imp-init() come here.")
}

func Print() {
    fmt.Println("Hello!")
}
  1. 其他示例
1
2
3
4
5
6
7
import "database/sql"
import _ "github.com/go-sql-driver/mysql"

// 第二个import就是不直接使用mysql包
//  1. 只是执行一下这个包的init函数
//  2. 把mysql的驱动注册到sql包里
//  3. 然后程序里就可以使用sql包来访问mysql数据库了

下划线当做变量使用

  1. _ :当做变量使用,表示丢弃,是只写变量,不能读取。
  2. 下划线用作判断切片下标是否越界时,_ = a[3],有助于帮助编译器进行边界越界检查。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "os"
)

func main() {
    buf := make([]byte, 1024)
    // os.Open 打开文件 返回文件句柄 *os.File 和错误类型 error
    f, _ := os.Open("/Users/***/Desktop/text.txt")  // _表示抛弃函数返回的err
    defer f.Close()
    for {
        n, _ := f.Read(buf)
        if n == 0 {
            break    
        }
        os.Stdout.Write(buf[:n])
    }
}

下划线在编译原理中

  1. 在编译原理中语法分析中,分析const ()关键字区分组时存在这样结构。
1
2
3
4
// 取地址 表示一类分组地址
type Group struct {
	_ int
}

总结

  1. 下划线用在import中,仅仅是执行导入包的所有init()函数。
  2. 下划线在变量中,表示抛弃该值:
    • 不占用命名空间,不会分配内存。
    • 多次使用不存在重复声明问题。
    • 很多时候_用于占位,表示忽略值。
  3. 我们可以把_当做是一个系统已经声明的全局只写变量,直接使用即可,不需要像_ := a这种形式

参考

  1. 为什么指针被誉为 C 语言灵魂?,关于指针。