Go 语言之变量

变量就是值的引用,是实现程序逻辑的基石之一,Go是一种静态类型语言,因此声明变量时必须显示或隐式的指定其类型。

常见变量的数据类型有: 整型、浮点型、布尔型、结构体等。

变量

Go 语言的每一个变量都拥有自己的类型,必须经过声明才能开始用。

一、声明变量:

下面先通过一段代码了解变量声明的基本样式,如下:

var a int
var b string
var c [] float32
var d func() bool
var e struct{
 x int
 }

代码说明如下:

  1. 第1 行,声明一个整型类型的变量,可以保存整数数值。
  2. 第2 行,声明一个字符串类型的变量。
  3. 第3 行,声明一个32 位浮点切片类型的变量,浮点切片表示由多个浮点类型组成的数据结构
  4. 第4 行,声明一个返回值为布尔类型的函数变量,这种形式一般用于回调函数,即将函数以变量的形式保存下来,在需要的时候重新调用这个函数
  5. 第5 行,声明一个结构体类型的变量,这个结构体拥有一个整型的x 宇段。

上面代码的共性是,以var 关键宇开头,要声明的变量名放在中间,而将其类型放在后面

变量的声明有几种形式,通过下面几节进行整理归纳:

1、标准格式:

Go 语言的变量声明格式为:

var 变量名 变量类型

注意:变量声明以关键字var 开头,后置变量类型,行尾无须分号。

例如:声明一个字符串类型的变量,执行命令如下:

var s string = “hello”

如果要声明多个相同类型的变量,可以执行命令如下:

var s,t string = “hello” ,”world”

2、批量格式:

觉得每行都用var 声明变量比较烦琐?没关系, 还有一种为懒人提供的定义变量的方法:

var (
     a int
     b string
     c [] float32
     d func () bool
     e struct {
     x int
     }
}

使用关键字var 和括号,可以将一组变量定义放在一起

注:值类型的变量通过var声明后即可直接赋值,但是指针类型的不可以,声明后需要初始化才可以赋值,如图:

指针类型的变量如果没有分配内存,就默认是零值 nil,它没有指向的内存,所以无法使用,因此上图中如果不用new来初始化而是直接赋值,会报错

二、初始化变量:

Go 语言在声明变量时, 自动对变量对应的内存区域进行初始化操作。每个变量会初始化其类型的默认值,例如:

  • 整型和浮点型变量的默认值为0
  • 字符串变量的默认值为空字符串
  • 布尔型变量默认为bool
  • 切片、函数、指针变量的默认为nil

当然,也可以在变量声明时赋予变量一个初始值,方法如下:

1、标准格式:

var 变量名 类型=表达式

例如:游戏中,玩家的血量初始值为100 。可以这样写:var hp int = 100

说明:hp 为变量名,类型为int, hp 的初始值为100

2、编译器推导类型的格式:

在标准格式的基础上,将int 省略后,编译器会尝试根据等号右边的表达式推导hp 变量的类型。

var hp = 100 //等号右边的部分在编译原理里被称做“右值”

例如:下面是编译器根据右值推导变量类型完成初始化的例子

var attack = 40

var damageRate float32 = 0 . 17

第一个右值为整数,变量attack类型为int

第二个右值为0.17,指定了类型为float32,如果不指定damageRate 变量的类型, Go 语言编译器会将类型推导为float64 。由于这个例子中不需要float64 的精度,所以强制指定类型为float32

3、短变量声明井初始化

var 的变量声明还有一种更为精简的写法,例如:

hp := 100

这是Go 语言的推导声明写法,编译器会自动根据右值类型推断出左值的对应类型。

由于使用了”:=”,而不是赋值的”=”, 因此推导声明写法的左值变量必须是没有定义过的变量。若定义过,将会发生编译错误(no new variables on left side of :=)

注意:提示, 在”:=”的左边没有新变量出现,意思就是”:=”的左边变量已经被声明了。在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即使其他变量名可能是重复声明的,编译器也不会报错,例如:conn, err : =net . Dial (” tcp ”,”127.0 . 0 . 1 : 8080 ”),conn2 , err : =net.Dial ( ” tcp ”,” 127.0 . 0 . 1 : 8080” )上面的代码片段,编译器不会报err 重复定义。

使用简单变量声明时,编译器会推断变量的类型,因此无须显式的指定变量的类型,注意:简短变量只能在函数中使用,在函数外使用将会报错

三、多个变量同时赋值:

使用Go语言多重赋值的特性,可以轻松完成变量交换的任务,如下:

var a int = 100
var b int = 200
a,b = b,a
fmt.Println(a,b)

多重赋值时, 变量的左值和右值按从左到右的顺序赋值

多重赋值在Go 语言的错误处理和函数返回值中会大量地使用

四、匿名变量:

在使用多重赋值时,如果不需要在左值中接收变量, 可以使用匿名变量。

医名变量的标识是一个”_”下画线,使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。

例如:下面通过匿名变量进行赋值,如下:

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

GetData()是一个函数,拥有两个整型返回值。每次调用将会返回100 和200 两个数值。

匿名变量不占用命名空间, 不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用

如果函数返回一个值,并且用匿名变量来接受,那么直接使用=即可,不用 :=,如图:

五、变量作用域:

  • 在Go语言中,一对大括号{}表示一个块
  • 对于在大括号中声明的变量,可以在响应块的任何地方访问
  • 大括号内的大括号定义了一个新的块—内部块
  • 在内部块中,可以访问外部块中声明的变量
  • 在外部块中,不能访问在内部块中声明的变量

简而言之,每个内部块都可以访问其外部块,但是外部块不能访问内部块。

例如:下面的例子演示了在内部块中调用外部块中的变量,如图:

