回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,8 +1,8 @@
## 2.1. 命名
Go言中的函名、量名、常量名、型名、語句標號和包名等所有的命名,都遵循一個簡單的命名規則:一名字必以一字母Unicode字母或下劃線開頭,後面可以跟任意量的字母、字或下劃線。大字母和小字母是不同的heapSort和Heapsort是兩個不同的名字。
Go言中的函名、量名、常量名、型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:一名字必以一字母Unicode字母或下划线开头,后面可以跟任意量的字母、字或下划线。大字母和小字母是不同的heapSort和Heapsort是两个不同的名字。
Go言中似if和switch的關鍵字有25個;關鍵字不能用自定名字,能在特定語法結構中使用。
Go言中似if和switch的关键字有25个;关键字不能用自定名字,能在特定语法结构中使用。
```
break default func interface select
@@ -12,25 +12,25 @@ const fallthrough if range type
continue for import return var
```
此外,有大30多個預定義的名字比如int和true等主要對應內建的常量、型和函
此外,有大30多个预定义的名字比如int和true等主要对应内建的常量、型和函
```
建常量: true false iota nil
建常量: true false iota nil
內建類型: int int8 int16 int32 int64
内建类型: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
建函: make len cap new append copy close delete
建函: make len cap new append copy close delete
complex real imag
panic recover
```
這些內部預先定的名字不是關鍵字,你可以再定中重新使用它。在一些特殊的景中重新定義它們也是有意的,但是也要意避免度而引起語義混亂
这些内部预先定的名字不是关键字,你可以再定中重新使用它。在一些特殊的景中重新定义它们也是有意的,但是也要意避免度而引起语义混乱
如果一名字是在函數內部定,那它的就在函數內部有效。如果是在函外部定,那麽將在當前包的所有文件中都可以訪問。名字的開頭字母的大小寫決定了名字在包外的可性。如果一名字是大字母開頭的(譯註:必是在函外部定的包名字;包級函數名本身也是包名字),那麽它將是導出的,也就是可以被外部的包訪問例如fmt包的Printf函就是出的可以在fmt包外部訪問。包本身的名字一般是用小字母。
如果一名字是在函数内部定,那它的就在函数内部有效。如果是在函外部定,那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可性。如果一名字是大字母开头的(译注:必是在函外部定的包名字;包级函数名本身也是包名字),那么它将是导出的,也就是可以被外部的包访问例如fmt包的Printf函就是出的可以在fmt包外部访问。包本身的名字一般是用小字母。
名字的長度沒有邏輯限製但是Go言的格是量使用短小的名字,對於局部量尤其是這樣;你會經常看到i之的短名字,而不是冗的theLoopIndex命名。通常來説,如果一名字的作用域比大,生命期也比較長,那麽用長的名字將會更有意
名字的长度没有逻辑限制但是Go言的格是量使用短小的名字,对于局部量尤其是这样;你会经常看到i之的短名字,而不是冗的theLoopIndex命名。通常来说,如果一名字的作用域比大,生命期也比较长,那么用长的名字将会更有意
習慣Go言程序員推薦使用 **駝峯式** 命名,名字有幾個單詞組成的時優先使用大小分隔,而不是先用下劃線分隔。因此,在標準庫有QuoteRuneToASCII和parseRequestLine這樣的函命名,但是一般不用quote_rune_to_ASCII和parse_request_line這樣的命名。而像ASCII和HTML這樣的縮略詞則避免使用大小混合的法,它可能被稱爲htmlEscape、HTMLEscape或escapeHTML但不是escapeHtml。
习惯Go言程序员推荐使用 **驼峰式** 命名,名字有几个单词组成的时优先使用大小分隔,而不是先用下划线分隔。因此,在标准库有QuoteRuneToASCII和parseRequestLine这样的函命名,但是一般不用quote_rune_to_ASCII和parse_request_line这样的命名。而像ASCII和HTML这样的缩略词则避免使用大小混合的法,它可能被称为htmlEscape、HTMLEscape或escapeHTML但不是escapeHtml。

View File

