Go从入门到精通——理解变量
理解变量
本章介绍如下内容:
- 变量是什么?
- 快捷变量声明。
- 理解变量和零值。
- 编写间断变量声明。
- 哪种变量声明方式更好?
- 变量作用域
- 使用指针。
- 声明常量。
变量是计算机程序不可或缺的部分。
1.1 变量是什么
变量就是值的引用,是实现程序逻辑的基石之一。在 Go 语言中,声明变量的方式有多种。Go 是一种静态类型语言, 因此声明变量时必须显式或隐式地指定其类型。
程序清单:声明 string 变量
package main import ( "fmt" ) func main() { var s string = "Hello world" fmt.Println(s) }
对程序解读如下:
-
- 1、使用关键字 var 声明一个变量。
- 2、这个变量名为 s。
- 3、这个变量的类型为 string。
- 4、赋值运算符 "=" 表示将它右边的值赋给变量。
- 5、将字符串字面量 Hello World 赋值给变量 s。
- 6、标准库中的 fmt 包通过变量 s 来引用其值,并将这个值传递给方法 Println。
- 7、打印 s 的值。
程序清单:声明变量后再给它赋值
package main import "fmt" func main() { var s string s = "Hello World" fmt.Println(s) }
Go 语言是静态编程语言,变量的类型很重要,因为这决定了可将什么值赋给变量。例如,对于类型为 string 的变量,不能将整数值赋给它;同理,不能将字符串赋给布尔变量。将类型不正确的值赋给变量时,将导致编译错误。现在我们举个错误赋值的例子:
程序清单:将类型不正确的值赋给变量
package main import ( "fmt" ) func main() { var i int i = "ONE" fmt.Println(i) }
1.2 快捷变量声明
Go 支持多种快捷变量声明方式。可在一行内声明多个类型相同的变量并给它们赋值。
程序清单:快捷变量声明
package main import ( "fmt" ) func main() { var s, t string = "foo", "bar" fmt.Println(s) fmt.Println(t) }
程序清单:以快捷方式声明类型不同的变量
package main import ( "fmt" ) func main() { var ( s string = "foo" i int = 4 ) fmt.Println(s) fmt.Println(i) }
变量声明后,就不能再次声明它。虽然可以给变量重新赋值,但不能重新声明变量,否则将导致编译阶段错误。
程序清单:变量声明后,就不能再次声明它。
package main import ( "fmt" ) func main() { var s int = 1 fmt.Println(s) var s string = "Hello World" }
1.3 理解变量和零值
在 Go 语言中,声明变量时如果没有给它指定值,则变量将为默认值,这种默认值被称为零值。这不同于其他语言,因为在这些语言中,未赋值的变量的值为 nil 或 undefined。
程序清单:演示变量的零值
package main import ( "fmt" ) func main() { var i int var f float32 var b bool var s string fmt.Println("%v %v %v %q\n", i, f, b, s) }
使用变量时,知道 Go 语言的这种设计决策很重要。不久之后,您可能就要检查变量是否赋值了。注意,在 Go 语言中,为确定变量是否已经赋值,不能检查她是否为 null,而必须检查它是否为默认值。由于类型 string 的零值为 "",因此对于类型为 string 的变量,要确定是否已经给它赋值,可检查其值是否为零值。
程序清单:检查变量的值是否为零值
package main import ( "fmt" ) func main() { var s string if s == "" { fmt.Printf("s has not bee assigned a value and is zero valued") } }
1.4 编写简短变量声明
在函数中声明变量时,可使用更简短的方式——简短变量声明。
程序清单:简短变量声明
package main import ( "fmt" ) func main() { s := "Hello World" fmt.Println(s) }
对上程序清单中程序解读:
-
- 声明一个名为 s 的变量。注意,这里没有指定关键字 var 和 类型。
- 简短变量赋值语句 := 表明声明的是简短变量声明,这意味着不需要使用关键字 var,也不用指定变量的类型。同时还意味着应将 := 右边的值赋给变量。
- 将字符串字面量 "Hello World" 赋值给变量 s。
使用简短变量声明时,编译器会推断变量的类型,因此无需显式地指定变量的类型,这只能用在函数中使用简短变量声明。
1.5 变量声明方式
Go 语言提供了多种变量声明方式。为完整起见,下面列出所有的变量声明方式:
var s string = "Hello World" var s = "Hello World" var t string t = "Hello World" u := "Hello World"
该使用哪种方式呢?Go 对此有一定的限制——不能在函数外面使用简短变量声明。在遵循这条规则的前提下,怎么做都可以。
当然,如何声明变量是风格的问题。在同一行内声明变量并给它赋值时,Go 语言设计者在标准库中遵循的约定如下:在函数内使用简短变量声明,在函数外省略类型。
程序清单:惯用的变量声明
package main import ( "fmt" ) var s = "Hello World" func main() { i := 42 fmt.Println(s) fmt.Println(i) }
1.6 理解变量作用域
作用域指的是变量在什么地方可以使用,而不是变量在什么地方声明的。Go 语言使用基于块的词法作用域。词法是一个形容词,意思与语言的词汇表相关。这意味着 Go 定义了变量在什么地方可以引用,在什么地方无法引用。对于编程来说,这很有必要,因为这样可根据应用变量的位置,确定引用的是哪个变量。在 Go 语言中,块是位于一对大括号内的一系列声明和语句,但可以是空的。
可使用通俗的语言前述概念做如下诠释:
-
- 在 Go 语言中,一对大括号({})表示一个块。
- 对于在大括号({})内声明的变量,可在相应块的任何地方访问。
- 大括号内的大括号定义了一个新块——内部块。
- 在内部块中,可访问外部块中声明的变量。
- 在外部块中,不能访问在内部块中声明的变量。
简而言之,每个内部快都可访问其外部块声明的变量,但外部块不能反问内部块声明的变量。
- 大括号定义的程序结构和变量作用域。每对大括号都表示一个块。
- 代码的缩进程度反映了块作用域的层级。在每个块中,代码都被缩进。
- 在内部块总,可引用外部块中声明的变量。
程序清单:Go语言中的词法作用域
package main import ( "fmt" ) var s = "Hello World" func main() { fmt.Printf("Print 's' variable from outer block %v\n", s) b := true if b { fmt.Printf("Printing 'b' variable from outer block %v \n", b) i := 42 if b != false { fmt.Printf("Printing 'i' variable from outer block %v \n", i) } } }
注意,变量 s 不是在大括号内声明的,但可在内部块中访问它。这是因为 Go 语言将文件也视为块,所以在第一级大括号外声明的变量可以在所有块中访问。
1.7 使用指针
值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
什么是指针?
就是变量在内存中的地址,它所指向的值的内存地址在32位和64位机器上分别占用4和8字节;当一个指针被定义后,没有分配到任何变量时,它的默认值为nil;
指针是另一个与变量相关且必须掌握的要素。在 Go 语言中声明变量时,将在计算机内存中给它分配一个位置,以便能够存储、修改和获取变量的值。要获取变量在计算机内存中的地址,可在变量名前加上 & 字符。
程序清单:打印变量在内存中的地址
package main import ( "fmt" ) func main() { s := "Hello World" fmt.Println(&s) }
运行代码后,打印出一个由字母和数字组成的序列,它表示变量在内存中的地址,并不是变量的值。
将变量传递给函数时,会分配新内存并将变量的值复制到其中,这样将有两个变量实例,它们位于不同的内存单元中。一般而言,这不可取,因为这将占用更多的内存,严格地说,Go方法或函数只有一种传递方式,那就是值传递
。每次将一个变量作为参数传递时,都会创建一个新的变量副本并将其传递给所调用的函数或方法。副本分配在不同的内存地址。
同时由于存在变量的多个副本,很容易引入 Bug。考虑到这点,Go 提供了指针。指针是 Go 语言中的一种类型,指向变量所在的内存单元。要声明指针,可在变量前加上星号字符。
取地址操作符&和取值操作符*
是一对互补操作符。
&取出内存地址,*
根据内存地址取出 内存地址 实际指向的值。
程序清单:将变量作为值传递.go
package main import "fmt" func showMemoryAddress(i int) { fmt.Println(i) return } func main() { i := 1 showMemoryAddress(i) fmt.Println(&i) }
程序清单:将变量作为指针传递.go
package main import "fmt" func showMemoryAddress(x *int) { fmt.Println(x) return } func main() { i := 1 showMemoryAddress(&i) fmt.Println(&i) }
1.8 声明常量
常量指的是在整个程序生命周期内都不变的值。常量初始化后,可以引用它,但不能修改它。
程序清单:声明常量
package main import ( "fmt" ) const greeting string = "Hello Word" func main() { fmt.Println(greeting) }
我们现在试着修改常量后,运行看看:
程序清单:修改声明后常量的值
package main import ( "fmt" ) const greeting string = "Hello Word" func main() { greeting = "Goodbye, cruel world" fmt.Println(greeting) }