03、变量

Go 变量核心特性

1. 声明方式区分作用域

  1. var:全局、函数内都能用;可只声明不赋值
  2. := 短变量:仅函数内部,必须直接赋值,不能单独声明

2. 自动零值初始化

只要用 var 声明不赋值,自动赋予对应类型零值,无未定义垃圾值:

  • 数值:0 / 0.0
  • bool:false
  • string:空字符串 ""
  • 指针、切片、map、chan、接口:nil

3. 强类型,类型不可变更

变量类型一旦确定,不能存其他类型数据

a := 10
a = "abc" // 编译报错

4. 声明后必须使用

局部变量只定义不使用,直接编译失败;全局变量允许只声明不用。

匿名变量 _ 例外,专门用来丢弃返回值。

5. 命名导出特性(大小写控制访问权限)

  • 首字母小写:包内私有,其他包无法访问
  • 首字母大写:导出,跨包可读取修改

6. 作用域隔离

  • 全局变量:包级别,所有函数共享
  • 局部变量:函数 / 代码块 {} 内有效,出块销毁
  • 内层变量同名会遮蔽外层变量

7. 短变量声明特殊规则

同一行赋值至少要有一个新变量才能复用旧变量:

a, b := 1, 2
a, b := 3, 4 // 报错,无新变量
a, c := 3, 4 // 合法,c是新变量

8. 多变量并行赋值

支持一次性交换、接收多返回值,无需中间变量

a, b := 10, 20
a, b = b, a

9. 可批量块声明

var() 批量定义一组变量,整洁统一

var (
    x int
    s string
    flag bool
)

10. 区分变量与常量

变量可重复修改;const 常量定义后只读,不能修改。

Go 变量完整用法

一、变量声明三种方式

1.标准声明 var(可全局 / 函数内)

// 格式 var 变量名 类型
var a int
var b string
var c bool

// 声明并赋值
var num int = 10
var str string = "hello"

// 多变量同类型
var x, y int = 1, 2

// 块声明(批量)
var (
    age  int    = 20
    name string = "张三"
)

2.类型推导(省略类型,自动识别)

var num = 100   // 自动int
var s = "golang"// 自动string

3.短变量声明 :=(仅函数内部可用,最常用)

func main() {
    a := 10
    b := "test"
    c, d := 1, true

    // 同一行至少一个新变量才能重复赋值
    c, e := 99, "new"
}

二、变量规则

1.命名规范

  • 字母 / 数字 / 下划线,不能数字开头
  • 小驼峰:userName(包内私有);大驼峰:UserName(导出,外部包访问)

2.零值机制:Go 变量声明默认赋零值,不用手动初始化

类型零值
int0
float0.0
string""
boolfalse
指针 / 切片 /mapnil
var n int
fmt.Println(n) // 输出 0

3.变量必须使用:声明后不编译报错

4.作用域

  • 全局变量:函数外,整个包生效
  • 局部变量:函数 / 代码块{}内,块结束销毁
  • 内层变量同名会遮蔽外层变量

三、多变量交换

a, b := 10, 20
a, b = b, a // 不用中间变量交换

四、匿名变量 _(丢弃值,不占用内存)

// 函数返回两个值,只取第二个
_, res := test()

五、完整示例代码

package main

import "fmt"

// 全局变量
var global int = 666

func main() {
    // 短声明
    name := "Go语言"
    age := 18

    // var推导
    var score = 95.5

    // 多变量
    var m, n int = 1, 3
    m, n = n, m

    fmt.Println(global, name, age, score, m, n)
}

示例 1:全局变量 + 函数局部变量同名遮蔽

package main

import "fmt"

// 全局变量 num
var num int = 100

func main() {
	// 局部变量 num,同名,遮蔽全局
	num := 20
	fmt.Println(num) // 输出:20(使用局部,看不到全局)

	// 想要访问全局只能换名,无法在块内直接取外层同名变量
}

示例 2:函数局部 + 代码块内层变量遮蔽

package main

import "fmt"

func main() {
	name := "外层张三"
	if true {
		// 内层同名变量,遮蔽外层 name
		name := "内层李四"
		fmt.Println(name) // 内层李四
	}
	// 代码块结束,内层销毁,恢复外层
	fmt.Println(name) // 外层张三
}

示例 3:多层嵌套连续遮蔽

package main

import "fmt"

func main() {
	a := 1
	fmt.Println(a) // 1
	{
		a := 2
		fmt.Println(a) // 2
		{
			a := 3
			fmt.Println(a) // 3
		}
		fmt.Println(a) // 2
	}
	fmt.Println(a) // 1
}

示例 4:容易踩坑的短变量遮蔽(高频错误)

package main

import "fmt"

func main() {
	x := 10
	if true {
		// 错误写法::= 新建同名x,不是修改外层x
		x, y := 20, "test"
		fmt.Println(x) // 20(内层)
	}
	fmt.Println(x) // 10(外层没变)

	// 正确修改外层:不用 :=,直接 =
	if true {
		var y string
		x, y = 30, "ok"
		fmt.Println(x) // 30
	}
	fmt.Println(x) // 30
}

示例5:匿名变量示例 函数多返回值,丢弃其中一个

package main
import "fmt"

// 返回两个值
func getInfo() (int, string) {
    return 20, "张三"
}

func main() {
    // 只需要姓名,舍弃年龄
    _, name := getInfo()
    fmt.Println(name) // 张三

    // 只需要年龄,舍弃姓名
    age, _ := getInfo()
    fmt.Println(age) // 20
}

示例6:匿名变量 range 遍历,丢弃下标 / 值

package main
import "fmt"

func main() {
    arr := []int{10,20,30}

    // 只要值,不要索引
    for _, v := range arr {
        fmt.Println(v)  //10 20 30
    }

    // 只要索引,不要值
    for i, _ := range arr {
        fmt.Println(i)  //0 1 2
    }
}

示例7:匿名变量 map 取值,忽略是否存在标识

package main
import "fmt"

func main() {
    m := map[string]int{"a":1}
    // 只取value,不在乎key是否存在
    val, _ := m["a"]
    fmt.Println(val)  //1
}

示例8:匿名变量 函数调用只执行,不接收返回值

package main
import "fmt"

func test() int {
    fmt.Println("执行函数")
    return 99
}

func main() {
    // 接收返回值但直接丢弃
    _ = test() //执行函数
}

/*
① test()
仅单纯调用函数,完全不接收返回值,返回值直接被运行时丢弃,无赋值操作。
② _ = test()
接收函数返回值,再赋值给匿名变量 _,显式处理返回值,再主动舍弃。
推荐 _ = test() 的场景

test():无返回值 / 不在意返回 → 简写
_ = test():有返回值,主动舍弃,代码表意清晰,工程推荐

函数存在返回值(尤其包含 error),你明确选择不校验结果:
// 文件关闭、资源释放,规范显式丢弃错误
_ = resp.Body.Close()
*/