@@ -1,8 +1,8 @@
## 2.2.
## 2.2.
聲明語句定了程序的各種實體對象以及部分或全部的性。Go言主要有四種類型的聲明語var、const、type和func分别對應變量、常量、型和函數實體對象的明。一章我們重點討論變量和型的明,第三章將討論常量的明,第五章將討論函數的聲明。
声明语句定了程序的各种实体对象以及部分或全部的性。Go言主要有四种类型的声明语var、const、type和func分别对应变量、常量、型和函数实体对象的明。一章我们重点讨论变量和型的明,第三章将讨论常量的明,第五章将讨论函数的声明。
Go語言編寫的程序對應一個或多以.go文件後綴名的源文件中。每源文件以包的聲明語句開始,説明該源文件是屬於哪個包。包聲明語句之是import語句導入依的其它包,然是包一級的類型、量、常量、函數的聲明語句,包一的各種類型的聲明語句的順序無關緊要(譯註:函數內部的名字則必須先聲明之才能使用)。例如,下面的例子中明了一常量、一個函數和兩個變量:
Go语言编写的程序对应一个或多以.go文件后缀名的源文件中。每源文件以包的声明语句开始,说明该源文件是属于哪个包。包声明语句之是import语句导入依的其它包,然是包一级的类型、量、常量、函数的声明语句,包一的各种类型的声明语句的顺序无关紧要(译注:函数内部的名字则必须先声明之才能使用)。例如,下面的例子中明了一常量、一个函数和两个变量:
<u><i>gopl.io/ch2/boiling</i></u>
```Go
@@ -22,11 +22,11 @@ func main() {
}
```
其中常量boilingF是在包一級范圍聲明語句聲明的,然f和c兩個變量是在main函數內部聲明的聲明語句聲明的。在包一級聲明語句聲明的名字可在整個包對應的每源文件中訪問,而不是僅僅在其聲明語句所在的源文件中訪問。相比之下,局部明的名字就能在函數內部很小的范圍被訪問
其中常量boilingF是在包一级范围声明语句声明的,然f和c两个变量是在main函数内部声明的声明语句声明的。在包一级声明语句声明的名字可在整个包对应的每源文件中访问,而不是仅仅在其声明语句所在的源文件中访问。相比之下,局部明的名字就能在函数内部很小的范围被访问
個函數的聲明由一個函數名字、參數列表(由函數的調用者提供參數變量的具值)、一個可選的返值列表和包含函數定義的函數體組成。如果函數沒有返值,那麽返迴值列表是省略的。行函數從函數的第一個語句開始,依次順序執行直到遇到renturn返迴語句,如果有返迴語句則是執行到函末尾,然後返迴到函數調用者。
个函数的声明由一个函数名字、参数列表(由函数的调用者提供参数变量的具值)、一个可选的返值列表和包含函数定义的函数体组成。如果函数没有返值,那么返回值列表是省略的。行函数从函数的第一个语句开始,依次顺序执行直到遇到renturn返回语句,如果有返回语句则是执行到函末尾,然后返回到函数调用者。
們已經看到很多函數聲明和函數調用的例子了,在第五章深入討論函數的相關細節這里隻簡單解釋下。下面的fToC函數封裝了溫度轉換的處理邏輯這樣它隻需要被定一次,就可以在多地方多次被使用。在這個例子中main函數就調用了次fToC函,分别是使用在局部定義的兩個常量作爲調用函數的參數
们已经看到很多函数声明和函数调用的例子了,在第五章深入讨论函数的相关细节这里只简单解释下。下面的fToC函数封装了温度转换的处理逻辑这样它只需要被定一次,就可以在多地方多次被使用。在这个例子中main函数就调用了次fToC函,分别是使用在局部定义的两个常量作为调用函数的参数
<u><i>gopl.io/ch2/ftoc</i></u>
```Go

View File

@@ -1,6 +1,6 @@
### 2.3.1. 簡短變量聲
### 2.3.1. 简短变量声
在函數內部,有一種稱爲簡短變量聲明語句的形式可用於聲明和初始化局部量。它以“名字 := 表式”形式聲明變量,量的型根據表達式來自動推導。下面是lissajous函中的三個簡短變量聲明語§1.4
在函数内部,有一种称为简短变量声明语句的形式可用于声明和初始化局部量。它以“名字 := 表式”形式声明变量,量的型根据表达式来自动推导。下面是lissajous函中的三个简短变量声明语§1.4
```Go
anim := gif.GIF{LoopCount: nframes}
@@ -8,7 +8,7 @@ freq := rand.Float64() * 3.0
t := 0.0
```
爲簡潔和靈活的特點,簡短變量聲明被泛用大部分的局部量的明和初始化。var形式的聲明語句往往是用需要式指定變量類型地方,或者因爲變量稍後會被重新值而初始值無關緊要的地方。
为简洁和灵活的特点,简短变量声明被广泛用大部分的局部量的明和初始化。var形式的声明语句往往是用需要式指定变量类型地方,或者因为变量稍后会被重新值而初始值无关紧要的地方。
```Go
i := 100 // an int
@@ -18,21 +18,21 @@ var err error
var p Point
```
和var形式聲明變語句一樣,簡短變量聲明語句也可以用來聲明和初始化一組變量:
和var形式声明变语句一样,简短变量声明语句也可以用来声明和初始化一组变量:
```Go
i, j := 0, 1
```
但是這種同時聲明多個變量的方式應該限製隻在可以提高代碼可讀性的地方使用比如for句的循的初始化句部分。
但是这种同时声明多个变量的方式应该限制只在可以提高代码可读性的地方使用比如for句的循的初始化句部分。
請記住“:=”是一個變量聲明語句,而“=‘是一個變量賦值操作。也不要混淆多個變量的明和元的多重§2.4.1者是將右邊各個的表式值賦值給左邊對應位置的各個變量:
请记住“:=”是一个变量声明语句,而“=‘是一个变量赋值操作。也不要混淆多个变量的明和元的多重§2.4.1者是将右边各个的表式值赋值给左边对应位置的各个变量:
```Go
i, j = j, i // 交 i 和 j 的值
i, j = j, i // 交 i 和 j 的值
```
和普通var形式的變量聲明語句一樣,簡短變量聲明語句也可以用函的返迴值來聲明和初始化像下面的os.Open函數調用將返迴兩個值:
和普通var形式的变量声明语句一样,简短变量声明语句也可以用函的返回值来声明和初始化像下面的os.Open函数调用将返回两个值:
```Go
f, err := os.Open(name)
@@ -43,9 +43,9 @@ if err != nil {
f.Close()
```
里有一個比較微妙的地方:簡短變量聲明左邊的變量可能不是全部都是剛剛聲明的。如果有一些已在相同的法域聲明過§2.7),那麽簡短變量聲明語句對這些已經聲明過的變量就隻有賦值行了。
里有一个比较微妙的地方:简短变量声明左边的变量可能不是全部都是刚刚声明的。如果有一些已在相同的法域声明过§2.7),那么简短变量声明语句对这些已经声明过的变量就只有赋值行了。
在下面的代中,第一個語句聲明了in和err兩個變量。在第二個語句隻聲明了out一個變量,然後對已經聲明的err行了值操作。
在下面的代中,第一个语句声明了in和err两个变量。在第二个语句只声明了out一个变量,然后对已经声明的err行了值操作。
```Go
in, err := os.Open(infile)
@@ -53,7 +53,7 @@ in, err := os.Open(infile)
out, err := os.Create(outfile)
```
簡短變量聲明語句中必至少要明一新的量,下面的代碼將不能編譯通過
简短变量声明语句中必至少要明一新的量,下面的代码将不能编译通过
```Go
f, err := os.Open(infile)
@@ -61,9 +61,9 @@ f, err := os.Open(infile)
f, err := os.Create(outfile) // compile error: no new variables
```
的方法是第二個簡短變量聲明語句改用普通的多重賦值語言。
的方法是第二个简短变量声明语句改用普通的多重赋值语言。
簡短變量聲明語句隻有對已經在同級詞法域聲明過的變量才和值操作句等,如果量是在外部法域明的,那麽簡短變量聲明語句將會在當前詞法域重新明一新的量。我在本章後面將會看到似的例子。
简短变量声明语句只有对已经在同级词法域声明过的变量才和值操作句等,如果量是在外部法域明的,那么简短变量声明语句将会在当前词法域重新明一新的量。我在本章后面将会看到似的例子。

View File

@@ -1,10 +1,10 @@
### 2.3.2. 指
### 2.3.2. 指
個變量對應一個保存了變量對應類型值的存空。普通量在聲明語句創建時被綁定到一個變量名比如叫x的量,但是有很多量始以表式方式引入例如x[i]或x.f量。所有些表式一般都是取一個變量的值,除非它是出現在賦值語句的左邊,這種時候是給對應變量賦予一新的值。
个变量对应一个保存了变量对应类型值的存空。普通量在声明语句创建时被绑定到一个变量名比如叫x的量,但是有很多量始以表式方式引入例如x[i]或x.f量。所有些表式一般都是取一个变量的值,除非它是出现在赋值语句的左边,这种时候是给对应变量赋予一新的值。
個指針的值是另一個變量的地址。一個指針對應變量在存中的存位置。不是每一值都有一個內存地址,但是對於每一個變量必然有對應的內存地址。通過指針,我可以直接或更新對應變量的值,而不需要知道該變量的名字(如果量有名字的)。
个指针的值是另一个变量的地址。一个指针对应变量在存中的存位置。不是每一值都有一个内存地址,但是对于每一个变量必然有对应的内存地址。通过指针,我可以直接或更新对应变量的值,而不需要知道该变量的名字(如果量有名字的)。
如果用“var x int”聲明語句聲明一個x變量,那&x表取x量的存地址)將産生一指向該整數變量的指,指針對應的數據類型是`*int`,指針被稱之爲“指向int型的指”。如果指名字p可以“p指指向量x”或者“p指保存了x量的存地址”。同`*p`達式對應p指指向的量的值。一般`*p`達式讀取指指向的量的值,這里爲int型的值,同時因爲`*p`對應一個變量,所以該表達式也可以出現在賦值語句的左,表示更新指所指向的量的值。
如果用“var x int”声明语句声明一个x变量,那&x表取x量的存地址)将产生一指向该整数变量的指,指针对应的数据类型是`*int`,指针被称之为“指向int型的指”。如果指名字p可以“p指指向量x”或者“p指保存了x量的存地址”。同`*p`达式对应p指指向的量的值。一般`*p`达式读取指指向的量的值,这里为int型的值,同时因为`*p`对应一个变量,所以该表达式也可以出现在赋值语句的左,表示更新指所指向的量的值。
```Go
x := 1
@@ -14,18 +14,18 @@ fmt.Println(*p) // "1"
fmt.Println(x) // "2"
```
對於聚合型每個成員——比如結構體的每字段、或者是數組的每元素——也都是對應一個變量,因此可以被取地址。
对于聚合型每个成员——比如结构体的每字段、或者是数组的每元素——也都是对应一个变量,因此可以被取地址。
量有候被稱爲可尋址的值。卽使變量由表達式臨時生成,那麽表達式也必能接受`&`取地址操作。
量有候被称为可寻址的值。即使变量由表达式临时生成,那么表达式也必能接受`&`取地址操作。
任何型的指的零值都是nil。如果`p != nil`測試爲眞,那p是指向某有效量。指針之間也是可以行相等測試的,隻有當它們指向同一個變量或全部是nil才相等。
任何型的指的零值都是nil。如果`p != nil`测试为真,那p是指向某有效量。指针之间也是可以行相等测试的,只有当它们指向同一个变量或全部是nil才相等。
```Go
var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
```
在Go言中,返迴函數中局部量的地址也是安全的。例如下面的代調用f函數時創建局部量v在局部量地址被返迴之後依然有效,因爲指針p依然引用這個變量。
在Go言中,返回函数中局部量的地址也是安全的。例如下面的代调用f函数时创建局部量v在局部量地址被返回之后依然有效,因为指针p依然引用这个变量。
```Go
var p = f()
@@ -36,17 +36,17 @@ func f() *int {
}
```
每次調用f函數都將返迴不同的果:
每次用f函数都将返回不同的果:
```Go
fmt.Println(f() == f()) // "false"
```
爲指針包含了一個變量的地址,因此如果將指針作爲參數調用函,那可以在函中通過該指針來更新量的值。例如下面這個例子就是通過指針來更新量的值,然後返迴更新的值,可用在一個表達式中譯註這是對C語言中`++v`操作的模擬,這里隻是爲了説明指的用法incr函數模擬的做法不推
为指针包含了一个变量的地址,因此如果将指针作为参数调用函,那可以在函中通过该指针来更新量的值。例如下面这个例子就是通过指针来更新量的值,然后返回更新的值,可用在一个表达式中译注这是对C语言中`++v`操作的模拟,这里只是为了说明指的用法incr函数模拟的做法不推
```Go
func incr(p *int) int {
*p++ // 非常重要:是增加p指向的量的值,不改p指
*p++ // 非常重要:是增加p指向的量的值,不改p指
return *p
}
@@ -55,9 +55,9 @@ incr(&v) // side effect: v is now 2
fmt.Println(incr(&v)) // "3" (and v is 3)
```
每次我們對一個變量取地址,或者複製指針,我都是爲原變量創建了新的别名。例如,`*p`就是是 量v的别名。指特别有值的地方在於我們可以不用名字而訪問一個變量,但是是一把雙刃劍:要找到一個變量的所有訪問者併不容易,我們必須知道量全部的别名(譯註:這是Go言的垃圾收器所做的工作)。不僅僅是指針會創建别名,很多其他引用型也會創建别名例如slice、map和chan甚至結構體、數組和接口都會創建所引用量的别名。
每次我们对一个变量取地址,或者复制指针,我都是为原变量创建了新的别名。例如,`*p`就是是 量v的别名。指特别有值的地方在于我们可以不用名字而访问一个变量,但是是一把双刃剑:要找到一个变量的所有访问者并不容易,我们必须知道量全部的别名(译注:这是Go言的垃圾收器所做的工作)。不仅仅是指针会创建别名,很多其他引用型也会创建别名例如slice、map和chan甚至结构体、数组和接口都会创建所引用量的别名。
針是實現標準庫中flag包的關鍵技術,它使用命令行參數來設置對應變量的值,而這些對應命令行標誌參數的變量可能零散分布在整程序中。爲了説明這一點在早些的echo版本中就包含了兩個可選的命令行參數`-n`忽略行尾的行符,`-s sep`指定分隔字符(默是空格)。下面是第四版本,對應包路徑爲gopl.io/ch2/echo4。
针是实现标准库中flag包的关键技术,它使用命令行参数来设置对应变量的值,而这些对应命令行标志参数的变量可能零散分布在整程序中。为了说明这一点在早些的echo版本中就包含了两个可选的命令行参数`-n`忽略行尾的行符,`-s sep`指定分隔字符(默是空格)。下面是第四版本,对应包路径为gopl.io/ch2/echo4。
<u><i>gopl.io/ch2/echo4</i></u>
```Go
@@ -82,11 +82,11 @@ func main() {
}
```
調用flag.Bool函數會創建一新的對應布爾型標誌參數的變量。它有三個屬性:第一是的命令行標誌參數的名字“n”後是該標誌參數的默值(里是false後是該標誌參數對應的描述信息。如果用在命令行入了一個無效的標誌參數,或者`-h``-help`參數,那麽將打印所有標誌參數的名字、默值和描述信息。似的,調用flag.String函數將於創建一個對應字符串型的標誌參數變量,同包含命令行標誌參數對應的參數名、默值、和描述信息。程序中的`sep``n`量分别是指向對應命令行標誌參數變量的指,因此必`*sep``*n`形式的指針語法間接引用它
用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測試用例:
让我们运行一些echo测试用例:
```
$ go build gopl.io/ch2/echo4

View File

@@ -1,17 +1,17 @@
### 2.3.3. new函
### 2.3.3. new函
另一個創建變量的方法是調用用建的new函。表式new(T)將創建一個T類型的匿名量,初始化爲T類型的零值,然後返迴變量地址,返的指針類型爲`*T`
另一个创建变量的方法是用用建的new函。表式new(T)将创建一个T类型的匿名量,初始化为T类型的零值,然后返回变量地址,返的指针类型为`*T`
```Go
p := new(int) // p, *int 型, 指向匿名的 int
p := new(int) // p, *int 型, 指向匿名的 int
fmt.Println(*p) // "0"
*p = 2 // 置 int 匿名量的值 2
*p = 2 // 置 int 匿名量的值 2
fmt.Println(*p) // "2"
```
用new創建變量和普通變量聲明語句方式創建變量沒有什麽區别,除了不需要明一個臨時變量的名字外,我們還可以在表式中使用new(T)。言之new函數類似是一種語法糖,而不是一新的基概念。
用new创建变量和普通变量声明语句方式创建变量没有什么区别,除了不需要明一个临时变量的名字外,我们还可以在表式中使用new(T)。言之new函数类似是一种语法糖,而不是一新的基概念。
下面的兩個newInt函有着相同的行
下面的两个newInt函有着相同的行
```Go
func newInt() *int {
@@ -24,7 +24,7 @@ func newInt() *int {
}
```
每次調用new函都是返迴一個新的量的地址,因此下面兩個地址是不同的:
每次用new函都是返回一个新的量的地址,因此下面两个地址是不同的:
```Go
p := new(int)
@@ -32,15 +32,15 @@ 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名字重新定義爲别的型。例如下面的例子:
new是一个预定义的函,它不是一个关键字,因此我可以new名字重新定义为别的型。例如下面的例子:
```Go
func delta(old, new int) int { return new - old }
```
new被定義爲int型的量名因此在delta函數內部是法使用置的new函的。
new被定义为int型的量名因此在delta函数内部是法使用置的new函的。

View File

@@ -1,8 +1,8 @@
### 2.3.4. 量的生命
### 2.3.4. 量的生命
量的生命期指的是在程序行期間變量有效存在的時間間隔。對於在包一級聲明的變量來説,它的生命期和整程序的運行週期是一致的。而相比之下,在局部量的聲明週期則是動態的:每次建一個新變量的聲明語句開始,直到該變量不再被引用止,然後變量的存儲空間可能被收。函數的參數變量和返迴值變量都是局部量。它在函每次被調用的時候創建。
量的生命期指的是在程序行期间变量有效存在的时间间隔。对于在包一级声明的变量来说,它的生命期和整程序的运行周期是一致的。而相比之下,在局部量的声明周期则是动态的:每次建一个新变量的声明语句开始,直到该变量不再被引用止,然后变量的存储空间可能被收。函数的参数变量和返回值变量都是局部量。它在函每次被用的时候创建。
例如,下面是1.4的Lissajous程序摘的代片段:
例如,下面是1.4的Lissajous程序摘的代片段:
```Go
for t := 0.0; t < cycles*2*math.Pi; t += res {
@@ -13,7 +13,7 @@ for t := 0.0; t < cycles*2*math.Pi; t += res {
}
```
譯註:函的有右小括弧也可以另起一行縮進,同時爲了防止編譯器在行尾自插入分號而導致的編譯錯誤,可以在末尾的參數變量後面顯式插入逗。像下面這樣
译注:函的有右小括弧也可以另起一行缩进,同时为了防止编译器在行尾自插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗。像下面这样
```Go
for t := 0.0; t < cycles*2*math.Pi; t += res {
@@ -21,18 +21,18 @@ for t := 0.0; t < cycles*2*math.Pi; t += res {
y := math.Sin(t*freq + phase)
img.SetColorIndex(
size+int(x*size+0.5), size+int(y*size+0.5),
blackIndex, // 最插入的逗號不會導致編譯錯誤,這是Go編譯器的一特性
) // 小括弧另起一行縮進,和大括弧的格保存一致
blackIndex, // 最插入的逗号不会导致编译错误,这是Go编译器的一特性
) // 小括弧另起一行缩进,和大括弧的格保存一致
}
```
在每次循環的開始會創建臨時變量t在每次循迭代中創建臨時變量x和y。
在每次循环的开始会创建临时变量t在每次循迭代中创建临时变量x和y。
垃Go言的自圾收集器是如何知道一個變量是何可以被收的呢?里我可以避完整的技術細節,基本的實現思路是,從每個包級的變量和每個當前運行函的每一局部變量開始,通過指針或引用的訪問路徑遍歷,是否可以找到該變量。如果不存在這樣的訪問路徑,那麽説明該變量是不可的,也就是它是否存在併不會影響程序後續的計算結果。
垃Go言的自圾收集器是如何知道一个变量是何可以被收的呢?里我可以避完整的技术细节,基本的实现思路是,从每个包级的变量和每个当前运行函的每一局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可的,也就是它是否存在并不会影响程序后续的计算结果。
爲一個變量的有效週期隻取決於是否可,因此一個循環迭代部的局部量的生命期可能超出其局部作用域。同,局部量可能在函數返迴之後依然存在。
为一个变量的有效周期只取决于是否可,因此一个循环迭代部的局部量的生命期可能超出其局部作用域。同,局部量可能在函数返回之后依然存在。
編譯器會自動選擇在棧上還是在堆上分配局部量的存儲空間,但可能令人驚訝的是,這個選擇併不是由用var是new聲明變量的方式定的。
编译器会自动选择在栈上还是在堆上分配局部量的存储空间,但可能令人惊讶的是,这个选择并不是由用var是new声明变量的方式定的。
```Go
var global *int
@@ -49,9 +49,9 @@ func g() {
}
```
f函里的x量必在堆上分配,因它在函退出依然可以通包一的global量找到,然它是在函數內部定用Go言的術語説這個x局部變量從函數f中逃逸了。相反g函數返迴時,變`*y`是不可的,也就是可以上被收的。因此,`*y`併沒有從函數g中逃逸編譯器可以選擇在棧上分配`*y`的存儲空間(譯註:也可以選擇在堆上分配,然由Go言的GC迴收這個變量的存空雖然這里用的是new方式。其在任何候,你不需爲了編寫正確的代而要考慮變量的逃逸行,要住的是,逃逸的量需要外分配存,同時對性能的化可能會産生細微的影
f函里的x量必在堆上分配,因它在函退出依然可以通包一的global量找到,然它是在函数内部定用Go言的术语说这个x局部变量从函数f中逃逸了。相反g函数返回时,变`*y`是不可的,也就是可以上被收的。因此,`*y`并没有从函数g中逃逸编译器可以选择在栈上分配`*y`的存储空间(译注:也可以选择在堆上分配,然由Go言的GC回收这个变量的存空虽然这里用的是new方式。其在任何候,你不需为了编写正确的代而要考虑变量的逃逸行,要住的是,逃逸的量需要外分配存,同时对性能的化可能会产生细微的影
Go言的自垃圾收集器對編寫正確的代是一個鉅大的助,但也不是你完全不用考慮內存了。你然不需要式地分配和釋放內存,但是要編寫高效的程序你依然需要了解量的生命期。例如,如果指向短生命週期對象的指保存到具有生命期的象中,特别是保存到全局變量時,會阻止短生命週期對象的垃圾收(而可能影程序的性能)。
Go言的自垃圾收集器对编写正确的代是一个巨大的助,但也不是你完全不用考虑内存了。你然不需要式地分配和释放内存,但是要编写高效的程序你依然需要了解量的生命期。例如,如果指向短生命周期对象的指保存到具有生命期的象中,特别是保存到全局变量时,会阻止短生命周期对象的垃圾收(而可能影程序的性能)。

View File

@@ -1,32 +1,32 @@
## 2.3.
## 2.3.
var聲明語句可以建一特定型的量,然後給變量附加一名字,併且設置變量的初始值。變量聲明的一般法如下:
var声明语句可以建一特定型的量,然后给变量附加一名字,并且设置变量的初始值。变量声明的一般法如下:
```Go
var 量名字 =
var 量名字 =
```
其中“*型*”或“*= 表式*”兩個部分可以省略其中的一。如果省略的是型信息,那麽將根據初始化表達式來推導變量的型信息。如果初始化表式被省略,那麽將用零值初始化該變量。 數值類型變量對應的零值是0布爾類型變量對應的零值是false字符串類型對應的零值是空字符串,接口或引用包括slice、map、chan和函數)變量對應的零值是nil。數組或結構體等聚合類型對應的零值是每元素或字段都是對應該類型的零值。
其中“*型*”或“*= 表式*”两个部分可以省略其中的一。如果省略的是型信息,那么将根据初始化表达式来推导变量的型信息。如果初始化表式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0布尔类型变量对应的零值是false字符串类型对应的零值是空字符串,接口或引用包括slice、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每元素或字段都是对应该类型的零值。
零值初始化機製可以保每個聲明的變量總是有一良好定的值因此在Go言中不存在未初始化的量。這個特性可以化很多代,而且可以在有增加外工作的前提下確保邊界條件下的合理行。例如:
零值初始化机制可以保每个声明的变量总是有一良好定的值因此在Go言中不存在未初始化的量。这个特性可以化很多代,而且可以在有增加外工作的前提下确保边界条件下的合理行。例如:
```Go
var s string
fmt.Println(s) // ""
```
段代碼將打印一空字符串,而不是導致錯誤或産生不可知的行。Go言程序員應該讓一些聚合型的零值也具有意義,這樣可以保不管任何型的變量總是有一合理有效的零值狀態
段代码将打印一空字符串,而不是导致错误或产生不可知的行。Go言程序员应该让一些聚合型的零值也具有意义,这样可以保不管任何型的变量总是有一合理有效的零值状态
也可以在一個聲明語句中同時聲明一組變量,或用一初始化表達式聲明併初始化一組變量。如果省略每個變量的型,可以明多個類型不同的量(型由初始化表式推
也可以在一个声明语句中同时声明一组变量,或用一初始化表达式声明并初始化一组变量。如果省略每个变量的型,可以明多个类型不同的量(型由初始化表式推
```Go
var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string
```
初始化表式可以是字面量或任意的表式。在包級别聲明的變量會在main入口函數執行前完成初始化§2.6.2),局部變量將在聲明語句被行到的候完成初始化。
初始化表式可以是字面量或任意的表式。在包级别声明的变量会在main入口函数执行前完成初始化§2.6.2),局部变量将在声明语句被行到的候完成初始化。
組變量也可以通過調用一個函數,由函數返迴的多個返迴值初始化:
组变量也可以通过调用一个函数,由函数返回的多个返回值初始化:
```Go
var f, err = os.Open(name) // os.Open returns a file and an error

View File

@@ -1,6 +1,6 @@
### 2.4.1. 元組賦
### 2.4.1. 元组赋
組賦值是另一形式的賦值語句,它允許同時更新多個變量的值。在值之前,賦值語句右的所有表達式將會先進行求值,然後再統一更新左邊對應變量的值。這對於處理有些同時出現在元組賦值語句左右兩邊的變量很有助,例如我可以這樣交換兩個變量的值:
组赋值是另一形式的赋值语句,它允许同时更新多个变量的值。在值之前,赋值语句右的所有表达式将会先进行求值,然后再统一更新左边对应变量的值。这对于处理有些同时出现在元组赋值语句左右两边的变量很有助,例如我可以这样交换两个变量的值:
```Go
x, y = y, x
@@ -8,7 +8,7 @@ x, y = y, x
a[i], a[j] = a[j], a[i]
```
或者是計算兩個整數值的的最大公約數GCD譯註GCD不是那敏感字而是greatest common divisor的縮寫,歐幾里德的GCD是最早的非平凡算法
或者是计算两个整数值的的最大公约数GCD译注GCD不是那敏感字而是greatest common divisor的缩写,欧几里德的GCD是最早的非平凡算法
```Go
func gcd(x, y int) int {
@@ -19,7 +19,7 @@ func gcd(x, y int) int {
}
```
或者是算斐波納契數Fibonacci的第N個數
或者是算斐波纳契数Fibonacci的第N个数
```Go
func fib(n int) int {
@@ -31,21 +31,21 @@ func fib(n int) int {
}
```
組賦值也可以使一繫列瑣碎賦值更加緊湊(譯註: 特别是在for循的初始化部分),
组赋值也可以使一系列琐碎赋值更加紧凑(译注: 特别是在for循的初始化部分),
```Go
i, j, k = 2, 3, 5
```
但如果表式太複雜的話,應該盡量避免度使用元組賦值;因爲每個變量單獨賦值語句的法可讀性會更好。
但如果表式太复杂的话,应该尽量避免度使用元组赋值;因为每个变量单独赋值语句的法可读性会更好。
有些表達式會産生多值,比如調用一有多個返迴值的函數。當這樣一個函數調用出在元組賦值右的表式中時(譯註:右不能再有其它表式),左邊變量的目必和右一致。
有些表达式会产生多值,比如用一有多个返回值的函数。当这样一个函数调用出在元组赋值右的表式中时(译注:右不能再有其它表式),左边变量的目必和右一致。
```Go
f, err = os.Open("foo.txt") // function call returns two values
```
通常,這類函數會用額外的返迴值來表達某種錯誤類例如os.Open是用外的返值返迴一個error型的錯誤,還有一些是用來返迴布爾值,通常被稱爲ok。在稍後我們將看到的三操作都是似的用法。如果map§4.3)、類型斷§7.10或通道接收§8.4.2)出現在賦值語句的右,它都可能會産生兩個結果,有一個額外的布爾結果表示操作是否成功:
通常,这类函数会用额外的返回值来表达某种错误类例如os.Open是用外的返值返回一个error型的错误,还有一些是用来返回布尔值,通常被称为ok。在稍后我们将看到的三操作都是似的用法。如果map§4.3)、类型断§7.10或通道接收§8.4.2)出现在赋值语句的右,它都可能会产生两个结果,有一个额外的布尔结果表示操作是否成功:
```Go
v, ok = m[key] // map lookup
@@ -53,22 +53,22 @@ v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive
```
譯註map§4.3)、類型斷§7.10或通道接收§8.4.2)出現在賦值語句的右邊時,併不一定是産生兩個結果,也可能隻産生一個結果。對於值産生一個結果的情形map找失敗時會返迴零值,類型斷言失敗時會發送運行時panic常,通道接收失敗時會返迴零值(阻塞不算是失)。例如下面的例子:
译注map§4.3)、类型断§7.10或通道接收§8.4.2)出现在赋值语句的右边时,并不一定是产生两个结果,也可能只产生一个结果。对于值产生一个结果的情形map找失败时会返回零值,类型断言失败时会发送运行时panic常,通道接收失败时会返回零值(阻塞不算是失)。例如下面的例子:
```Go
v = m[key] // map找,失敗時返迴零值
v = x.(T) // type言,失敗時panic
v = <-ch // 管道接收,失敗時返迴零值(阻塞不算是失
v = m[key] // map找,失败时返回零值
v = x.(T) // type言,失败时panic
v = <-ch // 管道接收,失败时返回零值(阻塞不算是失
_, ok = m[key] // map返迴2個
_, ok = mm[""], false // map返迴1個
_ = mm[""] // map返迴1個
_, ok = m[key] // map返回2个
_, ok = mm[""], false // map返回1个
_ = mm[""] // map返回1个
```
變量聲明一,我可以用下劃線空白標識`_`來丟棄不需要的值。
变量声明一,我可以用下划线空白标识`_`来丢弃不需要的值。
```Go
_, err = io.Copy(dst, src) // 丟棄字節數
_, ok = x.(T) // 隻檢測類型,忽略具
_, err = io.Copy(dst, src) // 丢弃字节数
_, ok = x.(T) // 只检测类型,忽略具
```

