mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 03:34:15 +08:00
314 lines
14 KiB
Markdown
314 lines
14 KiB
Markdown
##啊哦,亲,你看得也太快了。。。还没翻译完呢 0 0
|
||
要不等到 ***2013 年 4 月 25 日*** 再来看看吧~~
|
||
|
||
这里还有一些其它的学习资源噢~
|
||
|
||
- [《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming):已更新至 [第9课](https://github.com/Unknwon/go-fundamental-programming/blob/master/lecture9/lecture9.md)
|
||
- [《Go Web编程》](https://github.com/astaxie/build-web-application-with-golang)
|
||
|
||
神马?你说你不想学习?那好吧,去逛逛论坛看看行情也行~
|
||
|
||
- [Golang中文社区](http://bbs.mygolang.com/forum.php)
|
||
- [Go语言学习园地](http://studygolang.com/)
|
||
- [Golang中国](http://golang.tc)
|
||
|
||
#4.4 变量
|
||
#4.4.1 简介
|
||
声明变量的一般形式是使用 `var` 关键字:`var identifier type`。
|
||
|
||
需要注意的是,Go 和许多编程语言不同,它在声明变量时将变量的类型放在变量的名称之后。Go 为什么要选择这么做呢?
|
||
|
||
首先,它是为了避免像 C 语言中那样含糊不清的声明形式,例如:`int* a, b;`。在这个例子中,只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写(你可以在该页面找到有关于这个话题的更多讨论:[http://blog.golang.org/2010/07/gos-declaration-syntax.html](http://blog.golang.org/2010/07/gos-declaration-syntax.html))。
|
||
|
||
而在 Go 中,则可以和轻松地将它们都声明为指针类型:`var a, b *int`。
|
||
|
||
其次,这种语法能够按照从左至右的顺序阅读,使得代码更加容易理解。
|
||
|
||
示例:
|
||
|
||
var a int
|
||
var b bool
|
||
var str string
|
||
|
||
你也可以改写成这种形式:
|
||
|
||
var (
|
||
a int
|
||
b bool
|
||
str string
|
||
)
|
||
|
||
这种因式分解关键字的写法一般用于声明全局变量。
|
||
|
||
当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,flost 为 0.0,bool 为 false,string 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。
|
||
|
||
变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:`numShips`, `startDate`。
|
||
|
||
但如果你的全局变量希望能够被外部包所使用,则需要将首个单词的首字母也大写(第 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 a` 这种语法是不正确的,因为编译器没有任何可以用于自动推断类型的依据。变量的类型也可以在运行时实现自动推断,例如:
|
||
|
||
var (
|
||
HOME = os.Getenv(“HOME”)
|
||
USER = os.Getenv(“USER”)
|
||
GOROOT = os.Getenv(“GOROOT”)
|
||
)
|
||
|
||
这种写法主要用于声明包级别的全局变量,当你在函数体内声明局部变量时,应使用简短声明语法 `:=`,例如:`a := 1`。
|
||
|
||
下面这个例子展示了如何在运行时获取所在的操作系统类型,它通过 `os` 包中的函数 `os.Getenv()` 来获取环境变量中的值,并保存到 string 类型的局部变量 `path` 中。
|
||
|
||
Example 4.5 [goos.go](examples/chapter_4/goos.go)
|
||
|
||
package main
|
||
|
||
import (
|
||
“fmt”
|
||
“os”
|
||
)
|
||
|
||
func main() {
|
||
var goos string = os.Getenv(“GOOS”)
|
||
fmt.Printf(“The operating system is: %s\n”, goos)
|
||
path := os.Getenv(“PATH”)
|
||
fmt.Printf(“Path is %s\n”, path)
|
||
}
|
||
|
||
如果你在 Windows 下运行这段代码,则会输出 `The operating system is: windows` 以及相应的环境变量的值;如果你在 Linux 下运行这段代码,则会输出 `The operating system is: linux` 以及相应的的环境变量的值。
|
||
|
||
这里用到了 `Printf` 的格式化输出的功能(第 4.4.3 节)。
|
||
|
||
#4.4.2 值类型和引用类型
|
||
程序中所用到的内存在计算机中使用一堆箱子来表示(这也是人们在讲解它的时候的画法),这些箱子被称为 “ 字 ”。根据不同的处理器以及操作系统类型,所有的字都具有 32 位(4 字节)或 64 位(8 字节)的相同长度;所有的字都使用相关的内存地址来进行表示(以十六进制数表示)。
|
||
|
||
所有像 int,float,bool,string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:
|
||
|
||

|
||
|
||
另外,像数组(第 7 章)和结构(第 10 章)这些复合类型也是值类型。
|
||
|
||
当使用等号 `=` 将一个变量的值赋值给另一个变量时,如:`j = i`,实际上是在内存中将 i 的值进行了拷贝:
|
||
|
||

|
||
|
||
你可以通过 &i 来获取变量 i 的内存地址(第 4.9 节),例如:0xf840000040(每次的地址都可能不一样)。值类型的变量的值存储在栈中。
|
||
|
||
内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。
|
||
|
||
更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。
|
||
|
||
一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。
|
||
|
||

|
||
|
||
这个内存地址为称之为指针(你可以从上图中很清晰地看到,第 4.9 节将会详细说明),这个指针实际上也被存在另外的某一个字中。
|
||
|
||
同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。
|
||
|
||
当使用赋值语句 `r2 = r1` 时,只有引用(地址)被复制。
|
||
|
||
如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。
|
||
|
||
在 Go 语言中,指针(第 4.9 节)属于引用类型,其它的引用类型还包括 slices(第 7 章),maps(第 8 章)和 channel(第 13 章)。被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。
|
||
|
||
## 4.4.3 打印CHECK
|
||
|
||
函数 Printf 在 fmt 包外也是可见的,因为它以大写字母 P 开头,主要用于打印输出到控制台。通常使用的格式化字符串作为第一个参数:
|
||
|
||
func Printf(format string, list of variables to be printed)
|
||
|
||
在 Listing 4.5 中,格式化字符串为: **"The operating system is: %s\n"**
|
||
|
||
这个格式化字符串含有一个或更多的格式化标识符 `%..`, `..` 表示可以替换不同的值,如 **%s** 代表一个字符串值。**%v** 标识默认的格式化标识符。这些标识符的值从逗号之后顺序排列,如果有超过1个,它们之间用逗号分隔。这些 % 占位符的格式可以精细控制。
|
||
|
||
函数 **fmt.Sprintf** 与 **Printf** 的行为完全相同。但是只简单的返回格式化后的字符串:所以可以在你的程序中使用字符串包含变量值使用(例子,见 Listing 15.4-simple_tcp_server.go)。
|
||
|
||
函数 **fmt.Print** 和 **fmt.Println** 表现完全自动化使用格式化标识符 **%v** 进行格式化,在每个参数后添加空格,后者在最后添加一个换行符。所以 **fmt.Print("Hello:", 23)** 将输出:**Hello: 23**
|
||
|
||
## 4.4.4 简短形式,使用 := 赋值操作符
|
||
|
||
忽略类型,关键字 var 在 § 4.4.1 最后一段中是非常多于的,我们可以简写为:**a := 50** 或 **b := false**。
|
||
|
||
a 和 b 的类型(int 和 bool)将被编译器推断出。
|
||
|
||
这是首选形式,但它只能在*函数内部使用,而不是在包的范围*。 操作符 := 将有效地创建一个新的变量,它也被称为初始化声明。
|
||
|
||
*提醒*:如果在这行之后写相同的代码块,如我们声明 a := 20,这是不允许的:编译器会给出错误提示 “**no new variables on left side of :=**”;但 a = 20 是可以的,因为这是给相同的变量一个新的值。
|
||
|
||
一个变量 a 被使用,但是没有定义,会得到一个编译错误:**undefined: a**
|
||
|
||
声明一个*本地*变量,但是不使用它,也会得到编译错误;如变量 a 在如下的 main 函数中:
|
||
|
||
func main() {
|
||
var a string = "abc"
|
||
fmt.Println(“hello, world”)
|
||
}
|
||
|
||
将得到错误:**a declared and not used**
|
||
|
||
当然,设置 a 的值也不是足够的,这个值必须被使用,所以 **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 的值是 5, b 的值是 7,c 的值是 "abc"。
|
||
|
||
这被称为*并行或同时赋值*。
|
||
|
||
使用两个变量,它可以被用来执行交换的值:**a, b = b, a**
|
||
|
||
(在 Go 语言,这样省去了使用交换函数的必要)
|
||
|
||
空标识符 _ 也被用于扔掉值,如值 5 在:**_, b = 5, 7** 被扔掉。
|
||
|
||
_ 实际上是一个只写变量,你不能得到它的值。这样做是因为一个声明的变量必须在 Go 语言中必须被使用,但有时你并不需要使用从一个函数得到的所有返回值。
|
||
|
||
多赋值也被用于当一个函数返回多于一个值,如这里 val 和一个错误 err 被 Func1 这个函数返回:**val, err = Func1(var1)**
|
||
|
||
## 4.4.5 初始函数
|
||
|
||
除了在全局声明中初始化,变量也可以在一个 init() 函数中初始化。这是一个特殊的函数名称 init(),它不能被调用,但在 package main 中自动在 main() 函数之前,或者自动在导入含有该函数的包之前执行。
|
||
|
||
每一个源文件都可以包含且只包含一个 init() 方法。初始化总是单线程的,并且包依赖关系保证其正确的执行顺序。
|
||
|
||
一个可能的用途是在真正执行之前,检验或修复程序状态的正确性。
|
||
|
||
例子: Listing 4.6—[init.go](examples/chapter_4/init.go):
|
||
|
||
package trans
|
||
import "math"
|
||
var Pi float64
|
||
func init() {
|
||
Pi = 4 * math.Atan(1) // init() function computes Pi
|
||
}
|
||
|
||
在它的 init() 函数中,变量 Pi 被计算初始值。
|
||
|
||
程序在 Listing 4.7 [user_init.go](examples/chapter_4/user_init.go) 中导入了包 trans (在相同的路径中) 并且使用 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() 函数也经常被用在当一个程序开始之前,一个 backend() goroutine 需要被执行,如:
|
||
|
||
func init() {
|
||
// setup preparations
|
||
go backend()
|
||
}
|
||
|
||
*练习*:推断以下程序的输出,并解释你的答案,然后编译并执行它们。
|
||
|
||
练习 4.1: [local_scope.go](examples/chapter_4/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](examples/chapter_4/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](examples/chapter_4/function_calls_function.go)
|
||
|
||
package main
|
||
var a string
|
||
func main() {
|
||
a = "G"
|
||
print(a)
|
||
f1()
|
||
}
|
||
func f1() {
|
||
a := "O"
|
||
print(a)
|
||
f2()
|
||
}
|
||
func f2() {
|
||
print(a)
|
||
}
|
||
|
||
|
||
##链接
|
||
- [目录](directory.md)
|
||
- 上一节:[常量](04.3.md)
|
||
- 下一节:[基本类型和运算符](04.5.md) |