变量

Go语言是静态强类型语言,所以变量是有明确类型的。变量实质上就是在内存中的一小块空间,用来存储特定类型的可变数据。如果没有变量我们的程序只能将数值写死都是静态的数据,无法更改,变量可以让我们进行动态的操作。在数学概念中变量表示没有固定的值,可以随时改变的数。例如:除数、减数与被减数。

类型
地址
如何声明变量
var
var age int = 1
varageint1
0falseslicemapchannil

零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在 未初始化的变量。这个特性可以简化很多代码,而且可以在没有增加额外工作的前提下确保边界条件下的合理行为

int       0
string   ""
bool     false
float     0.0
如何赋值
var age int // 声明 未赋值默认为0
age = 18    // 赋值

var name int = 20 // 声明并赋值

Go语言支持根据数据推导数据类型的方法。所以在定义的时候可以不用写数据类型,直接根据你所初始化的值,来推导出定义的数据类型。

var name = "小明"
var age = 18
简短定义

在函数内部,有一种称为简短变量声明语句的形式可用于声明和初始化局部变量。它以 名字 := 表达式 形式声明变量,变量的类型根据表达式来自动推导。

// 简短定义方式,声明并赋值
name := "小明"
age := 18
var
多变量定义
// var方式声明多变量
var a, b, c int
a = 1
b = 2
c = 3

// 也可以写在一行
var a1, a2, a3 int = 10, 20, 30

// 也可以省略类型 根据数据进行类型推导
var a1, a2, a3 = 10, 20, "小明"

// 如果是多种类型 也可以使用集合
var (
    a1 = ""
    a2 = 10
)

简短定义方式定义多个变量。需要注意,一个变量在程序中只能够定义一次,重复定义就会报错。

// 简短定义多变量
name, age := "小明", 18
println(name, age)

// 重复定义编译器就会提示错误 no new variables on left side of :=
name, age := "小红", 18

// 如果定义的左边有一个新的变量,就不会有问题了
name, age, sex :=  "小红", 18, "女"
// 左边有一个新的变量声明,对于前两个就是修改操作,后一个是声明并赋值操作。
变量使用

起初计算机最昂贵的莫过于内存啦,在使用多变量进行变量交换的时候,使用传统的方法进行变量的交换,需要多申请一块内存来交换。

// a变量的值 要和b变量交换 需要一个第三方变量来转换
var a int = 100 
var b int = 20
var c int

c = a
a = b
b = c
fmt.Println(a, b)

而在Go语言中,可以轻松实现变量自由交换。

var a int = 100
var b int = 20

b, a = a, b

fmt.Println(a, b)
匿名变量
__
package main

import (
    "fmt"
)

func main() {
    a, _ := 100, 200
    //这里第二个值200赋给了匿名变量_ 也就忽略了不需要再次打印出来
    fmt.Println(a)
}
指针
xx[i]x.f

一个指针的值是另一个变量的地址。一个指针对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址,但是对于每一个变量必然有对应的内存地址。通过指针,我们可以 直接读或更新对应变量的值,而不需要知道该变量的名字(如果变量有名字的话)。

var x intx&xx*intp*pp*pint*p
x := 1 
p := &x // p, of type *int, points to x 
fmt.Println(*p) // "1" 
*p = 2 // equivalent to x = 2 
fmt.Println(x) // "2"

对于聚合类型每个成员——比如结构体的每个字段、或者是数组的每个元素——也都是对应一个变量,因此可以被取地址。

&
nilp != nilpnil
var x, y int 
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
v指针p
var p = f() 
func f() *int { 
  v := 1 
  return &v 
}
f函数
fmt.Println(f() == f()) // "false"

因为指针包含了一个变量的地址,因此如果将指针作为参数调用函数,那将可以在函数中通过该指针来更新变量的值。例如下面这个例子就是通过指针来更新变量的值,然后返回更新后的值,可用在一个表达式中(译注:这是对C语言中 ++v 操作的模拟,这里只是为了说明指针的用法,incr函数模拟的做法并不推荐):

func incr(p *int) int {
  *p++ // 非常重要:只是增加p指向的变量的值,并不改变p指针!!! 
  return *p 
}

v := 1
incr(&v) // side effect: v is now 2 
fmt.Println(incr(&v)) // "3" (and v is 3)
*pslicemapchan
new函数
newnew(T)*T
p := new(int) // p, *int 类型, 指向匿名的 int 变量 
fmt.Println(*p) // "0" 
*p = 2 // 设置 int 匿名变量的值为 2 
fmt.Println(*p) // "2"
newnew(T)newnewInt
func newInt() *int {
  return new(int) 
}

func newInt() *int {
   var dummy int return &dummy 
}
new
p := new(int) 
q := new(int) 
fmt.Println(p == q) // "false"
struct{}[0]int
new
newnew
func delta(old, new int) int { return new - old }
newintdeltanew
变量的作用域
package
package main

import (
    "fmt"
    "os"
)

//全局变量
var name = "小明"

//主函数 程序的入口
func main() {
    fmt.Println(name) //可以访问到全局变量name

    myfunc()
}

//自定义函数 
func myfunc() {
    fmt.Println(name) //这里也可以访问到全局变量name

    age := 30
    fmt.Println(age) //age为myfunc的局部变量 只能够在函数内部使用

    if t, err := os.Open("file.txt"); err != nil {
        fmt.Print(t) //t作为局部变量 只能在if内部使用
    }
    fmt.Println(t) //在if外部使用变量则会报错 undefined: t  未声明的变量t
}
变量的生命周期

变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,在局部变量的声明周期则是动态的:从每次创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。

那么Go语言的自动圾收集器是如何知道一个变量是何时可以被回收的呢?这里我们可以避开完整的技术细节,基本的实现思路是,从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。

因为一个变量的有效周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可能在函数返回之后依然存在。

varnew
var global *int 

func f() { 
  var x int 
  x = 1
  global = &x 
}

func g() { 
  y := new(int) 
  *y = 1 
}
global*y*y*ynew

Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助,但也并不是说你完全不用考虑内存了。你虽然不需要显式地分配和释放内存,但是要编写高效的程序你依然需要了解变量的生命周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中, 特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收(从而可能影响程序的性能)。

总结一下
  • 变量在使用前必须先声明。
  • 变量名不能重复定义。
  • 如果是简短定义方式,左边至少有一个是新的变量。
  • 如果定义了变量,必须使用,否则编译无法通过。
  • 全局变量可以不使用也能编译通过,定义的全局变量和局部变量名称如果相同,则会优先使用局部变量。
  • 简短定义方式不能定义全局变量,也就是不能声明在函数外部。
  • 匿名变量不会占用内存,表示后续代码不需要再使用此变量。
  • 一个指针的值是另一个变量的地址。一个指针对应变量在内存中的存储位置。
  • 编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,这个选择并不是由用 var 还是 new 声明变量的方式决定的。
  • 逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。