Go语言的变量定义详情

目录
  • 一、变量
    • 声明变量
  • 二、短声明
    • 指针
  • 三、new函数
  • 四、变量的生命期
  • 五、变量的作用域

一、变量

声明变量

go定义变量的方式和c,c++,java语法不一样,如下:

var 变量名 类型, 比如 : var a int

var在前,变量名在中间,类型在后面

我们以代码举例,如下:

var i int = 0
var i = 0
var i int

以上三个表达式均是合法的,第三个表达式会将i初始化为int类型的零值,0;如果i是bool类型,则为false;i是float64类型,则为0.0;i为string类型,则为"";i为interface类型,则为nil;i为引用类型,则为nil;如果i是struct,则是将struct中所有的字段初始化为对应类型的零值。

这种初始化机制可以保证任何一个变量都是有初始值的,这样在做边界条件条件检查时不需要担心值未初始化,可以避免一些潜在的错误,相信C和C++程序员的体会更加深入。

fmt.Println(s) // ""

这里的s是可以正常打印的,而不是导致某种不可预期的错误。

可以在同一条语句中声明多个变量:

var b, f, s = true, 2.3, "four"// bool, float64, string

包内可见的变量在main函数开始执行之前初始化,本地变量在函数执行到对应的声明语句时初始化。

变量也可以通过函数的返回值来初始化:

var f, err = os.Open(name) // os.Open returns a file and an error

二、短声明

在函数内部,有一种短声明的方式,形式是name := expression,这里,变量的类型是由编译器自动确定的。

anim := gif.GIF{LoopCount: nframes}
freq := rand.Float64() * 3.0
t := 0.0

因为这种形式非常简洁,因此在函数内部(本地变量)大量使用。如果需要为本地变量显式的指定类型,或者先声明一个变量后面再赋值,那么应该使用var:

i := 100// an int
var boiling float64 = 100// a float64
var names []string
var err error
var p Point

就像var声明一样,短声明也可以并行初始化。

i, j := 0, 1

要谨记的是,:=是一个声明,=是一个赋值,因此在需要赋值的场所不能使用 :=

var i int
i := 10//panic : no new variables on left side of :=

可以利用并行赋值的特性来进行值交换:

i, j = j, i // swap values of i and j

有一点需要注意的:短声明左边的变量未必都是新声明的!:

//...
out, err := os.Create(path2) 

/因为err已经声明过,因此这里只新声明一个变量out。

虽然这里使用:=,但是err是在上个语句声明的,这里仅仅是赋值/

而且,短声明的左边变量必须有一个是新的,若都是之前声明过的,会报编译错误:

f, err := os.Open(infile)
// ...
f, err := os.Create(outfile) // compile error: no new variables

正确的写法是这样的:

f, err := os.Open(infile)
// ...
f, err = os.Create(outfile) // compile ok

指针

值变量的存储地址存的是一个值。例如 x = 1 就是在x的存储地址存上1这个值; x[i] = 1代表在数组第i + 1的位置存上1这个值;x.f = 1,代表struct x中的f字段所在的存储位置存上1这个值。

指针值是一个变量的存储地址。注意:不是所有的值都有地址,但是变量肯定是有地址的!这个概念一定要搞清楚! 通过指针,我们可以间接的去访问一个变量,甚至不需要知道变量名。

var x int = 10
p := &x 
/*&x是取x变量的地址,因此p是一个指针,指向x变量.
这里p的类型是*int,意思是指向int的指针*/
fmt.Printf("addr:%p, value:%d\n", p, *p)
//output: addr:0xc820074d98, value:10
*p = 20// 更新x到20

上面的代码中,我们说p指向x或者p包含了x的地址。p的意思是从p地址中取出对应的变量值,因此p就是x的值:10。因为p是一个变量,因此可以作为左值使用,p = 20,这时代表p地址中的值更新为20,因此这里x会变为20。下面的例子也充分解释了指针的作用:

x := 1
p := &x         // p类型:*int,指向x
fmt.Println(*p) // "1"
*p = 2// 等价于x = 2
fmt.Println(x)  // "2"

聚合类型struct或者array中的元素也是变量,因此是可以通过寻址(&)获取指针的。

