变量声明语法
介绍
- 变量必须先声明后使用,局部变量如果声明后不使用的话将会报错,全局变量声明后不使用将是警告
- 同一个作用域里,不能重复声明,局部变量和全局变量可以重复,局部变量会覆盖全局变量的值
- 函数外部的变量叫全局变量,对函数内部可见
- 函数里面的变量、for 里的变量叫做局部变量,只有该局部可见,函数里的局部变量,对 for 这类的代码块里的局部变量可见
- 变量都有自己的类型,只能赋值该类型的值
var 关键字声明
通常声明一个变量的语法格式如下所示:
var name type = expression
其中类型和表达式可以省略其中一个,不能全部省略。
如果表达式省略,会自动推导变量的零值:
// 例如
var name string // ""
var age int // 0
var isOk bool // false
- 数字为 0
- 布尔值为 false
- 字符串是空字符串
- 接口和引用类型(slice、指针、map、通道、函数)是 nil
- 数字和结构体这样的复合类型,是其所有元素的零值
如果省略类型,自动类型推导:
var a = "张三" // string
var b = 2 // int
var c = false // bool
如果一次性声明多个变量,还可以将 var 关键字提取出来
var (
a string = "zhangsan" // 空字符串
b int // 0
c bool // false
d float32 // 0
)
如果变量的类型都一样,可以使用变量列表方式声明:
var a,b,c int // a,b,c都是int
如果忽略变量列表类型,还可以声明多个不同类型的变量,会自动类型推导:
var b,f,s = true,2.3,"four"// bool,float64,string
var name,_ = "xiaoming",20 // _是匿名变量,忽略不需要的值
变量初始化,还可以通过调用有多个返回值的函数进行初始化:
var f,err = os.Open(name)// 返回一个文件和一个错误
需要注意变量的初始化时机:
- 包级别的初始化,是在 main 函数开始之前进行,应该是加载到该包的时候,就进行初始化
- 局部变量的初始化,和声明一样,在函数的执行期间进行
短变量声明
在函数的中的局部变量,可以使用短变量声明。
// name 的类型由expression决定
name := expression
// 例如
age := 30
i,j := 0,1 // 和var一样,也可以多变量声明
f,err := os.Open(name) // 和var变量一样,函数返回多个值
短变量短小灵活,所以在局部变量需要声明并且初始化的时候,用这个就很合适。局部变量的 var 声明的场景经常用于:
- 需要声明,但是不需要初始化的场景,后续可以给该变量赋值
- 或者初始化的值和赋值的值的类型不一样的场景,例如
var score float = 100
,如果直接用短变量,直接会变成 int 类型
需要注意的是:短变量声明不一定需要声明 :=
左边的所有变量:
如果其中变量已经存在了,那就是赋值,例如下例中的 b 变量
a, b := 1, 2
// 声明了c,因为b变量已经存在,所以直接赋值
c, b := 3, 4
但是需要注意的是,短变量声明,至少要声明一个新的变量,否则代码编译不通过,如下所示就无法编译通过
a, b := 1, 2
// a,b都已经存在了,会报错
a, b := 3, 4
new 函数创建变量
表达式 new(T)
创建一个未命名的 T 类型的变量,并且初始化该类型的零值,并且返回其地址(类型为 *T
)
p := new(int)
fmt.Println(*p) // 指针
*p = 10
fmt.Println(*p)
使用 new 创建变量,只是语法上更加的变量,其实和对普通变量取地址的操作一样,例如以下 2 个函数效果是一样的
func newInt1() *int {
return new(int)
}
func newInt2() *int {
var name int
return &name
}
每次调用,都返回不同的唯一地址:
q := newInt1()
p := newInt2()
fmt.Println(q == p) // false
但是,两个变量的类型不携带任何信息(例如两个 new(struct{})
之间, 两个 new([0]int)
之间),并且是零值,以前的版本里,可能有相同的地址
New 是个预声明的函数,不是关键字,所以 new 可以作为变量名称啥的。
生命周期
- 包级别的变量,生命周期是整个程序的执行时间
- 局部变量有一个动态的生命周期,从声明,一直到到它变动不可访问,这时它占用的存储空间被回收
- 函数的参数,返回值也是局部变量,他们在被调用的时候创建
for i := 0; i < 10; i++ {
a := i + 2
fmt.Println(a)
}
变量 i 是在每次 for 循环的时候被创建,变量 a 是在循环体执行到的时候被创建。
什么是变量逃逸?
局部变量的指针,赋值给了全局变量,导致函数结束后,不能回收该变量。因为每次变量逃逸都需要一次额外的内存分配过程,会消耗性能。而且在长生命周期的变量中,保存了短生命周期的变量也是不合理的。
var global *int
func main() {
var x int
x = 1
global = &x // 变量作用域逃逸
}
赋值
赋值语句是用来修改变量的值,用一个 =
号就可以了,左边是变量,右边是表达
x = 1 //有名称的变量
*p = true //间接变量
person.name = "张三" //结构体成员
count[x] = 10 //数组或者slice、map的元素
每一个算数运算和二进制位操作符,都有一个对对应的赋值操作符,例如:
a += 1 //有名称的变量
b -= 1 //有名称的变量
变量还可以通过 ++
或 --
语句来递增和递减:
v := 1
v++ // v = v + 1,变成2
v-- // v= v - 1 等于1
多重赋值
允许几个变量一次性被赋值
x,y = y,x // 交换变量的值
多重赋值可以使得一个普通的赋值序列变得紧凑,但是,如果表达式比较复杂,则避免使用多重赋值,一系列独立的语句可读性更好。
i,j,k = 2,3,4
如果函数有多个返回值值的时候,也可以使用多重赋值,但是左边变量的数量,需要和函数的返回值一样多
// 返回额外的err对象,来指定错误情况
f,err = os.Open("foot.txt")
以下三个操作符也会返回额外的变量类型,不过这里 ok,不一定需要
// 返回额外的布尔类型ok,来指示一些错误情况
v,ok = m[key] // map查询
v,ok = x.(T) // 类型断言,必须在空接口上作断言
v,ok = <-ch // 通道接受
隐式赋值
一个函数调用,隐式的将参数的值,赋予参数变量。
复合类型的字面量表达式,如下切片就是这样(当然 map,channel 也是这样):
color := []string{"yellow","red"}
隐式的给每一个元素赋值
color[0] = "yellow"
color[1] = "red"
需要注意的是:
- 类型必须精准匹配,赋值语句左右两边的类型必须要一致
- 对于
==
,或者!=
左右两边类型也必须一致 - nil 可以赋值给任何接口变量或者引用类型,不能用于标量
评论 (0)