变量和常量

变量

声明、赋值变量

var name type

// 一次声明多个同一类型的变量
var a, b int

// 批量声明变量
var (
    a int
    b string
    c []float32
    d func() bool
    e struct {
        x int
    }
)

// 声明变量并赋值
var a int = 10 

// 声明变量但是没有初始化的,将会赋予指定数据类型
var b int // b = 0

// 变量类型自动推断
var age = 100 // 自动推断为int
fmt.Println("type=", reflect.TypeOf(age))

// := 简短声明
age := 10 // 自动推断类型为int

// 多变量赋值,a b 变量交换
var a int = 100
var b int = 200
b, a = a, b
fmt.Println(a, b)

匿名变量_

匿名变量的特点是一个下画线“”,“”本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。例如:

func GetData() (int, int) {
    return 100, 200
}
func main(){
    a, _ := GetData()
    _, b := GetData()
    fmt.Println(a, b)
}

堆和栈空间

栈:线性表,后进先出,适合可预知大小的变量分配

堆:类似于往一个房间里摆放各种家具,家具的尺寸有大有小,分配内存时,需要找一块足够装下家具的空间再摆放家具。堆适合不可预知大小的内存分配,但是速度相对较慢,并且容易出现内存碎片

func calc(a, b int) int {
    var c int // 在栈中分配一个指定大小的内存
    c = a * b
    var x int // 在栈中分配一个指定大小的内存
    x = c * 10 
    return x
}

变量存储在堆还是栈?

Go语言在编译期会帮助开发者判断变量应该放到堆上还是栈上(C++需要开发者自行判断),他的判断规则为:

  • 在编译期间堆变量进行逃逸分析

    • 没有发生:栈

    • 发生了:堆

变量逃逸分析

package main
import "fmt"
// 本函数测试入口参数和返回值情况
func dummy(b int) int {
    // 声明一个变量c并赋值
    var c int
    c = b
    return c
}
// 空函数, 什么也不做
func void() {
}
func main() {
    // 声明a变量并打印
    var a int
    // 调用void()函数
    void()
    // 打印a变量的值和dummy()函数返回
    fmt.Println(a, dummy(0))
}

使用如下命令对上面代码进行内存分析:

# -m 内存分配分析
# -l 避免程序内联,也就是避免程序进行优化
go run -gcflags "-m -l" main.go

# command-line-arguments
./main.go:19:13: ... argument does not escape
./main.go:19:13: a escapes to heap # 变量a逃逸到了堆
./main.go:19:22: dummy(0) escapes to heap # dumy(0)的返回值逃逸到了堆

变量逃逸分析:取地址符

package main
import "fmt"
// 声明空结构体测试结构体逃逸情况
type Data struct {
}
func dummy() *Data {
	// 实例化c为Data类型
	var c Data
	//返回函数局部变量地址
	return &c
}
func main() {
	fmt.Println(dummy())
}

执行go run -gcflags "-m -l" main.go的结果:

# command-line-arguments
./main.go:8:6: moved to heap: c # 变量c在第八行的位置,因为使用&取地址符,为了保证程序的最终运行结果,所以将变量c从栈转移到了堆空间中
./main.go:13:13: ... argument does not escape
&{}

变量的作用域和生命周期

变量类型定义位置生命周期

全局变量

函数外部定义

等同于整个程序的运行周期

局部变量

函数内定义

创建变量的语句开始,到这个变量不再被使用为止

形参

函数签名定义

函数被调用的时候创建,函数调用结束后被销毁

变量逃逸会导致变量作用域的改变

常量

  • 用于存储不会发生改变的数据

  • 在编译时被创建,即使是函数内部的常量

  • 常量值只能是布尔型、数字型、字符串型

  • 定义常量的表达式必须是编译器可以进行求值的常量表达式

显示类型定义

const pi float64 = 3.14159

隐式类型定义:将会由值推断类型

const pi = 3.14159

值必须是编译期间就可以确定的

const c1 = 2/3
const c2 = getNumber() // 引发构建错误: getNumber() 用做值

批量声明常量

const (
    e  = 2.7182818
    pi = 3.1415926
)

模拟枚举:iota常量生成器

package main
import "fmt"
// 声明芯片类型(类型别名)
type ChipType int

const (
    None ChipType = iota
    CPU    // 中央处理器
    GPU    // 图形处理器
)

// 定义 ChipType 类型的方法 String(),返回值为字符串类型
// String() 是类型进行字符串输出时调用的方法
func (c ChipType) String() string {
    switch c {
    case None:
        return "None"
    case CPU:
        return "CPU"
    case GPU:
        return "GPU"
    }
    return "N/A"
}

func main() {
    // 输出CPU的值并以整型格式显示
    fmt.Printf("%s %d", CPU, CPU)
}
// 在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加一。

最后更新于