上一篇 Go圣经-学习笔记入门bufio.Writergolang
它包括四种类型:const常量,type类型,func函数和var变量。函数
var 变量名字 类型= 表达式
若是去掉= 表达式
, 则Go语言将使用零值初始化该变量。数值类型变量对应的值的0,bool类型变量对应的零值是false,字符串类型变量对应的零值是空字符串,接口或者引用类型(包括slice、map、chan和函数)变量对应的零值是nil。数组或者结构体变量对应的零值是各个元素对应的零值。因此,go语言中不存在没有初始化的类型变量学习
这里要讨论一个问题:咱们知道slice类型实际底层是struct结构体类型: struct { byte *array; uintgo len; uintgo cap; }
; 理应slice零值是各个元素的初始化零值,实际上slice在Go语言内部把slice当作了一种新的类型。slices类型:types.TSLICE和struct类型:types.TSTRUCT, 因此初始化零值的策略也有所不一样。ui
先来个DEMO.net
var p= f() func f() *int{ v:=1 return &v }
这段程序跑起来不会发生段错误,非法地址。知道其余主流语言的开发人员,可能会认为v变量是在stack上分配的,函数执行完成,则函数的stack被释放到,这是返回值非法引用v变量,则致使程序崩溃。指针
可是Go语言编译器会静态分析源码,它发现f函数外部有引用局部变量v。则v在函数内的内存空间分配是在heap上分配,使得p可使用它,这就是一个简单的逃逸分析。你们能够网上查查编译器的逃逸分析。至于内存的释放是由Go语言提供的自动垃圾回收机制作的,不须要人工参与。code
指针特别有价值的地方在于,咱们能够不用知道变量的名字而访问一个变量。言外之意,只要你给我一块内存地址,给我这个这块内存地址块的数据类型,我就能够正确访问原来这个变量值。1.经过内存地址,则能够知道数据的起始位置;2.经过数据类型,则能够知道,简单数据类型或者复杂数据类型中的各个元素所占内存空间的大小,因此,咱们就能够经过指针正确访问内存变量值。可是一个缺点在于:在垃圾回收时,要找到一个变量的全部访问者并不容易,咱们必须知道全部变量所有的别名。只有在全部变量不在使用这块内存时,咱们才能回收内存。blog
借助于上面的指针理解,咱们如今分析下flag标准库的部分使用。接口
可能有些开发者常常会看到形以下面的使用,但不怎么会使用:
var n = flag.Bool("n", false, "omit trailing newline") var sep = flag.String("sep", " ", "separator") func main(){ flag.Parse() fmt.Print(strings.Join(flag.Args(), *sep)) if !*n{ fmt.Println() } }
有童鞋可能想问,为啥n和sep没有任何赋值操做, main函数就能够直接使用这两个指针变量了呢?由于这个程序在运行时,有一个全局变量CommandLine, 当flag.Parse解析os.Stdin的全部输入参数时,把CommandLine的指针变量n和sep所有赋值,同时经过flag.Args方法,能够获取到用户输入的数据列表。明白了这个,就知道怎么使用flag包了。
注意一点:对于bool变量,只须要:./program -n -sep , hello world
, 输出: hello,world
,且不换行。若是去掉-n
, 则自动换行。
变量的生命周期是指在程序运行期间变量有效存在的时间间隔。
垃圾收集器的基本思路:从每一个包级别和每一个当前运行函数的局部变量开始,经过指针或者引用的方式遍历路径,是否能够找到该变量。若是找不到,则变量不可达。
一个变量的生命周期只取决因而否可达,所以局部变量的生命周期可能会超出其做用域,因此局部变量可能在函数返回以后依然存在。编译器会自动选择变量是分配在heap上仍是stack上。这个选择不是由var或者new决定的
var global *int func f() { // 由于global引用了x,因此x变量逃逸了,分配在heap上 var x int x = 1 global = &x } func g() { // 由于外部没有引用y,y不可达,分配在stack上。 y := new(int) *y = 1 }
x, y = y, x+y
在赋值以前,赋值语句右边的全部表达式将会先进行求值,而后再统一更新左边对应变量的值
当编译器遇到一个名字引用时,若是它看起来像一个声明,它首先从最内层的词法域向全局的做用域查找。若是查找失败,则报告“未声明的变量名”错误。若是该变量名在内部和外部都声明过,这内部块的声明首先被找到。内部声明屏蔽了外部声明。
说明:做用域和变量的生命周期是两个不一样概念,前者是编译时的一个静态属性,后者是程序运行过程当中变量存在的有效时间段,它是一个运行时概念。
一个隐晦的bug
var cwd string func assignCwd() { cwd, err := os.Getwd() if err !=nil{ log.Fatalf("os.Getwd failed. %v", err) return } fmt.Println(cwd) return } func main(){ assignCwd() fmt.Println(cwd) }
这个程序的答案:通常初学者认为cwd不为空。由于他们遇到过这样的DEMO
func readLine() { var data []byte data, err := ioutil.ReadAll(filename) // 这个data是赋值操做,err是声明操做。 if err !=nil{ log.Fatalf(err) return } }
因此认为assignCwd函数的cwd是赋值操做,因此cwd不为空字符串。实际上后面这个DEMO的词法域都在一块儿,而前者cwd分别是包级别和函数块级别的变量。内部声明的cwd屏蔽了外部的变量,因此致使隐晦bug。若是在assignCwd函数内不打印cwd变量,则直接报“cwd变量没有使用”的错误。