mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-19 04:04:20 +08:00
rebuild
This commit is contained in:
@@ -21,6 +21,10 @@
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-katex/katex.min.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="2.1" data-chapter-title="命名" data-filepath="ch2/ch2-01.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="2.1" data-chapter-title="命名" data-filepath="ch2/ch2-01.md" data-basepath=".." data-revision="Mon Dec 28 2015 16:03:52 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -238,7 +242,7 @@
|
||||
|
||||
<b>1.5.</b>
|
||||
|
||||
穫取URL
|
||||
獲取URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -253,7 +257,7 @@
|
||||
|
||||
<b>1.6.</b>
|
||||
|
||||
併發穫取多個URL
|
||||
併發獲取多個URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -802,7 +806,7 @@
|
||||
|
||||
<b>5.10.</b>
|
||||
|
||||
Recover捕穫異常
|
||||
Recover捕獲異常
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1315,7 +1319,7 @@
|
||||
|
||||
<b>8.9.</b>
|
||||
|
||||
併發的退齣
|
||||
併發的退出
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1834,7 +1838,7 @@
|
||||
|
||||
<b>12.7.</b>
|
||||
|
||||
穫取結構體字段標識
|
||||
獲取結構體字段標識
|
||||
</a>
|
||||
|
||||
|
||||
@@ -2020,28 +2024,28 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="21-命名">2.1. 命名</h2>
|
||||
<p>Go語言中的的函數名, 變量名, 常量名, 類型名, 語句段標籤名, 和 包名 等所有的命名, 都遵循一個命名規則: 一個名字必鬚以一個字母(Unicode字母)或下劃線開頭, 後面可以跟任意數量的字母,數字或下劃線. 不同大小寫字母是不同的: <code>heapSort</code> 和 <code>Heapsort</code> 是兩個不同的名字.</p>
|
||||
<p>Go語言類似 <code>if</code> 和 <code>switch</code> 的關鍵字有25個; 關鍵字不能用於自定義名字, 隻能在特定語法中使用.</p>
|
||||
<p>Go語言中的函數名、變量名、常量名、類型名、語句標號和包名等所有的命名,都遵循一個簡單的命名規則:一個名字必鬚以一個字母(Unicode字母)或下劃線開頭,後面可以跟任意數量的字母、數字或下劃線。大寫字母和小寫字母是不同的:heapSort和Heapsort是兩個不同的名字。</p>
|
||||
<p>Go語言中類似if和switch的關鍵字有25個;關鍵字不能用於自定義名字,隻能在特定語法結構中使用。</p>
|
||||
<pre><code>break default func interface select
|
||||
case defer go map struct
|
||||
chan else goto package switch
|
||||
const fallthrough if range type
|
||||
continue for import return var
|
||||
</code></pre><p>此外, 還有大約30多個預先定義的名字, 比如 <code>int</code> 和 <code>true</code> 等, 主要用於內建的常量, 類型, 和 函數.</p>
|
||||
<pre><code>Constants: true false iota nil
|
||||
</code></pre><p>此外,還有大約30多個預定義的名字,比如int和true等,主要對應內建的常量、類型和函數。</p>
|
||||
<pre><code>內建常量: true false iota nil
|
||||
|
||||
Types: int int8 int16 int32 int64
|
||||
uint uint8 uint16 uint32 uint64 uintptr
|
||||
float32 float64 complex128 complex64
|
||||
bool byte rune string error
|
||||
內建類型: int int8 int16 int32 int64
|
||||
uint uint8 uint16 uint32 uint64 uintptr
|
||||
float32 float64 complex128 complex64
|
||||
bool byte rune string error
|
||||
|
||||
Functions: make len cap new append copy close delete
|
||||
complex real imag
|
||||
panic recover
|
||||
</code></pre><p>這些內部預先定義的名字不是關鍵字, 你可以在定義中重現使用它們. 在一些特殊的場景重新定義是有意義的, 但是也要註意避免引起混亂.</p>
|
||||
<p>如果一個實體是在函數內部定義, 那麽它的就隻在函數內部有效. 如果是在函數外部定義, 那麽將在當前包的所有文件中都可以訪問. 名字的開頭字母的大小寫決定了名字在包外的可見性. 如果一個名字是大寫字母開頭的, 那麽它將是導齣的, 也就是可以被外部的包訪問, 例如 <code>fmt</code> 包的 <code>Printf</code> 函數就是導齣的, 可以在 <code>fmt</code> 包外部訪問. 包本身的名字一般總是用小寫字母.</p>
|
||||
<p>名字的長度沒有限製, 但是Go的風格是盡量使用短小的名字, 對於局部變量尤其是這樣; 你會經常看到 <code>i</code> 之類的名字, 而是冗長的 <code>theLoopIndex</code>. 通常來説, 如果一個名字的作用域比較大, 生命週期較長, 那麽用長的名字將更有意義.</p>
|
||||
<p>在習慣上, Go程序員推薦使用<code>駝峯式</code>命名, 當名字有幾個單詞的時優先使用大小寫分隔, 而不是優先用下劃線分隔. 因此, 標準庫有 <code>QuoteRuneToASCII</code> 和 <code>parseRequestLine</code> 這樣的函數命名, 但是不會用 <code>quote_rune_to_ASCII</code> 和 <code>parse_request_line</code> 這樣的命名. 像 <code>ASCII</code> 和 <code>HTML</code> 這樣的縮略詞避免使用大小寫混合, 它們可能被稱爲 <code>htmlEscape</code>, <code>HTMLEscape</code> 或 <code>escapeHTML</code>, 但不會是 <code>escapeHtml</code>.</p>
|
||||
內建函數: make len cap new append copy close delete
|
||||
complex real imag
|
||||
panic recover
|
||||
</code></pre><p>這些內部預先定義的名字併不是關鍵字,你可以再定義中重新使用它們。在一些特殊的場景中重新定義它們也是有意義的,但是也要註意避免過度而引起語義混亂。</p>
|
||||
<p>如果一個名字是在函數內部定義,那麽它的就隻在函數內部有效。如果是在函數外部定義,那麽將在當前包的所有文件中都可以訪問。名字的開頭字母的大小寫決定了名字在包外的可見性。如果一個名字是大寫字母開頭的(譯註:必鬚是在函數外部定義的包級名字;包級函數名本身也是包級名字),那麽它將是導出的,也就是説可以被外部的包訪問,例如fmt包的Printf函數就是導出的,可以在fmt包外部訪問。包本身的名字一般總是用小寫字母。</p>
|
||||
<p>名字的長度沒有邏輯限製,但是Go語言的風格是盡量使用短小的名字,對於局部變量尤其是這樣;你會經常看到i之類的短名字,而不是冗長的theLoopIndex命名。通常來説,如果一個名字的作用域比較大,生命週期也比較長,那麽用長的名字將會更有意義。</p>
|
||||
<p>在習慣上,Go語言程序員推薦使用 <strong>駝峯式</strong> 命名,當名字有幾個單詞組成的時優先使用大小寫分隔,而不是優先用下劃線分隔。因此,在標準庫有QuoteRuneToASCII和parseRequestLine這樣的函數命名,但是一般不會用quote_rune_to_ASCII和parse_request_line這樣的命名。而像ASCII和HTML這樣的縮略詞則避免使用大小寫混合的寫法,它們可能被稱爲htmlEscape、HTMLEscape或escapeHTML,但不會是escapeHtml。</p>
|
||||
|
||||
|
||||
</section>
|
||||
@@ -2073,7 +2077,7 @@ Functions: make len cap new append copy close delete
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"katex":{},"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-katex/katex.min.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="2.2" data-chapter-title="聲明" data-filepath="ch2/ch2-02.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="2.2" data-chapter-title="聲明" data-filepath="ch2/ch2-02.md" data-basepath=".." data-revision="Mon Dec 28 2015 16:03:52 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -238,7 +242,7 @@
|
||||
|
||||
<b>1.5.</b>
|
||||
|
||||
穫取URL
|
||||
獲取URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -253,7 +257,7 @@
|
||||
|
||||
<b>1.6.</b>
|
||||
|
||||
併發穫取多個URL
|
||||
併發獲取多個URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -802,7 +806,7 @@
|
||||
|
||||
<b>5.10.</b>
|
||||
|
||||
Recover捕穫異常
|
||||
Recover捕獲異常
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1315,7 +1319,7 @@
|
||||
|
||||
<b>8.9.</b>
|
||||
|
||||
併發的退齣
|
||||
併發的退出
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1834,7 +1838,7 @@
|
||||
|
||||
<b>12.7.</b>
|
||||
|
||||
穫取結構體字段標識
|
||||
獲取結構體字段標識
|
||||
</a>
|
||||
|
||||
|
||||
@@ -2020,9 +2024,8 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="22-聲明">2.2. 聲明</h2>
|
||||
<p>聲明定義了程序的入口以及部分或全部的屬性. Go主要有四種聲明類型: var, const, type, 和 func, 分别對應 變量, 常量, 類型, 和 函數的 聲明. 這一章我們重點討論變量和類型的聲明, 第三章將討論常量的聲明, 第五章將討論函數的聲明.</p>
|
||||
<p>一個Go程序存儲在一個或多個以<code>.go</code>爲後綴名的文件中. 每個文件以個包的聲明開始, 以説明文件是屬於包的一部分.
|
||||
包聲明之後是 import 導入聲明, 然後是包一級的類型/變量/常量/函數的聲明, 聲明的順序無關緊要. 例如, 下面的例子聲明了一個常量, 一個函數和兩個變量:</p>
|
||||
<p>聲明語句定義了程序的各種實體對象以及部分或全部的屬性。Go語言主要有四種類型的聲明語句:var、const、type和func,分别對應變量、常量、類型和函數實體對象的聲明。這一章我們重點討論變量和類型的聲明,第三章將討論常量的聲明,第五章將討論函數的聲明。</p>
|
||||
<p>一個Go語言編寫的程序對應一個或多個以.go爲文件後綴名的源文件中。每個源文件以包的聲明語句開始,説明該源文件是屬於哪個包。包聲明語句之後是import語句導入依賴的其它包,然後是包一級的類型、變量、常量、函數的聲明語句,包一級的各種類型的聲明語句的順序無關緊要(譯註:函數內部的名字則必鬚先聲明之後才能使用)。例如,下面的例子中聲明了一個常量、一個函數和兩個變量:</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch2/boiling
|
||||
<span class="hljs-comment">// Boiling prints the boiling point of water.</span>
|
||||
<span class="hljs-keyword">package</span> main
|
||||
@@ -2039,9 +2042,9 @@
|
||||
<span class="hljs-comment">// boiling point = 212°F or 100°C</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>其中 常量 <code>boilingF</code> 是在包一級聲明的, 然後 <code>f</code> 和 <code>c</code> 是在 main 函數內部聲明的. 在包一級聲明的名字可在整個包訪問, 而不僅僅在其聲明的文件中訪問. 相比之下, 局部聲明的名字就隻能在函數內部很小的部分可訪問.</p>
|
||||
<p>一個函數的聲明有一個函數名字, 參數列表(由函數的調用者提供參數變量的具體值), 一個可選的返迴值列表, 和包含函數語句定義的函數體. 如果函數沒有返迴值, 那麽返迴值列表是省略的. 執行函數從函數的第一個語句開始, 但是順序執行直到遇到 renturn 返迴語言, 如果沒有返迴語句則是到函數末尾, 然後返迴到調用者.</p>
|
||||
<p>我們已經看到過很多函數的例子了, 在第五章將深入討論函數的細節, 這里隻粗略説下. 下面的 <code>fToC</code> 函數封裝了溫度轉換的邏輯, 這樣它隻需要定義一次, 就可以在多個地方多次使用. 這個例子中, main 函數就調用了兩次 <code>fToC</code> 函數, 分别是使用局部定義的兩個常量作爲函數參數.</p>
|
||||
<p>其中常量boilingF是在包一級范圍聲明語句聲明的,然後f和c兩個變量是在main函數內部聲明的聲明語句聲明的。在包一級聲明語句聲明的名字可在整個包對應的每個源文件中訪問,而不是僅僅在其聲明語句所在的源文件中訪問。相比之下,局部聲明的名字就隻能在函數內部很小的范圍被訪問。</p>
|
||||
<p>一個函數的聲明由一個函數名字、參數列表(由函數的調用者提供參數變量的具體值)、一個可選的返迴值列表和包含函數定義的函數體組成。如果函數沒有返迴值,那麽返迴值列表是省略的。執行函數從函數的第一個語句開始,依次順序執行直到遇到renturn返迴語句,如果沒有返迴語句則是執行到函數末尾,然後返迴到函數調用者。</p>
|
||||
<p>我們已經看到過很多函數聲明和函數調用的例子了,在第五章將深入討論函數的相關細節,這里隻簡單解釋下。下面的fToC函數封裝了溫度轉換的處理邏輯,這樣它隻需要被定義一次,就可以在多個地方多次被使用。在這個例子中,main函數就調用了兩次fToC函數,分别是使用在局部定義的兩個常量作爲調用函數的參數。</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch2/ftoc
|
||||
<span class="hljs-comment">// Ftoc prints two Fahrenheit-to-Celsius conversions.</span>
|
||||
<span class="hljs-keyword">package</span> main
|
||||
@@ -2089,7 +2092,7 @@
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"katex":{},"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
158
ch2/ch2-03.html
158
ch2/ch2-03.html
@@ -21,6 +21,10 @@
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-katex/katex.min.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="2.3" data-chapter-title="變量" data-filepath="ch2/ch2-03.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="2.3" data-chapter-title="變量" data-filepath="ch2/ch2-03.md" data-basepath=".." data-revision="Mon Dec 28 2015 16:03:52 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -238,7 +242,7 @@
|
||||
|
||||
<b>1.5.</b>
|
||||
|
||||
穫取URL
|
||||
獲取URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -253,7 +257,7 @@
|
||||
|
||||
<b>1.6.</b>
|
||||
|
||||
併發穫取多個URL
|
||||
併發獲取多個URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -802,7 +806,7 @@
|
||||
|
||||
<b>5.10.</b>
|
||||
|
||||
Recover捕穫異常
|
||||
Recover捕獲異常
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1315,7 +1319,7 @@
|
||||
|
||||
<b>8.9.</b>
|
||||
|
||||
併發的退齣
|
||||
併發的退出
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1834,7 +1838,7 @@
|
||||
|
||||
<b>12.7.</b>
|
||||
|
||||
穫取結構體字段標識
|
||||
獲取結構體字段標識
|
||||
</a>
|
||||
|
||||
|
||||
@@ -2020,45 +2024,44 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="23-變量">2.3. 變量</h2>
|
||||
<p>var 聲明可以創建一個特定類型的變量, 然後給變量附加一個名字, 併且設置變量的初始值. 變量聲明的一般語法:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> name <span class="hljs-keyword">type</span> = 表達式
|
||||
<p>var聲明語句可以創建一個特定類型的變量,然後給變量附加一個名字,併且設置變量的初始值。變量聲明的一般語法如下:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> 變量名字 類型 = 表達式
|
||||
</code></pre>
|
||||
<p>其中類型或 <code>= 表達式</code> 可以省略其中的一個. 如果省略的是類型信息, 那麽將根據初始化表達式類推導類型信息. 如果初始化表達式被省略, 那麽將用零值初始化變量. 數值類型變量的零值是0, 布爾類型變量的零值是 false, 字符串的零值是空字符串, 接口或引用類型(包括 切片, 字典, 通道 和 函數)的變量的零值是 nil. 數組或結構體等聚合類型的零值是每個元素或字段都是零值.</p>
|
||||
<p>零值機製可以確保每個聲明的變量總是有一個良好定義的值, 在 Go 中不存在未初始化的變量. 這個可以簡化很多代碼, 在沒有增加額外工作的前提下確保邊界條件下的合理行爲. 例如:</p>
|
||||
<p>其中“<em>類型</em>”或“<em>= 表達式</em>”兩個部分可以省略其中的一個。如果省略的是類型信息,那麽將根據初始化表達式來推導變量的類型信息。如果初始化表達式被省略,那麽將用零值初始化該變量。 數值類型變量對應的零值是0,布爾類型變量對應的零值是false,字符串類型對應的零值是空字符串,接口或引用類型(包括slice、map、chan和函數)變量對應的零值是nil。數組或結構體等聚合類型對應的零值是每個元素或字段都是對應該類型的零值。</p>
|
||||
<p>零值初始化機製可以確保每個聲明的變量總是有一個良好定義的值,因此在Go語言中不存在未初始化的變量。這個特性可以簡化很多代碼,而且可以在沒有增加額外工作的前提下確保邊界條件下的合理行爲。例如:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> s <span class="hljs-typename">string</span>
|
||||
fmt.Println(s) <span class="hljs-comment">// ""</span>
|
||||
</code></pre>
|
||||
<p>這段代碼將打印一個空字符串, 而不是導致錯誤或産生不可預知的行爲. Go 程序員經常讓一些聚合類型的零值也有意義, 這樣不管任何類型的變量總是有一個合理的零值狀態.</p>
|
||||
<p>可以在一個聲明語句中同時聲明一組變量, 或用一組初始化表達式聲明併初始化一組變量.
|
||||
如果省略每個變量的類型, 將可以聲明多個不同類型的變量(類型由初始化表達式推導):</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> i, j, k <span class="hljs-typename">int</span> <span class="hljs-comment">// int, int, int</span>
|
||||
<p>這段代碼將打印一個空字符串,而不是導致錯誤或産生不可預知的行爲。Go語言程序員應該讓一些聚合類型的零值也具有意義,這樣可以保證不管任何類型的變量總是有一個合理有效的零值狀態。</p>
|
||||
<p>也可以在一個聲明語句中同時聲明一組變量,或用一組初始化表達式聲明併初始化一組變量。如果省略每個變量的類型,將可以聲明多個類型不同的變量(類型由初始化表達式推導):</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> i, j, k <span class="hljs-typename">int</span> <span class="hljs-comment">// int, int, int</span>
|
||||
<span class="hljs-keyword">var</span> b, f, s = <span class="hljs-constant">true</span>, <span class="hljs-number">2.3</span>, <span class="hljs-string">"four"</span> <span class="hljs-comment">// bool, float64, string</span>
|
||||
</code></pre>
|
||||
<p>初始化可以是字面量或任意的表達式. 包級别聲明的變量會在 main 函數執行前完成初始化 (§2.6.2), 局部變量將在聲明語句被執行到的時候初始化.</p>
|
||||
<p>一組變量的初始化也可以通過調用一個函數, 由函數返迴的多個返迴值初始化:</p>
|
||||
<p>初始化表達式可以是字面量或任意的表達式。在包級别聲明的變量會在main入口函數執行前完成初始化(§2.6.2),局部變量將在聲明語句被執行到的時候完成初始化。</p>
|
||||
<p>一組變量也可以通過調用一個函數,由函數返迴的多個返迴值初始化:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> f, err = os.Open(name) <span class="hljs-comment">// os.Open returns a file and an error</span>
|
||||
</code></pre>
|
||||
<h3 id="231-簡短變量聲明">2.3.1. 簡短變量聲明</h3>
|
||||
<p>在函數內部, 有一種稱爲簡短變量聲明的形式可用於聲明和初始化局部變量. 以 <code>名字 := 表達式</code> 方式聲明變量, 變量的類型根據表達式來推導. 這里函數中是三個簡短變量聲明語句(§1.4):</p>
|
||||
<p>在函數內部,有一種稱爲簡短變量聲明語句的形式可用於聲明和初始化局部變量。它以“名字 := 表達式”形式聲明變量,變量的類型根據表達式來自動推導。下面是lissajous函數中的三個簡短變量聲明語句(§1.4):</p>
|
||||
<pre><code class="lang-Go">anim := gif.GIF{LoopCount: nframes}
|
||||
freq := rand.Float64() * <span class="hljs-number">3.0</span>
|
||||
t := <span class="hljs-number">0.0</span>
|
||||
</code></pre>
|
||||
<p>因爲簡潔和靈活性, 簡短變量聲明用於大部分的局部變量的聲明和初始化. var 方式的聲明往往是用於需要顯示指定類型的局部變量, 或者因爲稍後會被賦值而初始值無關緊要的變量.</p>
|
||||
<pre><code class="lang-Go">i := <span class="hljs-number">100</span> <span class="hljs-comment">// an int</span>
|
||||
<p>因爲簡潔和靈活的特點,簡短變量聲明被廣泛用於大部分的局部變量的聲明和初始化。var形式的聲明語句往往是用於需要顯式指定變量類型地方,或者因爲變量稍後會被重新賦值而初始值無關緊要的地方。</p>
|
||||
<pre><code class="lang-Go">i := <span class="hljs-number">100</span> <span class="hljs-comment">// an int</span>
|
||||
<span class="hljs-keyword">var</span> boiling <span class="hljs-typename">float64</span> = <span class="hljs-number">100</span> <span class="hljs-comment">// a float64</span>
|
||||
<span class="hljs-keyword">var</span> names []<span class="hljs-typename">string</span>
|
||||
<span class="hljs-keyword">var</span> err error
|
||||
<span class="hljs-keyword">var</span> p Point
|
||||
</code></pre>
|
||||
<p>於 var 聲明變量一樣, 簡短變量聲明也可以用來聲明和初始化一組變量:</p>
|
||||
<p>和var形式聲明變語句一樣,簡短變量聲明語句也可以用來聲明和初始化一組變量:</p>
|
||||
<pre><code class="lang-Go">i, j := <span class="hljs-number">0</span>, <span class="hljs-number">1</span>
|
||||
</code></pre>
|
||||
<p>但是這種聲明多個變量的方式隻簡易在可以提高代碼可讀性的地方使用, 比如 for 循環的初始化部分.</p>
|
||||
<p>請記住 <code>:=</code> 是一個變量聲明, 而 <code>=</code> 是一個賦值操作. 不要混淆多個變量的聲明和元組的多重(§2.4.1), 後者是將右邊的表達式值賦給左邊對應位置的變量:</p>
|
||||
<p>但是這種同時聲明多個變量的方式應該限製隻在可以提高代碼可讀性的地方使用,比如for語句的循環的初始化語句部分。</p>
|
||||
<p>請記住“:=”是一個變量聲明語句,而“=‘是一個變量賦值操作。也不要混淆多個變量的聲明和元組的多重賦值(§2.4.1),後者是將右邊各個的表達式值賦值給左邊對應位置的各個變量:</p>
|
||||
<pre><code class="lang-Go">i, j = j, i <span class="hljs-comment">// 交換 i 和 j 的值</span>
|
||||
</code></pre>
|
||||
<p>和普通 var 變量聲明一樣, 簡短變量聲明也可以用調用函數的返迴值來聲明, 像 os.Open 函數返迴兩個值:</p>
|
||||
<p>和普通var形式的變量聲明語句一樣,簡短變量聲明語句也可以用函數的返迴值來聲明和初始化變量,像下面的os.Open函數調用將返迴兩個值:</p>
|
||||
<pre><code class="lang-Go">f, err := os.Open(name)
|
||||
<span class="hljs-keyword">if</span> err != <span class="hljs-constant">nil</span> {
|
||||
<span class="hljs-keyword">return</span> err
|
||||
@@ -2066,36 +2069,36 @@ t := <span class="hljs-number">0.0</span>
|
||||
<span class="hljs-comment">// ...use f...</span>
|
||||
f.Close()
|
||||
</code></pre>
|
||||
<p>這里有一個比較微妙的地方: 簡短變量聲明左邊的全部變量可能併不是全部都是剛剛聲明的. 如果有一些已經在相同的詞法塊聲明過了(§2.7), 那麽簡短變量聲明對這些已經聲明過的變量就隻有賦值行爲了.</p>
|
||||
<p>在下面的代碼中, 第一個語句聲明了 in 和 err 變量. 第二個語句隻聲明了 out, 然後對已經聲明的 err 進行賦值.</p>
|
||||
<p>這里有一個比較微妙的地方:簡短變量聲明左邊的變量可能併不是全部都是剛剛聲明的。如果有一些已經在相同的詞法域聲明過了(§2.7),那麽簡短變量聲明語句對這些已經聲明過的變量就隻有賦值行爲了。</p>
|
||||
<p>在下面的代碼中,第一個語句聲明了in和err兩個變量。在第二個語句隻聲明了out一個變量,然後對已經聲明的err進行了賦值操作。</p>
|
||||
<pre><code class="lang-Go">in, err := os.Open(infile)
|
||||
<span class="hljs-comment">// ...</span>
|
||||
out, err := os.Create(outfile)
|
||||
</code></pre>
|
||||
<p>簡短變量聲明必鬚至少聲明一個新的變量, 否則編譯將不能通過:</p>
|
||||
<p>簡短變量聲明語句中必鬚至少要聲明一個新的變量,下面的代碼將不能編譯通過:</p>
|
||||
<pre><code class="lang-Go">f, err := os.Open(infile)
|
||||
<span class="hljs-comment">// ...</span>
|
||||
f, err := os.Create(outfile) <span class="hljs-comment">// compile error: no new variables</span>
|
||||
</code></pre>
|
||||
<p>解決的方法是第二個語句改用普通的賦值語言.</p>
|
||||
<p>簡短變量聲明隻有對在變量已經在同級詞法域聲明過的變量纔和賦值操作等同, 如果變量是在外部詞法域聲明了, 那麽將會聲明一個新變量. 我們在本章後面將會看到類似的例子.</p>
|
||||
<h3 id="232-指針">2.3.2 指針</h3>
|
||||
<p>一個變量對應一個保存了一個值的內存空間. 變量在聲明語句創建時綁定一個名字, 比如 x, 但是還有很多變量始終以表達式方式引入, 例如 x[i] 或 x.f. 所有這些表達式都讀取一個變量的值, 除非它們是齣現在賦值語句的左邊, 這種時候是給變量賦予一個新值.</p>
|
||||
<p>一個指針的值是一個變量的地址. 一個指針對應變量在內存中的存儲位置. 併不是每一個值都會有一個地址, 但是對於每一個變量必然有對應的地址. 通過指針, 我們可以直接讀或更新變量的值, 而不需要知道變量的名字(卽使變量有名字的話).</p>
|
||||
<p>如果這樣聲明一個變量 <code>var x int</code>, 那麽 <code>&x</code> 表達式(x的地址)將産生一個指向整數變量的指針, 對應的數據類型是 <code>*int</code>, 稱之爲 "指向 int 的指針". 如果指針名字爲 p, 那麽可以説 "p 指針指向 x", 或者説 "p 指針保存了 x 變量的地址". <code>*p</code> 對應 p 指針指向的變量的值. <code>*p</code> 表達式讀取變量的值, 爲 int 類型, 同時因爲 <code>*p</code> 對應一個變量, 所以可以齣現在賦值語句的左邊, 用於更新所指向的變量的值.</p>
|
||||
<p>解決的方法是第二個簡短變量聲明語句改用普通的多重賦值語言。</p>
|
||||
<p>簡短變量聲明語句隻有對已經在同級詞法域聲明過的變量才和賦值操作語句等價,如果變量是在外部詞法域聲明的,那麽簡短變量聲明語句將會在當前詞法域重新聲明一個新的變量。我們在本章後面將會看到類似的例子。</p>
|
||||
<h3 id="232-指針">2.3.2. 指針</h3>
|
||||
<p>一個變量對應一個保存了變量對應類型值的內存空間。普通變量在聲明語句創建時被綁定到一個變量名,比如叫x的變量,但是還有很多變量始終以表達式方式引入,例如x[i]或x.f變量。所有這些表達式一般都是讀取一個變量的值,除非它們是出現在賦值語句的左邊,這種時候是給對應變量賦予一個新的值。</p>
|
||||
<p>一個指針的值是另一個變量的地址。一個指針對應變量在內存中的存儲位置。併不是每一個值都會有一個內存地址,但是對於每一個變量必然有對應的內存地址。通過指針,我們可以直接讀或更新對應變量的值,而不需要知道該變量的名字(如果變量有名字的話)。</p>
|
||||
<p>如果用“var x int”聲明語句聲明一個x變量,那麽&x表達式(取x變量的內存地址)將産生一個指向該整數變量的指針,指針對應的數據類型是<code>*int</code>,指針被稱之爲“指向int類型的指針”。如果指針名字爲p,那麽可以説“p指針指向變量x”,或者説“p指針保存了x變量的內存地址”。同時<code>*p</code>表達式對應p指針指向的變量的值。一般<code>*p</code>表達式讀取指針指向的變量的值,這里爲int類型的值,同時因爲<code>*p</code>對應一個變量,所以該表達式也可以出現在賦值語句的左邊,表示更新指針所指向的變量的值。</p>
|
||||
<pre><code class="lang-Go">x := <span class="hljs-number">1</span>
|
||||
p := &x <span class="hljs-comment">// p, of type *int, points to x</span>
|
||||
fmt.Println(*p) <span class="hljs-comment">// "1"</span>
|
||||
*p = <span class="hljs-number">2</span> <span class="hljs-comment">// equivalent to x = 2</span>
|
||||
fmt.Println(x) <span class="hljs-comment">// "2"</span>
|
||||
</code></pre>
|
||||
<p>對於聚合類型, 比如結構體的每個字段, 或者是數組的每個元素, 也都是對應一個變量, 併且可以被穫取地址.</p>
|
||||
<p>變量有時候被稱爲可尋址的值. 如果變量由表達式臨時生成, 那麽表達式必鬚能接受 <code>&</code> 取地址操作.</p>
|
||||
<p>任何類型的指針的零值都是 nil. 如果 <code>p != nil</code> 測試爲眞, 那麽 p 是指向變量. 指針直接也是可以進行相等測試的, 隻有當它們指向同一個變量或全部是 nil 時纔相等.</p>
|
||||
<p>對於聚合類型每個成員——比如結構體的每個字段、或者是數組的每個元素——也都是對應一個變量,因此可以被取地址。</p>
|
||||
<p>變量有時候被稱爲可尋址的值。卽使變量由表達式臨時生成,那麽表達式也必鬚能接受<code>&</code>取地址操作。</p>
|
||||
<p>任何類型的指針的零值都是nil。如果<code>p != nil</code>測試爲眞,那麽p是指向某個有效變量。指針之間也是可以進行相等測試的,隻有當它們指向同一個變量或全部是nil時才相等。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> x, y <span class="hljs-typename">int</span>
|
||||
fmt.Println(&x == &x, &x == &y, &x == <span class="hljs-constant">nil</span>) <span class="hljs-comment">// "true false false"</span>
|
||||
</code></pre>
|
||||
<p>在Go語言中, 返迴函數中局部變量的地址是安全的. 例如下面的代碼, 調用 f 函數時創建 v 局部變量, 在地址被返迴之後依然有效, 因爲指針 p 依然引用這個變量.</p>
|
||||
<p>在Go語言中,返迴函數中局部變量的地址也是安全的。例如下面的代碼,調用f函數時創建局部變量v,在局部變量地址被返迴之後依然有效,因爲指針p依然引用這個變量。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> p = f()
|
||||
|
||||
<span class="hljs-keyword">func</span> f() *<span class="hljs-typename">int</span> {
|
||||
@@ -2103,12 +2106,12 @@ fmt.Println(&x == &x, &x == &y, &x == <span class="hljs-cons
|
||||
<span class="hljs-keyword">return</span> &v
|
||||
}
|
||||
</code></pre>
|
||||
<p>每次調用 f 函數都將返迴不同的結果:</p>
|
||||
<p>每次調用f函數都將返迴不同的結果:</p>
|
||||
<pre><code class="lang-Go">fmt.Println(f() == f()) <span class="hljs-comment">// "false"</span>
|
||||
</code></pre>
|
||||
<p>因爲指針包含了一個變量的地址, 因此將指針作爲參數調用函數, 將可以在函數中通過指針更新變量的值. 例如這個通過指針來更新變量的值, 然後返迴更新後的值, 可用在一個表達式中:</p>
|
||||
<p>因爲指針包含了一個變量的地址,因此如果將指針作爲參數調用函數,那將可以在函數中通過該指針來更新變量的值。例如下面這個例子就是通過指針來更新變量的值,然後返迴更新後的值,可用在一個表達式中(譯註:這是對C語言中<code>++v</code>操作的模擬,這里隻是爲了説明指針的用法,incr函數模擬的做法併不推薦):</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> incr(p *<span class="hljs-typename">int</span>) <span class="hljs-typename">int</span> {
|
||||
*p++ <span class="hljs-comment">// increments what p points to; does not change p</span>
|
||||
*p++ <span class="hljs-comment">// 非常重要:隻是增加p指向的變量的值,併不改變p指針!!!</span>
|
||||
<span class="hljs-keyword">return</span> *p
|
||||
}
|
||||
|
||||
@@ -2116,8 +2119,8 @@ v := <span class="hljs-number">1</span>
|
||||
incr(&v) <span class="hljs-comment">// side effect: v is now 2</span>
|
||||
fmt.Println(incr(&v)) <span class="hljs-comment">// "3" (and v is 3)</span>
|
||||
</code></pre>
|
||||
<p>每次我們對變量取地址, 或者複製指針, 我們都創建了變量的新的别名. 例如, *p 是 變量 v 的别名. 指針特别有加載的地方在於我們可以不用名字而訪問一個變量, 但是這是一把雙刃劍: 要找到一個變量的所有訪問者, 我們必鬚知道變量全部的别名. 不僅僅是指針創建别名, 很多其他引用類型也會創建别名, 例如 切片, 字典和管道, 甚至結構體, 數組和接口都會創建所引用變量的别名.</p>
|
||||
<p>指針是 flag 包的關鍵, 它使用命令行參數來設置對應的變量, 而這些分布在整個程序中. 爲了説明這一點, 在早些的echo版本中, 包含了兩個可選的命令行參數: <code>-n</code> 用於忽略行尾的換行符, <code>-s sep</code> 用於指定分隔字符(默認是空格). 這是第四個版本, 對應包 gopl.io/ch2/echo4.</p>
|
||||
<p>每次我們對一個變量取地址,或者複製指針,我們都是爲原變量創建了新的别名。例如,<code>*p</code>就是是 變量v的别名。指針特别有價值的地方在於我們可以不用名字而訪問一個變量,但是這是一把雙刃劍:要找到一個變量的所有訪問者併不容易,我們必鬚知道變量全部的别名(譯註:這是Go語言的垃圾迴收器所做的工作)。不僅僅是指針會創建别名,很多其他引用類型也會創建别名,例如slice、map和chan,甚至結構體、數組和接口都會創建所引用變量的别名。</p>
|
||||
<p>指針是實現標準庫中flag包的關鍵技術,它使用命令行參數來設置對應變量的值,而這些對應命令行標誌參數的變量可能會零散分布在整個程序中。爲了説明這一點,在早些的echo版本中,就包含了兩個可選的命令行參數:<code>-n</code>用於忽略行尾的換行符,<code>-s sep</code>用於指定分隔字符(默認是空格)。下面這是第四個版本,對應包路徑爲gopl.io/ch2/echo4。</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch2/echo4
|
||||
<span class="hljs-comment">// Echo4 prints its command-line arguments.</span>
|
||||
<span class="hljs-keyword">package</span> main
|
||||
@@ -2139,9 +2142,9 @@ fmt.Println(incr(&v)) <span class="hljs-comment">// "3" (and v is
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p><code>flag.Bool</code> 函數調用創建了一個新的布爾型標誌參數變量. 它有三個屬性: 第一個是的名字"n", 然後是標誌的默認值(這里是false), 最後是對應的描述信息. 如果用戶輸入了無效的標誌參數, 或者輸入 <code>-h</code> 或 <code>-help</code> 標誌參數, 將打印標誌參數的名字, 默認值和描述信息. 類似的, flag.String 用於創建一個字符串類型的標誌參數變量, 同樣包含參數名, 默認值, 和描述信息. 變量 <code>sep</code> 和 <code>n</code> 是一個指向標誌參數變量的指針, 因此必鬚用 <em>sep 和 </em>n 的方式間接引用.</p>
|
||||
<p>當程序運行時, 必鬚在標誌參數變量使用之前調用 flag.Parse 函數更新標誌參數變量的值(之前是默認值). 非標誌參數的普通類型參數可以用 flag.Args() 訪問, 對應一個 字符串切片. 如果 flag.Parse 解析遇到錯誤, 將打印提示信息, 然後調用 os.Exit(2) 終止程序.</p>
|
||||
<p>讓我們運行一些 echo 測試用例:</p>
|
||||
<p>調用flag.Bool函數會創建一個新的對應布爾型標誌參數的變量。它有三個屬性:第一個是的命令行標誌參數的名字“n”,然後是該標誌參數的默認值(這里是false),最後是該標誌參數對應的描述信息。如果用戶在命令行輸入了一個無效的標誌參數,或者輸入<code>-h</code>或<code>-help</code>參數,那麽將打印所有標誌參數的名字、默認值和描述信息。類似的,調用flag.String函數將於創建一個對應字符串類型的標誌參數變量,同樣包含命令行標誌參數對應的參數名、默認值、和描述信息。程序中的<code>sep</code>和<code>n</code>變量分别是指向對應命令行標誌參數變量的指針,因此必鬚用<code>*sep</code>和<code>*n</code>形式的指針語法間接引用它們。</p>
|
||||
<p>當程序運行時,必鬚在使用標誌參數對應的變量之前調用先flag.Parse函數,用於更新每個標誌參數對應變量的值(之前是默認值)。對於非標誌參數的普通命令行參數可以通過調用flag.Args()函數來訪問,返迴值對應對應一個字符串類型的slice。如果在flag.Parse函數解析命令行參數時遇到錯誤,默認將打印相關的提示信息,然後調用os.Exit(2)終止程序。</p>
|
||||
<p>讓我們運行一些echo測試用例:</p>
|
||||
<pre><code>$ go build gopl.io/ch2/echo4
|
||||
$ ./echo4 a bc def
|
||||
a bc def
|
||||
@@ -2154,56 +2157,65 @@ Usage of ./echo4:
|
||||
-n omit trailing newline
|
||||
-s string
|
||||
separator (default " ")
|
||||
</code></pre><h3 id="233-new-函數">2.3.3 new 函數</h3>
|
||||
<p>另一個創建變量的方法是用內建的 new 函數. 表達式 <code>new(T)</code> 創建一個T類型的匿名變量, 初始化爲T類型的零值, 返迴返迴變量地址, 返迴指針類型爲 <code>*T</code>.</p>
|
||||
</code></pre><h3 id="233-new函數">2.3.3. new函數</h3>
|
||||
<p>另一個創建變量的方法是調用用內建的new函數。表達式new(T)將創建一個T類型的匿名變量,初始化爲T類型的零值,然後返迴變量地址,返迴的指針類型爲<code>*T</code>。</p>
|
||||
<pre><code class="lang-Go">p := <span class="hljs-built_in">new</span>(<span class="hljs-typename">int</span>) <span class="hljs-comment">// p, *int 類型, 指向匿名的 int 變量</span>
|
||||
fmt.Println(*p) <span class="hljs-comment">// "0"</span>
|
||||
*p = <span class="hljs-number">2</span> <span class="hljs-comment">// 設置 int 匿名變量的值爲 2</span>
|
||||
fmt.Println(*p) <span class="hljs-comment">// "2"</span>
|
||||
</code></pre>
|
||||
<p>從 new 創建變量和普通聲明方式創建變量沒有什麽區别, 除了不需要聲明一個臨時變量的名字外, 我們還可以在表達式中使用 <code>new(T)</code>. 換言之, new 類似是一種語法醣, 而不是一個新的基礎概念.</p>
|
||||
<p>下面的兩個 newInt 函數有着相同的行爲:</p>
|
||||
<p>用new創建變量和普通變量聲明語句方式創建變量沒有什麽區别,除了不需要聲明一個臨時變量的名字外,我們還可以在表達式中使用new(T)。換言之,new函數類似是一種語法醣,而不是一個新的基礎概念。</p>
|
||||
<p>下面的兩個newInt函數有着相同的行爲:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> newInt() *<span class="hljs-typename">int</span> { <span class="hljs-keyword">func</span> newInt() *<span class="hljs-typename">int</span> {
|
||||
<span class="hljs-keyword">return</span> <span class="hljs-built_in">new</span>(<span class="hljs-typename">int</span>) <span class="hljs-keyword">var</span> dummy <span class="hljs-typename">int</span>
|
||||
} <span class="hljs-keyword">return</span> &dummy
|
||||
}
|
||||
</code></pre>
|
||||
<p>每次調用 new 都是返迴一個新的變量的地址, 因此下面兩個地址是不同的:</p>
|
||||
<p>每次調用new函數都是返迴一個新的變量的地址,因此下面兩個地址是不同的:</p>
|
||||
<pre><code class="lang-Go">p := <span class="hljs-built_in">new</span>(<span class="hljs-typename">int</span>)
|
||||
q := <span class="hljs-built_in">new</span>(<span class="hljs-typename">int</span>)
|
||||
fmt.Println(p == q) <span class="hljs-comment">// "false"</span>
|
||||
</code></pre>
|
||||
<p>當然也有特殊情況: 如果兩個類型都是空的, 也就是説類型的大小是0, 例如 <code>struct{}</code> 和 <code>[0]int</code>, 有可能有相同的地址(依賴具體的語言實現).</p>
|
||||
<p>new 函數使用相對比較少, 因爲對應結構體來説, 可以直接用字面量語法創建新變量的方法更靈活 (§4.4.1).</p>
|
||||
<p>由於 new 隻是一個預定義的函數, 它併不是一個關鍵字, 因此我們可以將 new 重新定義爲别的類型. 例如:</p>
|
||||
<p>當然也可能有特殊情況:如果兩個類型都是空的,也就是説類型的大小是0,例如<code>struct{}</code>和 <code>[0]int</code>, 有可能有相同的地址(依賴具體的語言實現)(譯註:請謹慎使用大小爲0的類型,因爲如果類型的大小位0好話,可能導致Go語言的自動垃圾迴收器有不同的行爲,具體請査看<code>runtime.SetFinalizer</code>函數相關文檔)。</p>
|
||||
<p>new函數使用常見相對比較少,因爲對應結構體來説,可以直接用字面量語法創建新變量的方法會更靈活(§4.4.1)。</p>
|
||||
<p>由於new隻是一個預定義的函數,它併不是一個關鍵字,因此我們可以將new名字重新定義爲别的類型。例如下面的例子:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> delta(old, <span class="hljs-built_in">new</span> <span class="hljs-typename">int</span>) <span class="hljs-typename">int</span> { <span class="hljs-keyword">return</span> <span class="hljs-built_in">new</span> - old }
|
||||
</code></pre>
|
||||
<p>因爲 new 被定義爲 int 類型的變量, 因此 delta 函數內部就無法在使用內置的 new 函數了.</p>
|
||||
<p>由於new被定義爲int類型的變量名,因此在delta函數內部是無法使用內置的new函數的。</p>
|
||||
<h3 id="234-變量的生命週期">2.3.4. 變量的生命週期</h3>
|
||||
<p>變量的生命週期指的是程序運行期間變量存在的有效時間間隔. 包級聲明的變量的生命週期和程序的生命週期是一致的. 相比之下, 局部變量的聲明週期是動態的: 從每次創建一個新變量的聲明語句被執行開始, 直到變量不在被引用爲止, 然後變量的存儲空間可能被迴收. 函數的參數變量和返迴值變量都是局部變量. 它們在函數每次被調用的時候創建.</p>
|
||||
<p>例如, 下面是從 1.4 節的 Lissajous 程序摘録的代碼片段:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">for</span> t := <span class="hljs-number">0.0</span>; t < cycles*<span class="hljs-number">2</span>*math.Pi; t += res {
|
||||
x := math.Sin(t)
|
||||
y := math.Sin(t*freq + phase)
|
||||
img.SetColorIndex(size+<span class="hljs-typename">int</span>(x*size+<span class="hljs-number">0.5</span>), size+<span class="hljs-typename">int</span>(y*size+<span class="hljs-number">0.5</span>),
|
||||
blackIndex)
|
||||
<p>變量的生命週期指的是在程序運行期間變量有效存在的時間間隔。對於在包一級聲明的變量來説,它們的生命週期和整個程序的運行週期是一致的。而相比之下,在局部變量的聲明週期則是動態的:從每次創建一個新變量的聲明語句開始,直到該變量不再被引用爲止,然後變量的存儲空間可能被迴收。函數的參數變量和返迴值變量都是局部變量。它們在函數每次被調用的時候創建。</p>
|
||||
<p>例如,下面是從1.4節的Lissajous程序摘録的代碼片段:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">for</span> t := <span class="hljs-number">0.0</span>; t < cycles*<span class="hljs-number">2</span>*math.Pi; t += res {
|
||||
x := math.Sin(t)
|
||||
y := math.Sin(t*freq + phase)
|
||||
img.SetColorIndex(size+<span class="hljs-typename">int</span>(x*size+<span class="hljs-number">0.5</span>), size+<span class="hljs-typename">int</span>(y*size+<span class="hljs-number">0.5</span>),
|
||||
blackIndex)
|
||||
}
|
||||
</code></pre>
|
||||
<p>在每次循環的開始創建變量 t, 然後在每次循環迭代中創建 x 和 y.</p>
|
||||
<p>那麽垃圾收集器是如何知道一個變量是何時可以被迴收的呢? 這里我們先避開完整的技術細節, 但是基本的思路是, 從每個包級的變量和每個當前運行函數的每一個局部變量開始, 通過指針或引用的路徑, 是否可以找到該變量. 如果不存在這樣的路徑, 那麽説明該變量是不可達的, 也就是説它併不會影響其餘的計算.</p>
|
||||
<p>因爲一個變量的聲明週期隻取決於是否可達, 因此一個循環迭代內部的局部變量的生命週期可能超齣其局部作用域. 它可能在函數返迴之後依然存在.</p>
|
||||
<p>編譯器會選擇在棧上還是在堆上分配局部變量的存儲空間, 但可能令人驚訝的是, 這個選擇併不是由 var 或 new 來決定的.</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> global *<span class="hljs-typename">int</span>
|
||||
<p>譯註:函數的有右小括弧也可以另起一行縮進,同時爲了防止編譯器在行尾自動插入分號而導致的編譯錯誤,可以在末尾的參數變量後面顯式插入逗號。像下面這樣:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">for</span> t := <span class="hljs-number">0.0</span>; t < cycles*<span class="hljs-number">2</span>*math.Pi; t += res {
|
||||
x := math.Sin(t)
|
||||
y := math.Sin(t*freq + phase)
|
||||
img.SetColorIndex(
|
||||
size+<span class="hljs-typename">int</span>(x*size+<span class="hljs-number">0.5</span>), size+<span class="hljs-typename">int</span>(y*size+<span class="hljs-number">0.5</span>),
|
||||
blackIndex, <span class="hljs-comment">// 最後插入的逗號不會導致編譯錯誤,這是Go編譯器的一個特性</span>
|
||||
) <span class="hljs-comment">// 小括弧另起一行縮進,和大括弧的風格保存一致</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>在每次循環的開始會創建臨時變量t,然後在每次循環迭代中創建臨時變量x和y。</p>
|
||||
<p>那麽垃Go語言的自動圾收集器是如何知道一個變量是何時可以被迴收的呢?這里我們可以避開完整的技術細節,基本的實現思路是,從每個包級的變量和每個當前運行函數的每一個局部變量開始,通過指針或引用的訪問路徑遍歷,是否可以找到該變量。如果不存在這樣的訪問路徑,那麽説明該變量是不可達的,也就是説它是否存在併不會影響程序後續的計算結果。</p>
|
||||
<p>因爲一個變量的有效週期隻取決於是否可達,因此一個循環迭代內部的局部變量的生命週期可能超出其局部作用域。同時,局部變量可能在函數返迴之後依然存在。</p>
|
||||
<p>編譯器會自動選擇在棧上還是在堆上分配局部變量的存儲空間,但可能令人驚訝的是,這個選擇併不是由用var還是new聲明變量的方式決定的。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> global *<span class="hljs-typename">int</span>
|
||||
|
||||
<span class="hljs-keyword">func</span> f() { <span class="hljs-keyword">func</span> g() {
|
||||
<span class="hljs-keyword">var</span> x <span class="hljs-typename">int</span> y := <span class="hljs-built_in">new</span>(<span class="hljs-typename">int</span>)
|
||||
x = <span class="hljs-number">1</span> *y = <span class="hljs-number">1</span>
|
||||
global = &x }
|
||||
<span class="hljs-keyword">func</span> f() { <span class="hljs-keyword">func</span> g() {
|
||||
<span class="hljs-keyword">var</span> x <span class="hljs-typename">int</span> y := <span class="hljs-built_in">new</span>(<span class="hljs-typename">int</span>)
|
||||
x = <span class="hljs-number">1</span> *y = <span class="hljs-number">1</span>
|
||||
global = &x }
|
||||
}
|
||||
</code></pre>
|
||||
<p>這里的 x 必鬚在堆上分配, 因爲它在函數退齣後依然可以通過包的 global 變量找到, 雖然它是在函數內部定義的; 我們説這個 x 局部變量從 函數 f 中逃逸了. 相反, 當 g 函數返迴時, 變量 <code>*y</code> 將是不可達的, 也就是可以被迴收的. 因此, <code>*y</code> 併沒有從 函數 g 逃逸, 編譯器可以選擇在棧上分配 <code>*y</code> 的存儲空間, 雖然這里用的是 new 方式.
|
||||
在任何時候, 你併不需爲了編寫正確的代碼而要考慮變量的逃逸行爲, 要記住的是, 逃逸的變量需要額外分配內存, 同時對性能的優化會産生一定的影響.</p>
|
||||
<p>垃圾收集器對編寫正確的代碼是一個鉅大的幫助, 但併不是説你完全不用考慮內存了. 你雖然不需要顯式地分配和釋放內存, 但是要編寫高效的程序你還是需要知道變量的生命週期. 例如, 將指向短生命週期對象的指針保存到具有長生命週期的對象中, 特别是全局變量時, 會阻止對短生命週期對象的垃圾迴收.</p>
|
||||
<p>這里的x變量必鬚在堆上分配,因爲它在函數退出後依然可以通過包一級的global變量找到,雖然它是在函數內部定義的;用Go語言的術語説,這個x局部變量從函數f中逃逸了。相反,當g函數返迴時,變量<code>*y</code>將是不可達的,也就是説可以馬上被迴收的。因此,<code>*y</code>併沒有從函數g中逃逸,編譯器可以選擇在棧上分配<code>*y</code>的存儲空間(譯註:也可以選擇在堆上分配,然後由Go語言的GC迴收這個變量的內存空間),雖然這里用的是new方式。其實在任何時候,你併不需爲了編寫正確的代碼而要考慮變量的逃逸行爲,要記住的是,逃逸的變量需要額外分配內存,同時對性能的優化可能會産生細微的影響。</p>
|
||||
<p>Go語言的自動垃圾收集器對編寫正確的代碼是一個鉅大的幫助,但也併不是説你完全不用考慮內存了。你雖然不需要顯式地分配和釋放內存,但是要編寫高效的程序你依然需要了解變量的生命週期。例如,如果將指向短生命週期對象的指針保存到具有長生命週期的對象中,特别是保存到全局變量時,會阻止對短生命週期對象的垃圾迴收(從而可能影響程序的性能)。</p>
|
||||
|
||||
|
||||
</section>
|
||||
@@ -2235,7 +2247,7 @@ fmt.Println(p == q) <span class="hljs-comment">// "false"</span>
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"katex":{},"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-katex/katex.min.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="2.4" data-chapter-title="賦值" data-filepath="ch2/ch2-04.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="2.4" data-chapter-title="賦值" data-filepath="ch2/ch2-04.md" data-basepath=".." data-revision="Mon Dec 28 2015 16:03:52 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -238,7 +242,7 @@
|
||||
|
||||
<b>1.5.</b>
|
||||
|
||||
穫取URL
|
||||
獲取URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -253,7 +257,7 @@
|
||||
|
||||
<b>1.6.</b>
|
||||
|
||||
併發穫取多個URL
|
||||
併發獲取多個URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -802,7 +806,7 @@
|
||||
|
||||
<b>5.10.</b>
|
||||
|
||||
Recover捕穫異常
|
||||
Recover捕獲異常
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1315,7 +1319,7 @@
|
||||
|
||||
<b>8.9.</b>
|
||||
|
||||
併發的退齣
|
||||
併發的退出
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1834,7 +1838,7 @@
|
||||
|
||||
<b>12.7.</b>
|
||||
|
||||
穫取結構體字段標識
|
||||
獲取結構體字段標識
|
||||
</a>
|
||||
|
||||
|
||||
@@ -2020,28 +2024,28 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="24-賦值">2.4. 賦值</h2>
|
||||
<p>使用賦值語句可以更新一個變量的值, 最簡單的賦值語句是將要被賦值的變量放在 <code>=</code> 的左邊, 新值的表達式放在 <code>=</code> 右邊.</p>
|
||||
<p>使用賦值語句可以更新一個變量的值,最簡單的賦值語句是將要被賦值的變量放在=的左邊,新值的表達式放在=的右邊。</p>
|
||||
<pre><code class="lang-Go">x = <span class="hljs-number">1</span> <span class="hljs-comment">// 命令變量的賦值</span>
|
||||
*p = <span class="hljs-constant">true</span> <span class="hljs-comment">// 通過指針間接賦值</span>
|
||||
person.name = <span class="hljs-string">"bob"</span> <span class="hljs-comment">// 結構體字段賦值</span>
|
||||
count[x] = count[x] * scale <span class="hljs-comment">// 數組, 切片 或 字典的 元素賦值</span>
|
||||
count[x] = count[x] * scale <span class="hljs-comment">// 數組、slice或map的元素賦值</span>
|
||||
</code></pre>
|
||||
<p>特定的賦值語句和二元算術複合操作有一個簡潔形式, 例如上面最後的語句可以重寫爲:</p>
|
||||
<p>特定的二元算術運算符和賦值語句的複合操作有一個簡潔形式,例如上面最後的語句可以重寫爲:</p>
|
||||
<pre><code class="lang-Go">count[x] *= scale
|
||||
</code></pre>
|
||||
<p>這樣可以省去對變量表達式的重複計算.</p>
|
||||
<p>數值變量也可以支持 <code>++</code> 遞增和 <code>--</code> 遞減語句:</p>
|
||||
<p>這樣可以省去對變量表達式的重複計算。</p>
|
||||
<p>數值變量也可以支持<code>++</code>遞增和<code>--</code>遞減語句(譯註:自增和自減是語句,而不是表達式,因此<code>x = i++</code>之類的表達式是錯誤的):</p>
|
||||
<pre><code class="lang-Go">v := <span class="hljs-number">1</span>
|
||||
v++ <span class="hljs-comment">// 等價方式 v = v + 1; v 變成 2 </span>
|
||||
v-- <span class="hljs-comment">// 等價方式 v = v - 1; v 變成 1</span>
|
||||
v++ <span class="hljs-comment">// 等價方式 v = v + 1;v 變成 2 </span>
|
||||
v-- <span class="hljs-comment">// 等價方式 v = v - 1;v 變成 1</span>
|
||||
</code></pre>
|
||||
<h3 id="241-元組賦值">2.4.1. 元組賦值</h3>
|
||||
<p>元組賦值是另一種形式的賦值語句, 允許同時更新多個變量的值. 在賦值之前, 賦值語句右邊的所有表達式將會先進行求值, 然後再統一更新左邊變量的值. 這對於處理有些同時齣現在元組賦值語句左右兩邊的變量很有幫助, 例如我們可以這樣交換兩個變量的值:</p>
|
||||
<p>元組賦值是另一種形式的賦值語句,它允許同時更新多個變量的值。在賦值之前,賦值語句右邊的所有表達式將會先進行求值,然後再統一更新左邊對應變量的值。這對於處理有些同時出現在元組賦值語句左右兩邊的變量很有幫助,例如我們可以這樣交換兩個變量的值:</p>
|
||||
<pre><code class="lang-Go">x, y = y, x
|
||||
|
||||
a[i], a[j] = a[j], a[i]
|
||||
</code></pre>
|
||||
<p>或者是計算兩個整數值的的最大公約數(GCD):</p>
|
||||
<p>或者是計算兩個整數值的的最大公約數(GCD)(譯註:GCD不是那個敏感字,而是greatest common divisor的縮寫,歐幾里德的GCD是最早的非平凡算法):</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> gcd(x, y <span class="hljs-typename">int</span>) <span class="hljs-typename">int</span> {
|
||||
<span class="hljs-keyword">for</span> y != <span class="hljs-number">0</span> {
|
||||
x, y = y, x%y
|
||||
@@ -2049,7 +2053,7 @@ a[i], a[j] = a[j], a[i]
|
||||
<span class="hljs-keyword">return</span> x
|
||||
}
|
||||
</code></pre>
|
||||
<p>或者是計算斐波納契數列(Fibonacci)的第N個數:</p>
|
||||
<p>或者是計算斐波納契數列(Fibonacci)的第N個數:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> fib(n <span class="hljs-typename">int</span>) <span class="hljs-typename">int</span> {
|
||||
x, y := <span class="hljs-number">0</span>, <span class="hljs-number">1</span>
|
||||
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < n; i++ {
|
||||
@@ -2058,39 +2062,44 @@ a[i], a[j] = a[j], a[i]
|
||||
<span class="hljs-keyword">return</span> x
|
||||
}
|
||||
</code></pre>
|
||||
<p>元組賦值也可以使一繫列瑣碎賦值更緊湊(譯註: 特别是在for循環的初始化部分),</p>
|
||||
<p>元組賦值也可以使一繫列瑣碎賦值更加緊湊(譯註: 特别是在for循環的初始化部分),</p>
|
||||
<pre><code class="lang-Go">i, j, k = <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">5</span>
|
||||
</code></pre>
|
||||
<p>但如果表達式太複雜的話, 應該盡量避免元組賦值; 因爲一個個單獨的賦值語句的可讀性會更好.</p>
|
||||
<p>某些表達式會産生多個值, 比如調用一個有多個返迴值的函數.
|
||||
當這樣一個函數調用齣現在元組賦值右邊的表達式中時(譯註: 右邊不能再有其他表達式), 左邊變量的數目必鬚和右邊一致.</p>
|
||||
<p>但如果表達式太複雜的話,應該盡量避免過度使用元組賦值;因爲每個變量單獨賦值語句的寫法可讀性會更好。</p>
|
||||
<p>有些表達式會産生多個值,比如調用一個有多個返迴值的函數。當這樣一個函數調用出現在元組賦值右邊的表達式中時(譯註:右邊不能再有其它表達式),左邊變量的數目必鬚和右邊一致。</p>
|
||||
<pre><code class="lang-Go">f, err = os.Open(<span class="hljs-string">"foo.txt"</span>) <span class="hljs-comment">// function call returns two values</span>
|
||||
</code></pre>
|
||||
<p>通常, 這類函數會用額外的返迴值表達某種錯誤類型, 例如 os.Open 是返迴一個 error 類型的錯誤, 還有一些是返迴布爾值, 通常被稱爲ok. 在稍後我們看到的三個操作都是類似的行爲. 如果 字典査找(§4.3), 類型斷言(§7.10), 或 通道接收(§8.4.2) 齣現在賦值語句的右邊, 它們都將産生兩個結果, 有一個額外的布爾結果表示操作是否成功:</p>
|
||||
<p>通常,這類函數會用額外的返迴值來表達某種錯誤類型,例如os.Open是用額外的返迴值返迴一個error類型的錯誤,還有一些是用來返迴布爾值,通常被稱爲ok。在稍後我們將看到的三個操作都是類似的用法。如果map査找(§4.3)、類型斷言(§7.10)或通道接收(§8.4.2)出現在賦值語句的右邊,它們都可能會産生兩個結果,有一個額外的布爾結果表示操作是否成功:</p>
|
||||
<pre><code class="lang-Go">v, ok = m[key] <span class="hljs-comment">// map lookup</span>
|
||||
v, ok = x.(T) <span class="hljs-comment">// type assertion</span>
|
||||
v, ok = <-ch <span class="hljs-comment">// channel receive</span>
|
||||
</code></pre>
|
||||
<p>和變量的聲明一樣, 我們可以用下劃線空白標識符 <code>_</code> 來丟棄不需要的值.</p>
|
||||
<p>譯註:map査找(§4.3)、類型斷言(§7.10)或通道接收(§8.4.2)出現在賦值語句的右邊時,併不一定是産生兩個結果,也可能隻産生一個結果。對於值産生一個結果的情形,map査找失敗時會返迴零值,類型斷言失敗時會發送運行時panic異常,通道接收失敗時會返迴零值(阻塞不算是失敗)。例如下面的例子:</p>
|
||||
<pre><code class="lang-Go">v = m[key] <span class="hljs-comment">// map査找,失敗時返迴零值</span>
|
||||
v = x.(T) <span class="hljs-comment">// type斷言,失敗時panic異常</span>
|
||||
v = <-ch <span class="hljs-comment">// 管道接收,失敗時返迴零值(阻塞不算是失敗)</span>
|
||||
|
||||
_, ok = m[key] <span class="hljs-comment">// map返迴2個值</span>
|
||||
_, ok = mm[<span class="hljs-string">""</span>], <span class="hljs-constant">false</span> <span class="hljs-comment">// map返迴1個值</span>
|
||||
_ = mm[<span class="hljs-string">""</span>] <span class="hljs-comment">// map返迴1個值</span>
|
||||
</code></pre>
|
||||
<p>和變量聲明一樣,我們可以用下劃線空白標識符<code>_</code>來丟棄不需要的值。</p>
|
||||
<pre><code class="lang-Go">_, err = io.Copy(dst, src) <span class="hljs-comment">// 丟棄字節數</span>
|
||||
_, ok = x.(T) <span class="hljs-comment">// 隻檢測類型, 忽略具體值</span>
|
||||
_, ok = x.(T) <span class="hljs-comment">// 隻檢測類型,忽略具體值</span>
|
||||
</code></pre>
|
||||
<h3 id="242-可賦值性">2.4.2. 可賦值性</h3>
|
||||
<p>賦值語句是顯示的賦值形式, 但是程序中還有很多地方會發送隱式的賦值行爲: 函數調用將隱式地將調用參數的值賦值給函數的參數變量, 一個返迴語句將隱式地將返迴操作的值賦值給結果變量, 一個複合類型的字面量(§4.2)也會産生賦值行爲. 例如下面的語句:</p>
|
||||
<p>賦值語句是顯式的賦值形式,但是程序中還有很多地方會發生隱式的賦值行爲:函數調用會隱式地將調用參數的值賦值給函數的參數變量,一個返迴語句將隱式地將返迴操作的值賦值給結果變量,一個複合類型的字面量(§4.2)也會産生賦值行爲。例如下面的語句:</p>
|
||||
<pre><code class="lang-Go">medals := []<span class="hljs-typename">string</span>{<span class="hljs-string">"gold"</span>, <span class="hljs-string">"silver"</span>, <span class="hljs-string">"bronze"</span>}
|
||||
</code></pre>
|
||||
<p>隱式地對切片的每個元素進行賦值操作, 類似這樣寫的行爲:</p>
|
||||
<p>隱式地對slice的每個元素進行賦值操作,類似這樣寫的行爲:</p>
|
||||
<pre><code class="lang-Go">medals[<span class="hljs-number">0</span>] = <span class="hljs-string">"gold"</span>
|
||||
medals[<span class="hljs-number">1</span>] = <span class="hljs-string">"silver"</span>
|
||||
medals[<span class="hljs-number">2</span>] = <span class="hljs-string">"bronze"</span>
|
||||
</code></pre>
|
||||
<p>字典和管道的元素, 雖然不是普通的變量, 但是也有類似的隱式賦值行爲.</p>
|
||||
<p>不管是隱式還是顯示地賦值, 在賦值語句坐標的變量和右邊最終的求到的值必鬚有相同的數據類型. 更直白地説, 隻有右邊的值對於左邊的變量是可賦值的, 賦值語句纔是允許的.</p>
|
||||
<p>可賦值性的規則對於不同類型有不同要求, 對每個新類型有關的地方我們會專門解釋.
|
||||
對於目前我們已經討論過的類型, 它的規則是簡單的: 類型必鬚完全匹配, nil 可以賦值給任何指針或引用類型的變量. 常量(§3.6)有更靈活的規則, 這樣可以避免不必要的顯示類型轉換.</p>
|
||||
<p>對於兩個值是否可以用 <code>==</code> 或 <code>!=</code> 進行相等比較的能力也和可賦值能力有關繫:
|
||||
對於任何的比較, 第一個操作必鬚是可用於第二個操作類型的變量的賦值的, 反之依然.
|
||||
和前面一樣, 我們會對每個新類型比較有關的地方會做專門解釋.</p>
|
||||
<p>map和chan的元素,雖然不是普通的變量,但是也有類似的隱式賦值行爲。</p>
|
||||
<p>不管是隱式還是顯式地賦值,在賦值語句左邊的變量和右邊最終的求到的值必鬚有相同的數據類型。更直白地説,隻有右邊的值對於左邊的變量是可賦值的,賦值語句才是允許的。</p>
|
||||
<p>可賦值性的規則對於不同類型有着不同要求,對每個新類型特殊的地方我們會專門解釋。對於目前我們已經討論過的類型,它的規則是簡單的:類型必鬚完全匹配,nil可以賦值給任何指針或引用類型的變量。常量(§3.6)則有更靈活的賦值規則,因爲這樣可以避免不必要的顯式的類型轉換。</p>
|
||||
<p>對於兩個值是否可以用<code>==</code>或<code>!=</code>進行相等比較的能力也和可賦值能力有關繫:對於任何類型的值的相等比較,第二個值必鬚是對第一個值類型對應的變量是可賦值的,反之依然。和前面一樣,我們會對每個新類型比較特殊的地方做專門的解釋。</p>
|
||||
|
||||
|
||||
</section>
|
||||
@@ -2122,7 +2131,7 @@ medals[<span class="hljs-number">2</span>] = <span class="hljs-string">"bro
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"katex":{},"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-katex/katex.min.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="2.5" data-chapter-title="類型" data-filepath="ch2/ch2-05.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="2.5" data-chapter-title="類型" data-filepath="ch2/ch2-05.md" data-basepath=".." data-revision="Mon Dec 28 2015 16:03:52 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -238,7 +242,7 @@
|
||||
|
||||
<b>1.5.</b>
|
||||
|
||||
穫取URL
|
||||
獲取URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -253,7 +257,7 @@
|
||||
|
||||
<b>1.6.</b>
|
||||
|
||||
併發穫取多個URL
|
||||
併發獲取多個URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -802,7 +806,7 @@
|
||||
|
||||
<b>5.10.</b>
|
||||
|
||||
Recover捕穫異常
|
||||
Recover捕獲異常
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1315,7 +1319,7 @@
|
||||
|
||||
<b>8.9.</b>
|
||||
|
||||
併發的退齣
|
||||
併發的退出
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1834,7 +1838,7 @@
|
||||
|
||||
<b>12.7.</b>
|
||||
|
||||
穫取結構體字段標識
|
||||
獲取結構體字段標識
|
||||
</a>
|
||||
|
||||
|
||||
@@ -2019,18 +2023,19 @@
|
||||
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="25-類型聲明">2.5. 類型聲明</h2>
|
||||
<p>變量或表達式的類型定義了對應存儲值的特徵, 例如數值的存儲大小(或者是元素的bit個數), 它們在內部是如何表達的, 是否支持一些操作符, 以及它們自己關聯的方法集,</p>
|
||||
<p>在任何程序中都會有一些變量有着相同的內部實現, 但是表示完全不同的概念.
|
||||
例如, int 類型的變量可以用來表示一個循環的迭代索引, 或者一個時間戳, 或者一個文件描述符, 或者一個月份; 一個 float64 類型的變量可以用來表示每秒幾米的速度, 或者是不同溫度單位的溫度;
|
||||
一個字符串可以用來表示一個密碼或者一個顔色的名稱.</p>
|
||||
<p>一個類型的聲明創建了一個新的類型名稱, 和現有類型具有相同的底層結構.
|
||||
新命名的類型提供了一個方法, 用來分隔不同概念的類型, 卽使它們底層類型相同也是不兼容的.</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">type</span> name underlying-<span class="hljs-keyword">type</span>
|
||||
<h2 id="25-類型">2.5. 類型</h2>
|
||||
<p>變量或表達式的類型定義了對應存儲值的屬性特徵,例如數值在內存的存儲大小(或者是元素的bit個數),它們在內部是如何表達的,是否支持一些操作符,以及它們自己關聯的方法集等。</p>
|
||||
<p>在任何程序中都會存在一些變量有着相同的內部結構,但是卻表示完全不同的概念。例如,一個int類型的變量可以用來表示一個循環的迭代索引、或者一個時間戳、或者一個文件描述符、或者一個月份;一個float64類型的變量可以用來表示每秒移動幾米的速度、或者是不同溫度單位下的溫度;一個字符串可以用來表示一個密碼或者一個顔色的名稱。</p>
|
||||
<p>一個類型聲明語句創建了一個新的類型名稱,和現有類型具有相同的底層結構。新命名的類型提供了一個方法,用來分隔不同概念的類型,這樣卽使它們底層類型相同也是不兼容的。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">type</span> 類型名字 底層類型
|
||||
</code></pre>
|
||||
<p>類型的聲明一般齣現在包級别, 因此如果新創建的類型名字名字的首字符大寫, 則在外部包也可以使用.</p>
|
||||
<p>爲了説明類型聲明, 我們將不同溫度單位分别定義爲不同的類型:</p>
|
||||
<p>爲了説明類型聲明,讓我們把不同溫度范圍分爲不同的類型:</p>
|
||||
<p>類型聲明語句一般出現在包一級,因此如果新創建的類型名字的首字符大寫,則在外部包也可以使用。</p>
|
||||
<p>譯註:對於中文漢字,Unicode標誌都作爲小寫字母處理,因此中文的命名默認不能導出;不過国內的用戶針對該問題提出了我們自己的間接,根據RobPike的迴複,在Go2中有可能會將中日韓等字符當作大寫字母處理。下面是RobPik在 <a href="https://github.com/golang/go/issues/5763" target="_blank">Issue763</a> 的迴複:</p>
|
||||
<blockquote>
|
||||
<p>A solution that's been kicking around for a while:</p>
|
||||
<p>For Go 2 (can't do it before then): Change the definition to “lower case letters and <em> are package-local; all else is exported”. Then with non-cased languages, such as Japanese, we can write 日本語 for an exported name and </em>日本語 for a local name. This rule has no effect, relative to the Go 1 rule, with cased languages. They behave exactly the same.</p>
|
||||
</blockquote>
|
||||
<p>爲了説明類型聲明,我們將不同溫度單位分别定義爲不同的類型:</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch2/tempconv0
|
||||
<span class="hljs-comment">// Package tempconv performs Celsius and Fahrenheit temperature computations.</span>
|
||||
<span class="hljs-keyword">package</span> tempconv
|
||||
@@ -2043,27 +2048,23 @@
|
||||
<span class="hljs-keyword">const</span> (
|
||||
AbsoluteZeroC Celsius = -<span class="hljs-number">273.15</span> <span class="hljs-comment">// 絶對零度</span>
|
||||
FreezingC Celsius = <span class="hljs-number">0</span> <span class="hljs-comment">// 結冰點溫度</span>
|
||||
BoilingC Celsius = <span class="hljs-number">100</span> <span class="hljs-comment">// 沸水問題</span>
|
||||
BoilingC Celsius = <span class="hljs-number">100</span> <span class="hljs-comment">// 沸水溫度</span>
|
||||
)
|
||||
|
||||
<span class="hljs-keyword">func</span> CToF(c Celsius) Fahrenheit { <span class="hljs-keyword">return</span> Fahrenheit(c*<span class="hljs-number">9</span>/<span class="hljs-number">5</span> + <span class="hljs-number">32</span>) }
|
||||
|
||||
<span class="hljs-keyword">func</span> FToC(f Fahrenheit) Celsius { <span class="hljs-keyword">return</span> Celsius((f - <span class="hljs-number">32</span>) * <span class="hljs-number">5</span> / <span class="hljs-number">9</span>) }
|
||||
</code></pre>
|
||||
<p>這個包定義了兩種類型, Celsius 和 Fahrenheit 分别對應不同的溫度單位. 它們都有着相同的底層類型 float64, 但是它們是不同的數據類型, 因此它們不可以被相互比較或混在一個表達式計算. 可以區分類型, 可以避免一些像無意中結合單位的溫度進行計算的錯誤; 因爲需要一個類似 Celsius(t) 或 Fahrenheit(t) 顯式的轉型操作纔能將 float64 轉爲對應的類型. Celsius(t) 和 Fahrenheit(t) 是類型轉換操作, 併不是函數調用. 類型轉換不會改變值本身, 但是會使它們的語義發生變化. 另一方面, 函數 CToF 和 FToC 則是對兩個不同的溫度單位進行轉換, 它們會返迴不同的值.</p>
|
||||
<p>對於每一個類型 T, 都有一個對應的類型轉換操作 T(x), 用於將 x 轉爲 T 類型.
|
||||
隻有當兩個類型的底層基礎類型相同時, 纔允許這種轉型操作, 或者是兩者都是指向相同底層結構的指針類型,
|
||||
這些轉換隻改變類型而不會影響值本身. 如果x是可以賦值給T類型的, 那麽x必然可以被轉爲T類型, 但是一般沒有必要.</p>
|
||||
<p>數值類型之間的轉型也是允許的, 併且在字符串和一些特定切片之間也是可以轉換的, 在下一章我們會看到這樣的例子. 這類轉換可能改變值的表現. 例如, 將一個浮點數轉爲整數將丟棄小數部分, 將一個字符串轉爲 []byte 切片將拷貝一個字符串數據的副本. 在任何情況下, 運行時不會發送轉換失敗的錯誤(譯註: 錯誤隻會發生在編譯階段).</p>
|
||||
<p>底層數據類型決定了內部結構和表達方式, 也包決定是否可以像底層類型一樣對內置運算符的支持.
|
||||
這意味着, Celsius 和 Fahrenheit 類型的算術行爲和底層的 float64 類型一樣, 正如你所期望的.</p>
|
||||
<p>我們在這個包聲明了兩種類型:Celsius和Fahrenheit分别對應不同的溫度單位。它們雖然有着相同的底層類型float64,但是它們是不同的數據類型,因此它們不可以被相互比較或混在一個表達式運算。刻意區分類型,可以避免一些像無意中使用不同單位的溫度混合計算導致的錯誤;因此需要一個類似Celsius(t)或Fahrenheit(t)形式的顯式轉型操作才能將float64轉爲對應的類型。Celsius(t)和Fahrenheit(t)是類型轉換操作,它們併不是函數調用。類型轉換不會改變值本身,但是會使它們的語義發生變化。另一方面,CToF和FToC兩個函數則是對不同溫度單位下的溫度進行換算,它們會返迴不同的值。</p>
|
||||
<p>對於每一個類型T,都有一個對應的類型轉換操作T(x),用於將x轉爲T類型(譯註:如果T是指針類型,可能會需要用小括弧包裝T,比如<code>(*int)(0)</code>)。隻有當兩個類型的底層基礎類型相同時,才允許這種轉型操作,或者是兩者都是指向相同底層結構的指針類型,這些轉換隻改變類型而不會影響值本身。如果x是可以賦值給T類型的值,那麽x必然也可以被轉爲T類型,但是一般沒有這個必要。</p>
|
||||
<p>數值類型之間的轉型也是允許的,併且在字符串和一些特定類型的slice之間也是可以轉換的,在下一章我們會看到這樣的例子。這類轉換可能改變值的表現。例如,將一個浮點數轉爲整數將丟棄小數部分,將一個字符串轉爲<code>[]byte</code>類型的slice將拷貝一個字符串數據的副本。在任何情況下,運行時不會發生轉換失敗的錯誤(譯註: 錯誤隻會發生在編譯階段)。</p>
|
||||
<p>底層數據類型決定了內部結構和表達方式,也決定是否可以像底層類型一樣對內置運算符的支持。這意味着,Celsius和Fahrenheit類型的算術運算行爲和底層的float64類型是一樣的,正如我們所期望的那樣。</p>
|
||||
<pre><code class="lang-Go">fmt.Printf(<span class="hljs-string">"%g\n"</span>, BoilingC-FreezingC) <span class="hljs-comment">// "100" °C</span>
|
||||
boilingF := CToF(BoilingC)
|
||||
fmt.Printf(<span class="hljs-string">"%g\n"</span>, boilingF-CToF(FreezingC)) <span class="hljs-comment">// "180" °F</span>
|
||||
fmt.Printf(<span class="hljs-string">"%g\n"</span>, boilingF-FreezingC) <span class="hljs-comment">// compile error: type mismatch</span>
|
||||
</code></pre>
|
||||
<p>比較運算符 <code>==</code> 和 <code><</code> 也可以用來比較一個命名類型的變量和另一個有相同類型的變量或相同的底層類型的值做比較.
|
||||
但是如果兩個值有着不同的類型, 則不能直接進行比較:</p>
|
||||
<p>比較運算符<code>==</code>和<code><</code>也可以用來比較一個命名類型的變量和另一個有相同類型的變量或有相同底層類型的值做比較。但是如果兩個值有着不同的類型,則不能直接進行比較:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> c Celsius
|
||||
<span class="hljs-keyword">var</span> f Fahrenheit
|
||||
fmt.Println(c == <span class="hljs-number">0</span>) <span class="hljs-comment">// "true"</span>
|
||||
@@ -2071,13 +2072,13 @@ fmt.Println(f >= <span class="hljs-number">0</span>) <span class="hl
|
||||
fmt.Println(c == f) <span class="hljs-comment">// compile error: type mismatch</span>
|
||||
fmt.Println(c == Celsius(f)) <span class="hljs-comment">// "true"!</span>
|
||||
</code></pre>
|
||||
<p>註意最後那個語句. 盡管看起來想函數調用, 但是Celsius(f)類型轉換, 併不會改變值, 它僅僅是改變值的類型而已. 測試爲眞的原因是因爲 c 和 g 都是零值.</p>
|
||||
<p>一個命名的類型可以提供符號方便, 特别是可以避免一遍又一遍地書寫複雜類型(譯註: 例如用匿名的結構體定義變量). 雖然對於像float64這種簡單的底層類型沒有簡潔很多, 但是如果是複雜的類型將會簡潔很多, 正如我們卽將討論的結構體類型:</p>
|
||||
<p>命名類型還可以爲該類型的值定義新的行爲. 這些行爲表示爲一組關聯到類型的函數, 我們成爲類型的方法集. 我們將在第六章討論方法的細節, 這里值説寫簡單用法.</p>
|
||||
<p>下面的聲明, Celsius 類型的參數 c 齣現在了函數名的前面, 表示聲明一個 Celsius 類型的 名叫 String 的方法, 方法返迴 帶着 °C 溫度單位 的參數 c 的數字打印字符串:</p>
|
||||
<p>註意最後那個語句。盡管看起來想函數調用,但是Celsius(f)是類型轉換操作,它併不會改變值,僅僅是改變值的類型而已。測試爲眞的原因是因爲c和g都是零值。</p>
|
||||
<p>一個命名的類型可以提供書寫方便,特别是可以避免一遍又一遍地書寫複雜類型(譯註:例如用匿名的結構體定義變量)。雖然對於像float64這種簡單的底層類型沒有簡潔很多,但是如果是複雜的類型將會簡潔很多,特别是我們卽將討論的結構體類型。</p>
|
||||
<p>命名類型還可以爲該類型的值定義新的行爲。這些行爲表示爲一組關聯到該類型的函數集合,我們稱爲類型的方法集。我們將在第六章中討論方法的細節,這里值説寫簡單用法。</p>
|
||||
<p>下面的聲明語句,Celsius類型的參數c出現在了函數名的前面,表示聲明的是Celsius類型的一個叫名叫String的方法,該方法返迴該類型對象c帶着°C溫度單位的字符串:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> (c Celsius) String() <span class="hljs-typename">string</span> { <span class="hljs-keyword">return</span> fmt.Sprintf(<span class="hljs-string">"%g°C"</span>, c) }
|
||||
</code></pre>
|
||||
<p>許多類型都會定義個 String 方法, 因爲當然用 fmt 包的打印方法時, 將會優先使用 String 方法返迴的結果打印, 將在 7.1節 講述.</p>
|
||||
<p>許多類型都會定義一個String方法,因爲當使用fmt包的打印方法時,將會優先使用該類型對應的String方法返迴的結果打印,我們將在7.1節講述。</p>
|
||||
<pre><code class="lang-Go">c := FToC(<span class="hljs-number">212.0</span>)
|
||||
fmt.Println(c.String()) <span class="hljs-comment">// "100°C"</span>
|
||||
fmt.Printf(<span class="hljs-string">"%v\n"</span>, c) <span class="hljs-comment">// "100°C"; no need to call String explicitly</span>
|
||||
@@ -2117,7 +2118,7 @@ fmt.Println(<span class="hljs-typename">float64</span>(c)) <span class="hljs-com
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"katex":{},"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-katex/katex.min.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="2.6" data-chapter-title="包和文件" data-filepath="ch2/ch2-06.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="2.6" data-chapter-title="包和文件" data-filepath="ch2/ch2-06.md" data-basepath=".." data-revision="Mon Dec 28 2015 16:03:52 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -238,7 +242,7 @@
|
||||
|
||||
<b>1.5.</b>
|
||||
|
||||
穫取URL
|
||||
獲取URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -253,7 +257,7 @@
|
||||
|
||||
<b>1.6.</b>
|
||||
|
||||
併發穫取多個URL
|
||||
併發獲取多個URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -802,7 +806,7 @@
|
||||
|
||||
<b>5.10.</b>
|
||||
|
||||
Recover捕穫異常
|
||||
Recover捕獲異常
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1315,7 +1319,7 @@
|
||||
|
||||
<b>8.9.</b>
|
||||
|
||||
併發的退齣
|
||||
併發的退出
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1834,7 +1838,7 @@
|
||||
|
||||
<b>12.7.</b>
|
||||
|
||||
穫取結構體字段標識
|
||||
獲取結構體字段標識
|
||||
</a>
|
||||
|
||||
|
||||
@@ -2020,12 +2024,12 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="26-包和文件">2.6. 包和文件</h2>
|
||||
<p>Go語言中的包和其他語言的庫或模塊概念類似, 目的都是爲了支持模塊好, 封裝, 單獨編譯和代碼重用. 一個包的源代碼保存在一個或多個以.爲後綴名的文件中, 通常一個包所在目録路徑的後綴是包的導入路徑; 例如包 gopl.io/ch1/helloworld 對應的目録路徑是 $GOPATH/src/gopl.io/ch1/helloworld.</p>
|
||||
<p>每個包作爲一個獨立的名字空間. 例如, 在 image 包中的 Decode 函數 和 unicode/utf16 包中的 Decode 函數是不同的. 要在外部包引用該函數, 必鬚顯式使用 image.Decode 或 utf16.Decode 訪問.</p>
|
||||
<p>包可以讓我們通過控製那些名字是外部可見的來隱藏信息. 在Go中, 一個簡單的規則是: 如果一個名字是大寫字母開頭的, 那麽該名字是導齣的.</p>
|
||||
<p>爲了演示基本的用法, 假設我們的溫度轉換軟件已經很流行, 我們希望到Go社區也能使用這個包. 我們該如何做呢?</p>
|
||||
<p>讓我們創建一個名爲 gopl.io/ch2/tempconv 的包, 是前面例子的一個改進版本. (我們約定我們的例子都是以章節順序來編號的, 這樣的路徑更容易閲讀.) 包代碼存儲在兩個文件, 用來演示如何在一個文件聲明然後在其他的文件訪問; 在現實中, 這樣小的包一般值需要一個文件.</p>
|
||||
<p>我們把變量的聲明, 對應的常量, 還有方法都放到 tempconv.go 文件:</p>
|
||||
<p>Go語言中的包和其他語言的庫或模塊的概念類似,目的都是爲了支持模塊化、封裝、單獨編譯和代碼重用。一個包的源代碼保存在一個或多個以.go爲文件後綴名的源文件中,通常一個包所在目録路徑的後綴是包的導入路徑;例如包gopl.io/ch1/helloworld對應的目録路徑是$GOPATH/src/gopl.io/ch1/helloworld。</p>
|
||||
<p>每個包都對應一個獨立的名字空間。例如,在image包中的Decode函數和在unicode/utf16包中的 Decode函數是不同的。要在外部引用該函數,必鬚顯式使用image.Decode或utf16.Decode形式訪問。</p>
|
||||
<p>包還可以讓我們通過控製哪些名字是外部可見的來隱藏內部實現信息。在Go語言中,一個簡單的規則是:如果一個名字是大寫字母開頭的,那麽該名字是導出的(譯註:因爲漢字不區分大小寫,因此漢字開頭的名字是沒有導出的)。</p>
|
||||
<p>爲了演示包基本的用法,先假設我們的溫度轉換軟件已經很流行,我們希望到Go語言社區也能使用這個包。我們該如何做呢?</p>
|
||||
<p>讓我們創建一個名爲gopl.io/ch2/tempconv的包,這是前面例子的一個改進版本。(我們約定我們的例子都是以章節順序來編號的,這樣的路徑更容易閲讀)包代碼存儲在兩個源文件中,用來演示如何在一個源文件聲明然後在其他的源文件訪問;雖然在現實中,這樣小的包一般隻需要一個文件。</p>
|
||||
<p>我們把變量的聲明、對應的常量,還有方法都放到tempconv.go源文件中:</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch2/tempconv
|
||||
<span class="hljs-comment">// Package tempconv performs Celsius and Fahrenheit conversions.</span>
|
||||
<span class="hljs-keyword">package</span> tempconv
|
||||
@@ -2044,7 +2048,7 @@
|
||||
<span class="hljs-keyword">func</span> (c Celsius) String() <span class="hljs-typename">string</span> { <span class="hljs-keyword">return</span> fmt.Sprintf(<span class="hljs-string">"%g°C"</span>, c) }
|
||||
<span class="hljs-keyword">func</span> (f Fahrenheit) String() <span class="hljs-typename">string</span> { <span class="hljs-keyword">return</span> fmt.Sprintf(<span class="hljs-string">"%g°F"</span>, f) }
|
||||
</code></pre>
|
||||
<p>轉換函數放在 conv.go 文件中:</p>
|
||||
<p>轉換函數則放在另一個conv.go源文件中:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">package</span> tempconv
|
||||
|
||||
<span class="hljs-comment">// CToF converts a Celsius temperature to Fahrenheit.</span>
|
||||
@@ -2053,22 +2057,19 @@
|
||||
<span class="hljs-comment">// FToC converts a Fahrenheit temperature to Celsius.</span>
|
||||
<span class="hljs-keyword">func</span> FToC(f Fahrenheit) Celsius { <span class="hljs-keyword">return</span> Celsius((f - <span class="hljs-number">32</span>) * <span class="hljs-number">5</span> / <span class="hljs-number">9</span>) }
|
||||
</code></pre>
|
||||
<p>每個文件都是以包的聲明語句開始, 用來指定包的名字. 當包被導入的時候, 包內部的成員將通過類似 tempconv.CToF 的方式訪問. 包級别的名字, 例如在一個文件聲明的類型和常量, 在同一個包的其他文件也是可以直接訪問的,
|
||||
就好像所有代碼都在一個文件一樣. 要註意的是 tempconv.go 文件導入了 fmt 包, 但是 conv.go 文件併沒有, 因爲它併沒有用到 fmt 包.</p>
|
||||
<p>因爲包級别的常量名都是以大寫字母開頭, 它們也是可以像 tempconv.AbsoluteZeroC 這樣被訪問的:</p>
|
||||
<p>每個源文件都是以包的聲明語句開始,用來指名包的名字。當包被導入的時候,包內的成員將通過類似tempconv.CToF的形式訪問。而包級别的名字,例如在一個文件聲明的類型和常量,在同一個包的其他源文件也是可以直接訪問的,就好像所有代碼都在一個文件一樣。要註意的是tempconv.go源文件導入了fmt包,但是conv.go源文件併沒有,因爲這個源文件中的代碼併沒有用到fmt包。</p>
|
||||
<p>因爲包級别的常量名都是以大寫字母開頭,它們可以像tempconv.AbsoluteZeroC這樣被外部代碼訪問:</p>
|
||||
<pre><code class="lang-Go">fmt.Printf(<span class="hljs-string">"Brrrr! %v\n"</span>, tempconv.AbsoluteZeroC) <span class="hljs-comment">// "Brrrr! -273.15°C"</span>
|
||||
</code></pre>
|
||||
<p>要將 攝氏溫度轉換爲 華氏溫度, 需要先導入 gopl.io/ch2/tempconv, 然後就可以使用下面的代碼轉換了:</p>
|
||||
<p>要將攝氏溫度轉換爲華氏溫度,需要先用import語句導入gopl.io/ch2/tempconv包,然後就可以使用下面的代碼進行轉換了:</p>
|
||||
<pre><code class="lang-Go">fmt.Println(tempconv.CToF(tempconv.BoilingC)) <span class="hljs-comment">// "212°F"</span>
|
||||
</code></pre>
|
||||
<p>在每個文件的包聲明前僅跟着的註釋是包註釋(§10.7.4). 通常, 第一句應該先是包的功能概要.
|
||||
一個包通常隻有一個文件有包註釋. 如果包註釋很大, 通常會放到一個獨立的 doc.go 文件中.</p>
|
||||
<p><strong>練習 2.1:</strong> 向 tempconv 包 添加類型, 常量和函數用來處理 Kelvin 絶對溫度的轉換,
|
||||
Kelvin 絶對零度是 −273.15°C, Kelvin 絶對溫度1K和攝氏度1°C的單位間隔是一樣的.</p>
|
||||
<p>在每個源文件的包聲明前僅跟着的註釋是包註釋(§10.7.4)。通常,包註釋的第一句應該先是包的功能概要説明。一個包通常隻有一個源文件有包註釋(譯註:如果有多個包註釋,目前的文檔工具會根據源文件名的先後順序將它們鏈接爲一個包註釋)。如果包註釋很大,通常會放到一個獨立的doc.go文件中。</p>
|
||||
<p><strong>練習 2.1:</strong> 向tempconv包添加類型、常量和函數用來處理Kelvin絶對溫度的轉換,Kelvin 絶對零度是−273.15°C,Kelvin絶對溫度1K和攝氏度1°C的單位間隔是一樣的。</p>
|
||||
<h3 id="261-導入包">2.6.1. 導入包</h3>
|
||||
<p>在Go程序中, 每個包都是有一個全局唯一的導入路徑. 聲明中類似 "gopl.io/ch2/tempconv" 的字符串對應導入路徑. 語言的規范併沒有定義這些字符串的具體含義或包來自哪里, 它們是由工具來解釋. 當使用 go 工具箱時(第十章), 一個導入路徑代表一個目録中的一個或多個Go源文件.</p>
|
||||
<p>除了到導入路徑, 每個包還有一個包名, 包名一般是短小的(也不要求是是唯一的), 包名在包的聲明處指定. 按照慣例, 一個包的名字和包的導入路徑的最後一個字段相同, 例如 gopl.io/ch2/tempconv 包的名字是 tempconv.</p>
|
||||
<p>要使用 gopl.io/ch2/tempconv 包, 需要先導入:</p>
|
||||
<p>在Go語言程序中,每個包都是有一個全局唯一的導入路徑。導入語句中類似"gopl.io/ch2/tempconv"的字符串對應包的導入路徑。Go語言的規范併沒有定義這些字符串的具體含義或包來自哪里,它們是由構建工具來解釋的。當使用Go語言自帶的go工具箱時(第十章),一個導入路徑代表一個目録中的一個或多個Go源文件。</p>
|
||||
<p>除了包的導入路徑,每個包還有一個包名,包名一般是短小的名字(併不要求包名是唯一的),包名在包的聲明處指定。按照慣例,一個包的名字和包的導入路徑的最後一個字段相同,例如gopl.io/ch2/tempconv包的名字一般是tempconv。</p>
|
||||
<p>要使用gopl.io/ch2/tempconv包,需要先導入:</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch2/cf
|
||||
<span class="hljs-comment">// Cf converts its numeric argument to Celsius and Fahrenheit. </span>
|
||||
<span class="hljs-keyword">package</span> main
|
||||
@@ -2095,8 +2096,8 @@ Kelvin 絶對零度是 −273.15°C, Kelvin &#
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>導入聲明將導入的包綁定到一個短小的名字, 然後通過該名字就可以引用包中導齣的全部內容. 上面的導入聲明將允許我們以 tempconv.CToF 的方式來訪問 gopl.io/ch2/tempconv 包中的內容. 默認情況下, 導入的包綁定到 tempconv 名字, 但是我們也可以綁定到另一個名稱, 以避免名字衝突(§10.3).</p>
|
||||
<p>cf 程序將命令行輸入的一個溫度在 Celsius 和 Fahrenheit 之間轉換:</p>
|
||||
<p>導入語句將導入的包綁定到一個短小的名字,然後通過該短小的名字就可以引用包中導出的全部內容。上面的導入聲明將允許我們以tempconv.CToF的形式來訪問gopl.io/ch2/tempconv包中的內容。在默認情況下,導入的包綁定到tempconv名字(譯註:這包聲明語句指定的名字),但是我們也可以綁定到另一個名稱,以避免名字衝突(§10.3)。</p>
|
||||
<p>cf程序將命令行輸入的一個溫度在Celsius和Fahrenheit溫度單位之間轉換:</p>
|
||||
<pre><code>$ go build gopl.io/ch2/cf
|
||||
$ ./cf 32
|
||||
32°F = 0°C, 32°C = 89.6°F
|
||||
@@ -2104,25 +2105,24 @@ $ ./cf 212
|
||||
212°F = 100°C, 212°C = 413.6°F
|
||||
$ ./cf -40
|
||||
-40°F = -40°C, -40°C = -40°F
|
||||
</code></pre><p>如果導入一個包, 但是沒有使用該包將被當作一個錯誤. 這種強製檢測可以有效減少不必要的依賴, 雖然在調試期間會讓人討厭, 因爲刪除一個類似 log.Print("got here!") 的打印可能導致需要同時刪除 log 包導入聲明, 否則, 編譯器將會發齣一個錯誤. 在這種情況下, 我們需要將不必要的導入刪除或註釋掉.</p>
|
||||
<p>不過有更好的解決方案, 我們可以使用 golang.org/x/tools/cmd/goimports 工具, 它可以根據需要自動添加或刪除導入的包; 許多編輯器都可以集成 goimports 工具, 然後在保存文件的時候自動允許它. 類似的還有 gofmt 工具, 可以用來格式化Go源文件.</p>
|
||||
<p><strong>練習 2.2:</strong> 寫一個通用的單位轉換程序, 用類似 cf 程序的方式從命令行讀取參數, 如果缺省的話則是從標準輸入讀取參數, 然後做類似 Celsius 和 Fahrenheit 的轉換,
|
||||
長度單位對應英尺和米, 重量單位對應磅和公斤 等等.</p>
|
||||
</code></pre><p>如果導入了一個包,但是又沒有使用該包將被當作一個編譯錯誤處理。這種強製規則可以有效減少不必要的依賴,雖然在調試期間可能會讓人討厭,因爲刪除一個類似log.Print("got here!")的打印語句可能導致需要同時刪除log包導入聲明,否則,編譯器將會發出一個錯誤。在這種情況下,我們需要將不必要的導入刪除或註釋掉。</p>
|
||||
<p>不過有更好的解決方案,我們可以使用golang.org/x/tools/cmd/goimports導入工具,它可以根據需要自動添加或刪除導入的包;許多編輯器都可以集成goimports工具,然後在保存文件的時候自動運行。類似的還有gofmt工具,可以用來格式化Go源文件。</p>
|
||||
<p><strong>練習 2.2:</strong> 寫一個通用的單位轉換程序,用類似cf程序的方式從命令行讀取參數,如果缺省的話則是從標準輸入讀取參數,然後做類似Celsius和Fahrenheit的單位轉換,長度單位可以對應英尺和米,重量單位可以對應磅和公斤等。</p>
|
||||
<h3 id="262-包的初始化">2.6.2. 包的初始化</h3>
|
||||
<p>包的初始化首先是解決包級變量的依賴順序, 然後安裝包級變量聲明齣現的順序依次初始化:</p>
|
||||
<p>包的初始化首先是解決包級變量的依賴順序,然後安照包級變量聲明出現的順序依次初始化:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> a = b + c <span class="hljs-comment">// a 第三個初始化, 爲 3</span>
|
||||
<span class="hljs-keyword">var</span> b = f() <span class="hljs-comment">// b 第二個初始化, 爲 2, 通過調用 f (依賴c)</span>
|
||||
<span class="hljs-keyword">var</span> c = <span class="hljs-number">1</span> <span class="hljs-comment">// c 第一個初始化, 爲 1</span>
|
||||
|
||||
<span class="hljs-keyword">func</span> f() <span class="hljs-typename">int</span> { <span class="hljs-keyword">return</span> c + <span class="hljs-number">1</span> }
|
||||
</code></pre>
|
||||
<p>如果包中含有多個 .go 文件, 它們按照發給編譯器的順序進行初始化, Go的構建工具首先將 .go 文件根據文件名排序, 然後依次調用編譯器編譯.</p>
|
||||
<p>對於在包級别聲明的變量, 如果有初始化表達式則用表達式初始化, 還有一些沒有初始化表達式的, 例如 某些表格數據 初始化併不是一個簡單的賦值過程. 在這種情況下, 我們可以用 init 初始化函數來簡化工作. 每個文件都可以包含多個 init 初始化函數</p>
|
||||
<p>如果包中含有多個.go源文件,它們將按照發給編譯器的順序進行初始化,Go語言的構建工具首先會將.go文件根據文件名排序,然後依次調用編譯器編譯。</p>
|
||||
<p>對於在包級别聲明的變量,如果有初始化表達式則用表達式初始化,還有一些沒有初始化表達式的,例如某些表格數據初始化併不是一個簡單的賦值過程。在這種情況下,我們可以用一個特殊的init初始化函數來簡化初始化工作。每個文件都可以包含多個init初始化函數</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> init() { <span class="hljs-comment">/* ... */</span> }
|
||||
</code></pre>
|
||||
<p>這樣的init初始化函數除了不能被調用或引用外, 其他行爲和普通函數類似. 在每個文件中的init初始化函數, 在程序開始執行時按照它們聲明的順序被自動調用.</p>
|
||||
<p>每個包在解決依賴的前提下, 以導入聲明的順序初始化, 每個包隻會被初始化一次. 因此, 如果一個 p 包導入了 q 包, 那麽在 p 包初始化的時候可以認爲 q 包已經初始化過了. 初始化工作是自下而上進行的, main 包最後被初始化. 以這種方式, 確保 在 main 函數執行之前, 所有的包都已經初始化了.</p>
|
||||
<p>下面的代碼定義了一個 PopCount 函數, 用於返迴一個數字中含二進製1bit的個數. 它使用 init 初始化函數來生成輔助表格 pc, pc 表格用於處理每個8bit寬度的數字含二進製的1bit的個數, 這樣的話在處理64bit寬度的數字時就沒有必要循環64次, 隻需要8次査表就可以了. (這併不是最快的統計1bit數目的算法, 但是他可以方便演示init函數的用法, 併且演示了如果預生成輔助表格, 這是編程中常用的技術.)</p>
|
||||
<p>這樣的init初始化函數除了不能被調用或引用外,其他行爲和普通函數類似。在每個文件中的init初始化函數,在程序開始執行時按照它們聲明的順序被自動調用。</p>
|
||||
<p>每個包在解決依賴的前提下,以導入聲明的順序初始化,每個包隻會被初始化一次。因此,如果一個p包導入了q包,那麽在p包初始化的時候可以認爲q包必然已經初始化過了。初始化工作是自下而上進行的,main包最後被初始化。以這種方式,可以確保在main函數執行之前,所有依然的包都已經完成初始化工作了。</p>
|
||||
<p>下面的代碼定義了一個PopCount函數,用於返迴一個數字中含二進製1bit的個數。它使用init初始化函數來生成輔助表格pc,pc表格用於處理每個8bit寬度的數字含二進製的1bit的bit個數,這樣的話在處理64bit寬度的數字時就沒有必要循環64次,隻需要8次査表就可以了。(這併不是最快的統計1bit數目的算法,但是它可以方便演示init函數的用法,併且演示了如果預生成輔助表格,這是編程中常用的技術)。</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch2/popcount
|
||||
<span class="hljs-keyword">package</span> popcount
|
||||
|
||||
@@ -2147,14 +2147,21 @@ $ ./cf -40
|
||||
pc[<span class="hljs-typename">byte</span>(x>>(<span class="hljs-number">7</span>*<span class="hljs-number">8</span>))])
|
||||
}
|
||||
</code></pre>
|
||||
<p>要註意的是 init 函數中, range 循環隻使用了索引, 省略了沒有用到的值部分.
|
||||
循環也可以這樣寫:</p>
|
||||
<p>譯註:對於pc這類需要複雜處理的初始化,可以通過將初始化邏輯包裝爲一個匿名函數處理,像下面這樣:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-comment">// pc[i] is the population count of i.</span>
|
||||
<span class="hljs-keyword">var</span> pc [<span class="hljs-number">256</span>]<span class="hljs-typename">byte</span> = <span class="hljs-keyword">func</span>() (pc [<span class="hljs-number">256</span>]<span class="hljs-typename">byte</span>) {
|
||||
<span class="hljs-keyword">for</span> i := <span class="hljs-keyword">range</span> pc {
|
||||
pc[i] = pc[i/<span class="hljs-number">2</span>] + <span class="hljs-typename">byte</span>(i&<span class="hljs-number">1</span>)
|
||||
}
|
||||
}()
|
||||
</code></pre>
|
||||
<p>要註意的是在init函數中,range循環隻使用了索引,省略了沒有用到的值部分。循環也可以這樣寫:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">for</span> i, _ := <span class="hljs-keyword">range</span> pc {
|
||||
</code></pre>
|
||||
<p>我們在下一節和10.5節還將看到其它使用init函數的地方.</p>
|
||||
<p><strong>練習2.3:</strong> 重寫 PopCount 函數, 用一個循環代替單一的表達式. 比較兩個版本的性能. (11.4節將展示如何繫統地比較兩個不同實現的性能.)</p>
|
||||
<p><strong>練習2.4:</strong> 用移位的算法重寫 PopCount 函數, 每次測試最右邊的1bit, 然後統計總數. 比較和査表算法的性能差異.</p>
|
||||
<p><strong>練習2.5:</strong> 表達式 <code>x&(x-1)</code> 用於將 x 的最低的一個1bit位清零. 使用這個格式重寫 PopCount 函數, 然後比較性能.</p>
|
||||
<p>我們在下一節和10.5節還將看到其它使用init函數的地方。</p>
|
||||
<p><strong>練習 2.3:</strong> 重寫PopCount函數,用一個循環代替單一的表達式。比較兩個版本的性能。(11.4節將展示如何繫統地比較兩個不同實現的性能。)</p>
|
||||
<p><strong>練習 2.4:</strong> 用移位算法重寫PopCount函數,每次測試最右邊的1bit,然後統計總數。比較和査表算法的性能差異。</p>
|
||||
<p><strong>練習 2.5:</strong> 表達式<code>x&(x-1)</code>用於將x的最低的一個非零的bit位清零。使用這個算法重寫PopCount函數,然後比較性能。</p>
|
||||
|
||||
|
||||
</section>
|
||||
@@ -2186,7 +2193,7 @@ $ ./cf -40
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"katex":{},"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-katex/katex.min.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="2.7" data-chapter-title="作用域" data-filepath="ch2/ch2-07.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="2.7" data-chapter-title="作用域" data-filepath="ch2/ch2-07.md" data-basepath=".." data-revision="Mon Dec 28 2015 16:03:52 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -238,7 +242,7 @@
|
||||
|
||||
<b>1.5.</b>
|
||||
|
||||
穫取URL
|
||||
獲取URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -253,7 +257,7 @@
|
||||
|
||||
<b>1.6.</b>
|
||||
|
||||
併發穫取多個URL
|
||||
併發獲取多個URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -802,7 +806,7 @@
|
||||
|
||||
<b>5.10.</b>
|
||||
|
||||
Recover捕穫異常
|
||||
Recover捕獲異常
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1315,7 +1319,7 @@
|
||||
|
||||
<b>8.9.</b>
|
||||
|
||||
併發的退齣
|
||||
併發的退出
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1834,7 +1838,7 @@
|
||||
|
||||
<b>12.7.</b>
|
||||
|
||||
穫取結構體字段標識
|
||||
獲取結構體字段標識
|
||||
</a>
|
||||
|
||||
|
||||
@@ -2020,13 +2024,13 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="27-作用域">2.7. 作用域</h2>
|
||||
<p>一個聲明語句將程序中的實體和一個名字關聯, 比如一個函數或一個變量. 聲明的作用域是指源代碼中可以有效使用這個名字的范圍.</p>
|
||||
<p>不要將作用域和生命週期混爲一談. 聲明的作用域對應的是一個源代碼的文本區域; 它是一個編譯時的屬性. 一個變量的生命週期是程序運行時變量存在的有效時間段, 在此時間區域內存它可以被程序的其他部分引用. 是一個運行時的概念.</p>
|
||||
<p>語法塊是由花括弧所包含的一繫列語句, 就像函數體或循環體那樣. 語法塊內部聲明的名字是無法被外部語法塊訪問的. 語法決定了內部聲明的名字的作用域范圍. 我們可以這樣理解, 語法塊可以包含其他類似組批量聲明等沒有用花括弧包含的代碼, 我們稱之爲詞滙塊. 有一個語法決爲整個源代碼, 稱爲全局塊; 然後是每個包的語法決; 每個 for, if 和 switch 語句的語法決; 每個 switch 或 select 分支的 語法決; 當然也包含顯示編寫的語法塊(花括弧包含).</p>
|
||||
<p>聲明的詞法域決定了作用域范圍是大還是小. 內置的類型, 函數和常量, 比如 int, len 和 true 等是在全局作用域的, 可以在整個程序中直接使用. 任何在在函數外部(也就是包級作用域)聲明的名字可以在同一個包的任何Go文件訪問. 導入的包, 例如 tempconv 導入的 fmt 包, 則是對應文件級的作用域, 因此隻能在當前的文件中訪問 fmt 包, 當前包的其它文件無法訪問當前文件導入的包. 還有許多聲明, 比如 tempconv.CToF 函數中的變量 c, 則是局部作用域的, 它隻能在函數內部(甚至隻能是某些部分)訪問.</p>
|
||||
<p>控製流標籤, 例如 break, continue 或 goto 後面跟着的那種標籤, 則是函數級的作用域.</p>
|
||||
<p>一個程序可能包含多個同名的聲明, 隻有它們在不同的詞法域就沒有關繫. 例如, 你可以聲明一個局部變量, 和包級的變量同名. 或者是 2.3.3節的那樣, 你可以將一個函數參數的名字聲明爲 new, 雖然內置的new是全局作用域的. 但是物極必反, 如果濫用重名的特性, 可能導致程序很難閲讀.</p>
|
||||
<p>當編譯器遇到一個名字引用, 它看起來像一個聲明, 它首先從最內層的詞法域向全局的作用域査找. 如果査找失敗, 則報告 "未聲明的名字" 這樣的錯誤. 如果名字在內部和外部的塊分别聲明, 則內部塊的聲明首先被找到. 在這種情況下, 內部聲明屏蔽了外部同名的聲明, 讓外部的聲明無法被訪問:</p>
|
||||
<p>一個聲明語句將程序中的實體和一個名字關聯,比如一個函數或一個變量。聲明語句的作用域是指源代碼中可以有效使用這個名字的范圍。</p>
|
||||
<p>不要將作用域和生命週期混爲一談。聲明語句的作用域對應的是一個源代碼的文本區域;它是一個編譯時的屬性。一個變量的生命週期是指程序運行時變量存在的有效時間段,在此時間區域內它可以被程序的其他部分引用;是一個運行時的概念。</p>
|
||||
<p>語法塊是由花括弧所包含的一繫列語句,就像函數體或循環體花括弧對應的語法塊那樣。語法塊內部聲明的名字是無法被外部語法塊訪問的。語法決定了內部聲明的名字的作用域范圍。我們可以這樣理解,語法塊可以包含其他類似組批量聲明等沒有用花括弧包含的代碼,我們稱之爲語法塊。有一個語法塊爲整個源代碼,稱爲全局語法塊;然後是每個包的包語法決;每個for、if和switch語句的語法決;每個switch或select的分支也有獨立的語法決;當然也包括顯式書寫的語法塊(花括弧包含的語句)。</p>
|
||||
<p>聲明語句對應的詞法域決定了作用域范圍的大小。對於內置的類型、函數和常量,比如int、len和true等是在全局作用域的,因此可以在整個程序中直接使用。任何在在函數外部(也就是包級語法域)聲明的名字可以在同一個包的任何源文件中訪問的。對於導入的包,例如tempconv導入的fmt包,則是對應源文件級的作用域,因此隻能在當前的文件中訪問導入的fmt包,當前包的其它源文件無法訪問在當前源文件導入的包。還有許多聲明語句,比如tempconv.CToF函數中的變量c,則是局部作用域的,它隻能在函數內部(甚至隻能是局部的某些部分)訪問。</p>
|
||||
<p>控製流標號,就是break、continue或goto語句後面跟着的那種標號,則是函數級的作用域。</p>
|
||||
<p>一個程序可能包含多個同名的聲明,隻要它們在不同的詞法域就沒有關繫。例如,你可以聲明一個局部變量,和包級的變量同名。或者是像2.3.3節的例子那樣,你可以將一個函數參數的名字聲明爲new,雖然內置的new是全局作用域的。但是物極必反,如果濫用不同詞法域可重名的特性的話,可能導致程序很難閲讀。</p>
|
||||
<p>當編譯器遇到一個名字引用時,如果它看起來像一個聲明,它首先從最內層的詞法域向全局的作用域査找。如果査找失敗,則報告“未聲明的名字”這樣的錯誤。如果該名字在內部和外部的塊分别聲明過,則內部塊的聲明首先被找到。在這種情況下,內部聲明屏蔽了外部同名的聲明,讓外部的聲明的名字無法被訪問:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> f() {}
|
||||
|
||||
<span class="hljs-keyword">var</span> g = <span class="hljs-string">"g"</span>
|
||||
@@ -2038,7 +2042,7 @@
|
||||
fmt.Println(h) <span class="hljs-comment">// compile error: undefined: h</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>在函數中詞法域可以深度嵌套, 因此內部的一個聲明可能屏蔽外部的聲明. 還有許多塊是if或for等控製流語句構造的. 下面的代碼有三個不同的變量x, 因爲它們是定義在不同的詞法域的原因. (這個例子隻是爲了演示作用域規則, 但不是好的編程風格.)</p>
|
||||
<p>在函數中詞法域可以深度嵌套,因此內部的一個聲明可能屏蔽外部的聲明。還有許多語法塊是if或for等控製流語句構造的。下面的代碼有三個不同的變量x,因爲它們是定義在不同的詞法域(這個例子隻是爲了演示作用域規則,但不是好的編程風格)。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> main() {
|
||||
x := <span class="hljs-string">"hello!"</span>
|
||||
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < <span class="hljs-built_in">len</span>(x); i++ {
|
||||
@@ -2050,9 +2054,9 @@
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>在 <code>x[i]</code> 和 <code>x + 'A' - 'a'</code> 聲明初始化的表達式中都引用了外部作用域聲明的x變量, 稍後我們會解釋這個. (註意, 後面的表達式和unicode.ToUpper併不等價.)</p>
|
||||
<p>正如上面所示, 併不是所有的詞法域都顯示地對應到由花括弧包含的語句; 還有一些隱含的規則. 上面的for語句創建了兩個詞法域: 花括弧包含的是顯式的部分是for的循環體, 另外一個隱式的部分則是循環的初始化部分, 比如用於迭代變量 i 的初始化. 隱式的部分的作用域還包含條件測試部分和循環後的迭代部分(i++), 當然也包含循環體.</p>
|
||||
<p>下面的例子同樣有三個不同的x變量, 每個聲明在不同的塊, 一個在函數體塊, 一個在for語句塊, 一個在循環體塊; 隻有兩個塊是顯式創建的:</p>
|
||||
<p>在<code>x[i]</code>和<code>x + 'A' - 'a'</code>聲明語句的初始化的表達式中都引用了外部作用域聲明的x變量,稍後我們會解釋這個。(註意,後面的表達式與unicode.ToUpper併不等價。)</p>
|
||||
<p>正如上面例子所示,併不是所有的詞法域都顯式地對應到由花括弧包含的語句;還有一些隱含的規則。上面的for語句創建了兩個詞法域:花括弧包含的是顯式的部分是for的循環體部分詞法域,另外一個隱式的部分則是循環的初始化部分,比如用於迭代變量i的初始化。隱式的詞法域部分的作用域還包含條件測試部分和循環後的迭代部分(<code>i++</code>),當然也包含循環體詞法域。</p>
|
||||
<p>下面的例子同樣有三個不同的x變量,每個聲明在不同的詞法域,一個在函數體詞法域,一個在for隱式的初始化詞法域,一個在for循環體詞法域;隻有兩個塊是顯式創建的:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> main() {
|
||||
x := <span class="hljs-string">"hello"</span>
|
||||
<span class="hljs-keyword">for</span> _, x := <span class="hljs-keyword">range</span> x {
|
||||
@@ -2061,7 +2065,7 @@
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>和彿如循環類似, if和switch語句也會在條件部分創建隱式塊, 還有它們對應的執行體塊. 下面的 if-else 測試鏈演示的 x 和 y 的作用域范圍:</p>
|
||||
<p>和for循環類似,if和switch語句也會在條件部分創建隱式詞法域,還有它們對應的執行體詞法域。下面的if-else測試鏈演示了x和y的有效作用域范圍:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">if</span> x := f(); x == <span class="hljs-number">0</span> {
|
||||
fmt.Println(x)
|
||||
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> y := g(x); x == y {
|
||||
@@ -2071,17 +2075,17 @@
|
||||
}
|
||||
fmt.Println(x, y) <span class="hljs-comment">// compile error: x and y are not visible here</span>
|
||||
</code></pre>
|
||||
<p>第二個if語句嵌套在第一個內部, 因此一個if語句條件塊聲明的變量在第二個if中也可以訪問. switch語句的每個分支也有類似的規則: 條件部分爲一個隱式塊, 然後每個是每個分支的主體塊.</p>
|
||||
<p>在包級别, 聲明的順序併不會影響作用域范圍, 因此一個先聲明的可以引用它自身或者是引用後面的一個聲明, 這可以讓我們定義一些相互嵌套或遞歸的類型或函數. 但是如果一個變量或常量遞歸引用了自身, 則會産生編譯錯誤.</p>
|
||||
<p>在這個程序中:</p>
|
||||
<p>第二個if語句嵌套在第一個內部,因此第一個if語句條件初始化詞法域聲明的變量在第二個if中也可以訪問。switch語句的每個分支也有類似的詞法域規則:條件部分爲一個隱式詞法域,然後每個是每個分支的詞法域。</p>
|
||||
<p>在包級别,聲明的順序併不會影響作用域范圍,因此一個先聲明的可以引用它自身或者是引用後面的一個聲明,這可以讓我們定義一些相互嵌套或遞歸的類型或函數。但是如果一個變量或常量遞歸引用了自身,則會産生編譯錯誤。</p>
|
||||
<p>在這個程序中:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">if</span> f, err := os.Open(fname); err != <span class="hljs-constant">nil</span> { <span class="hljs-comment">// compile error: unused: f</span>
|
||||
<span class="hljs-keyword">return</span> err
|
||||
}
|
||||
f.ReadByte() <span class="hljs-comment">// compile error: undefined f</span>
|
||||
f.Close() <span class="hljs-comment">// compile error: undefined f</span>
|
||||
</code></pre>
|
||||
<p>變量 f 的作用域隻有if語句內, 因此後面的語句將無法引入它, 將導致編譯錯誤. 你可能會收到一個局部變量f沒有聲明的錯誤提示, 具體錯誤信息依賴編譯器的實現.</p>
|
||||
<p>通常需要在if之前聲明變量, 這樣可以確保後面的語句依然可以訪問變量:</p>
|
||||
<p>變量f的作用域隻有在if語句內,因此後面的語句將無法引入它,這將導致編譯錯誤。你可能會收到一個局部變量f沒有聲明的錯誤提示,具體錯誤信息依賴編譯器的實現。</p>
|
||||
<p>通常需要在if之前聲明變量,這樣可以確保後面的語句依然可以訪問變量:</p>
|
||||
<pre><code class="lang-Go">f, err := os.Open(fname)
|
||||
<span class="hljs-keyword">if</span> err != <span class="hljs-constant">nil</span> {
|
||||
<span class="hljs-keyword">return</span> err
|
||||
@@ -2089,7 +2093,7 @@ f.Close() <span class="hljs-comment">// compile error: undefined f</span>
|
||||
f.ReadByte()
|
||||
f.Close()
|
||||
</code></pre>
|
||||
<p>你可能會考慮通過將ReadByte和Close移動到if的else塊來解決這個問題:</p>
|
||||
<p>你可能會考慮通過將ReadByte和Close移動到if的else塊來解決這個問題:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">if</span> f, err := os.Open(fname); err != <span class="hljs-constant">nil</span> {
|
||||
<span class="hljs-keyword">return</span> err
|
||||
} <span class="hljs-keyword">else</span> {
|
||||
@@ -2098,8 +2102,8 @@ f.Close()
|
||||
f.Close()
|
||||
}
|
||||
</code></pre>
|
||||
<p>但這不是Go推薦的做法, Go的習慣是在if中處理錯誤然後直接返迴, 這樣可以確保正常成功執行的語句不需要代碼縮進.</p>
|
||||
<p>要特别註意短的變量聲明的作用域范圍, 考慮下面的程序, 它的目的是穫取當前的工作目録然後保存到一個包級的變量中. 這可以通過直接調用 os.Getwd 完成, 但是將這個從主邏輯中分離齣來可能會更好, 特别是在需要處理錯誤的時候. 函數 log.Fatalf 打印信息, 然後調用 os.Exit(1) 終止程序.</p>
|
||||
<p>但這不是Go語言推薦的做法,Go語言的習慣是在if中處理錯誤然後直接返迴,這樣可以確保正常執行的語句不需要代碼縮進。</p>
|
||||
<p>要特别註意短變量聲明語句的作用域范圍,考慮下面的程序,它的目的是獲取當前的工作目録然後保存到一個包級的變量中。這可以本來通過直接調用os.Getwd完成,但是將這個從主邏輯中分離出來可能會更好,特别是在需要處理錯誤的時候。函數log.Fatalf用於打印日誌信息,然後調用os.Exit(1)終止程序。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> cwd <span class="hljs-typename">string</span>
|
||||
|
||||
<span class="hljs-keyword">func</span> init() {
|
||||
@@ -2109,8 +2113,8 @@ f.Close()
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>雖然cwd在外部已經聲明過, 但是 <code>:=</code> 語句還是將 cwd 和 err 重新聲明爲局部變量. 內部聲明的 cwd 將屏蔽外部的聲明, 因此上面的代碼併不會更新包級聲明的 cwd 變量.</p>
|
||||
<p>當前的編譯器將檢測到局部聲明的cwd併沒有本使用, 然後報告這可能是一個錯誤, 但是這種檢測併不可靠. 一些小的代碼變更, 例如增加一個局部cwd的打印語句, 就可能導致這種檢測失效.</p>
|
||||
<p>雖然cwd在外部已經聲明過,但是<code>:=</code>語句還是將cwd和err重新聲明爲新的局部變量。因爲內部聲明的cwd將屏蔽外部的聲明,因此上面的代碼併不會正確更新包級聲明的cwd變量。</p>
|
||||
<p>由於當前的編譯器會檢測到局部聲明的cwd併沒有本使用,然後報告這可能是一個錯誤,但是這種檢測併不可靠。因爲一些小的代碼變更,例如增加一個局部cwd的打印語句,就可能導致這種檢測失效。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> cwd <span class="hljs-typename">string</span>
|
||||
|
||||
<span class="hljs-keyword">func</span> init() {
|
||||
@@ -2121,8 +2125,8 @@ f.Close()
|
||||
log.Printf(<span class="hljs-string">"Working directory = %s"</span>, cwd)
|
||||
}
|
||||
</code></pre>
|
||||
<p>全局的cwd變量依然是沒有被正確初始化的, 而且看似正常的日誌輸齣更是這個BUG更加隱晦.</p>
|
||||
<p>有許多方式可以避免齣現類似潛在的問題. 最直接的是通過單獨聲明err變量, 來避免使用 <code>:=</code> 的簡短聲明方式:</p>
|
||||
<p>全局的cwd變量依然是沒有被正確初始化的,而且看似正常的日誌輸出更是讓這個BUG更加隱晦。</p>
|
||||
<p>有許多方式可以避免出現類似潛在的問題。最直接的方法是通過單獨聲明err變量,來避免使用<code>:=</code>的簡短聲明方式:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> cwd <span class="hljs-typename">string</span>
|
||||
|
||||
<span class="hljs-keyword">func</span> init() {
|
||||
@@ -2133,8 +2137,7 @@ f.Close()
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>我們已經看到包, 文件, 聲明和語句如何來表達一個程序結構. 在下面的兩個章節, 我們將探討數據的結構.</p>
|
||||
<p><strong>譯註: 本章的詞法域和作用域概念有些混淆, 需要重譯一遍.</strong></p>
|
||||
<p>我們已經看到包、文件、聲明和語句如何來表達一個程序結構。在下面的兩個章節,我們將探討數據的結構。</p>
|
||||
|
||||
|
||||
</section>
|
||||
@@ -2166,7 +2169,7 @@ f.Close()
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"katex":{},"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
22
ch2/ch2.html
22
ch2/ch2.html
@@ -21,6 +21,10 @@
|
||||
<link rel="stylesheet" href="../gitbook/style.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-katex/katex.min.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-highlight/website.css">
|
||||
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="2" data-chapter-title="程序結構" data-filepath="ch2/ch2.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="2" data-chapter-title="程序結構" data-filepath="ch2/ch2.md" data-basepath=".." data-revision="Mon Dec 28 2015 16:03:52 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -238,7 +242,7 @@
|
||||
|
||||
<b>1.5.</b>
|
||||
|
||||
穫取URL
|
||||
獲取URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -253,7 +257,7 @@
|
||||
|
||||
<b>1.6.</b>
|
||||
|
||||
併發穫取多個URL
|
||||
併發獲取多個URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -802,7 +806,7 @@
|
||||
|
||||
<b>5.10.</b>
|
||||
|
||||
Recover捕穫異常
|
||||
Recover捕獲異常
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1315,7 +1319,7 @@
|
||||
|
||||
<b>8.9.</b>
|
||||
|
||||
併發的退齣
|
||||
併發的退出
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1834,7 +1838,7 @@
|
||||
|
||||
<b>12.7.</b>
|
||||
|
||||
穫取結構體字段標識
|
||||
獲取結構體字段標識
|
||||
</a>
|
||||
|
||||
|
||||
@@ -2020,8 +2024,8 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h1 id="第2章-程序結構">第2章 程序結構</h1>
|
||||
<p>Go語言和任何其他語言一樣, 一個大的程序是有很多小的基礎構件組成的. 變量保存值. 簡單的加法和減法運算被組合成較大的表達式. 基礎類型被聚合爲數組或結構體. 然後使用if和for之類的控製語句來組織和控製表達式的執行順序. 然後多個語句被組織到函數中, 以便代碼的隔離和複用. 函數以源文件和包的方式組織.</p>
|
||||
<p>我們已經在前面的章節的例子中看到了大部分的例子. 在本章中, 我們將深入討論Go程序的基礎結構的一些細節. 每個示例程序都是刻意寫的簡單, 這樣我們可以減少被複雜的算法和數據結構所榦擾, 從而專註於語言本身的學習. </p>
|
||||
<p>Go語言和其他編程語言一樣,一個大的程序是由很多小的基礎構件組成的。變量保存值,簡單的加法和減法運算被組合成較複雜的表達式。基礎類型被聚合爲數組或結構體等更複雜的數據結構。然後使用if和for之類的控製語句來組織和控製表達式的執行流程。然後多個語句被組織到一個個函數中,以便代碼的隔離和複用。函數以源文件和包的方式被組織。</p>
|
||||
<p>我們已經在前面章節的例子中看到了很多例子。在本章中,我們將深入討論Go程序基礎結構方面的一些細節。每個示例程序都是刻意寫的簡單,這樣我們可以減少複雜的算法或數據結構等不相關的問題帶來的榦擾,從而可以專註於Go語言本身的學習。 </p>
|
||||
|
||||
|
||||
</section>
|
||||
@@ -2053,7 +2057,7 @@
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"katex":{},"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user