Merged in akfork/gopl-zh (pull request #3)

Master
This commit is contained in:
alex cao
2016-04-26 14:50:58 +08:00
10 changed files with 32 additions and 41 deletions
+1 -1
View File
@@ -84,7 +84,7 @@ func main() {
调用flag.Bool函数会创建一个新的对应布尔型标志参数的变量。它有三个属性:第一个是的命令行标志参数的名字“n”,然后是该标志参数的默认值(这里是false),最后是该标志参数对应的描述信息。如果用户在命令行输入了一个无效的标志参数,或者输入`-h``-help`参数,那么将打印所有标志参数的名字、默认值和描述信息。类似的,调用flag.String函数将于创建一个对应字符串类型的标志参数变量,同样包含命令行标志参数对应的参数名、默认值、和描述信息。程序中的`sep``n`变量分别是指向对应命令行标志参数变量的指针,因此必须用`*sep``*n`形式的指针语法间接引用它们。
当程序运行时,必须在使用标志参数对应的变量之前调用flag.Parse函数,用于更新每个标志参数对应变量的值(之前是默认值)。对于非标志参数的普通命令行参数可以通过调用flag.Args()函数来访问,返回值对应对应一个字符串类型的slice。如果在flag.Parse函数解析命令行参数时遇到错误,默认将打印相关的提示信息,然后调用os.Exit(2)终止程序。
当程序运行时,必须在使用标志参数对应的变量之前调用flag.Parse函数,用于更新每个标志参数对应变量的值(之前是默认值)。对于非标志参数的普通命令行参数可以通过调用flag.Args()函数来访问,返回值对应对应一个字符串类型的slice。如果在flag.Parse函数解析命令行参数时遇到错误,默认将打印相关的提示信息,然后调用os.Exit(2)终止程序。
让我们运行一些echo测试用例:
+2 -3
View File
@@ -32,9 +32,9 @@ q := new(int)
fmt.Println(p == q) // "false"
```
当然也可能有特殊情况:如果两个类型都是空的,也就是说类型的大小是0,例如`struct{}``[0]int`, 有可能有相同的地址(依赖具体的语言实现)(译注:请谨慎使用大小为0的类型,因为如果类型的大小位0好话,可能导致Go语言的自动垃圾回收器有不同的行为,具体请查看`runtime.SetFinalizer`函数相关文档)。
当然也可能有特殊情况:如果两个类型都是空的,也就是说类型的大小是0,例如`struct{}``[0]int`, 有可能有相同的地址(依赖具体的语言实现)(译注:请谨慎使用大小为0的类型,因为如果类型的大小为0的话,可能导致Go语言的自动垃圾回收器有不同的行为,具体请查看`runtime.SetFinalizer`函数相关文档)。
new函数使用常相对比较少,因为对结构体来说,可以直接用字面量语法创建新变量的方法会更灵活(§4.4.1)。
new函数使用常相对比较少,因为对结构体来说,直接用字面量语法创建新变量的方法会更灵活(§4.4.1)。
由于new只是一个预定义的函数,它并不是一个关键字,因此我们可以将new名字重新定义为别的类型。例如下面的例子:
@@ -43,4 +43,3 @@ func delta(old, new int) int { return new - old }
```
由于new被定义为int类型的变量名,因此在delta函数内部是无法使用内置的new函数的。
+1 -4
View File
@@ -1,6 +1,6 @@
### 2.3.4. 变量的生命周期
变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,局部变量的声明周期则是动态的:每次创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。
变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,局部变量的声明周期则是动态的:每次创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。
例如,下面是从1.4节的Lissajous程序摘录的代码片段:
@@ -52,6 +52,3 @@ func g() {
f函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。相反,当g函数返回时,变量`*y`将是不可达的,也就是说可以马上被回收的。因此,`*y`并没有从函数g中逃逸,编译器可以选择在栈上分配`*y`的存储空间(译注:也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。
Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助,但也并不是说你完全不用考虑内存了。你虽然不需要显式地分配和释放内存,但是要编写高效的程序你依然需要了解变量的生命周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收(从而可能影响程序的性能)。
+6 -8
View File
@@ -1,17 +1,17 @@
### 2.4.2. 可赋值性
赋值语句是显式的赋值形式,但是程序中还有很多地方会发生隐式的赋值行为:函数调用会隐式地将调用参数的值赋值给函数的参数变量,一个返回语句隐式地将返回操作的值赋值给结果变量,一个复合类型的字面量(§4.2)也会产生赋值行为。例如下面的语句:
赋值语句是显式的赋值形式,但是程序中还有很多地方会发生隐式的赋值行为:函数调用会隐式地将调用参数的值赋值给函数的参数变量,一个返回语句隐式地将返回操作的值赋值给结果变量,一个复合类型的字面量(§4.2)也会产生赋值行为。例如下面的语句:
```Go
medals := []string{"gold", "silver", "bronze"}
medals := []string{"gold", "silver", "bronze"}
```
隐式地对slice的每个元素进行赋值操作,类似这样写的行为:
```Go
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"
```
map和chan的元素,虽然不是普通的变量,但是也有类似的隐式赋值行为。
@@ -20,6 +20,4 @@ map和chan的元素,虽然不是普通的变量,但是也有类似的隐式
可赋值性的规则对于不同类型有着不同要求,对每个新类型特殊的地方我们会专门解释。对于目前我们已经讨论过的类型,它的规则是简单的:类型必须完全匹配,nil可以赋值给任何指针或引用类型的变量。常量(§3.6)则有更灵活的赋值规则,因为这样可以避免不必要的显式的类型转换。
对于两个值是否可以用`==``!=`进行相等比较的能力也和可赋值能力有关系:对于任何类型的值的相等比较,第二个值必须是对第一个值类型对应的变量是可赋值的,反之然。和前面一样,我们会对每个新类型比较特殊的地方做专门的解释。
对于两个值是否可以用`==``!=`进行相等比较的能力也和可赋值能力有关系:对于任何类型的值的相等比较,第二个值必须是对第一个值类型对应的变量是可赋值的,反之然。和前面一样,我们会对每个新类型比较特殊的地方做专门的解释。
+2 -2
View File
@@ -67,11 +67,11 @@ fmt.Println(c == f) // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"!
```
注意最后那个语句。尽管看起来函数调用,但是Celsius(f)是类型转换操作,它并不会改变值,仅仅是改变值的类型而已。测试为真的原因是因为c和g都是零值。
注意最后那个语句。尽管看起来函数调用,但是Celsius(f)是类型转换操作,它并不会改变值,仅仅是改变值的类型而已。测试为真的原因是因为c和g都是零值。
一个命名的类型可以提供书写方便,特别是可以避免一遍又一遍地书写复杂类型(译注:例如用匿名的结构体定义变量)。虽然对于像float64这种简单的底层类型没有简洁很多,但是如果是复杂的类型将会简洁很多,特别是我们即将讨论的结构体类型。
命名类型还可以为该类型的值定义新的行为。这些行为表示为一组关联到该类型的函数集合,我们称为类型的方法集。我们将在第六章中讨论方法的细节,这里说写简单用法。
命名类型还可以为该类型的值定义新的行为。这些行为表示为一组关联到该类型的函数集合,我们称为类型的方法集。我们将在第六章中讨论方法的细节,这里说写简单用法。
下面的声明语句,Celsius类型的参数c出现在了函数名的前面,表示声明的是Celsius类型的一个叫名叫String的方法,该方法返回该类型对象c带着°C温度单位的字符串:
+2 -2
View File
@@ -6,7 +6,7 @@
要使用gopl.io/ch2/tempconv包,需要先导入:
<u><i>gopl.io/ch2/cf</i></u>
<u><i>gopl.io/ch2/cf</i></u>
```Go
// Cf converts its numeric argument to Celsius and Fahrenheit.
package main
@@ -34,7 +34,7 @@ func main() {
}
```
导入语句将导入的包绑定到一个短小的名字,然后通过该短小的名字就可以引用包中导出的全部内容。上面的导入声明将允许我们以tempconv.CToF的形式来访问gopl.io/ch2/tempconv包中的内容。在默认情况下,导入的包绑定到tempconv名字(译注:包声明语句指定的名字),但是我们也可以绑定到另一个名称,以避免名字冲突(§10.4)。
导入语句将导入的包绑定到一个短小的名字,然后通过该短小的名字就可以引用包中导出的全部内容。上面的导入声明将允许我们以tempconv.CToF的形式来访问gopl.io/ch2/tempconv包中的内容。在默认情况下,导入的包绑定到tempconv名字(译注:包声明语句指定的名字),但是我们也可以绑定到另一个名称,以避免名字冲突(§10.4)。
cf程序将命令行输入的一个温度在Celsius和Fahrenheit温度单位之间转换:
+2 -2
View File
@@ -1,6 +1,6 @@
### 2.6.2. 包的初始化
包的初始化首先是解决包级变量的依赖顺序,然后照包级变量声明出现的顺序依次初始化:
包的初始化首先是解决包级变量的依赖顺序,然后照包级变量声明出现的顺序依次初始化:
```Go
var a = b + c // a 第三个初始化, 为 3
@@ -22,7 +22,7 @@ func init() { /* ... */ }
每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。因此,如果一个p包导入了q包,那么在p包初始化的时候可以认为q包必然已经初始化过了。初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依然的包都已经完成初始化工作了。
下面的代码定义了一个PopCount函数,用于返回一个数字中含二进制1bit的个数。它使用init初始化函数来生成辅助表格pc,pc表格用于处理每个8bit宽度的数字含二进制的1bit的bit个数,这样的话在处理64bit宽度的数字时就没有必要循环64次,只需要8次查表就可以了。(这并不是最快的统计1bit数目的算法,但是它可以方便演示init函数的用法,并且演示了如预生成辅助表格,这是编程中常用的技术)。
下面的代码定义了一个PopCount函数,用于返回一个数字中含二进制1bit的个数。它使用init初始化函数来生成辅助表格pc,pc表格用于处理每个8bit宽度的数字含二进制的1bit的bit个数,这样的话在处理64bit宽度的数字时就没有必要循环64次,只需要8次查表就可以了。(这并不是最快的统计1bit数目的算法,但是它可以方便演示init函数的用法,并且演示了如预生成辅助表格,这是编程中常用的技术)。
<u><i>gopl.io/ch2/popcount</i></u>
```Go
+3 -3
View File
@@ -12,7 +12,7 @@ Go语言中的包和其他语言的库或模块的概念类似,目的都是为
我们把变量的声明、对应的常量,还有方法都放到tempconv.go源文件中:
<u></i>gopl.io/ch2/tempconv</i></u>
<u><i>gopl.io/ch2/tempconv</i></u>
```Go
// Package tempconv performs Celsius and Fahrenheit conversions.
package tempconv
@@ -44,7 +44,7 @@ func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
```
每个源文件都是以包的声明语句开始,用来指包的名字。当包被导入的时候,包内的成员将通过类似tempconv.CToF的形式访问。而包级别的名字,例如在一个文件声明的类型和常量,在同一个包的其他源文件也是可以直接访问的,就好像所有代码都在一个文件一样。要注意的是tempconv.go源文件导入了fmt包,但是conv.go源文件并没有,因为这个源文件中的代码并没有用到fmt包。
每个源文件都是以包的声明语句开始,用来指包的名字。当包被导入的时候,包内的成员将通过类似tempconv.CToF的形式访问。而包级别的名字,例如在一个文件声明的类型和常量,在同一个包的其他源文件也是可以直接访问的,就好像所有代码都在一个文件一样。要注意的是tempconv.go源文件导入了fmt包,但是conv.go源文件并没有,因为这个源文件中的代码并没有用到fmt包。
因为包级别的常量名都是以大写字母开头,它们可以像tempconv.AbsoluteZeroC这样被外部代码访问:
@@ -58,7 +58,7 @@ fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC) // "Brrrr! -273.15°C"
fmt.Println(tempconv.CToF(tempconv.BoilingC)) // "212°F"
```
在每个源文件的包声明前跟着的注释是包注释(§10.7.4)。通常,包注释的第一句应该先是包的功能概要说明。一个包通常只有一个源文件有包注释(译注:如果有多个包注释,目前的文档工具会根据源文件名的先后顺序将它们链接为一个包注释)。如果包注释很大,通常会放到一个独立的doc.go文件中。
在每个源文件的包声明前跟着的注释是包注释(§10.7.4)。通常,包注释的第一句应该先是包的功能概要说明。一个包通常只有一个源文件有包注释(译注:如果有多个包注释,目前的文档工具会根据源文件名的先后顺序将它们链接为一个包注释)。如果包注释很大,通常会放到一个独立的doc.go文件中。
**练习 2.1** 向tempconv包添加类型、常量和函数用来处理Kelvin绝对温度的转换,Kelvin 绝对零度是−273.15°CKelvin绝对温度1K和摄氏度1°C的单位间隔是一样的。
+1 -2
View File
@@ -4,7 +4,7 @@
不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
语法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧对应的语法块那样。语法块内部声明的名字是无法被外部语法块访问的。语法定了内部声明的名字的作用域范围。我们可以这样理解,语法块可以包含其他类似组批量声明等没有用花括弧包含的代码,我们称之为语法块。有一个语法块为整个源代码,称为全局语法块;然后是每个包的包语法;每个for、if和switch语句的语法;每个switch或select的分支也有独立的语法;当然也包括显式书写的语法块(花括弧包含的语句)。
语法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧对应的语法块那样。语法块内部声明的名字是无法被外部语法块访问的。语法定了内部声明的名字的作用域范围。我们可以这样理解,语法块可以包含其他类似组批量声明等没有用花括弧包含的代码,我们称之为语法块。有一个语法块为整个源代码,称为全局语法块;然后是每个包的包语法;每个for、if和switch语句的语法;每个switch或select的分支也有独立的语法;当然也包括显式书写的语法块(花括弧包含的语句)。
声明语句对应的词法域决定了作用域范围的大小。对于内置的类型、函数和常量,比如int、len和true等是在全局作用域的,因此可以在整个程序中直接使用。任何在在函数外部(也就是包级语法域)声明的名字可以在同一个包的任何源文件中访问的。对于导入的包,例如tempconv导入的fmt包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文件导入的包。还有许多声明语句,比如tempconv.CToF函数中的变量c,则是局部作用域的,它只能在函数内部(甚至只能是局部的某些部分)访问。
@@ -158,4 +158,3 @@ func init() {
```
我们已经看到包、文件、声明和语句如何来表达一个程序结构。在下面的两个章节,我们将探讨数据的结构。
+12 -14
View File
@@ -1,8 +1,8 @@
## 3.1. 整型
Go语言的数值类型包括几种不同大小的整数、浮点数和复数。每种数值类型都决定了对应的大小范围和是否支持正负符号。让我们先从整数类型开始介绍。
Go语言的数值类型包括几种不同大小的整数、浮点数和复数。每种数值类型都决定了对应的大小范围和是否支持正负符号。让我们先从整数类型开始介绍。
Go语言同时提供了有符号和无符号类型的整数运算。这里有int8、int16、int32和int64四种截然不同大小的有符号整数类型,分别对应8、16、32、64bit大小的有符号整数,与此对应的是uint8、uint16、uint32和uint64四种无符号整数类型。
Go语言同时提供了有符号和无符号类型的整数运算。这里有int8、int16、int32和int64四种截然不同大小的有符号整数类型,分别对应8、16、32、64bit大小的有符号整数,与此对应的是uint8、uint16、uint32和uint64四种无符号整数类型。
这里还有两种一般对应特定CPU平台机器字大小的有符号和无符号整数int和uint;其中int是应用最广泛的数值类型。这两种类型都有同样的大小,32或64bit,但是我们不能对此做任何的假设;因为不同的编译器即使在相同的硬件平台上可能产生不同的大小。
@@ -12,7 +12,7 @@ Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unic
不管它们的具体大小,int、uint和uintptr是不同类型的兄弟类型。其中int和int32也是不同的类型,即使int的大小也是32bit,在需要将int当作int32类型的地方需要一个显式的类型转换操作,反之亦然。
其中有符号整数采用2的补码形式表示,也就是最高bit位用表示符号位,一个n-bit的有符号数的值域是从$$-2^{n-1}$$到$$2^{n-1}-1$$。无符号整数的所有bit位都用于表示非负数,值域是0到$$2^n-1$$。例如,int8类型整数的值域是从-128到127,而uint8类型整数的值域是从0到255。
其中有符号整数采用2的补码形式表示,也就是最高bit位用表示符号位,一个n-bit的有符号数的值域是从$$-2^{n-1}$$到$$2^{n-1}-1$$。无符号整数的所有bit位都用于表示非负数,值域是0到$$2^n-1$$。例如,int8类型整数的值域是从-128到127,而uint8类型整数的值域是从0到255。
下面是Go语言中关于算术运算、逻辑运算和比较运算的二元运算符,它们按照先级递减的顺序的排列:
@@ -28,7 +28,7 @@ Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unic
对于上表中前两行的运算符,例如+运算符还有一个与赋值相结合的对应运算符+=,可以用于简化赋值语句。
算术运算符+、-`*``/`可以适用于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。对于不同编程语言,%取模运算的行为可能并不相同。在Go语言中,%取模运算符的符号和被取模数的符号总是一致的,因此`-5%3``-5%-3`结果都是-2。除法运算符`/`的行为则依赖于操作数是否为全为整数,比如`5.0/4.0`的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。
算术运算符`+``-``*``/`可以适用于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。对于不同编程语言,%取模运算的行为可能并不相同。在Go语言中,%取模运算符的符号和被取模数的符号总是一致的,因此`-5%3``-5%-3`结果都是-2。除法运算符`/`的行为则依赖于操作数是否为全为整数,比如`5.0/4.0`的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。
如果一个算术运算的结果,不管是有符号或者是无符号的,如果需要更多的bit位才能正确表示的话,就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型,而且最左边的bit为是1的话,那么最终结果可能是负的,例如int8的例子:
@@ -43,12 +43,12 @@ fmt.Println(i, i+1, i*i) // "127 -128 1"
两个相同的整数类型可以使用下面的二元比较运算符进行比较;比较表达式的结果是布尔类型。
```
== equal to
!= not equal to
< less than
<= less than or equal to
> greater than
>= greater than or equal to
== 等于
!= 不等于
< 小于
<= 小于等于
> 大于
>= 大于等于
```
事实上,布尔型、数字类型和字符串等基本类型都是可比较的,也就是说两个相同类型的值可以用==和!=进行比较。此外,整数、浮点数和字符串可以根据比较结果排序。许多其它类型的值可能是不可比较的,因此也就可能是不可排序的。对于我们遇到的每种类型,我们需要保证规则的一致性。
@@ -73,7 +73,7 @@ Go语言还提供了以下的bit位操作运算符,前面4个操作运算符
>> 右移
```
位操作运算符`^`作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;也就是说,它返回一个每个bit位都取反的数。位操作运算符`&^`用于按位置零(AND NOT):表达式`z = x &^ y`结果z的bit位为0,如果对应y中bit位为1的话,否则对应的bit位等于x相应的bit位的值。
位操作运算符`^`作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;也就是说,它返回一个每个bit位都取反的数。位操作运算符`&^`用于按位置零(AND NOT):如果对应y中bit位为1的话, 表达式`z = x &^ y`结果z的对应的bit位为0,否则z对应的bit位等于x相应的bit位的值。
下面的代码演示了如何使用位操作解释uint8类型值的8个独立的bit位。它使用了Printf函数的%b参数打印二进制格式的数字;其中%08b中08表示打印至少8个字符宽度,不足的前缀部分用0填充。
@@ -140,7 +140,7 @@ invalid operation: apples + oranges (mismatched types int32 and int16)
var compote = int(apples) + int(oranges)
```
如2.5节所述,对于每种类型T,如果转换允许的话,类型转换操作T(x)将x转换为T类型。许多整数之间的相互转换并不会改变数值;它们只是告诉编译器如何解释这个值。但是对于将一个大尺寸的整数类型转为一个小尺寸的整数类型,或者是将一个浮点数转为整数,可能会改变数值或丢失精度:
如2.5节所述,对于每种类型T,如果转换允许的话,类型转换操作T(x)将x转换为T类型。许多整数之间的相互转换并不会改变数值;它们只是告诉编译器如何解释这个值。但是对于将一个大尺寸的整数类型转为一个小尺寸的整数类型,或者是将一个浮点数转为整数,可能会改变数值或丢失精度:
```Go
f := 3.141 // a float64
@@ -184,5 +184,3 @@ fmt.Printf("%d %[1]c %[1]q\n", ascii) // "97 a 'a'"
fmt.Printf("%d %[1]c %[1]q\n", unicode) // "22269 国 '国'"
fmt.Printf("%d %[1]q\n", newline) // "10 '\n'"
```