diff --git a/eBook/04.4.md b/eBook/04.4.md new file mode 100644 index 0000000..c58f81f --- /dev/null +++ b/eBook/04.4.md @@ -0,0 +1,305 @@ +#4.4 变量 + +##4.4.1 介绍 + +声明变量的一般形式是使用关键字 ***var***: *var identifier type* + +值得注意的是,类型是写在要标识的变量之后,着不同去几乎其他任何编程语言。为什么 Go 设计者选择了这个约定? + +首先,它消除了像在C语言式声明的一些模糊,如:在写 `int* a, b`; + +只有 a 是一个指针,而 b 不是。为了表示他俩都是指针,必须重复星号。(这个主题有一个很长的讨论,详见:) + +但是在 Go 语言中,他们可以同时声明,如: `var a, b *int` + +其次,这样从左到右,便于阅读。 + +一些例子: + + var a int + var b bool + var str string + +也可以写成: + + var ( + a int + b bool + str string + ) + +这种形式主要用于全局变量声明。 + +当一个变量被声明,根据它的类型自动包含默认值或 null : int 的默认值为 0,float 的默认值为 0.0,bool 的默认值为 false, string 的默认值为空的字符串`string("")`,指针的默认值为 nil,零值的结构体,等等: + +_所有变量在 Go 语言中都初始化._ + +变量的标识符命名遵循驼峰命名法(以小写字母开头,其后每个单词以大写字母开头),如: numShips,startDate。 + +但是如果变量要被导出,它必须以大写字母开头(可见性规则 §4.2)。 + +一个变量(常量,类型,函数)只作用于程序的一定范围,称为作用域。在函数外声明的变量(换句话说在顶层)是全局(或包)变量:他们在整个包内都是可见的。 + +在函数内声明的变量有本地作用范围:只能在函数内被调用,参数和返回值也一样。在第 5 章将遇到控制语句如 if 和 for;一个变量在这样的机构中被定义也只能在它内部被调用(构造范围)。大多数情况你可以将作用域理解为代码块(被`{ }`包围)内的变量声明。 + +当然,标识符必须唯一,在一个代码块中的一个标识符可以在它的内部代码块中重新声明:在这个代码块(惟独这里)内,重新声明名称相同的变量有更高的优先级,请小心使用,以避免 subtle 错误(见§16.1)。 + +变量可以在编译期得到他们的值(这被称为赋值,并且使用赋值操作符 `=`),当然,这个值在运行时也可以被计算和改变。 + +例子 + + a = 15 + b = false + +一般情况,当变量 a 和变量 b 是相同类型时,变量 b 可以赋值给变量 a 如: `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 编译器足够智能得从一个变量的值推导出他的类型(动态的,也被称为动态类型推断,有点像脚本语言 Python 和 Ruby,但是只在运行时执行),所以如下的形式(忽略类型)也是正确的: + + 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` 是不正确的,因为编译器没有 a 的类型线索。变量也可以在运行时计算表达式,如: + + var ( + HOME = os.Getenv("HOME") + USER = os.Getenv("USER") + GOROOT = os.Getenv("GOROOT") + ) + +这个 var 的语法主要用于全局级和包级,在函数中,它被这个短声明语法 `:=` 代替(见 § 4.4)。 + +下面这个例子展示了程序运行在那种操作系统上。它有一个本地的 string 变量调用 Getenv 函数(这用来取得环境变量)来自 os 包。 + +Listing 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) + } + +本例的输出根据本地环境变量的值,可能是: `The operating system is: windows`,或 `The operating system is: linux` 等。 + +这里的 Printf 被用于格式化输出(见 § 4.4.3)。 + +## 4.4.2 Value types and reference types + +内存在计算机程序看来,好比一堆箱子(也就是我们将如何写入它们),叫做单词写入。以目前的操作系统开发而言,所有单词具有相同的长度为32位(4字节)或64位(8字节);所有词都有确定的*内存地址*(以十六进制数表示)。 + +所有变量的基本(原始)类型,如int,float,bool,string,... 都是*值类型*,它们直接指向其内存中的值: + +![](images/4.4.2_fig4.1.jpg?raw=true) + +此外复合类型如数组(见第 7 章)和结构体(见第 10 章)也都是值类型。 + +当使用 `=` 将一个类型的值赋值给另一个变量:**j = i**,i的原始值的副本拷贝到内存中。 + +![](images/4.4.2_fig4.2.jpg?raw=true) + +变量i的内存地址是&i(见 §4.9),例如:这可能是0xf840000040。值类型的变量在栈中存储。 + +内存地址根据机器的不同会有所不同,甚至相同的程序在不同的机器上执行也会不同,因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。 + +更复杂的数据类型,通常需要几个单词被视为引用类型。 + +一个引用类型的变量 r1 包含内存位置的地址(一个号码),这个地址存储着 r1 的值(或者是它的值的第一个词) + +![](images/4.4.2_fig4.3.jpg?raw=true) + +这个地址被叫做指针(这与写值不同,更多详情见 § 4.9)也只包含一个单词。 + +同一个引用类型指针指向不同的单词可以是连续的内存地址(内存布局被想成连续的)对计算来说是最有效的存储;或者这个词可以扩展,一个指向另一个。 + +当分配 r2 = r1, 只有引用(地址)被复制。 + +如果 r1 的值改变了,所有引用这个值(如 r1 和 r2)都指向了更改的内容。 + +在 Go 语言中,指针(见 § 4.9)是引用类型,slices(见第 7 章),maps(见第 7 章)和 channels (见第 13 章)也是引用类型。所引用的变量存储在堆中,进行垃圾收集,相比栈,有更大的内存空间。 + +## 4.4.3 打印 + +函数 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) \ No newline at end of file diff --git a/eBook/examples/chapter_4/function_calls_function.go b/eBook/examples/chapter_4/function_calls_function.go new file mode 100644 index 0000000..12ebdab --- /dev/null +++ b/eBook/examples/chapter_4/function_calls_function.go @@ -0,0 +1,15 @@ +package main +var a string +func main() { + a = "G" + print(a) + f1() +} +func f1() { + a := "O" + print(a) + f2() +} +func f2() { + print(a) +} \ No newline at end of file diff --git a/eBook/examples/chapter_4/global_scope.go b/eBook/examples/chapter_4/global_scope.go new file mode 100644 index 0000000..5b1ff29 --- /dev/null +++ b/eBook/examples/chapter_4/global_scope.go @@ -0,0 +1,14 @@ +package main +var a = "G" +func main() { + n() + m() + n() +} +func n() { + print(a) +} +func m() { + a = "O" + print(a) +} \ No newline at end of file diff --git a/eBook/examples/chapter_4/goos.go b/eBook/examples/chapter_4/goos.go index c7f0741..90fb6eb 100644 --- a/eBook/examples/chapter_4/goos.go +++ b/eBook/examples/chapter_4/goos.go @@ -1,9 +1,13 @@ package main +<<<<<<< HEAD +======= +>>>>>>> 0364d180eb1d1b8067d1248ace49ca46c816e541 import ( "fmt" "os" ) +<<<<<<< HEAD func main() { var goos string = os.Getenv("GOOS") @@ -11,3 +15,11 @@ func main() { path := os.Getenv("PATH") fmt.Printf("Path is %s\n", path) } +======= +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) +} +>>>>>>> 0364d180eb1d1b8067d1248ace49ca46c816e541 diff --git a/eBook/examples/chapter_4/init.go b/eBook/examples/chapter_4/init.go index 3555ce4..26f07ca 100644 --- a/eBook/examples/chapter_4/init.go +++ b/eBook/examples/chapter_4/init.go @@ -1,4 +1,5 @@ package trans +<<<<<<< HEAD import "math" @@ -7,3 +8,10 @@ var Pi float64 func init() { Pi = 4 * math.Atan(1) // init() function computes Pi } +======= +import "math" +var Pi float64 +func init() { + Pi = 4 * math.Atan(1) // init() function computes Pi +} +>>>>>>> 0364d180eb1d1b8067d1248ace49ca46c816e541 diff --git a/eBook/examples/chapter_4/local_scope.go b/eBook/examples/chapter_4/local_scope.go new file mode 100644 index 0000000..7fd3aea --- /dev/null +++ b/eBook/examples/chapter_4/local_scope.go @@ -0,0 +1,12 @@ +package main +var a = "G" +func main() { + n() + m() + n() +} +func n() { print(a) } +func m() { + a := "O" + print(a) +} \ No newline at end of file diff --git a/eBook/examples/chapter_4/user_init.go b/eBook/examples/chapter_4/user_init.go new file mode 100644 index 0000000..fc59361 --- /dev/null +++ b/eBook/examples/chapter_4/user_init.go @@ -0,0 +1,9 @@ +package main +import ( + "fmt" + "./trans" +) +var twoPi = 2 * trans.Pi +func main() { + fmt.Printf("2*Pi = %g\n", twoPi) // 2*Pi = 6.283185307179586 +} \ No newline at end of file diff --git a/eBook/images/4.4.2_fig4.1.jpg b/eBook/images/4.4.2_fig4.1.jpg new file mode 100644 index 0000000..44195cc Binary files /dev/null and b/eBook/images/4.4.2_fig4.1.jpg differ diff --git a/eBook/images/4.4.2_fig4.2.jpg b/eBook/images/4.4.2_fig4.2.jpg new file mode 100644 index 0000000..5b2d3c4 Binary files /dev/null and b/eBook/images/4.4.2_fig4.2.jpg differ diff --git a/eBook/images/4.4.2_fig4.3.jpg b/eBook/images/4.4.2_fig4.3.jpg new file mode 100644 index 0000000..d28332e Binary files /dev/null and b/eBook/images/4.4.2_fig4.3.jpg differ