# 变量和常量

## 变量

### 声明、赋值变量

```go
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)
```

### 匿名变量\_

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

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

### 堆和栈空间

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

![image-20210910160822165](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-33ca5b5420684f37d74923589bd927f24c698739%2Fimage-20210910160822165.png?alt=media)

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

![image-20210910160832864](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-f4554e6d230a13a7c7097a1c818c3cb80b841066%2Fimage-20210910160832864.png?alt=media)

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

```

### 变量存储在堆还是栈？

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

* 在编译期间堆变量进行**逃逸分析**
  * 没有发生：栈
  * 发生了：堆

#### 变量逃逸分析

```go
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))
}
```

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

```shell
# -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)的返回值逃逸到了堆
```

#### 变量逃逸分析：取地址符

```go
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`的结果：

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

### 变量的作用域和生命周期

| 变量类型 | 定义位置   | 生命周期                   |
| ---- | ------ | ---------------------- |
| 全局变量 | 函数外部定义 | 等同于整个程序的运行周期           |
| 局部变量 | 函数内定义  | 创建变量的语句开始，到这个变量不再被使用为止 |
| 形参   | 函数签名定义 | 函数被调用的时候创建，函数调用结束后被销毁  |

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

## 常量

* 用于存储不会发生改变的数据
* 在编译时被创建，即使是函数内部的常量
* 常量值只能是布尔型、数字型、字符串型
* 定义常量的表达式必须是编译器可以进行求值的常量表达式

### 显示类型定义

```go
const pi float64 = 3.14159
```

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

```go
const pi = 3.14159
```

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

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

### 批量声明常量

```go
const (
    e  = 2.7182818
    pi = 3.1415926
)
```

### 模拟枚举：iota常量生成器

```go
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，然后在每一个有常量声明的行加一。
```