View File

@@ -1,12 +1,12 @@
### 2.4.2. 可值性
### 2.4.2. 可值性
賦值語句是式的值形式,但是程序中有很多地方會發生隱式的值行:函數調用會隱式地將調用參數的值賦值給函數的參數變量,一個返迴語句將隱式地將返迴操作的值賦值給結果變量,一個複合類型的字面量§4.2)也會産生賦值行。例如下面的句:
赋值语句是式的值形式,但是程序中有很多地方会发生隐式的值行:函数调用会隐式地将调用参数的值赋值给函数的参数变量,一个返回语句将隐式地将返回操作的值赋值给结果变量,一个复合类型的字面量§4.2)也会产生赋值行。例如下面的句:
```Go
medals := []string{"gold", "silver", "bronze"}
```
式地slice的每元素進行賦值操作,類似這樣寫的行
式地slice的每元素进行赋值操作,类似这样写的行
```Go
medals[0] = "gold"
@@ -14,12 +14,12 @@ medals[1] = "silver"
medals[2] = "bronze"
```
map和chan的元素然不是普通的量,但是也有似的隱式賦值行
map和chan的元素然不是普通的量,但是也有似的隐式赋值行
不管是隱式還是顯式地值,在賦值語句左邊的變量和右邊最終的求到的值必有相同的數據類型。更直白地説,隻有右的值對於左邊的變量是可值的,賦值語句才是允的。
不管是隐式还是显式地值,在赋值语句左边的变量和右边最终的求到的值必有相同的数据类型。更直白地说,只有右的值对于左边的变量是可值的,赋值语句才是允的。
值性的規則對於不同型有着不同要求,對每個新類型特殊的地方我們會專門解釋。對於目前我們已經討論過的類型,它的規則是簡單的:型必完全匹配nil可以賦值給任何指或引用型的量。常量§3.6有更活的賦值規則,因爲這樣可以避免不必要的式的類型轉換
值性的规则对于不同型有着不同要求,对每个新类型特殊的地方我们会专门解释。对于目前我们已经讨论过的类型,它的规则是简单的:型必完全匹配nil可以赋值给任何指或引用型的量。常量§3.6有更活的赋值规则,因为这样可以避免不必要的式的类型转换
對於兩個值是否可以用`==``!=`行相等比的能力也和可值能力有關繫:對於任何型的值的相等比,第二值必須是對第一個值類型對應的變量是可值的,反之依然。和前面一,我們會對每個新類型比特殊的地方做專門的解
对于两个值是否可以用`==``!=`行相等比的能力也和可值能力有关系:对于任何型的值的相等比,第二值必须是对第一个值类型对应的变量是可值的,反之依然。和前面一,我们会对每个新类型比特殊的地方做专门的解

