在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
4.4 变量 4.4.1 简介声明变量的一般形式是使用 需要注意的是,Go 和许多编程语言不同,它在声明变量时将变量的类型放在变量的名称之后。Go 为什么要选择这么做呢? 首先,它是为了避免像 C 语言中那样含糊不清的声明形式,例如: 而在 Go 中,则可以很轻松地将它们都声明为指针类型: var a, b *int 其次,这种语法能够按照从左至右的顺序阅读,使得代码更加容易理解。 示例: var a int
var b bool
var str string 你也可以改写成这种形式: var (
a int
b bool
str string
) 这种因式分解关键字的写法一般用于声明全局变量。 当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。 变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如: 但如果你的全局变量希望能够被外部包所使用,则需要将首个单词的首字母也大写(第 4.2 节:可见性规则)。 一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。如果一个变量在函数体外声明,则被认为是全局变量,可以在整个包甚至外部包(被导出后)使用,不管你声明在哪个源文件里或在哪个源文件里调用该变量。 在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。在第 5 章,我们将会学习到像 if 和 for 这些控制结构,而在这些结构中声明的变量的作用域只在相应的代码块内。一般情况下,局部变量的作用域可以通过代码块(用大括号括起来的部分)判断。 尽管变量的标识符必须是唯一的,但你可以在某个代码块的内层代码块中使用相同名称的变量,则此时外部的同名变量将会暂时隐藏(结束内部代码块的执行后隐藏的外部同名变量又会出现,而内部同名变量则被释放),你任何的操作都只会影响内部代码块的局部变量。 变量可以编译期间就被赋值,赋值给变量使用运算符等号 示例: a = 15
b = false 一般情况下,当变量a和变量b之间类型相同时,才能进行如 声明与赋值(初始化)语句也可以组合起来。 示例: var identifier [type] = value
var a int = 15
var i = 5
var b bool = false
var str string = "Go says hello to the world!" 但是 Go 编译器的智商已经高到可以根据变量的值来自动推断其类型,这有点像 Ruby 和 Python 这类动态语言,只不过它们是在运行时进行推断,而 Go 是在编译时就已经完成推断过程。因此,你还可以使用下面的这些形式来声明及初始化变量: var a = 15
var b = false
var str = "Go says hello to the world!" 或: var (
a = 15
b = false
str = "Go says hello to the world!"
numShips = 50
city string
) 不过自动推断类型并不是任何时候都适用的,当你想要给变量的类型并不是自动推断出的某种类型时,你还是需要显式指定变量的类型,例如: var n int64 = 2 然而, var (
HOME = os.Getenv("HOME")
USER = os.Getenv("USER")
GOROOT = os.Getenv("GOROOT")
) 这种写法主要用于声明包级别的全局变量,当你在函数体内声明局部变量时,应使用简短声明语法 a := 1 下面这个例子展示了如何通过 示例 4.5 goos.go package main
import (
"fmt"
"runtime"
"os"
)
func main() {
var goos string = runtime.GOOS
fmt.Printf("The operating system is: %s\n", goos)
path := os.Getenv("PATH")
fmt.Printf("Path is %s\n", path)
} 如果你在 Windows 下运行这段代码,则会输出 这里用到了 4.4.2 值类型和引用类型程序中所用到的内存在计算机中使用一堆箱子来表示(这也是人们在讲解它的时候的画法),这些箱子被称为 “ 字 ”。根据不同的处理器以及操作系统类型,所有的字都具有 32 位(4 字节)或 64 位(8 字节)的相同长度;所有的字都使用相关的内存地址来进行表示(以十六进制数表示)。 所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值: 另外,像数组(第 7 章)和结构(第 10 章)这些复合类型也是值类型。 当使用等号 你可以通过 &i 来获取变量 i 的内存地址(第 4.9 节),例如:0xf840000040(每次的地址都可能不一样)。值类型的变量的值存储在栈中。 内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。 更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。 一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。 这个内存地址被称之为指针(你可以从上图中很清晰地看到,第 4.9 节将会详细说明),这个指针实际上也被存在另外的某一个字中。 同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。 当使用赋值语句 如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。 在 Go 语言中,指针(第 4.9 节)属于引用类型,其它的引用类型还包括 slices(第 7 章),maps(第 8 章)和 channel(第 13 章)。被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。 4.4.3 打印函数 func Printf(format string, list of variables to be printed) 在示例 4.5 中,格式化字符串为: 这个格式化字符串可以含有一个或多个的格式化标识符,例如: 函数 函数 fmt.Print("Hello:", 23) 将输出: 4.4.4 简短形式,使用 := 赋值操作符我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,而这个时候再在 Example 4.4.1 的最后一个声明语句写上 a 和 b 的类型(int 和 bool)将由编译器自动推断。 这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 注意事项 如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如: 如果你在定义变量 a 之前使用它,则会得到编译错误 如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a: func main() {
var a string = "abc"
fmt.Println("hello, world")
} 尝试编译这段代码将得到错误 此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用 但是全局变量是允许声明但不使用。 其他的简短形式为: 同一类型的多个变量可以声明在同一行,如: var a, b, c int (这是将类型写在标识符后面的一个重要原因) 多变量可以在同一行进行赋值,如: a, b, c = 5, 7, "abc" 上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用: a, b, c := 5, 7, "abc" 右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 这被称为 并行 或 同时 赋值。 如果你想要交换两个变量的值,则可以简单地使用 (在 Go 语言中,这样省去了使用交换函数的必要) 空白标识符
并行赋值也被用于当一个函数返回多个返回值时,比如这里的 4.4.5 init 函数变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。 每个源文件都只能包含一个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。 一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性。 示例 4.6 init.go: package trans
import "math"
var Pi float64
func init() {
Pi = 4 * math.Atan(1) // init() function computes Pi
} 在它的 init 函数中计算变量 Pi 的初始值。 示例 4.7 user_init.go 中导入了包 trans(需要init.go目录为./trans/init.go)并且使用到了变量 Pi: package main
import (
"fmt"
"./trans"
)
var twoPi = 2 * trans.Pi
func main() {
fmt.Printf("2*Pi = %g\n", twoPi) // 2*Pi = 6.283185307179586
} init 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 func init() {
// setup preparations
go backend()
} 练习 推断以下程序的输出,并解释你的答案,然后编译并执行它们。 练习 4.1 local_scope.go: package main
var a = "G"
func main() {
n()
m()
n()
}
func n() { print(a) }
func m() {
a := "O"
print(a)
} 练习 4.2 global_scope.go: package main
var a = "G"
func main() {
n()
m()
n()
}
func n() {
print(a)
}
func m() {
a = "O"
print(a)
} 练习 4.3 function_calls_function.go package main
var a string
func main() {
a = "G"
print(a)
f1()
}
func f1() {
a := "O"< |
请发表评论