Go语言的变量定义详情
作者:苏州程序大白 发布时间:2024-04-27 15:41:03
一、变量
声明变量
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语言的变量使用域名和这几门语言是一样的
一句话: 就近原则,定义在作用域用的变量只能在函数中使用。
如果外面有定义的同名变量,则就近原则。
来源:https://blog.csdn.net/weixin_46931877/article/details/122406474
猜你喜欢
- 有时我们不希望某个类天生就非常庞大,一次性包含许多职责。那么我们就可以使用装饰着模式。 装饰着模式可以动态地给某个对象添加一些额外的职责,从
- 一、介绍对于Visual Studio Code开发工具,有一款优秀的GoLang插件,它的主页为:https://github.com/m
- 多层感知器(Multilayer Perceptron)定义了用于二分类的多层感知器模型。模型输入32维特征,经过三个全连接层,每层使用re
- 本文实例为大家分享了Mysql实现通讯录的具体代码,供大家参考,具体内容如下#-*-code:utf-8-*-import pymysqld
- 初步介绍 当然,我知道现在有成千上万个关于 用CSS处理圆角 的教程,但不管怎么说,我仍然想把这篇文章展示给您。也希望您会发现这篇文章会非常
- 最终效果展示实现思路在绘图区域插入一个嵌入图,嵌入图与原图的绘画保持一致,通过限制嵌入图的x轴和y轴的显示范围,达到缩放的效果,并在原图上绘
- 有部分老项目是在Eclipse环境开发的,最近公司要求应用瘦身,老项目也在其中。如果在 AS 下开发就不会有这样的问题,但是在 Eclips
- 我在Web服务器端安装了Web Server IIS4.0、Oracle Net8 for Client,并创建好了和Oracle8数据库的
- 在网上找了各种安装教程,都没有在python3下安装nltk,于是我自己尝试着安装,算是成功了1、首先,假设你的python3已经安装好,并
- 发现很多朋友对 CSS 的优先权不甚了解,规则很简单。需要说明的一点,如果你的样式管理需要深层判断 CSS 的优先权,更应反思自己的 CSS
- 本文实例讲述了Python面向对象之私有属性和私有方法。分享给大家供大家参考,具体如下:01. 应用场景及定义方式应用场景在实际开发中,对象
- 比如我们有一张school表,里面有一个字段county_name,现在我们要查询county_name字段中包含a-w字母和数字以外字符的
- import numpy as npimport sysdef conv_(img, conv_filter): filter_
- 下面发一个简单的在线调试服务端js代码的asp源码。并可以提示代码具体错误信息。<%@language="javascrip
- 废话不多说,直接上代码吧!# -*- coding: utf-8 -*-import cv2import numpy as np# ----
- 在 ASP 的内建对象中除了用于发送、接收和处理数据的对象外,还有一些非常实用的代表 Active Server 应用程序和单个
- 1. 将Oracle 10g client安装包copy到本地才能安装:2. 双击setup 的到:3. 稍后进入安装界面:4. 选择下一步
- Pytorch损失函数torch.nn.NLLLoss()在各种深度学习框架中,我们最常用的损失函数就是交叉熵(torch.nn.Cross
- 好东西找起来很麻烦,好用的又不太容易找到,之前看到很多用JS写的,固定漂浮这种效果拖动时难免会产生抖动,自己对CSS还是蛮有好感的,找来找去
- 本文实例讲述了Python利用神经网络解决非线性回归问题。分享给大家供大家参考,具体如下:问题描述现在我们通常使用神经网络进行分类,但是有时