View File

@@ -1,28 +1,28 @@
## 2.4.
## 2.4.
使用賦值語句可以更新一個變量的值,最簡單的賦值語句是要被值的量放在=的左,新值的表式放在=的右
使用赋值语句可以更新一个变量的值,最简单的赋值语句是要被值的量放在=的左,新值的表式放在=的右
```Go
x = 1 // 命名量的
*p = true // 通過指針間接賦
person.name = "bob" // 結構體字段
count[x] = count[x] * scale // 數組、slice或map的元素
x = 1 // 命名量的
*p = true // 通过指针间接赋
person.name = "bob" // 结构体字段
count[x] = count[x] * scale // 数组、slice或map的元素
```
特定的二元算術運算符和賦值語句的合操作有一個簡潔形式,例如上面最後的語句可以重寫爲
特定的二元算术运算符和赋值语句的合操作有一个简洁形式,例如上面最后的语句可以重写为
```Go
count[x] *= scale
```
這樣可以省去對變量表式的重複計算。
这样可以省去对变量表式的重复计算。
數值變量也可以支持`++`增和`--`遞減語句(譯註:自增和自減是語句,而不是表式,因此`x = i++`的表式是錯誤的):
数值变量也可以支持`++`增和`--`递减语句(译注:自增和自减是语句,而不是表式,因此`x = i++`的表式是错误的):
```Go
v := 1
v++ // 等方式 v = v + 1v 成 2
v-- // 等方式 v = v - 1v 成 1
v++ // 等方式 v = v + 1v 成 2
v-- // 等方式 v = v - 1v 成 1
```
{% include "./ch2-04-1.md" %}

