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:
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>
|
||||
|
||||
Reference in New Issue
Block a user