精校:4.4

This commit is contained in:
Unknwon
2015-03-27 08:25:24 -04:00
parent a1f9f525a8
commit d9f9e59ba0

View File

@@ -6,31 +6,39 @@
需要注意的是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)
首先,它是为了避免像 C 语言中那样含糊不清的声明形式,例如:`int* a, b;`。在这个例子中,只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写(你可以在 [Go 语言的声明语法](http://blog.golang.org/2010/07/gos-declaration-syntax.html) 页面找到有关于这个话题的更多讨论
而在 Go 中,则可以和轻松地将它们都声明为指针类型:`var a, b *int`
而在 Go 中,则可以和轻松地将它们都声明为指针类型:
```go
var a, b *int
```
其次,这种语法能够按照从左至右的顺序阅读,使得代码更加容易理解。
示例:
var a int
var b bool
var str string
```go
var a int
var b bool
var str string
```
你也可以改写成这种形式:
var (
a int
b bool
str string
)
```go
var (
a int
b bool
str string
)
```
这种因式分解关键字的写法一般用于声明全局变量。
当一个变量被声明之后系统自动赋予它该类型的零值int 为 0float 为 0.0bool 为 falsestring 为空字符串,指针为 nil。记住所有的内存在 Go 中都是经过初始化的。
变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:`numShips`, `startDate`
变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:`numShips` `startDate`
但如果你的全局变量希望能够被外部包所使用,则需要将首个单词的首字母也大写(第 4.2 节:可见性规则)。
@@ -44,66 +52,90 @@
示例:
a = 15
b = false
```go
a = 15
b = false
```
一般情况下,只有类型相同的变量之间才可以相互赋值,例如:`a = b`
一般情况下,只有类型相同的变量之间才可以相互赋值,例如:
```go
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
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!”
```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
)
```go
var (
a = 15
b = false
str = "Go says hello to the world!"
numShips = 50
city string
)
```
不过自动推断类型并不是任何时候都适用的,当你想要给变量的类型并不是自动推断出的某种类型时,你还是需要显式指定变量的类型,例如:`var n int64 = 2`
不过自动推断类型并不是任何时候都适用的,当你想要给变量的类型并不是自动推断出的某种类型时,你还是需要显式指定变量的类型,例如:
```go
var n int64 = 2
```
然而,`var a` 这种语法是不正确的,因为编译器没有任何可以用于自动推断类型的依据。变量的类型也可以在运行时实现自动推断,例如:
var (
HOME = os.Getenv("HOME")
USER = os.Getenv("USER")
GOROOT = os.Getenv("GOROOT")
)
```go
var (
HOME = os.Getenv("HOME")
USER = os.Getenv("USER")
GOROOT = os.Getenv("GOROOT")
)
```
这种写法主要用于声明包级别的全局变量,当你在函数体内声明局部变量时,应使用简短声明语法 `:=`,例如:`a := 1`
这种写法主要用于声明包级别的全局变量,当你在函数体内声明局部变量时,应使用简短声明语法 `:=`,例如:
下面这个例子展示了如何在运行时获取所在的操作系统类型,它通过 `os` 包中的函数 `os.Getenv()` 来获取环境变量中的值,并保存到 string 类型的局部变量 `path` 中。
```go
a := 1
```
Example 4.5 [goos.go](examples/chapter_4/goos.go)
下面这个例子展示了如何在运行时获取所在的操作系统类型,它通过 `os` 包中的函数 `os.Getenv()` 来获取环境变量中的值,并保存到 string 类型的局部变量 path 中。
package main
示例 4.5 [goos.go](examples/chapter_4/goos.go)
import (
"fmt"
"os"
)
```go
package main
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)
}
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` 以及相应的的环境变量的值。
@@ -113,7 +145,7 @@ Example 4.5 [goos.go](examples/chapter_4/goos.go)
程序中所用到的内存在计算机中使用一堆箱子来表示(这也是人们在讲解它的时候的画法),这些箱子被称为 “ 字 ”。根据不同的处理器以及操作系统类型,所有的字都具有 32 位4 字节)或 64 位8 字节)的相同长度;所有的字都使用相关的内存地址来进行表示(以十六进制数表示)。
所有像 intfloatboolstring 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:
所有像 intfloatboolstring 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:
![](images/4.4.2_fig4.1.jpg?raw=true)
@@ -146,15 +178,23 @@ Example 4.5 [goos.go](examples/chapter_4/goos.go)
## 4.4.3 打印
函数 `Printf` 可以在 fmt 包外部使用,这是因为它以大写字母 P 开头,该函数主要用于打印输出到控制台。通常使用的格式化字符串作为第一个参数:
func Printf(format string, list of variables to be printed)
```go
func Printf(format string, list of variables to be printed)
```
Example 4.5 中,格式化字符串为:`"The operating system is: %s\n"`
示例 4.5 中,格式化字符串为:`"The operating system is: %s\n"`
这个格式化字符串可以含有一个或多个的格式化标识符,例如:`%..`,其中 `..` 可以被不同类型所对应的标识符替换,如 `%s` 代表字符串标识符、`%v` 代表使用类型的默认输出格式的标识符。这些标识符所对应的值从格式化字符串后的第一个逗号开始按照相同顺序添加,如果参数超过 1 个则同样需要使用逗号分隔。使用这些占位符可以很好地控制格式化输出的文本。
函数 `fmt.Sprintf``Printf` 的作用是完全相同的,不过前者将格式化后的字符串以返回值的形式返回给调用者,因此你可以在程序中使用包含变量的字符串,具体例子可以参见 Example 15.4 [simple_tcp_server.go](examples/chapter_15/simple_tcp_server.go)。
函数 `fmt.Sprintf``Printf` 的作用是完全相同的,不过前者将格式化后的字符串以返回值的形式返回给调用者,因此你可以在程序中使用包含变量的字符串,具体例子可以参见示例 15.4 [simple_tcp_server.go](examples/chapter_15/simple_tcp_server.go)。
函数 `fmt.Print``fmt.Println` 会自动使用格式化标识符 `%v` 对字符串进行格式化,两者都会在每个参数之间自动增加空格,而后者还会在字符串的最后加上一个换行符。例如:`fmt.Print("Hello:", 23)` 将输出:`Hello: 23`
函数 `fmt.Print``fmt.Println` 会自动使用格式化标识符 `%v` 对字符串进行格式化,两者都会在每个参数之间自动增加空格,而后者还会在字符串的最后加上一个换行符。例如:
```go
fmt.Print("Hello:", 23)
```
将输出:`Hello: 23`。
## 4.4.4 简短形式,使用 := 赋值操作符
@@ -172,10 +212,12 @@ a 和 b 的类型int 和 bool将由编译器自动推断。
如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a
func main() {
var a string = "abc"
fmt.Println("hello, world")
}
```go
func main() {
var a string = "abc"
fmt.Println("hello, world")
}
```
尝试编译这段代码将得到错误 `a declared and not used`
@@ -185,13 +227,25 @@ a 和 b 的类型int 和 bool将由编译器自动推断。
其他的简短形式为:
同一类型的多个变量可以声明在同一行,如:`var a, b, c int`
同一类型的多个变量可以声明在同一行,如:
```go
var a, b, c int
```
(这是将类型写在标识符后面的一个重要原因)
多变量可以在同一行进行赋值,如:`a, b, c = 5, 7, "abc"`
多变量可以在同一行进行赋值,如:
上面这行假设了变量 ab 和 c 都已经被声明,否则的话应该这样使用:`a, b, c := 5, 7, "abc"`
```go
a, b, c = 5, 7, "abc"
```
上面这行假设了变量 ab 和 c 都已经被声明,否则的话应该这样使用:
```go
a, b, c := 5, 7, "abc"
```
右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 `5` b 的值是 `7`c 的值是 `"abc"`
@@ -209,93 +263,123 @@ a 和 b 的类型int 和 bool将由编译器自动推断。
## 4.4.5 init 函数
变量除了可以在全局声明中初始化,也可以在 init() 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main() 函数高。
变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。
每一个源文件都可以包含且只包含一个 init() 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。
每一个源文件都可以包含且只包含一个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。
一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性。
Example 4.6 [init.go](examples/chapter_4/init.go):
示例 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
}
```go
package trans
在它的 init() 函数中计算变量 Pi 的初始值。
import "math"
Example 4.7 [user_init.go](examples/chapter_4/user_init.go) 中导入了包 trans在相同的路径中并且使用到了变量 Pi
var Pi float64
package main
import (
"fmt"
"./trans"
)
var twoPi = 2 * trans.Pi
func main() {
fmt.Printf("2*Pi = %g\n", twoPi) // 2*Pi = 6.283185307179586
}
func init() {
Pi = 4 * math.Atan(1) // init() function computes Pi
}
```
init() 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine如下面这个例子当中的 `backend()`
在它的 init 函数中计算变量 Pi 的初始值。
func init() {
// setup preparations
go backend()
}
示例 4.7 [user_init.go](examples/chapter_4/user_init.go) 中导入了包 trans在相同的路径中并且使用到了变量 Pi
```go
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如下面这个例子当中的 `backend()`
```go
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)
}
```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)
}
```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)
}
```go
package main
var a string
func main() {
a = "G"
print(a)
f1()
}
func f1() {
a := "O"
print(a)
f2()
}
func f2() {
print(a)
}
```
## 链接