变量
声明、赋值变量
复制 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 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,然后在每一个有常量声明的行加一。