Go变量声明及使用的详细指南

silverwq
2022-07-04 / 0 评论 / 300 阅读 / 正在检测是否收录...

变量声明语法

介绍

  1. 变量必须先声明后使用,局部变量如果声明后不使用的话将会报错,全局变量声明后不使用将是警告
  2. 同一个作用域里,不能重复声明,局部变量和全局变量可以重复,局部变量会覆盖全局变量的值
  3. 函数外部的变量叫全局变量,对函数内部可见
  4. 函数里面的变量、for 里的变量叫做局部变量,只有该局部可见,函数里的局部变量,对 for 这类的代码块里的局部变量可见
  5. 变量都有自己的类型,只能赋值该类型的值

var 关键字声明

通常声明一个变量的语法格式如下所示:

var name type = expression

其中类型表达式可以省略其中一个,不能全部省略。

如果表达式省略,会自动推导变量的零值

// 例如
var name string // ""
var age int // 0
var isOk bool // false
  1. 数字为 0
  2. 布尔值为 false
  3. 字符串是空字符串
  4. 接口和引用类型(slice、指针、map、通道、函数)是 nil
  5. 数字和结构体这样的复合类型,是其所有元素的零值

如果省略类型,自动类型推导

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)// 返回一个文件和一个错误

需要注意变量的初始化时机:

  1. 包级别的初始化,是在 main 函数开始之前进行,应该是加载到该包的时候,就进行初始化
  2. 局部变量的初始化,和声明一样,在函数的执行期间进行

短变量声明

在函数的中的局部变量,可以使用短变量声明。

// name 的类型由expression决定
name := expression

// 例如
age := 30
i,j := 0,1 // 和var一样,也可以多变量声明
f,err := os.Open(name) // 和var变量一样,函数返回多个值

短变量短小灵活,所以在局部变量需要声明并且初始化的时候,用这个就很合适。局部变量的 var 声明的场景经常用于:

  1. 需要声明,但是不需要初始化的场景,后续可以给该变量赋值
  2. 或者初始化的值和赋值的值的类型不一样的场景,例如 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 可以作为变量名称啥的。

生命周期

  1. 包级别的变量,生命周期是整个程序的执行时间
  2. 局部变量有一个动态的生命周期,从声明,一直到到它变动不可访问,这时它占用的存储空间被回收
  3. 函数的参数,返回值也是局部变量,他们在被调用的时候创建
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"

需要注意的是:

  1. 类型必须精准匹配,赋值语句左右两边的类型必须要一致
  2. 对于 ==,或者 != 左右两边类型也必须一致
  3. nil 可以赋值给任何接口变量或者引用类型,不能用于标量
0

评论 (0)

取消