若一个值是变量,那么它就是可寻址的,因此若一个表达式可以作为一个变量使用时,意味着该表达式可以寻址,也可以被使用&操作符。

`指针的零值是nil(记得之前的内容吗?go的所有类型在没有初始值时都默认会初始化为该类型的零值)。若p指向一个变量,那么p != nil 就是true,因为p会被赋予变量的地址。指针是可以比较的,两个指针相等意味着两个指针都指向同一个变量或者两个指针都为nil。

var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"    

在函数中返回一个本地变量的地址是很安全的。例如以下代码,本地变量v是在f中创建的,从f返回后依然会存在,指针p仍然会去引用v:

var p = f()
fmt.Println(*p) //output:1
func f() *int{
    v := 1
return &v
}

每次调用f都会返回不同的指针,因为f会创建新的本地变量并返回指针:

fmt.Println(f() == f()) // "false"
把变量的指针传递给函数,即可以在函数内部修改该变量(go的函数默认是值传递,所有的值类型都会进行内存拷贝)。

func incr(p *int)int{
        *p++ // increments what p points to; does not change p
return *p
}
v := 1
incr(&v)              // v现在是2
fmt.Println(incr(&v)) // "3" (and v is 3)

指针在flag包中是很重要的。flag会读取程序命令行的参数,然后设置程序内部的变量。下面的例子中,我们有两个命令行参数:-n,不打印换行符;-s sep,使用自定义的字符串分隔符进行打印。

package main
import(
"flag"
"fmt"
"strings"
)
var n = flag.Bool("n", false, "忽略换行符")
var sep = flag.String("s", " ", "分隔符")
func main(){
    flag.Parse()
    fmt.Print(strings.Join(flag.Args(), *sep))
if !*n {
          fmt.Println()
    }
}

flag.Bool会创建一个bool类型的flag变量,flag.Bool有三个参数:flag的名字,命令行没有传值时默认的flag值(false),flag的描述信息( 当用户传入一个非法的参数或者-h、 -help时,会打印该描述信息)。变量sep和n 都是flag变量的指针,因此要通过sep和n来访问原始的flag值。

当程序运行时,在使用flag值之前首先要调用flag.Parse。非flag参数可以通过args := flag.Args()来访问,args的类型是[]string(见后续章节)。如果flag.Parse报错,那么程序就会打印出一个使用说明,然后调用os.Exit(2)来结束。

让我们来测试一下上面的程序:

$ go build gopl.io/ch2/echo4
$ ./echo4 a bc def
a bc def
$ ./echo4 -s / a bc def
a/bc/def
$ ./echo4 -n a bc def
a bc def$
$ ./echo4 -help
Usage of ./echo4:
     -n    忽略换行符
     -s string
     分隔符 (default" ")

三、new函数

还可以通过内建(built-in)函数new来创建变量。new(T)会初始化一个类型为T的变量,值为类型T对应的零值,然后返回一个指针:*T。

p := new(int)   // p,类型*int,指向一个没有命名的int变量
fmt.Println(*p) // "0"
*p = 2
fmt.Println(*p) // "2"

这种声明方式和普通的var声明再取地址没有区别。如果不想绞尽脑汁的去思考一个变量名,那么就可以使用new:

func newInt() *int{            func newInt() *int{
returnnew(int)                 var dummy int
}                                   return &dummy
                                }

每次调用new都会返回一个唯一的地址:

p := new(int)
q := new(int)
fmt.Println(p == q) // "false"

但是有一个例外:比如struct{}或[0]int,这种类型的变量没有包含什么信息且为零值,可能会有同样的地址。

new函数相对来说是较少使用的,因为最常用的未具名变量是struct类型,对于这种类型而言,相应的struct语法更灵活也更适合。

因为new是预定义的函数名(参见上一节的保留字),不是语言关键字,因此可以用new做函数内的变量名:

func delta(old, new int)int{ returnnew - old }

当然,在delta函数内部,是不能再使用new函数了!

四、变量的生命期

变量的生命期就是程序执行期间变量的存活期。包内可见的变量的生命期是固定的:程序的整个执行期。作为对比,本地变量的生命期是动态的:每次声明语句执行时,都会创建一个新的变量实例,变量的生命期就是从创建到不可到达状态(见下文)之间的时间段,生命期结束后变量可能会被回收。

函数的参数和本地变量都是动态生命期,在每次函数调用和执行的时候,这些变量会被创建。例如下面的代码:

for t := 0.0; t < cycles*2*math.Pi; t += res {
    x := math.Sin(t)
    y := math.Sin(t*freq + phase)
    img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
          blackIndex)
    }

每次for循环执行时,t,x,y都会被重新创建。

那么GC是怎么判断一个变量应该被回收呢?完整的机制是很复杂的,但是基本的思想是寻找每个变量的过程路径,如果找不到这样的路径,那么变量就是不可到达的,因此就是可以被回收的。

一个变量的生命期只取决于变量是否是可到达的,因此一个本地变量可以在循环之外依然存活,甚至可以在函数return后依然存活。编译器会选择在堆上或者栈上去分配变量,但是请记住:编译器的选择并不是由var或者new这样的声明方式决定的。

var global *int
func f() {                      func g(){
    var x int                       y := new(int)
    x = 1                           *y = 1
    global = &x                 }
}

上面代码中,x是在堆上分配的变量,因为在f返回后,x也是可到达的(global指针)。这里x是f的本地变量,因此,这里我们说x从f中逃逸了。相反,当g返回时,变量y就变为不可到达的,然后会被垃圾回收。因为y没有从g中逃逸,所以编译器将*y分配在栈上(即使是用new分配的)。在绝大多数情况下,我们都不用担心变量逃逸的问题,只要在做性能优化时意识到:每一个逃逸的变量都需要进行一次额外的内存分配。

尽管自动GC对于写现代化的程序来说,是一个巨大的帮助,但是我们也要理解go语言的内存机制。程序不需要显式的内存分配或者回收,可是为了写出高效的程序,我们仍然需要清楚的知道变量的生命期。例如,在长期对象(特别是全局变量)中持有指向短期对象的指针,会阻止GC回收这些短期对象,因为在这种情况下,短期对象是可以到达的!!

五、变量的作用域

如果你有c,c++,java的经验,那么go语言的变量使用域名和这几门语言是一样的

一句话: 就近原则,定义在作用域用的变量只能在函数中使用。

如果外面有定义的同名变量,则就近原则。

到此这篇关于Go语言的变量定义详情的文章就介绍到这了,更多相关Go变量定义内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang语言的多种变量声明方式与使用场景详解

    目录 01介绍 02变量声明方式 标准声明变量 不显式赋初始值声明变量 省略类型声明变量 短变量声明 显式类型转换 变量列表声明 变量声明块 03使用场景 包级变量 全局变量 局部变量 04注意事项: 05总结 01介绍 在程序设计中,编译器必须将代表数据的变量名称替换成该数据所在的内存地址.变量的名称.类型及内存地址通常会维持固定,但该内存地址所存储的数据在程序执行期间则可能会改变. Golang 语言编译器需要先明确变量的内存边界,才可以使用变量.通过声明变量使用的类型,编译器可以明确变量的

  • Go获取与设置环境变量的方法详解

    目录 前言 01 从安装 Go 说起 02 Go 如何使用环境变量 03 小结 前言 今天的文章比较基础,但却是必须掌握的,而且本文有些内容,也许你之前没想过.希望这篇文章能够让你理解环境变量并掌握 Go 环境变量相关操作. 01 从安装 Go 说起 其实不止是安装 Go,其他语言一本也会有类似的问题.一般来说,安装完 Go 后,会建议将 go 可执行程序配置到 PATH 环境变量中. 比如我本地的 PATH 环境变量的值: $ echo $PATH /Users/xuxinhua/.go/bi

  • Go中变量命名规则与实例

    目录 前言 命名习惯很重要 良好命名喜欢的特质 经验法则 大小写混用的变量名 变量名避免冗余 函数参数的命名 返回值的命名 方法 Receiver 的命名 导出 package 级别变量命名 接口类型 error 命名 Packages 名 结论 总结 前言 来自 Google 的 Andrew Gerrand 曾经关于 Go 中的参数命名规范进行了分享,slides 为 https://talks.golang.org/2014/names.slide 命名习惯很重要 良好的可读性是高质量代码

  • go语言变量定义用法实例

    本文实例讲述了go语言变量定义用法.分享给大家供大家参考.具体如下: var语句定义了一个变量的列表:跟函数的参数列表一样,类型在后面. 复制代码 代码如下: package main import "fmt" var x, y, z int var c, python, java bool func main() {     fmt.Println(x, y, z, c, python, java) } 变量定义可以包含初始值,每个变量对应一个. 如果初始化是使用表达式,则可以省略类

  • Go语言基础变量的声明及初始化示例详解

    目录 一.概述 二.声明变量 三.编译器推导类型的格式[一定要赋值] 四.短变量声明并初始化 五.匿名变量--没有名字的变量 六.注意 七.案例 一.概述 变量的功能是存储用户的数据 二.声明变量 Go语言的每一个变量都拥有自己的类型,必须经过声明才能开始用 变量的声明格式: var <变量名称> [变量类型] var a int //声明一个整型类型的变量,可以保存整数数值 var b string //声明一个字符串类型的变量 var c float32 //声明一个32位浮点切片类型的变

  • Golang共享变量如何解决问题

    目录 1. 什么是竞态 2. 如何消除竞态 3. Go 提供的并发工具 3.1 互斥锁 3.2 读写互斥锁 3.3 Once 3.4 竞态检测器 4. 小结 在之前的文章中,我们详细说了 Go 语言中 goroutine + channel 通过通信的方式来共享内存,从而实现并发编程. 但同时 Go 也提供了传统通过共享变量,也就是共享内存的方式来实现并发.这篇文章会介绍 Go 提供的相关机制. 1. 什么是竞态 在一个 Go 程序运行起来之后,会有很多的 goroutine 同时运行,每个 g

  • Go语言变量与基础数据类型详情

    目录 一.基础介绍 1.Go 的特性 2.Go 的常用命令 3.Hello Word 二.变量 1.定义方式 三.常量 四.基础数据类型 1.数字 2.浮点型(小数,32/64表示小数点后长度多少位) 3.布尔(Bool) 4.字符串 一.基础介绍 Go 是静态(编译型)语言,是区别于解释型语言的弱类型语言(静态:类型固定,强类型:不同类型不允许直接运算) 例如 python 就是动态强类型语言 1.Go 的特性 跨平台的编译型语言,交叉编译 管道(channel),切片(slice),并发(r

  • 聊聊Golang的语言结构和变量问题

    一.语言结构和注意事项 package main // 声明 main 包,表明当前是一个可执行程序 import "fmt" // 导入内置的fmt包 func main(){ // main函数,是程序执行的入口 fmt.Println("Hello World!") // 在终端打印 Hello World! } 需要注意的是包裹代码的左大括号{在Golang中不能单独成为一行,例如下面这样: func main() { // 会报错 fmt.Println(

  • Golang开发中如何解决共享变量问题

    目录 1.什么是竞态 2.如何消除竞态 3.Go 提供的并发工具 3.1 互斥锁 3.2 读写互斥锁 3.3 Once 3.4 竞态检测器 4.小结 Go 语言中 goroutine + channel 通过通信的方式来共享内存,从而实现并发编程. 但同时 Go 也提供了传统通过共享变量,也就是共享内存的方式来实现并发.这篇文章会介绍 Go提供的相关机制. 1.什么是竞态 在一个 Go 程序运行起来之后,会有很多的 goroutine 同时运行,每个 goroutine 中代码的执行是顺序的,如

  • Go语言的变量定义详情

    目录 一.变量 声明变量 二.短声明 指针 三.new函数 四.变量的生命期 五.变量的作用域 一.变量 声明变量 go定义变量的方式和c,c++,java语法不一样,如下: var 变量名 类型, 比如 : var a int var在前,变量名在中间,类型在后面 我们以代码举例,如下: var i int = 0 var i = 0 var i int 以上三个表达式均是合法的,第三个表达式会将i初始化为int类型的零值,0:如果i是bool类型,则为false:i是float64类型,则为

  • go语言的变量定义示例详解

    目录 前言 定义单个变量 定义多个变量 定义相同类型的多个变量 变量的初始化 变量类型的省略 var关键字的省略(简短声明) 全局变量与局部变量 特别的变量名 未使用变量的限制 常量 前言 特别说明: 本文只适合新手学习 这篇文章带我们入门go语言的定义变量的方式,其实和javascript很相似,所以特意总结在此. 在go语言中,也有变量和常量两种,首先我们来看变量的定义,定义变量我们分为定义单个变量和多个变量. 本文知识点总结如下图所示: 定义单个变量 在定义单个变量中,我们通过var关键字

  • C语言入门篇--变量[定义,初始化赋值,外部声明]

    1.变量 变量即变化的量. C语言中用 常量 来表示不变的值,用 变量 表示变化的值. eg:输出26个字母 #include <stdio.h> int main() { char c = 'A';//定义一个为char类型的变量c,并对其进行初始化 for (; c <='Z'; c++) { printf("%c", c); } printf("\n"); return 0; } 2.变量的定义 int temp; int age = 21;

  • C语言队列和应用详情

    目录 1.队列的原理 2.队列的作用 3.队列程序设计思路 4.入列 5.出列 6.掌握队列程序编写 7.队列的应用 1.队列的原理 队列原理其实就像一个管道,如果我们不断往管道里面塞乒乓球,每个乒乓球在管道里就会排列一条队形.先进去的乒乓球就会先出来,这个就是队列的先进先出的规则. 球从左边进去,进去的动作就是入列,然后进去的球在管道里排成一个队列,这个叫队列缓存,说白了就是数组,那么这里存了5个球就相当于是buff[5];这样的意思,最右边出来的1号球就是最早进去的球,出来的这个动作叫出列,

  • Go语言的变量、函数、Socks5代理服务器示例详解

    Go语言中变量的声明和JavaScript很像,使用var关键字,变量的声明.定义有好几种形式 1. 变量和常量 // 声明并初始化一个变量 var m int = 10 // 声明初始化多个变量 var i, j, k = 1, 2, 3 // 多个变量的声明(注意小括号的使用) var( no int name string ) // 声明时不指明类型,通过初始化值来推导 var b = true // bool型 // := 隐含声明变量并赋值 str := "mimvp.com"

  • C 语言指针变量详细介绍

    数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量. 在C语言中,允许用一个变量来存放指针,这种变量称为指针变量.指针变量的值就是某份数据的地址,这样的一份数据可以是数组.字符串.函数,也可以是另外的一个普通变量或指针变量. 现在假设有一个 char 类型的变量 c,它存储了字符 'K'(ASCII码为十进制数 75),并占用了地址为 0X11A 的内存(地址通常用十六进制表示).另外有一个指针变量 p,它的值为 0X11A,正好等于变量 c 的地址,这种情况我

  • Go语言中常量定义方法实例分析

    本文实例讲述了Go语言中常量定义方法.分享给大家供大家参考.具体分析如下: 常量的定义与变量类似,只不过使用 const 关键字. 常量可以是字符.字符串.布尔或数字类型的值. 复制代码 代码如下: package main import "fmt" const Pi = 3.14 func main() {     const World = "世界"     fmt.Println("Hello", World)     fmt.Printl

  • C语言 指针变量作为函数参数详解

    在C语言中,函数的参数不仅可以是整数.小数.字符等具体的数据,还可以是指向它们的指针.用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁. 像数组.字符串.动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些数据集合. 有的时候,对于整数.小数.字符等基本类型数据的操作也必须要借助指针,一个典型的例子就是交换两个变量的值. 有些初学者可能会使用

  • C语言 常量,变量及数据详细介绍

    一.数据 图片文字等都是数据,在计算机中以0和1存储. (一)分类 数据分为静态数据和动态数据. ①. 静态数据:一些永久性的的数据,一般存储在硬盘中,只要硬盘没坏数据都是存在的.一般以文件的形式存储在硬盘上,电脑关机重启后依然存在. ②. 动态数据:程序运行过程中,动态产生的的临时数据,一般存储在内存中,内存的存储空间一般较小,计算机关闭后这些数据就会被清除.软件或者电脑关闭则这些临时数据会被清除. ③. 静态数据和动态数据可以转换. ④. 注意:为什么不把动态数据存放到硬盘?因为直接访问内存

随机推荐