View File

@@ -1,24 +1,24 @@
## 2.5.
## 2.5.
量或表式的型定義了對應存儲值的性特,例如值在存的存大小或者是元素的bit個數),它們在內部是如何表的,是否支持一些操作符,以及它自己關聯的方法集等。
量或表式的型定义了对应存储值的性特,例如值在存的存大小或者是元素的bit个数),它们在内部是如何表的,是否支持一些操作符,以及它自己关联的方法集等。
在任何程序中都存在一些量有着相同的內部結構,但是表示完全不同的概念。例如,一int型的量可以用表示一個循環的迭代索引、或者一個時間戳、或者一文件描述符、或者一月份;一float64型的量可以用表示每秒移動幾米的速度、或者是不同溫度單位下的度;一字符串可以用表示一個密碼或者一個顔色的名
在任何程序中都存在一些量有着相同的内部结构,但是表示完全不同的概念。例如,一int型的量可以用表示一个循环的迭代索引、或者一个时间戳、或者一文件描述符、或者一月份;一float64型的量可以用表示每秒移动几米的速度、或者是不同温度单位下的度;一字符串可以用表示一个密码或者一个颜色的名
個類型聲明語句創建了一新的型名,和現有類型具有相同的底層結構。新命名的型提供了一方法,用分隔不同概念的型,這樣卽使它們底層類型相同也是不兼容的。
个类型声明语句创建了一新的型名,和现有类型具有相同的底层结构。新命名的型提供了一方法,用分隔不同概念的型,这样即使它们底层类型相同也是不兼容的。
```Go
type 型名字 層類
type 型名字 层类
```
類型聲明語句一般出在包一,因此如果新建的型名字的首字符大寫,則在外部包也可以使用。
类型声明语句一般出在包一,因此如果新建的型名字的首字符大写,则在外部包也可以使用。
譯註:對於中文Unicode標誌都作爲小寫字母理,因此中文的命名默不能出;不過国內的用戶針對該問題提出了不同的看法,根RobPike的迴複在Go2中有可能會將中日等字符作大字母理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的迴複
译注:对于中文Unicode标志都作为小写字母理,因此中文的命名默不能出;不过国内的用户针对该问题提出了不同的看法,根RobPike的回复在Go2中有可能会将中日等字符作大字母理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的回复
> A solution that's been kicking around for a while:
>
> For Go 2 (can't do it before then): Change the definition to “lower case letters and _ are package-local; all else is exported”. Then with non-cased languages, such as Japanese, we can write 日本 for an exported name and _日本 for a local name. This rule has no effect, relative to the Go 1 rule, with cased languages. They behave exactly the same.
> For Go 2 (can't do it before then): Change the definition to “lower case letters and _ are package-local; all else is exported”. Then with non-cased languages, such as Japanese, we can write 日本 for an exported name and _日本 for a local name. This rule has no effect, relative to the Go 1 rule, with cased languages. They behave exactly the same.
爲了説明類型聲明,我們將不同溫度單位分别定義爲不同的型:
为了说明类型声明,我们将不同温度单位分别定义为不同的型:
<u><i>gopl.io/ch2/tempconv0</i></u>
```Go
@@ -27,13 +27,13 @@ package tempconv
import "fmt"
type Celsius float64 // 攝氏溫
type Fahrenheit float64 // 華氏溫
type Celsius float64 // 摄氏温
type Fahrenheit float64 // 华氏温
const (
AbsoluteZeroC Celsius = -273.15 // 絶對零度
FreezingC Celsius = 0 // 結冰點溫
BoilingC Celsius = 100 // 沸水
AbsoluteZeroC Celsius = -273.15 // 绝对零度
FreezingC Celsius = 0 // 结冰点温
BoilingC Celsius = 100 // 沸水
)
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
@@ -41,13 +41,13 @@ func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
```
們在這個包聲明了兩種類Celsius和Fahrenheit分别對應不同的溫度單位。它們雖然有着相同的底層類型float64但是它是不同的數據類型,因此它不可以被相互比或混在一個表達式運算。刻意區分類型,可以避免一些像意中使用不同位的度混合計算導致的錯誤;因此需要一個類似Celsius(t)或Fahrenheit(t)形式的顯式轉型操作才能float64轉爲對應的類型。Celsius(t)和Fahrenheit(t)是類型轉換操作,它們併不是函數調用。類型轉換不會改變值本身,但是使它們的語義發生變化。另一方面CToF和FToC兩個函數則是對不同溫度單位下的溫度進行換算,它們會返迴不同的值。
们在这个包声明了两种类Celsius和Fahrenheit分别对应不同的温度单位。它们虽然有着相同的底层类型float64但是它是不同的数据类型,因此它不可以被相互比或混在一个表达式运算。刻意区分类型,可以避免一些像意中使用不同位的度混合计算导致的错误;因此需要一个类似Celsius(t)或Fahrenheit(t)形式的显式转型操作才能float64转为对应的类型。Celsius(t)和Fahrenheit(t)是类型转换操作,它们并不是函数调用。类型转换不会改变值本身,但是使它们的语义发生变化。另一方面CToF和FToC两个函数则是对不同温度单位下的温度进行换算,它们会返回不同的值。
對於每一個類型T都有一個對應的類型轉換操作T(x)用於將x轉爲T類型譯註如果T是指針類型,可能需要用小括弧包T比如`(*int)(0)`)。隻有當兩個類型的底層基礎類型相同,才允許這種轉型操作,或者是者都是指向相同底層結構的指針類型,這些轉換隻改變類型而不會影響值本身。如果x是可以賦值給T類型的值,那x必然也可以被轉爲T類型,但是一般沒有這個必要。
对于每一个类型T都有一个对应的类型转换操作T(x)用于将x转为T类型译注如果T是指针类型,可能需要用小括弧包T比如`(*int)(0)`)。只有当两个类型的底层基础类型相同,才允许这种转型操作,或者是者都是指向相同底层结构的指针类型,这些转换只改变类型而不会影响值本身。如果x是可以赋值给T类型的值,那x必然也可以被转为T类型,但是一般没有这个必要。
數值類型之間的轉型也是允的,且在字符串和一些特定型的slice之也是可以轉換的,在下一章我們會看到這樣的例子。這類轉換可能改值的表。例如,將一個浮點數轉爲整數將丟棄小數部分,將一個字符串轉爲`[]byte`型的slice將拷貝一個字符串數據的副本。在任何情下,運行時不會發生轉換失敗的錯誤(譯註: 錯誤隻會發生在編譯階段)。
数值类型之间的转型也是允的,且在字符串和一些特定型的slice之也是可以转换的,在下一章我们会看到这样的例子。这类转换可能改值的表。例如,将一个浮点数转为整数将丢弃小数部分,将一个字符串转为`[]byte`型的slice将拷贝一个字符串数据的副本。在任何情下,运行时不会发生转换失败的错误(译注: 错误只会发生在编译阶段)。
層數據類型決定了內部結構和表方式,也定是否可以像底層類型一樣對內置運算符的支持。意味着Celsius和Fahrenheit型的算術運算行和底的float64型是一的,正如我所期望的那
层数据类型决定了内部结构和表方式,也定是否可以像底层类型一样对内置运算符的支持。意味着Celsius和Fahrenheit型的算术运算行和底的float64型是一的,正如我所期望的那
```Go
fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C
@@ -56,7 +56,7 @@ fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F
fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch
```
較運算符`==``<`也可以用來比較一個命名型的量和另一有相同型的量,或有着相同底層類型的未命名型的值之做比。但是如果兩個值有着不同的型,不能直接行比
较运算符`==``<`也可以用来比较一个命名型的量和另一有相同型的量,或有着相同底层类型的未命名型的值之做比。但是如果两个值有着不同的型,不能直接行比
```Go
var c Celsius
@@ -67,19 +67,19 @@ fmt.Println(c == f) // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"!
```
意最後那個語句。管看起想函數調但是Celsius(f)是類型轉換操作,它併不會改變值,僅僅是改值的型而已。測試爲眞的原因是因c和g都是零值。
意最后那个语句。管看起想函数调但是Celsius(f)是类型转换操作,它并不会改变值,仅仅是改值的型而已。测试为真的原因是因c和g都是零值。
命名的型可以提供書寫方便,特别是可以避免一遍又一遍地書寫複雜類型譯註例如用匿名的結構體定義變量。雖然對於像float64這種簡單的底層類型沒有簡潔很多但是如果是複雜的類型將會簡潔很多特别是我們卽將討論的結構體類型。
命名的型可以提供书写方便,特别是可以避免一遍又一遍地书写复杂类型译注例如用匿名的结构体定义变量。虽然对于像float64这种简单的底层类型没有简洁很多但是如果是复杂的类型将会简洁很多特别是我们即将讨论的结构体类型。
命名類型還可以爲該類型的值定新的行爲。這些行表示爲一組關聯到該類型的函集合,我們稱爲類型的方法集。我們將在第六章中討論方法的細節,這里值説寫簡單用法。
命名类型还可以为该类型的值定新的行为。这些行表示为一组关联到该类型的函集合,我们称为类型的方法集。我们将在第六章中讨论方法的细节,这里值说写简单用法。
下面的聲明語Celsius型的參數c出在了函名的前面,表示明的是Celsius型的一叫名叫String的方法方法返迴該類型對象c着°C溫度單位的字符串:
下面的声明语Celsius型的参数c出在了函名的前面,表示明的是Celsius型的一叫名叫String的方法方法返回该类型对象c着°C温度单位的字符串:
```Go
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
```
許多類型都會定義一個String方法爲當使用fmt包的打印方法時,將會優先使用該類型對應的String方法返迴的結果打印,我們將在7.1節講述。
许多类型都会定义一个String方法为当使用fmt包的打印方法时,将会优先使用该类型对应的String方法返回的结果打印,我们将在7.1节讲述。
```Go
c := FToC(212.0)

View File

@@ -1,10 +1,10 @@
### 2.6.1. 入包
### 2.6.1. 入包
在Go言程序中,每包都是有一全局唯一的入路徑。導入語句中似"gopl.io/ch2/tempconv"的字符串對應包的入路。Go言的規范併沒有定義這些字符串的具體含義或包自哪里,它是由建工具來解釋的。使用Go言自的go工具箱(第十章),一個導入路代表一個目録中的一或多Go源文件。
在Go言程序中,每包都是有一全局唯一的入路径。导入语句中似"gopl.io/ch2/tempconv"的字符串对应包的入路。Go言的规范并没有定义这些字符串的具体含义或包自哪里,它是由建工具来解释的。使用Go言自的go工具箱(第十章),一个导入路代表一个目录中的一或多Go源文件。
除了包的入路,每個包還有一包名,包名一般是短小的名字(不要求包名是唯一的),包名在包的聲明處指定。按照例,一包的名字和包的入路的最後一個字段相同例如gopl.io/ch2/tempconv包的名字一般是tempconv。
除了包的入路,每个包还有一包名,包名一般是短小的名字(不要求包名是唯一的),包名在包的声明处指定。按照例,一包的名字和包的入路的最后一个字段相同例如gopl.io/ch2/tempconv包的名字一般是tempconv。
要使用gopl.io/ch2/tempconv包需要先入:
要使用gopl.io/ch2/tempconv包需要先入:
<u><i>gopl.io/ch2/cf</i></u>
```Go
@@ -34,9 +34,9 @@ func main() {
}
```
導入語句將導入的包定到一短小的名字,然後通過該短小的名字就可以引用包中出的全部容。上面的導入聲明將允許我們以tempconv.CToF的形式來訪問gopl.io/ch2/tempconv包中的容。在默認情況下,入的包定到tempconv名字譯註:這包聲明語句指定的名字),但是我也可以定到另一個名稱,以避免名字§10.4)。
导入语句将导入的包定到一短小的名字,然后通过该短小的名字就可以引用包中出的全部容。上面的导入声明将允许我们以tempconv.CToF的形式来访问gopl.io/ch2/tempconv包中的容。在默认情况下,入的包定到tempconv名字译注:这包声明语句指定的名字),但是我也可以定到另一个名称,以避免名字§10.4)。
cf程序命令行入的一個溫度在Celsius和Fahrenheit溫度單位之間轉換
cf程序命令行入的一个温度在Celsius和Fahrenheit温度单位之间转换
```
$ go build gopl.io/ch2/cf
@@ -48,8 +48,8 @@ $ ./cf -40
-40°F = -40°C, -40°C = -40°F
```
如果入了一包,但是又有使用該包將被當作一個編譯錯誤處理。這種強製規則可以有效少不必要的依賴,雖然在調試期間可能會讓人討厭,因爲刪除一個類似log.Print("got here!")的打印句可能致需要同時刪除log包導入聲明,否則,編譯器將會發出一個錯誤。在這種情況下,我需要不必要的導入刪除或註釋掉。
如果入了一包,但是又有使用该包将被当作一个编译错误处理。这种强制规则可以有效少不必要的依赖,虽然在调试期间可能会让人讨厌,因为删除一个类似log.Print("got here!")的打印句可能致需要同时删除log包导入声明,否则,编译器将会发出一个错误。在这种情况下,我需要不必要的导入删除或注释掉。
有更好的解方案,我可以使用golang.org/x/tools/cmd/goimports入工具,它可以根需要自添加或刪除導入的包;許多編輯器都可以集成goimports工具在保存文件的候自動運行。似的有gofmt工具可以用格式化Go源文件。
有更好的解方案,我可以使用golang.org/x/tools/cmd/goimports入工具,它可以根需要自添加或删除导入的包;许多编辑器都可以集成goimports工具在保存文件的候自动运行。似的有gofmt工具可以用格式化Go源文件。
**練習 2.2** 寫一個通用的單位轉換程序,用似cf程序的方式命令行讀取參數,如果缺省的話則是從標準輸入讀取參數,然後做類似Celsius和Fahrenheit的單位轉換,長度單位可以對應英尺和米,重量位可以對應磅和公斤等。
**练习 2.2** 写一个通用的单位转换程序,用似cf程序的方式命令行读取参数,如果缺省的话则是从标准输入读取参数,然后做类似Celsius和Fahrenheit的单位转换,长度单位可以对应英尺和米,重量位可以对应磅和公斤等。

View File

@@ -1,28 +1,28 @@
### 2.6.2. 包的初始化
包的初始化首先是解決包級變量的依賴順序,然安照包級變量聲明出現的順序依次初始化:
包的初始化首先是解决包级变量的依赖顺序,然安照包级变量声明出现的顺序依次初始化:
```Go
var a = b + c // a 第三初始化, 3
var b = f() // b 第二初始化, 2, 通過調用 f (依c)
var c = 1 // c 第一初始化, 1
var a = b + c // a 第三初始化, 3
var b = f() // b 第二初始化, 2, 通过调用 f (依c)
var c = 1 // c 第一初始化, 1
func f() int { return c + 1 }
```
如果包中含有多.go源文件們將按照發給編譯器的順序進行初始化Go言的建工具首先會將.go文件根文件名排序,然依次調用編譯器編譯
如果包中含有多.go源文件们将按照发给编译器的顺序进行初始化Go言的建工具首先会将.go文件根文件名排序,然依次调用编译器编译
對於在包級别聲明的量,如果有初始化表達式則用表式初始化,有一些有初始化表式的,例如某些表格數據初始化不是一個簡單的賦值過程。在這種情況下,我可以用一特殊的init初始化函數來簡化初始化工作。每文件都可以包含多init初始化函
对于在包级别声明的量,如果有初始化表达式则用表式初始化,有一些有初始化表式的,例如某些表格数据初始化不是一个简单的赋值过程。在这种情况下,我可以用一特殊的init初始化函数来简化初始化工作。每文件都可以包含多init初始化函
```Go
func init() { /* ... */ }
```
這樣的init初始化函除了不能被調用或引用外,其他行和普通函數類似。在每文件中的init初始化函,在程序開始執行時按照它們聲明的序被自動調用。
这样的init初始化函除了不能被用或引用外,其他行和普通函数类似。在每文件中的init初始化函,在程序开始执行时按照它们声明的序被自动调用。
包在解決依賴的前提下,以導入聲明的序初始化,每個包隻會被初始化一次。因此,如果一p包入了q包在p包初始化的候可以認爲q包必然已初始化了。初始化工作是自下而上行的main包最被初始化。以這種方式,可以保在main函數執行之前,所有依然的包都已完成初始化工作了。
包在解决依赖的前提下,以导入声明的序初始化,每个包只会被初始化一次。因此,如果一p包入了q包在p包初始化的候可以认为q包必然已初始化了。初始化工作是自下而上行的main包最被初始化。以这种方式,可以保在main函数执行之前,所有依然的包都已完成初始化工作了。
下面的代碼定義了一PopCount函,用於返迴一個數字中含二進製1bit的個數。它使用init初始化函數來生成助表格pcpc表格用於處理每8bit度的字含二進製的1bit的bit個數,這樣的話在處理64bit度的數字時就沒有必要循64次需要8次表就可以了。(這併不是最快的統計1bit目的算法但是它可以方便演示init函的用法,且演示了如果生成助表格,這是編程中常用的技)。
下面的代码定义了一PopCount函,用于返回一个数字中含二进制1bit的个数。它使用init初始化函数来生成助表格pcpc表格用于处理每8bit度的字含二进制的1bit的bit个数,这样的话在处理64bit度的数字时就没有必要循64次需要8次表就可以了。(这并不是最快的统计1bit目的算法但是它可以方便演示init函的用法,且演示了如果生成助表格,这是编程中常用的技)。
<u><i>gopl.io/ch2/popcount</i></u>
```Go
@@ -50,7 +50,7 @@ func PopCount(x uint64) int {
}
```
譯註對於pc這類需要複雜處理的初始化,可以通過將初始化邏輯包裝爲一個匿名函數處理,像下面這樣
译注对于pc这类需要复杂处理的初始化,可以通过将初始化逻辑包装为一个匿名函数处理,像下面这样
```Go
// pc[i] is the population count of i.
@@ -62,16 +62,16 @@ var pc [256]byte = func() (pc [256]byte) {
}()
```
意的是在init函range循環隻使用了索引,省略了有用到的值部分。循也可以這樣寫
意的是在init函range循环只使用了索引,省略了有用到的值部分。循也可以这样写
```Go
for i, _ := range pc {
```
在下一和10.5節還將看到其它使用init函的地方。
在下一和10.5节还将看到其它使用init函的地方。
**練習 2.3**PopCount函,用一個循環代替一的表式。比較兩個版本的性能。11.4節將展示如何繫統地比較兩個不同實現的性能。)
**练习 2.3**PopCount函,用一个循环代替一的表式。比较两个版本的性能。11.4节将展示如何系统地比较两个不同实现的性能。)
**練習 2.4** 用移位算法重PopCount函,每次測試最右的1bit後統計總數。比較和査表算法的性能差
**练习 2.4** 用移位算法重PopCount函,每次测试最右的1bit后统计总数。比较和查表算法的性能差
**練習 2.5**`x&(x-1)`於將x的最低的一非零的bit位清零。使用這個算法重PopCount函,然後比較性能。
**练习 2.5**`x&(x-1)`于将x的最低的一非零的bit位清零。使用这个算法重PopCount函,然后比较性能。

View File

@@ -1,16 +1,16 @@
## 2.6. 包和文件
Go言中的包和其他言的或模的概念似,目的都是了支持模化、封裝、單獨編譯和代重用。一包的源代保存在一或多以.go文件後綴名的源文件中,通常一包所在目録路徑的後綴是包的入路例如包gopl.io/ch1/helloworld對應的目録路徑是$GOPATH/src/gopl.io/ch1/helloworld。
Go言中的包和其他言的或模的概念似,目的都是了支持模化、封装、单独编译和代重用。一包的源代保存在一或多以.go文件后缀名的源文件中,通常一包所在目录路径的后缀是包的入路例如包gopl.io/ch1/helloworld对应的目录路径是$GOPATH/src/gopl.io/ch1/helloworld。
包都對應一個獨立的名字空。例如在image包中的Decode函和在unicode/utf16包中的 Decode函是不同的。要在外部引用該函數,必須顯式使用image.Decode或utf16.Decode形式訪問
包都对应一个独立的名字空。例如在image包中的Decode函和在unicode/utf16包中的 Decode函是不同的。要在外部引用该函数,必须显式使用image.Decode或utf16.Decode形式访问
可以讓我們通過控製哪些名字是外部可見的來隱藏內部實現信息。在Go言中,一個簡單的規則是:如果一名字是大字母開頭的,那麽該名字是出的(譯註:因爲漢字不分大小,因此漢字開頭的名字是沒有導出的)。
可以让我们通过控制哪些名字是外部可见的来隐藏内部实现信息。在Go言中,一个简单的规则是:如果一名字是大字母开头的,那么该名字是出的(译注:因为汉字不分大小,因此汉字开头的名字是没有导出的)。
了演示包基本的用法,先假設我們的溫度轉換軟件已很流行,我希望到Go言社也能使用這個包。我們該如何做呢?
了演示包基本的用法,先假设我们的温度转换软件已很流行,我希望到Go言社也能使用这个包。我们该如何做呢?
讓我們創建一個名爲gopl.io/ch2/tempconv的包是前面例子的一個改進版本。(我們約定我的例子都是以章節順序來編號的,這樣的路更容易閲讀)包代碼存儲在兩個源文件中,用演示如何在一源文件明然在其他的源文件訪問;雖然在現實中,這樣小的包一般需要一文件。
让我们创建一个名为gopl.io/ch2/tempconv的包是前面例子的一个改进版本。(我们约定我的例子都是以章节顺序来编号的,这样的路更容易阅读)包代码存储在两个源文件中,用演示如何在一源文件明然在其他的源文件访问;虽然在现实中,这样小的包一般需要一文件。
們把變量的明、對應的常量,有方法都放到tempconv.go源文件中
们把变量的明、对应的常量,有方法都放到tempconv.go源文件中
<u></i>gopl.io/ch2/tempconv</i></u>
```Go
@@ -32,7 +32,7 @@ func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) }
```
轉換函數則放在另一conv.go源文件中
转换函数则放在另一conv.go源文件中
```Go
package tempconv
@@ -44,23 +44,23 @@ 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這樣被外部代碼訪問
为包级别的常量名都是以大字母开头,它可以像tempconv.AbsoluteZeroC这样被外部代码访问
```Go
fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC) // "Brrrr! -273.15°C"
```
將攝氏溫度轉換爲華氏溫需要先用import語句導入gopl.io/ch2/tempconv包就可以使用下面的代碼進行轉換了:
将摄氏温度转换为华氏温需要先用import语句导入gopl.io/ch2/tempconv包就可以使用下面的代码进行转换了:
```Go
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的單位間隔是一的。
**练习 2.1** 向tempconv包添加型、常量和函数用来处理Kelvin绝对温度的转换Kelvin 绝对零度是273.15°CKelvin绝对温度1K和氏度1°C的单位间隔是一的。
{% include "./ch2-06-1.md" %}

View File

@@ -1,18 +1,18 @@
## 2.7. 作用域
個聲明語句將程序中的實體和一名字關聯,比如一個函數或一個變量。聲明語句的作用域是指源代中可以有效使用這個名字的范
个声明语句将程序中的实体和一名字关联,比如一个函数或一个变量。声明语句的作用域是指源代中可以有效使用这个名字的范
不要作用域和生命期混爲一談。聲明語句的作用域對應的是一源代的文本域;它是一個編譯時的屬性。一個變量的生命期是指程序運行時變量存在的有效時間段,在此時間區域內它可以被程序的其他部分引用;是一個運行時的概念。
不要作用域和生命期混为一谈。声明语句的作用域对应的是一源代的文本域;它是一个编译时的属性。一个变量的生命期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
語法塊是由花括弧所包含的一繫列語句,就像函數體或循環體花括弧對應的語法塊那樣。語法塊內部聲明的名字是法被外部語法塊訪問的。語法決定了內部聲明的名字的作用域范。我可以這樣理解,語法塊可以包含其他類似組批量明等有用花括弧包含的代,我們稱之爲語法塊。有一個語法塊爲整個源代碼,稱爲全局語法塊;然是每包的包語法決;每for、if和switch句的語法決;每switch或select的分支也有立的語法決;當然也包括顯式書寫的語法塊(花括弧包含的句)。
语法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧对应的语法块那样。语法块内部声明的名字是法被外部语法块访问的。语法决定了内部声明的名字的作用域范。我可以这样理解,语法块可以包含其他类似组批量明等有用花括弧包含的代,我们称之为语法块。有一个语法块为整个源代码,称为全局语法块;然是每包的包语法决;每for、if和switch句的语法决;每switch或select的分支也有立的语法决;当然也包括显式书写的语法块(花括弧包含的句)。
聲明語句對應的詞法域定了作用域范的大小。對於內置的型、函和常量比如int、len和true等是在全局作用域的因此可以在整程序中直接使用。任何在在函外部(也就是包級語法域)明的名字可以在同一包的任何源文件中訪問的。對於導入的包例如tempconv入的fmt包則是對應源文件的作用域,因此能在前的文件中訪問導入的fmt包前包的其它源文件無法訪問在當前源文件入的包。還有許多聲明語比如tempconv.CToF函中的量c是局部作用域的,它能在函數內部(甚至能是局部的某些部分)訪問
声明语句对应的词法域定了作用域范的大小。对于内置的型、函和常量比如int、len和true等是在全局作用域的因此可以在整程序中直接使用。任何在在函外部(也就是包级语法域)明的名字可以在同一包的任何源文件中访问的。对于导入的包例如tempconv入的fmt包则是对应源文件的作用域,因此能在前的文件中访问导入的fmt包前包的其它源文件无法访问在当前源文件入的包。还有许多声明语比如tempconv.CToF函中的量c是局部作用域的,它能在函数内部(甚至能是局部的某些部分)访问
製流標號就是break、continue或goto語句後面跟着的那種標號,則是函數級的作用域。
制流标号就是break、continue或goto语句后面跟着的那种标号,则是函数级的作用域。
程序可能包含多同名的明,要它在不同的法域就沒有關繫。例如,你可以明一局部量,和包級的變量同名。或者是像2.3.3的例子那,你可以將一個函數參數的名字聲明爲new雖然內置的new是全局作用域的。但是物必反,如果用不同法域可重名的特性的,可能致程序很難閲讀
程序可能包含多同名的明,要它在不同的法域就没有关系。例如,你可以明一局部量,和包级的变量同名。或者是像2.3.3的例子那,你可以将一个函数参数的名字声明为new虽然内置的new是全局作用域的。但是物必反,如果用不同法域可重名的特性的,可能致程序很难阅读
當編譯器遇到一名字引用,如果它看起像一個聲明,它首先從最內層的詞法域向全局的作用域找。如果找失敗,則報告“未明的名字”這樣的錯誤。如果名字在部和外部的分别聲明過,則內部塊的聲明首先被找到。在這種情況下,內部聲明屏蔽了外部同名的明,外部的明的名字法被訪問
当编译器遇到一名字引用,如果它看起像一个声明,它首先从最内层的词法域向全局的作用域找。如果找失败,则报告“未明的名字”这样的错误。如果名字在部和外部的分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的明,外部的明的名字法被访问
```Go
func f() {}
@@ -27,7 +27,7 @@ func main() {
}
```
在函數中詞法域可以深度嵌套,因此部的一個聲明可能屏蔽外部的明。還有許多語法塊是if或for等控製流語句構造的。下面的代有三不同的量x爲它們是定在不同的法域(這個例子隻是爲了演示作用域規則,但不是好的編程風格)。
在函数中词法域可以深度嵌套,因此部的一个声明可能屏蔽外部的明。还有许多语法块是if或for等控制流语句构造的。下面的代有三不同的量x为它们是定在不同的法域(这个例子只是为了演示作用域规则,但不是好的编程风格)。
```Go
func main() {
@@ -42,11 +42,11 @@ func main() {
}
```
`x[i]``x + 'A' - 'a'`聲明語句的初始化的表式中都引用了外部作用域明的x量,稍後我們會解釋這個。(意,面的表達式與unicode.ToUpper不等。)
`x[i]``x + 'A' - 'a'`声明语句的初始化的表式中都引用了外部作用域明的x量,稍后我们会解释这个。(意,面的表达式与unicode.ToUpper不等。)
正如上面例子所示,不是所有的法域都式地對應到由花括弧包含的句;有一些含的規則。上面的for語句創建了兩個詞法域:花括弧包含的是式的部分是for的循環體部分法域,另外一個隱式的部分是循的初始化部分,比如用迭代量i的初始化。式的法域部分的作用域包含條件測試部分和循環後的迭代部分(`i++`然也包含循環體詞法域。
正如上面例子所示,不是所有的法域都式地对应到由花括弧包含的句;有一些含的规则。上面的for语句创建了两个词法域:花括弧包含的是式的部分是for的循环体部分法域,另外一个隐式的部分是循的初始化部分,比如用迭代量i的初始化。式的法域部分的作用域包含条件测试部分和循环后的迭代部分(`i++`然也包含循环体词法域。
下面的例子同有三不同的x量,每個聲明在不同的法域,一在函數體詞法域,一在for式的初始化法域,一在for循環體詞法域;隻有兩個塊是顯式創建的:
下面的例子同有三不同的x量,每个声明在不同的法域,一在函数体词法域,一在for式的初始化法域,一在for循环体词法域;只有两个块是显式创建的:
```Go
func main() {
@@ -58,7 +58,7 @@ func main() {
}
```
和for循環類if和switch句也會在條件部分創建隱式詞法域,有它們對應的執行體詞法域。下面的if-else測試鏈演示了x和y的有效作用域范
和for循环类if和switch句也会在条件部分创建隐式词法域,有它们对应的执行体词法域。下面的if-else测试链演示了x和y的有效作用域范
```Go
if x := f(); x == 0 {
@@ -71,11 +71,11 @@ if x := f(); x == 0 {
fmt.Println(x, y) // compile error: x and y are not visible here
```
第二if句嵌套在第一個內部,因此第一if語句條件初始化法域明的量在第二if中也可以訪問。switch句的每分支也有似的法域規則:條件部分爲一個隱式詞法域,然後每個是每分支的法域。
第二if句嵌套在第一个内部,因此第一if语句条件初始化法域明的量在第二if中也可以访问。switch句的每分支也有似的法域规则:条件部分为一个隐式词法域,然后每个是每分支的法域。
在包别,明的順序併不會影響作用域范,因此一個先聲明的可以引用它自身或者是引用面的一個聲明,可以讓我們定義一些相互嵌套或遞歸的類型或函。但是如果一個變量或常量遞歸引用了自身,則會産生編譯錯誤
在包别,明的顺序并不会影响作用域范,因此一个先声明的可以引用它自身或者是引用面的一个声明,可以让我们定义一些相互嵌套或递归的类型或函。但是如果一个变量或常量递归引用了自身,则会产生编译错误
這個程序中:
这个程序中:
```Go
if f, err := os.Open(fname); err != nil { // compile error: unused: f
@@ -85,9 +85,9 @@ f.ReadByte() // compile error: undefined f
f.Close() // compile error: undefined f
```
量f的作用域有在if語句內,因此面的語句將無法引入它,這將導致編譯錯誤。你可能收到一局部量f沒有聲明的錯誤提示,具體錯誤信息依賴編譯器的實現
量f的作用域有在if语句内,因此面的语句将无法引入它,这将导致编译错误。你可能收到一局部量f没有声明的错误提示,具体错误信息依赖编译器的实现
通常需要在if之前聲明變量,這樣可以確保後面的句依然可以訪問變量:
通常需要在if之前声明变量,这样可以确保后面的句依然可以访问变量:
```Go
f, err := os.Open(fname)
@@ -98,7 +98,7 @@ f.ReadByte()
f.Close()
```
你可能會考慮通過將ReadByte和Close移到if的else塊來解決這個問題
你可能会考虑通过将ReadByte和Close移到if的else块来解决这个问题
```Go
if f, err := os.Open(fname); err != nil {
@@ -110,9 +110,9 @@ if f, err := os.Open(fname); err != nil {
}
```
不是Go言推的做法Go言的習慣是在if中處理錯誤然後直接返迴,這樣可以保正常行的句不需要代碼縮進
不是Go言推的做法Go言的习惯是在if中处理错误然后直接返回,这样可以保正常行的句不需要代码缩进
要特别意短變量聲明語句的作用域范,考下面的程序,它的目的是獲取當前的工作目録然後保存到一個包級的變量中。可以本來通過直接調用os.Getwd完成但是將這個從主邏輯中分離出來可能更好,特别是在需要處理錯誤的時候。函log.Fatalf用打印日信息,然後調用os.Exit(1)止程序。
要特别意短变量声明语句的作用域范,考下面的程序,它的目的是获取当前的工作目录然后保存到一个包级的变量中。可以本来通过直接用os.Getwd完成但是将这个从主逻辑中分离出来可能更好,特别是在需要处理错误的时候。函log.Fatalf用打印日信息,然后调用os.Exit(1)止程序。
```Go
var cwd string
@@ -125,9 +125,9 @@ func init() {
}
```
然cwd在外部已經聲明過,但是`:=`語句還是將cwd和err重新聲明爲新的局部量。因爲內部聲明的cwd屏蔽外部的明,因此上面的代碼併不會正確更新包級聲明的cwd量。
然cwd在外部已经声明过,但是`:=`语句还是将cwd和err重新声明为新的局部量。因为内部声明的cwd屏蔽外部的明,因此上面的代码并不会正确更新包级声明的cwd量。
於當前的編譯器會檢測到局部明的cwd併沒有本使用,然後報告這可能是一個錯誤,但是這種檢測併不可靠。因一些小的代碼變更,例如增加一局部cwd的打印句,就可能導致這種檢測失效。
于当前的编译器会检测到局部明的cwd并没有本使用,然后报告这可能是一个错误,但是这种检测并不可靠。因一些小的代码变更,例如增加一局部cwd的打印句,就可能导致这种检测失效。
```Go
var cwd string
@@ -141,9 +141,9 @@ func init() {
}
```
全局的cwd量依然是有被正初始化的,而且看似正常的日誌輸出更是讓這個BUG更加晦。
全局的cwd量依然是有被正初始化的,而且看似正常的日志输出更是让这个BUG更加晦。
多方式可以避免出現類似潛在的問題。最直接的方法是通過單獨聲明err量,避免使用`:=`簡短聲明方式:
多方式可以避免出现类似潜在的问题。最直接的方法是通过单独声明err量,避免使用`:=`简短声明方式:
```Go
var cwd string
@@ -157,5 +157,5 @@ func init() {
}
```
們已經看到包、文件、明和句如何來表達一個程序結構。在下面的兩個章節,我們將探討數據的結構
们已经看到包、文件、明和句如何来表达一个程序结构。在下面的两个章节,我们将探讨数据的结构

View File

@@ -1,5 +1,5 @@
# 第2章 程序結構
# 第2章 程序结构
Go言和其他編程語言一,一大的程序是由很多小的基礎構件組成的。量保存值,簡單的加法和減法運算被合成較複雜的表式。基礎類型被聚合爲數組或結構體等更複雜的數據結構。然使用if和for之的控製語句來組織和控製表達式的行流程。然後多個語句被組織到一個個函數中,以便代的隔離和複用。函以源文件和包的方式被組織
Go言和其他编程语言一,一大的程序是由很多小的基础构件组成的。量保存值,简单的加法和减法运算被合成较复杂的表式。基础类型被聚合为数组或结构体等更复杂的数据结构。然使用if和for之的控制语句来组织和控制表达式的行流程。然后多个语句被组织到一个个函数中,以便代的隔离和复用。函以源文件和包的方式被组织
們已經在前面章的例子中看到了很多例子。在本章中,我們將深入討論Go程序基礎結構方面的一些細節。每示例程序都是刻意寫的簡單,這樣我們可以減少複雜的算法或數據結構等不相關的問題帶來的榦擾,從而可以專註於Go言本身的學習
们已经在前面章的例子中看到了很多例子。在本章中,我们将深入讨论Go程序基础结构方面的一些细节。每示例程序都是刻意写的简单,这样我们可以减少复杂的算法或数据结构等不相关的问题带来的干扰,从而可以专注于Go言本身的学习