上图中的str变量不是在大括号中声明的,但是可以在任何的内部块中访问,因为Go语言将文件页视为块,所以在第一级大括号外声明的变量可在所有块中访问

代码执行结果如下:

注意:简短声明的方式只能在函数中使用,切记切记!!!!!!

注意:变量声明后,就不能再次声明,可以重新赋值,但不能重新声明,否则将编译错误

声明变量的风格:遵循的约定如下:在函数内使用简短变量声明,在函数外省略类型,如图:

六、使用指针:

指针是另一个与变量相关且必须掌握的要素,在Go语言中声明变量时,将在计算机内存中给它分配一个位置,以便能够存储、修改和获取变量的值,要获取变量在计算机内存中的地址,可在变量名前加上&字符

下面还是以上面的为例子,在变量名前加&字符,打印变量地址,如图:

代码执行结果如下,后面的表示内存地址,如图:

下面的例子表示先声明一个变量,将变量的值传给一个函数,并打印传递前后两次的内存地址,如图:

代码执行后的结果如下:

从上图中可以看出,将变量传递给函数的时候,会分配新的内存地址并将变量的值复制到其中,这样将有两个变量实例,位于不同的内存单元中,一般而言,这不可取,因为这将占用更多的内存,同时存在变量的多个副本,容易引入bug,考虑到这一点,Go语言引入了指针

指针是Go语言中的一种类型,指向变量所在的内存单元,要声明指针,可在变量名前加上星号字符

将上面例子稍加修改如下:

代码执行结果如下:

从上图中可以看出,传递变量给函数后,通过指针,最后指向了同一内存地址

说明如下:

  • 将传递给test()函数的值从i改为&i,表示引用的变量i的值所在的内存地址
  • 将函数test()的第一个参数的类型从int改成了*int,加星号表示参数的类型为指向整数的指针,而不是整数
  • 在函数中打印变量时,不需要使用和(&)号,因为它本来就是指针

如果要使用指针指向的变量的值,而不是其内存地址,此时可以在指针变量前加上星号,如图:

七:声明常量:

1、常量是指在整个程序生命周期内都不变的值,常量初始化后,可以引用它,但是不能修改它,如图:

上图中声明常量test,并赋值为hello world,在main()函数中打印常量

多个常量可以一起声明:

const (
    a = 1
    b = 2
)

同时声明多个常量时,如果省略了值则表示和上面一行的值相同,如图:

const(
   a = 100
   b
   c
)

#上面中 a、b、c的值都是100

2、iota :

iota是go语言的常量计数器,只能在常量表达式在中使用,iota在const关键字出现时将被重置为0,const每新增一行常量声明将使用iota计数一次,使用iota可以简化定义,在定义枚举的时候很有用,如下:

iota常见实例:

通过”_” 跳过某些值:

打印结果为:

声明中间插队:

注意:c的值如果不重新赋值为iota,那么从c到后面的值都是100

执行结果如下:

Go语言中的零值(默认值)

类型零值
布尔类型(Boolean)false
整型(Integer)0
浮点型(Float)0.0
字符串(String)” “
指针(Pointer)nil
函数(Function)nil
接口(Interface)nil
切片(Slice)nil
通道(Channel)nil
映射(Map)nil

八、变量生命期一一变量能略使用的代码范围

1、什么是栈:

找C Stack )是一种拥有特殊规则的线性表数据结构。

战只允许往线性表的一端放入数据,之后在这一端取出数据,按照后进先出(LIFO,Last InFirst Out )的顺序

往栈中放入元素的过程叫做入栈。入栈会增加栈的元素数量,最后放入的元素总是位于栈的顶部,最先放入的元素总是位于栈的底部。

从栈中取出元素时,只能从栈顶部取出。取出元素后,拢的数量会变少。最先放入的元素总是最后被取出,最后放入的元素总是最先被取出。不允许从栈底获取数据,也不允许对栈成员(除栈顶外的成员)进行任何查看和修改操作

变量和栈有什么关系:

栈可用于内存分配, 栈的分配和回收速度非常快。下面代码展示 栈 在内存分配上的作用,代码如下:

上面的代码在没有任何优化情况下,会进行c 和d变量的分配过程。Go 语言默认情况下会将c 和d分配在栈上,这两个变量在 abc()函数退出时就不再使用,函数结束时,保存c 和d的校内存再出 栈 释放内存,整个分配内存的过程通过栈的分配和回收都会非常迅速

2、什么是堆:

堆在内存分配中类似于往一个房间里摆放各种家具,家具的尺寸有大有小。分配内存时,需要找一块足够装下家具的空间再摆放家具。经过反复摆放和腾空家具后,房间里的空间会变得乱七八糟,此时再往空间里摆放家具会存在虽然有足够的空间,但各空间分布在不同的区域,无法有一段连续的空间来摆放家具的问题。此时,内存分配器就需要对这些空间进行调整优化,如图:

堆分配内存和 栈 分配内存相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。

3、变量逃逸—自动决定变量分配方式,调高运行效率

堆和栈各有优缺点,该怎么在编程中处理这个问题呢?Go 语言将这个过程整合到编译器中,命名为”变量逃逸分析”,这个技术由编译器分析代码的特征和代码生命期,决定应该用堆还是栈进行内存分配,即使程序员使用Go 语言完成了整个工程后也不会感受到这个过程。

4、原则:

在使用Go 语言进行编程时·, Go语言的设计者不希望开发者将精力放在内存应该分配在栈还是堆上的问题,编译器会自动帮助开发者完成这个纠结的选择,但变量逃逸分析也是需要了解的一个编译器技术。

编译器觉得变量应该分配在堆和技上的原则是:

  • 变量是否被取地址。
  • 变量是否发生逃逸。

标签