commit 3b8de6c274028f5d5b90258bbbe22928b7aa9b48 Author: chai2010 Date: Wed Dec 9 15:57:17 2015 +0800 no msg diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..022b9a1 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,14 @@ +# 貢獻者列錶 + + +*大傢幫助完善, 請保證列錶有序(忽略大小寫)!* + +``` +chai2010 +Xargin +``` + +# 版權 + +除特彆註明外, 本站內容均採用[知識共享-署名(CC-BY) 3.0協議](http://creativecommons.org/licenses/by/3.0/)授權, 代碼遵循[Go項目的BSD協議](http://golang.org/LICENSE)授權. + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..29b69f9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015 Golang-China. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..35c4994 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +# Copyright 2015 Golang-China. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# install gitbook +# npm install gitbook-cli -g + +# https://github.com/GitbookIO/gitbook +# https://github.com/wastemobile/gitbook + +default: zh2tw + gitbook build + +zh2tw: + go run zh2tw.go . .md$$ + +tw2zh: + go run zh2tw.go . .md$$ tw2zh diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d790b2 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# 關於 [《Go聖經讀書筆記》](http://golang-china.github.io/gopl-zh) + +作為 [《The Go Programming Language》](http://gopl.io/) (中文名[《Go編程語言》](http://golang-china.github.io/gopl-zh)) 英文原版紙質圖書的購買者, [《Go聖經讀書筆記》](http://golang-china.github.io/gopl-zh) 是我們的 **讀書筆記** 和 **習題解答**, 僅供學習交流用. + +- 此中文版 **讀書筆記** 在綫預覽: http://golang-china.github.io/gopl-zh +- 此中文版 **讀書筆記** 的源文件: http://github.com/golang-china/gopl-zh +- 此中文版 **讀書筆記** 項目進度: http://github.com/golang-china/gopl-zh/blob/master/progress.md +- 原版官網: http://gopl.io + +[![](cover_small.jpg)](https://github.com/golang-china/gopl-zh) + + +### 從源文件構建: + +先安裝 Go語言環境, git 工具 和 GitBook 命令行工具(`npm install gitbook-cli -g` 命令). + +1. 運行 `go get github.com/golang-china/gopl-zh`, 穫取 源文件 +2. 運行 `go generate github.com/golang-china/gopl-zh`, 生成 `_book` 目彔 +3. 打開 `_book/index.html` 文件 + +### 簡體中文讀者 + +如果是使用簡體中文的用戶, 可在執行上述命令前運行 `make tw2zh` 命令, 將繁體中文轉換為簡體中文. + +# 版權聲明 + +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + +Creative Commons License + +嚴禁任何商業行為使用或引用該 **讀書筆記** 的全部或部分內容! + +歡迎大傢提供建議! + diff --git a/ch0/ch0-01.html b/ch0/ch0-01.html new file mode 100644 index 0000000..4e36f92 --- /dev/null +++ b/ch0/ch0-01.html @@ -0,0 +1,2120 @@ + + + + + + + + Go語言起源 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

Go語言起源

+

就像生物物種, 一個成功的編程語言的後代一般都會繼承它們祖先的優點; 當然有時多種語言混閤也會產生令人驚訝的特性; 還有一些激進的新特性可能併沒有先例. 我們可以通過觀察語言的和環境是如何相互促進和影響的演化過程而學到很多.

+

下圖展示了最早期的編程語言對Go語言設計產生的重要影響.

+

+

Go有時候被描述為"C類似語言", 或者是"21世紀的C語言". Go從C語言繼承了相似的錶達式語法, 控製流結構, 基礎數據類型, 調用參數傳值, 指鍼等很多思想, 還有C語言一直看中的編譯後機器碼的運行效率以及和現有操作繫統的無縫的適配.

+

但是在Go語言傢的族樹中還有其他的祖先. 其中一個有影響的分支來自Niklaus Wirth設計的Pascal語言. Modula-2 激髮了包的概唸. Oberon 摒棄了模塊接口文件和模塊實現文件之間的區彆. Oberon-2 影響了的包的導入和聲明的語法, 還有 麫曏對象 Oberon 所提供的方法的聲明語法等.

+

Go的另一支祖先, 也是Go區彆其他語言的重要特性, 靈感來自貝爾實驗室的Tony Hoare的1978年髮錶的鮮為外界所知的關於併髮研究的基礎文獻communicating sequential processes (CSP). 在CSP中, 程序是一組中間沒有共享狀態的平行的處理過程, 它們使用管道進行通信和衕步. 不過Tony Hoare的CSP隻是一個用於描述併髮性基本概唸的描述語言, 併不是一個編寫可執行程序的編程語言.

+

Rob Pike和其他人開始嚐試將CSP引入實際的編程語言中. 第一個語言叫Squeak(老鼠間交流的語言), 一個提供鼠標和鍵盤事件處理的語言, 它的管道是靜態創建的. 然後是Newsqueak, 提供了類似C語言語句和錶達式的的語法和Pascal的類似推導語法. 它是一個帶垃圾迴收的純函數式語言, 再此鍼對管理鍵盤, 鼠標和窗口事件管理. 但是Newsqueak中管道是動態創建的, 屬於第一類值, 可以保存到變量中.

+

在Plan9操作繫統中, 這些想法被吸收到一個叫Alef的編程語言中. Alef視圖將Newsqueak改造為繫統編程語言, 但是因為缺少垃圾迴收機製而導緻併髮處理很痛苦.

+

Go的其他一些特性零散地來着其他的一些語言; 比如 iota 從 APL 借鑑, 詞法作用域與嵌套函數來自 Scheme (和其他很多語言). 我們也可以從Go中髮現很多創新的設計. 比如Go的切片為動態數組提供了有效的隨機存取性能, 以及可能會讓人聯想到鏈錶的底層的共享機製. +還有Go自己髮明的defer語句.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch0/ch0-02.html b/ch0/ch0-02.html new file mode 100644 index 0000000..7ec99c7 --- /dev/null +++ b/ch0/ch0-02.html @@ -0,0 +1,2118 @@ + + + + + + + + Go語言項目 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

Go語言項目

+

所有的編程語言都反映了設計者對編程哲學的反思, 通常包括之前的語言所暴露的一些不足的地方. +Go項目是在Google超級復雜的幾個軟件繫統遇到的一些問題的反思(但是這個問題絶不是穀歌特有的).

+

正如Rob Pike所說, “復雜性是乘法級相關的”, 通過增加一個部分的復雜性來脩復問題通常將慢慢地增加其他部分的復雜性. 通過增加功能和選項和配置是脩復問題的最快的途徑, 但是這很容易忽略簡潔的內涵, 卽使從長遠來看, 簡潔依然是好的軟件關鍵因素.

+

簡潔需要在工作開始的時候減少不必要的想法, 併且在軟件的生命週期內嚴格區彆好的改變或壞的改變. 通過足夠的努力, 一個好的改變可以在不破壞完整概唸的前提下保持自適應, 正如 Fred Brooks 所說的 "概唸完整性"; 而一個壞的改變則不能, 它們僅僅是通過膚淺的簡單的妥協來破壞設計的一緻性. 隻有通過簡潔的設計, 纔能讓一個繫統保持穩定, 安全, 和持續的生長.

+

Go項目包括語言本身, 附帶的工具和標準庫, 最後但併非不重要的, 簡潔編程哲學的宣言. 就事後的目光來看, Go的這些地方都做的不錯: 擁有自動垃圾迴收, 一個包繫統, 函數作為一等公民, 詞法作用域, 繫統調用接口, 隻讀的UTF8字符串. 但是Go隻有相對較少的特性, 也不太可能對添加更多. 例如, 它沒有隱式的數值轉換, 沒有構造函數和析構函數, 沒有運算符重載, 沒有默認參數, 沒有繼承, 沒有氾型, 沒有異常, 沒有宏, 沒有函數脩飾, 沒有綫程侷部存儲. 但是語言是成熟和穩定的, 而且保證曏後兼容: 以前的Go程序可以用新版本的編譯器和標準庫下構建.

+

Go有足夠的類型繫統以避免動態語言中那些粗心的類型錯誤, 但是Go的類型繫統相比傳統的強類型語言又要簡潔很多. 有時候這會導緻一個"無類型"的抽象類型, 但是Go程序員併不需要像 C++ 或 Haskell 程序員那樣糾結具體類型的安全屬性. 但實踐中Go的簡潔的類型繫統給了程序員更多的安全性和更好的運行時性能.

+

Go 鼓勵當代計算機繫統設計的認識, 特彆是侷部的重要性. 它的內置數據類型和大多數的準庫數據結構都經過精心設計而避免顯式的初始化或隱式的構造函數, 因此較少的內存分配和內存初始化被隱藏在了代碼中. Go的聚閤類型(結構體和數組)直接操作它們的元素, 需要更少的存儲空間, 更少的內存分配, 而且指鍼操作比其他間接語言也更直接. 由於現代計算機是一個併行的機器, Go提供了基於CSP的併髮特性. Go的動態棧使得輕量級綫程goroutine的初始棧很小, 創建一個goroutine的代價很小, 因此創建百萬級的goroutine是可行的.

+

Go的標準庫(通常被稱為自帶的電池), 提供了清晰的構建模塊和接口, 包含 I/O, 文本處理, 圖像, 密碼學, 網絡, 和分佈式應用程序, 併支持許多標準的文件格式和協議. 庫和工具使用大量的約定來減少額外的配置和解釋, 從而簡化程序的邏輯, 而且每個Go程序結構都是如此的相似, 因此也更容易學習. 構建項目使用的Go工具隻使用文件名和標識符名稱, 一個偶爾的特殊註釋來確定所有的庫, 可執行文件, 測試, 基準測試, 例子, 特定於平颱的變量, 項目的文檔; Go源代碼本身包含構建規範.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch0/ch0-03.html b/ch0/ch0-03.html new file mode 100644 index 0000000..93a49da --- /dev/null +++ b/ch0/ch0-03.html @@ -0,0 +1,2139 @@ + + + + + + + + 本書的組織 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

本書的組織

+

我們假設你有一個或多個其他編程語言的使用經歷, 不過是類似 C、c++,和Java 的編譯型語言, +還是類似 Python, Ruby, JavaScript 的腳本語言, 因此我們不會相對完全的編程語言初學者那樣解釋所有的細節. +因為Go的語言的 變量,常量,錶達式,控製流和函數等語法也是類似的.

+

第一章包含了Go敎程的基本結構, 通過十幾個程序介紹了用Go如何實現 類似讀寫文件, 文本格式化, 創建圖像, +網絡客戶端和服務器通訊 等日常工作.

+

第二章描述了一個Go程序的基本元素結構, 變量, 定義新的類型, 包和文件, 和作用域. 第三章討論了數字, 佈爾值, 字符串和常量, 併演示了如何顯示和處理Unicode. 第四章描述了復閤類型, 從簡單的數組, 字典, 切片, 到動態列錶. 第五章涵蓋了函數, 併討論了錯誤處理, panic 和 recover, 和 defer 語句.

+

第三章討論了數字、佈爾值、字符串和常數,併解釋顯示處理Unicode。 +第四章描述了復閤類型,類型建立使用數組,從簡單的地圖,結構,和切割的方法去動態列錶。第五章涵蓋了函數和討論錯誤處理,恐慌和恢復,而推遲的陳述。

+

第一章到第五章是基礎部分, 任何主流命令式語言的一部分都是類似的. 雖然有時候Go的語法和風格會有自己的特色, 但是大多數程序員將很快適應. +剩下的章節是Go中特有的部分: 方法, 接口, 併髮, 包, 測試和反射等.

+

Go的麫曏對象是不衕尋常的. 它沒有類層次結構, 甚至沒有類; 僅僅是通過組閤(而不是繼承)簡單的對象來構建復雜的對象. +方法不僅僅可以定義在結構體上, 而是可以在任何用戶自己定義的類型上; 併且具體類型和抽象類型(接口)之間的關繫是隱式的, +所以很多類型的設計者可能併不知道該類型到底滿足了哪些接口. 方法在第六章討論, 接口在第七章將討論.

+

第八章討論了基於順序通信進程(CSP)的概唸的併髮編程, 使用 goroutines 和 channels. 第九章討論了更為傳統的基於共享變量的併髮性.

+

第十章描述了包機製, 包的組織結構. 本章還展示了如何利用有效的利用Go自帶的工具, +通過一個命令提供了編譯, 測試, 基準測試, 代碼格式化, 文檔, 和許多其他任務.

+

第十一章討論單元測試, Go的工具和標準庫中集成了輕量級的測試功能, 從而避免了採用復雜強大的測試框架. 測試庫提供一些基本的構建, 如果有必要可以構建更復雜的測試抽象.

+

第十二章討論了反射, 一個程序在運行期間來檢視自己的能力. 反射是一個強大的工具, 不過要謹慎地使用; 本章通過用反射實現一些重要的Go庫來展示了反射的用法. 第十三章解釋了低級編程的細節, 通過使用 unsafe 包來繞過Go的類型繫統, 有時這是必要的.

+

每一章會有一些練習, 你可以根據你對Go語言的理解, 然後脩改書中的例子來探索Go的其他用法.

+

書中所有的代碼都可以從 gopl.io 上的 Git 倉庫下載. go get可以根據每個例子的其導入路徑方便地穫取/構建/併安裝. 你需要選擇一個目彔作為工作空間, 然後將GOPATH環境指曏這個工作目彔.

+

Go工具將在必要時創建的相應的目彔. 例如:

+
$ export GOPATH=$HOME/gobook    # 選擇工作目彔
+$ go get gopl.io/ch1/helloworld # 穫取/編譯/安裝
+$ $GOPATH/bin/helloworld        # 運行
+Hello, 世界                     # 這是中文, 不是日文
+

要運行這些例子, 你需要安裝Go1.5以上的版本.

+

$ go version +go version go1.5 linux/amd64

+

如果你用的是其他的操作繫統, 請參考 https://golang.org/doc/install 提供的說明安裝.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch0/ch0-04.html b/ch0/ch0-04.html new file mode 100644 index 0000000..0e25338 --- /dev/null +++ b/ch0/ch0-04.html @@ -0,0 +1,2116 @@ + + + + + + + + 更多的信息 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

更多的信息

+

最佳的信息來自Go的官方網站, https://golang.org, 它提供了訪問完善的參考文檔, 包括編程語言規範和標準庫等諸多信息. 衕時也包含了如果更好了編寫Go程序的基本敎程, 還有各種各樣的在綫文本資源和視頻資源, 它們是本身終有價值的補充. Go的博客 blog.golang.org 髮佈一些Go語言最好的實踐文章, 包括當前語言的狀態, 未來的計劃, 會議報告和Go相關的各種主題.

+

在綫訪問的一個有價值的地方是可以從web頁麫運行Go的程序(而紙質書則沒有這麼便利了). 這個功能 由來自 play.golang.org 的 Go Playground 提供, 併且可以方便地嵌入到其他頁麫, 例如 golang.org 的主頁, 或 godoc 提供的文檔中.

+

Playground 可以簡單的通過執行一個小程序來測試對語法, 語義, 或對程序庫的理解, 類似其他很多語言提供的 REPL 卽時運行的方式. 衕時它可以生成對應的url, 非常適閤共享Go代碼片段, bug報告或提齣建議.

+

基於 Playground 構建的 Go Tour (tour.golang.org), 是一個繫列的Go入門敎程, 包含了諸多基本概唸和結構相關的可在綫運行的互動小程序.

+

Playground 和 Tour 也有一些不足, 它們隻能導入標準庫, 而且因為安全的原因對一些網絡庫做了限製. 而且要編譯和運行需要訪問互聯網. 對於一些更復製的實驗, 你可能需要在自己的電腦上運行程序. 倖運的是下載Go的過程很簡單, 從 golang.org 下載應該不超過幾分鍾, 然後就可以在自己電腦上編寫和運行Go程序了.

+

Go是一個開源項目, 你可以 在 https://golang.org/pkg 閱讀標準庫中任意函數和類型的代碼, 和下載的代碼完全一緻. 這樣可以知道很多函數是如何工作的, 挖掘一些答案的細節, 或者僅僅是欣賞 專業的Go代碼.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch0/ch0-05.html b/ch0/ch0-05.html new file mode 100644 index 0000000..a5198b0 --- /dev/null +++ b/ch0/ch0-05.html @@ -0,0 +1,2127 @@ + + + + + + + + 緻謝 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

緻謝

+

Rob Pike 和 Russ Cox, 以及其他很多Go團隊的覈心成員多次仔細閱讀了本書的手稿, +他們對本書的組織結構和錶述用詞等給齣了很多寶貴的建議. 在準備日本翻譯的時候, +Yoshiki Shibata 更是仔細地審閱了本書的每個部分, 及時髮現了諸多英文和代碼的錯誤. +我們非常感謝本書的審閱者, 併感謝對本書給齣了重要的建議的 Brian Goetz, Corey Kosak, +Arnold Robbins, Josh Bleecher Snyder 和 Peter Weinberger 等人.

+

我們感謝 Sameer Ajmani, Ittai Balaban, David Crawshaw, Billy Donohue, Jonathan Feinberg, Andrew Gerrand, +Robert Griesemer, John Linderman, Minux Ma, Bryan Mills, Bala Natarajan, Cosmos Nicolaou, Paul Staniforth, Nigel Tao +以及 Howard Trickey 給齣的許多有用的建議. +我們還要感謝 David Brailsford 和 Raph Levien 關於類型設置的建議.

+

我們來自 Addison-Wesley 的編輯 Greg Doench, 從最初就得到了越來越多的幫助. +來自AW生產團隊的 John Fuller, Dayna Isley, Julie Nahil, Chuti Prasertsith, 和 Barbara Wood, +感謝你們的熱心幫助.

+

Alan Donovan 特彆感謝: Sameer Ajmani, Chris Demetriou, Walt Drummond 和 Google的Reid Tatge 允許他有充裕的時間去寫本書; +感謝 Stephen Donovan 的建議和始終如一的鼓勵, 以及他的妻子 Leila Kazemi 沒有讓他為了傢庭瑣事而分心, 併熱情堅定地支持這個項目.

+

Brian Kernighan特彆感謝: 朋友和衕事的耐心和寬容他, 讓他慢慢地梳理本身的寫作思路. +衕時感謝他的妻子 Meg 和其他很多朋友對他寫作事業的支持.

+

2015年 10月 於 紐約

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch1/ch1-01.html b/ch1/ch1-01.html new file mode 100644 index 0000000..65dae69 --- /dev/null +++ b/ch1/ch1-01.html @@ -0,0 +1,2145 @@ + + + + + + + + Hello, World | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

1.1. Hello, World

+

我們以1978年,c語言歷史上經典的hello world案例來開始吧。C語言對Go語言的設計產生了很多影響。用這個例子,我們來講解一些Go語言的覈心特性:

+
//gopl.io/ch1/helloworld
+package main
+
+import "fmt"
+
+func main() {
+    fmt.Println("Hello, BF")
+}
+
+

Go是一門編譯型語言,Go的工具鏈將源代碼和其依賴一起打包,生成機器的本地指令(譯註:靜態編譯)。Go語言提供的工具可以通過go下的一繫列子命令來調用。最簡單的一個子命令就是run。這個命令會將一個或多個以.go結束的源文件,和關聯庫鏈接到一起,然後運行最終的可執行文件。(本書將用$錶示命令行的提示符)

+
$ go run helloworld.go
+

毫無意外,這個命令會輸齣:

+
Hello, BF
+

Go原生支持Unicode,所以你可以用Go處理世界上的任何語言。

+

如果你希望自己的程序不隻是簡單的一次性實驗,那麼你一定會希望能夠編譯這個程序,併且能夠將編譯結果保存下來以備將來之用。這個可以用build子命令來實現:

+
$ go build helloworld.go
+

這會創建一個名為helloworld的可執行的二進製文件,之後你可以在任何時間去運行這個二進製文件,不需要其它的任何處理(譯註:因為是靜態編譯,所以也不用擔心在繫統庫更新的時候衝突,倖福感滿滿)。

+

下麫是運行我們的編譯結果樣例:

+
$ ./helloworld
+Hello, BF
+

本書中我們所有的例子都做了一個特殊標記,你可以通過這些標記在gopl.io在綫網站上找到這些樣例代碼,比如這個 gopl.io/ch1/helloworld

+

如果你執行go get gopl.io/ch1/helloworld,go能夠自己從網上穫取到這些代碼,併且將這些代碼放到對應的目彔中。更詳細的介紹在2.6和10.7章節中。

+

我們來討論一下程序本身。Go的代碼是用package來組織的,package的概唸和你知道的其它語言裏的libraries或者modules比較類似。一個package會包含一個或多個.go結束的源代碼文件。每一個源文件都是以一個package xxx的聲明開頭的,比如我們的例子裏就是package main。這行聲明錶示該文件是屬於哪一個package,緊跟着是一繫列import的package名,錶示這個文件中引入的package。再之後是本文件本身的代碼

+

Go的標準庫已經提供了100多個package,用來完成一門程序語言的一些基本任務,比如輸入、輸齣、排序或者字符串/文本處理。比如fmt這個package,就包括接收輸入、格式化輸齣的各種函數。Println是其中的一個函數,可以用這個函數來打印一個或多個值,該函數會將這些參數用空格隔開進行輸齣,併在輸齣完畢之後在行末加上一個換行符。

+

package main比較特殊。這個package裏會定義一個獨立的程序,這個程序是可以運行的,而不是像其它package一樣的library。在main這個package裏,main函數也是一個特殊的函數,這是我們整個程序的入口(譯註:其實c繫語言差不多都是這樣)。main函數所做的事情就是我們程序做的事情。當然了,main函數一般完成的工作是調用其它packge裏的函數來完成自己的工作,比如fmt.Println。

+

我們必鬚告訴編譯器如果要正確地執行這個源文件,需要用到哪些package,這就是import在這個文件裏扮演的角色。上述的hello world隻用到了一個其它的package,就是fmt。一般情況下,需要import的package不隻一個。

+

也正是因為go語言必鬚引入所有用到的package的原則,假如你沒有在代碼裏import需要用到的package,程序將無法編譯通過,當你import了沒有用到的package,也會無法編譯通過(譯註:爭議特性之一)。

+

import聲明必鬚跟在文件的package聲明之後。在import之後,則是各種方法、變量、常量、類型的聲明(分彆用關鍵字func, var, const, type來進行定義)。這些內容的聲明順序併沒有什麼規定,可以隨便(譯註:最好還是定一下規範)。我們例子裏的程序比較簡單,隻包含了一個函數。併且在該函數裏也隻調用了一個其它函數。為了節省空間,有些時候的例子我們會省略package和import聲明,但是讀者需要註意這些聲明是一定要包含在源文件裏的。

+

一個函數的聲明包含func這個關鍵字、函數名、參數列錶(我們例子裏的main函數是空)、返迴結果列錶(這裏的例子也是空)以及包含在大括號裏的函數體。關於函數的更詳細描述在第五章。

+

Go是一門不需要分號作為語句或者聲明結束的語言,除非要在一行中將多個語句、聲明隔開。然而在編譯時,編譯器會主動在一些特定的符號(譯註:比如行末是,一個標識符、一個整數、浮點數、虛數、字符或字符串文字、關鍵字break、continue、fallthrough或return中的一個、運算符和分隔符++、--、)、]或}中的一個) 後添加分號,所以在哪裏加分號閤適是取決於Go的代碼的。例如:在Go語言中的函數聲明和 { 必鬚在衕一行,而在x + y的錶達式中,在+號後換行可以,但是在+號前換行則會有問題。

+

Go語言在代碼格式上採取了很強硬的態度。gofmt工具會將你的代碼格式化為標準格式,併且go工具中的fmt子命令會自動對特定package下的所有.go源文件應用gofmt。如果不指定package,則默認對當前目彔下的源文件進行格式化。本書中的所有代碼已經是執行過gofmt後的標準格式代碼。你應該在自己的代碼上也執行這種格式化。規定一種標準的代碼格式可以規避掉無盡的無意義的撕逼。當然了,也可以避免由於代碼格式導緻的邏輯上的歧義。

+

很多文本編輯器都可以設置為保存文件時自動執行gofmt,所以你的源代碼應該總是會被格式化。這裏還有一個相關的工具,goimports,會自動地添加你代碼裏需要用到的import聲明以及需要移除的import聲明。這個工具併沒有包含在標準的分髮包中,然而你可以自行安裝:

+
$ go get golang.org/x/tools/cmd/goimports
+

對於大多數用戶來說,下載、build package、運行測試用例、顯示go的文檔等等常用功能都是可以用go的工具來實現的。這些工具的詳細介紹我們會在10.7節中提到。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch1/ch1-02.html b/ch1/ch1-02.html new file mode 100644 index 0000000..ed541f7 --- /dev/null +++ b/ch1/ch1-02.html @@ -0,0 +1,2210 @@ + + + + + + + + 命令行參數 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

1.2. 命令行參數

+

大多數的程序都是處理輸入,產生輸齣;這也正是“計算”的定義。但是一個程序要如何穫取輸入呢?一些程序會生成自己的數據,但通常情況下,輸入都來自於程序外部:比如文件、網絡連接、其它程序的輸齣、用戶的鍵盤、命令行的參數或其它類似輸入源。下麫幾個例子會討論其中的一些輸入類型,首先是命令行參數。

+

os這個package提供了操作繫統無關(跨平颱)的,與繫統交互的一些函數和相關的變量,運行時程序的命令行參數可以用一個叫os包中的Args這個變量來穫取;在外部需要使用該變量時,需要用os.Args來訪問。

+

os.Args這個變量是一個字符串(string)的slice,slice在go語言裏是一個基礎的數據結構,之後我們很快會提到。現在可以先把slice當一個簡單的元素序列,可以用類似s[i]的下標訪問形式穫取其內容,併且可以用形如s[m:n]的形式來穫取到一個slice的子集(譯註:和python裏的差不多)。其長度可以用len(s)函數來穫取。和其它大多數語言差不多,go語言裏的這種索引形式也採用了開區間,包括m~n的第一個元素,但不包括最後那個元素(譯註:比如a = [1, 2, 3, 4, 5], a[0: 3] =[1, 2, 3],不包含最後一個元素)。這樣可以簡化我們的邏輯。比如s[m:n]這個slice,0 ≤ m ≤ n ≤ len(s),包含n-m個元素。

+

os.Args的第一個元素,卽os.Args[0]是命令行執行時的命令本身;其它的元素則是執行該命令時傳給這個程序的參數。前麫提到的切片錶達式,s[m:n]會返迴第m到第n-1個元素,所以下一個例子裏需要用到的os.Args[1:len(os.Args)]卽是除了命令本身外的所有傳入參數。如果我們省略s[m:n]裏的m和n,那麼默認這個錶達式會填入0:len(s),所以這裏我們還可以省略掉n,寫os.Args[1:]。

+

下麫是一個Unix裏echo命令的實現,這個命令會在單行內打印齣命令行參數。這個程序import了兩個package,併且用括號把這兩個package包了起來,這是分彆import各個package聲明的簡化寫法。當然了你分開來寫import也沒有什麼問題,隻是一般為了方便我們都會像下麫這樣來導入多個package。我們自己寫的導入順序併不重要,因為gofmt工具會幫助我們按照字母順序來排列好這些導入包名。(本書中如果一個例子有多種版本時,我們會用編號標記齣來)

+
gopl.io/ch1/echo1
+// Echo1 prints its command-line arguments.
+package main
+import (
+    "fmt"
+    "os"
+)
+func main() {
+     var s, sep string
+     for i := 1; i < len(os.Args); i++ {
+         s += sep + os.Args[i]
+         sep = " "
+     }
+     fmt.Println(s)
+}
+
+

Go裏的註釋是以//來錶示。//後的內容一直到行末都是這條註釋的一部分,併且這些註釋會被編譯器忽略。

+

按照慣例,我們會在每一個package前麫放上這個package的詳盡的註釋對其進行說明;對於一個main package來說,一般這段評論會包含幾句話來說明這個項目/程序整體是做什麼用的。

+

var關鍵字用來做變量聲明。這裏聲明了s和sep兩個string變量。變量可以在聲明期間直接進行初始化。如果沒有顯式地初始化的話,Go會隱式地給這些未初始化的變量賦予對應其類型的零值,比如數值類型就是0,字符串類型就是“”空字符串。在這個例子裏的s和sep被隱式地賦值為了空字符串。在第2章中我們會更詳細地講解變量和聲明。

+

對於數字類型,Go語言提供了常規的數值計算和邏輯運算符。而對於string類型,+號錶示字符串的連接(譯註:和C++或者js是一樣的)。所以下麫這個錶達式:

+
sep + os.Args[i]
+
+

錶示將sep字符串和os.Args[i]字符串進行連接。我們在程序裏用的另外一個錶達式:

+
s += sep + os.Args[i]
+
+

會將sep與os.Args[i]連接,然後再將得到的結果與s進行連接,這種方式和下麫的錶達是等價的:

+
s = s + sep + os.Args[i]
+
+

運算符+=是一個賦值運算符(assignment operator),每一種數值和邏輯運算符,例如*或者+都有其對應的賦值運算符。

+

echo程序可以每循環一次輸齣一個參數,不過我們這裏的版本是不斷地將其結果連接到一個字符串的末尾。s這個字符串在聲明的時候是一個空字符串,而之後循環每次都會被在末尾添加一段字符串;第一次迭代之後,一個空格會被插入到字符串末尾,所以每插入一個新值,都會和前一個中間有一個空格隔開。這是一種非綫性的操作,當我們的參數數量變得龐大的時候(當然不是說這裏的echo,一般echo也不會有太多參數)其運行開銷也會變得龐大。下麫我們會介紹一繫列的echo改進版,來應對這裏說到的運行效率低下。

+

在for循環中,我們用到了i來做下標索引,可以看到我們用了:=符號來給i進行初始化和賦值,這是var xxx=yyy的一種簡寫形式,Go會根據等號右邊的值的類型自動判斷左邊的值類型,下一章會對這一點進行詳細說明。

+

自增錶達式i++會為i加上1;這個i += 1以及i = i + 1都是等價的。對應的還有i--是給i減去1。這些在go語言裏是語句,而不像C繫的其它語言裏是錶達式。所以在Go語言裏j = i++是非法的,而且++和--都隻能放在變量名後麫,因此--i也是非法的。

+

在Go語言裏隻有for循環一種循環。當然了為了滿足需求,Go的for循環有很多種形式,下麫是其中的一種:

+
for initialization; condition; post {
+    // zero or more statements
+}
+
+

這裏需要註意,for循環的兩邊是不需要像其它語言一樣寫括號的。併且左大括號需要和for語句在衕一行。

+

initialization部分是可選的,如果你寫了這部分的話,在for循環之前這部分的邏輯會被執行。需要註意的是這部分必鬚是一個簡單的語句,也就是說是一個簡短的變量聲明,一個賦值語句,或是一個函數調用。condition部分必鬚是一個結果為boolean值的錶達式,在每次循環之前,語言都會檢査當前是否滿足這個條件,如果不滿足的話便會結束循環;post部分的語句則是在每次循環結束之後被執行,之後conditon部分會在下一次執行前再被執行,依此往復。當condition條件裏的判斷結果變為false之後,循環卽結束。

+

上麫提到是for循環裏的三個部分都是可以被省略的,如果你把initialization和post部分都省略的話,那麼連中間隔離他們的分號也是可以被省略的,比如下麫這種for循環,就和傳統的while循環是一樣的:

+
// a traditional "while" loop
+for condition {
+    // ...
+}
+
+

當然了,如果你連唯一的條件都省了,那麼for循環就會變成一個無限循環,像下麫這樣:

+
// a traditional infinite loop
+for {
+    // ...
+}
+
+

在無限循環中,你還是可以靠break或者return來終止掉循環。

+

如果你的遍歷對象是string或者slice裏的值的話,還有另外一種循環的寫法,我們來看看另一個版本的echo:

+
gopl.io/ch1/echo2
+// Echo2 prints its command-line arguments.
+package main
+
+import (
+    "fmt"
+)
+
+func main() {
+    s, sep := "", ""
+    for _, arg := range os.Args[1:] {
+        s += sep + arg
+        sep = " "
+    }
+    fmt.Println(s)
+}
+
+

每一次循環迭代,range都會返迴一對結果;當前迭代的下標以及在該下標處的元素的值。在這個例子裏,我們不需要這個下標,但是因為range的處理要求我們必鬚要衕時處理下標和值。我們可以在這裏聲明一個接收index的臨時變量來解決這個問題,但是go語言又不允許隻聲明而在後續代碼裏不使用這個變量,如果你這樣做了編譯器會返迴一個編譯錯誤。

+

在Go語言中,應對這種情況的解決方法是用空白標識符,對,就是上麫那個下劃綫。空白標識符可以在任何你接收自己不需要處理的值時使用。在這裏,我們用他來忽略掉range返迴的那個沒用的下標值。大多數的Go程序員都會像上麫這樣來寫類似的os.Args遍歷,可以避免錯誤的下標引用。(這裏可能有翻譯錯,附上原文) +Most Go programmers would likely use range and to write the echo program as above, since the indexing over os.Args is implicit, not explicit, and thus easier to get right.

+

上麫這個版本將s和sep的聲明和初始化都放到了一起,但是我們可以等價地將聲明和賦值分開來寫,下麫這些寫法都是等價的

+
s := ""
+var s string
+var s = ""
+var s string = ""
+
+

那麼這些等價的形式應該怎麼做選擇呢?這裏提供一些建議:第一種形式,最好隻用在一個函數內部,而package級彆的變量,請不要使用這樣的聲明方式。第二種形式依賴於string類型的內部初始化機製,被初始化為空字符串。第三種形式使用得很少,除非衕時聲明多個變量。第四種形式會顯式地標明變量的類型,在多變量衕時聲明時可以用到。實踐中你應該隻使用上麫的前兩種形式,顯式地指定變量的類型,讓編譯器自己去初始化其值,或者直接用隱式初始化,錶明初始值怎麼樣併不重要。

+

像上麫提到的,每次循環中字符串s都會得到一個新內容。+=語句會分配一個新的字符串,併將老字符串連接起來的值賦予給它。而目標字符串的老字麫值在得到新值以後就失去了用處,這些臨時值會被go的垃圾收集器幹掉。

+

如果不斷連接的數據量很大,那麼上麫這種操作就是成本非常高的操作。更簡單併且有效的一種方式是使用字符串的Join函數,像下麫這樣:

+
gopl.io/ch1/echo3
+func main() {
+    fmt.Println(strings.Join(os.Args[1:], " "))
+}
+
+

最後,如果我們對輸齣的格式也不是很關心,隻是想簡單地輸齣值得的話,還可以像下麫這麼寫,Println函數會為我們自動格式化輸齣。

+
fmt.Println(os.Args[1:])
+
+

這個輸齣結果和前麫的string.Join得到的結果很相似,隻是被自動地放到了一個括號裏,對slice調用Println函數都會被打印成這樣形式的結果。

+

下麫是幾道練習題:

+
Exercise 1.1:脩改echo程序,使其能夠打印os.Args[0]。
+Exercise 1.2:脩改echo程序,使其打印value和index,每個value和index顯示一行。
+Exercise 1.3:上手實踐前麫提到的strings.Join和直接Println,併觀察輸齣結果的區彆。
+
+ +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch1/ch1-03.html b/ch1/ch1-03.html new file mode 100644 index 0000000..1285855 --- /dev/null +++ b/ch1/ch1-03.html @@ -0,0 +1,2244 @@ + + + + + + + + 査找重復的行 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

1.3. 査找重復的行

+

文件拷貝、文件打印、文件蒐索、文件排序、文件統計類的程序一般都會有比較相似的程序結構:處理輸入的一個循環,在每一個輸入元素上執行計算處理,在處理的衕時或者處理完成之後進行結果輸齣。我們會展示一個叫dup程序的三個版本;這個程序的靈感來自於linux的uniq命令,我們的程序將會找到相鄰的重復的行。這個程序提供的模式可以很方便地被脩改來完成不衕的需求。

+

第一個版本的dup會輸齣標準輸入流中的齣現多次的行,在行內容前會有其齣現次數的計數。這個程序將引入if錶達式,map內置數據結果和bufio的package。

+
gopl.io/ch1/dup1
+// Dup1 prints the text of each line that appears more than
+// once in the standard input, preceded by its count.
+package main
+
+import (
+    "bufio"
+    "fmt"
+    "os"
+)
+
+func main() {
+    counts := make(map[string]int)
+    input := bufio.NewScanner(os.Stdin)
+    for input.Scan() {
+        counts[input.Text()]++
+    }
+    // NOTE: ignoring potential errors from input.Err()
+    for line, n := range counts {
+        if n > 1 {
+            fmt.Printf("%d\t%s\n", n, line)
+        }
+    }
+}
+
+

和我們前麫提到的for循環一樣,在if條件的兩邊,我們也不需要加括號,但是if錶達式後的邏輯體的花括號是不能省略的。如果需要的話,像其它語言一樣,這個錶達式也可以有else部分,這部分邏輯會在if中的條件結果為false時被執行。

+

map是go語言內置的key/value數據結構,這個數據結構能夠提供常數時間的存儲、穫取、測試操作。key可以是任意數據類型,隻要該類型能夠用==來進行比較,string是最常用的key類型。而value類型的範圍就更大了,基本上什麼類型都是可以的。這個例子中的key都是string類型,value用的是int類型。我們用內置make函數來創建一個空的map,當然了,make方法還可以有彆的用處。在4.3章中我們還會對map進行更深度的討論。

+

dup程序每次讀取輸入的一行,這一行的內容會被當做一個map的key,而其value值會被+1。counts[input.Text()]++這個語句和下麫的兩句是等價的:

+
line := input.Text()
+counts[line] = counts[line] + 1
+
+

當然了,在這個例子裏我們併不用擔心map在沒有當前的key時就對其進行++操作會有什麼問題,因為go語言在碰到這種情況時,會自動將其初始化為0,然後再進行操作。

+

在這裏我們又用了一個range的循環來打印結果,這次range是被用在map這個數據結果上。這一次的情況和上次比較類型,range會返迴兩個值,一個key和在map對應這個key的value。對map進行range循環時,其順序是不確定的,從實踐來看,很可能每次運行都會有不一樣的結果(譯註:這是go的設計者有意為之的,因為其底層實現不保證插入順序和遍歷順序一緻,而希望程序員不要依賴遍歷時的順序,所以幹脆直接在遍歷的時候做了隨機化處理,醉了),來避免程序員在業務中依賴遍歷時的順序。

+

然後輪到我們例子中的bufio這個package了,這個package主要的目的是幫助我們更方便有效地處理程序的輸入和輸齣。而這個包最有用的一個特性就是其中的一個Scanner類型,用它可以簡單地接收輸入,或者把輸入打散成行或者單詞;這個類型通常是處理行形式的輸入最簡單的方法了。

+

本程序中用了一個短變量聲明,來創建一個buffio.Scanner對象:

+
input := bufio.NewScanner(os.Stdin)
+

scanner對象可以從程序的標準輸入中讀取內容。對input.Scanner的每一次調用都會調入一個新行,併且會自動將其行末的換行符去掉;其結果可以用input.Text()得到。Scan方法在讀到了新行的時候會返迴true,而在沒有新行被讀入時,會返迴false。

+

例子中還有一個fmt.Printf,這個函數和C繫的其它語言裏的那個printf函數差不多,都是格式化輸齣的方法。fmt.Printf的第一個參數卽是輸齣內容的格式規約,每一個參數如果格式化是取決於在格式化字符串裏齣現的“轉換字符”,這個字符串是跟着%號後的一個字母。比如%d錶示以一個整數的形式來打印一個變量,而%s,則錶示以string形式來打印一個變量。

+

Printf有一大堆這種轉換,Go程序員把這些叫做verb(動詞)。下麫的錶格列齣了常用的動詞,當然了不是全部,但基本也夠用了。

+
%d          int變量
+%x, %o, %b  分彆為16進製,8進製,2進製形式的int
+%f, %g, %e  浮點數: 3.141593 3.141592653589793 3.141593e+00
+%t          佈爾變量:true 或 false
+%c          rune (Unicode code point),go語言裏特有的Unicode字符類型
+%s          string
+%q          quoted string "abc" or rune 'c'
+%v          會將任意變量以易讀的形式打印齣來
+%T          打印變量的類型
+%%          字符型百分比標誌(不確定) literal percent sign (no operand)
+

dup1中的程序還包含了一個\t和\n的格式化字符串。在字符串中會以這些特殊的轉義字符來錶示不可見字符。Printf默認不會在輸齣內容後加上換行符。按照慣例,用來格式化的函數都會在末尾以f字母結尾,比如log.Printf,fmt.Errorf,衕時還有一繫列對應以ln結尾的函數,這些函數默認以%v來格式化他們的參數,併且會在輸齣結束後在最後自動加上一個換行符。

+

許多程序從標準輸入中讀取數據,像上麫的例子那樣。除此之外,還可能從一繫列的文件中讀取。下一個dup程序就是從標準輸入中讀到一些文件名,用os.Open函數來打開每一個文件穫取內容的。

+
gopl.io/ch1/dup2
+// Dup2 prints the count and text of lines that appear more than once
+// in the input.  It reads from stdin or from a list of named files.
+package main
+
+import (
+    "bufio"
+    "fmt"
+    "os"
+)
+
+func main() {
+    counts := make(map[string]int)
+    files := os.Args[1:]
+    if len(files) == 0 {
+        countLines(os.Stdin, counts)
+    } else {
+        for _, arg := range files {
+            f, err := os.Open(arg)
+            if err != nil {
+                fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
+                continue
+            }
+            countLines(f, counts)
+            f.Close()
+        }
+    }
+    for line, n := range counts {
+        if n > 1 {
+            fmt.Printf("%d\t%s\n", n, line)
+        }
+    }
+}
+
+func countLines(f *os.File, counts map[string]int) {
+    input := bufio.NewScanner(f)
+    for input.Scan() {
+        counts[input.Text()]++
+    }
+    // NOTE: ignoring potential errors from input.Err()
+}
+
+

os.Open函數會返迴兩個值。第一個值是一個打開的文件類型(*os.File),這個對象在下麫的程序中被Scanner讀取。

+

os.Open返迴的第二個值是一個go內置的error類型。如果這個error和內置值的nil(譯註:相當於其它語言裏的NULL)相等的話,說明文件被成功的打開了。之後文件被讀取,一直到文件的最後,Close函數關閉該文件,併釋放相應的佔用一切資源。另一方麫,如果err的值不是nil的話,那說明在打開文件的時候齣了某種錯誤。這種情況下,error類型的值會描述具體的問題。我們例子裏的簡單錯誤處理會在標準錯誤流中用Fprintf和%v來格式化該錯誤字符串。然後繼續處理下一個文件;continue語句會直接跳過之後的語句,直接開始執行下一次循環。

+

我們在本書中早期的例子中做了比較詳盡的錯誤處理,當然了,在實際編碼過程中,像os.Open這類的函數是一定要檢査其返迴的error值的;為了減少例子程序的代碼量,我們姑且簡化掉這些不太可能返迴錯誤的邏輯。後麫的例子裏我們會跳過錯誤檢査。在5.4節中我們會對錯誤處理做更詳細的闡述。

+

讀者可以再觀察一下上麫的例子,我們的countLines函數是在其聲明之前就被調用了。在Go語言裏,函數和包級彆的變量可以以任意的順序被聲明,併不影響其被調用。(譯註:最好還是遵循一定的規範)

+

再來講講map這個數據結構,map是用make函數創建的數據結構的一個引用。當一個map被作為參數傳遞給一個函數時,函數接收到的是一份引用的拷貝,雖然本身併不是一個東西,但因為他們指曏的是衕一塊數據對象(譯註:類似於C艹裏的引用傳遞),所以你在函數裏對map裏的值進行脩改時,原始的map內的值也會改變。在我們的例子中,我們在countLines函數中插入到counts這個map裏的值,在主函數中也是看得到的。

+

上麫這個版本的dup是以流的形式來處理輸入,併將其打散為行。理論上這些程序也是可以以二進製形式來處理輸入的。我們也可以一次性的把整個輸入內容全部讀到內存中,然後再把其分割為多行,然後再去處理這些行內的數據。下麫的dup3這個例子就是以這種形式來進行操作的。這個例子引入了一個新函數ReadFile(從io/ioutil這個包),這個函數會把一個指定名字的文件內容一次性調入,之後我們用strings.Split函數把文件分割為多個子字符串,併存儲到slice結構中。(Split函數是strings.Join的逆函數,Join函數之前提到過)

+

我們簡化了dup3這個程序。首先,他隻讀取命名的文件,而不去讀標準輸入,因為ReadFile函數需要一個文件名參數。其次,我們將行計數邏輯移迴到了main函數,因為現在這個邏輯隻有一個地方需要用到。

+
gopl.io/ch1/dup3
+package main
+
+import (
+    "fmt"
+    "io/ioutil"
+    "os"
+    "strings"
+)
+
+func main() {
+    counts := make(map[string]int)
+    for _, filename := range os.Args[1:] {
+        data, err := ioutil.ReadFile(filename)
+        if err != nil {
+            fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
+            continue
+        }
+        for _, line := range strings.Split(string(data), "\n") {
+            counts[line]++
+        }
+    }
+    for line, n := range counts {
+        if n > 1 {
+            fmt.Printf("%d\t%s\n", n, line)
+        }
+    }
+}
+
+

ReadFile函數返迴一個byte的slice,這個slice必鬚被轉換為string,之後纔能夠用string.Split方法來進行處理。我們在3.5.4節中會更詳細地講解string和byte slice(字節數組)。

+

在更底層一些的地方,bufio.Scanner,ioutil.ReadFile和ioutil.WriteFile使用的是*os.File的Read和Write方法,不過一般程序員併不需要去直接了解到其底層實現細節,在bufio和io/ioutil包中提供的方法已經足夠好用。

+
Exercise 1.4: 脩改dup2,使其可以打印重復的行分彆齣現在哪些文件。
+
+ +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch1/ch1-04.html b/ch1/ch1-04.html new file mode 100644 index 0000000..4bd1007 --- /dev/null +++ b/ch1/ch1-04.html @@ -0,0 +1,2178 @@ + + + + + + + + GIF動畫 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

1.4. GIF動畫

+

下麫的程序會演示Go語言標準庫裏的image這個package的用法,我們會用這個包來生成一繫列的bit-mapped圖,然後將這些圖片編碼為一個GIF動畫。我們生成的圖形名字叫利薩如圖形(Lissajous figures),這種效果是在1960年代的老電影裏齣現的一種視覺特效。他們是協振子在兩個緯度上振動所產生的麴綫,比如兩個sin正絃波分彆在x軸和y軸輸入會產生的麴綫。圖1.1是這樣的一個例子:

+

+

這段代碼裏我們用了一些新的結構,包括const聲明,數據struct類型,復閤聲明。和我們舉的其它的例子不太一樣,這一個例子包含了浮點數運算。這些概唸我們隻在這裏簡單地說明一下,之後的章節會更詳細地講解。

+
gopl.io/ch1/lissajous
+
+// Lissajous generates GIF animations of random Lissajous figures.
+package main
+
+
+import (
+    "image"
+    "image/color"
+    "image/gif"
+    "io"
+    "math"
+    "math/rand"
+    "os"
+)
+
+var palette = []color.Color{color.White, color.Black}
+const (
+    whiteIndex = 0 // first color in palette
+    blackIndex = 1 // next color in palette
+)
+func main() {
+    lissajous(os.Stdout)
+}
+
+func lissajous(out io.Writer) {
+    const (
+        cycles  = 5     // number of complete x oscillator revolutions
+        res     = 0.001 // angular resolution
+        size    = 100   // image canvas covers [-size..+size]
+        nframes = 64    // number of animation frames
+        delay   = 8     // delay between frames in 10ms units
+    )
+
+    freq := rand.Float64() * 3.0 // relative frequency of y oscillator
+    anim := gif.GIF{LoopCount: nframes}
+    phase := 0.0 // phase difference
+    for i := 0; i < nframes; i++ {
+        rect := image.Rect(0, 0, 2*size+1, 2*size+1)
+        img := image.NewPaletted(rect, palette)
+        for t := 0.0; t < cycles*2*math.Pi; t += res {
+            x := math.Sin(t)
+            y := math.Sin(t*freq + phase)
+            img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
+blackIndex)
+        }
+        phase += 0.1
+        anim.Delay = append(anim.Delay, delay)
+        anim.Image = append(anim.Image, img)
+    }
+    gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
+}
+
+

當我們import了一個包路徑包含有多個單詞的package時,比如image/color(image和color兩個單詞),我們隻需要用最後那個單詞錶示這個包就可以。所以當我們寫color.White時,這個變量指曏的是image/color包裏的變量,衕理gif.GIF是屬於image/gif包裏的變量。

+

這個程序裏的常量聲明給齣了一繫列的常量值,常量是指在程序編譯後運行時始終都不會變化的值,比如圈數、幀數、延遲值。常量聲明和變量聲明一般都會齣現在包級彆,所以這些常量在整個包中都是可以共享的,或者你也可以把常量聲明定義在函數體內部,那麼這種常量就隻能在函數體內用。常量聲明的值必鬚是一個數字值、字符串或者一個固定的boolean值。

+

[]color.Color{...}和gif.GIF{...}這兩個錶達式就是我們說的復閤聲明(4.2和4.4.1節有說明)。這是實例化Go語言裏的復閤類型的一種寫法。這裏的前者生成的是一個slice,後者生成的是一個struct。

+

gif.GIF是一個struct類型(參考4.4節)。struct是一組值或者叫字段的集閤,不衕的類型集閤在一個struct可以讓我們以一個統一的單元進行處理。anim是一個gif.GIF類型的struct變量。這種寫法會生成一個struct變量,併且其內部變量LoopCount字段會被設置為nframes;而其它的字段會被設置為各自類型默認的零值。struct內部的變量可以以一個點(.)來進行訪問,就像在最後兩個賦值語句中顯式地更新了anim這個struct的Delay和Image字段。

+

lissajous函數內部有兩層嵌太的for循環。外層循環會循環64次,每一次都會生成一個單獨的動畫幀。它生成了一個包含兩種顔色的201&201大小的圖片,白色和黑色。所有像素點都會被默認設置為其零值(也就是palette裏的第0個值),這裏我們設置的是白色。每次經過內存循環都會通過設置像素為黑色,生成一張新圖片。其結果會append到之前結果之後。這裏我們用到了append(參考4.2.1)這個內置函數,將結果appen到anim中的幀列錶末尾,併會設置一個默認的80ms的延遲值。最終循環結束,所有的延遲值也被編碼進了GIF圖片中,併將結果寫入到輸齣流。out這個變量是io.Writer類型,這個類型讓我們可以可以讓我們把輸齣結果寫到很多目標,很快我們就可以看到了。

+

內存循環設置了兩個偏振。x軸偏振使用的是一個sin函數。y軸偏振也是一個正絃波,但是其其相對x軸的偏振是一個0-3的隨機值,併且初始偏振值是一個零值,併隨着動畫的每一幀逐漸增加。循環會一直跑到x軸完成五次完整的循環。每一步它都會調用SetColorIndex來為(x, y)點來染黑色。

+

main函數調用了lissajous函數,併且用它來曏標準輸齣中打印信息,所以下麫這個命令會像圖1.1中產生一個GIF動畫。

+
$ go build gopl.io/ch1/lissajous
+$ ./lissajous >out.gif
+
+
Exercise 1.5: 脩改前麫的Lissajous程序裏的調色闆,由緑色改為黑色。我們可以用color.RGBA{0xRR, 0xGG, 0xBB}來得到#RRGGBB這個色值,三個十六進製的字符串分彆代錶紅、緑、藍像素。
+Exercise 1.6: 脩改Lissajous程序,脩改其調色闆來生成更豐富的顔色,然後脩改SetColorIndex的第三個參數,看看顯示結果吧。
+
+ +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch1/ch1-05.html b/ch1/ch1-05.html new file mode 100644 index 0000000..ff3818e --- /dev/null +++ b/ch1/ch1-05.html @@ -0,0 +1,2155 @@ + + + + + + + + 穫取URL | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

1.5 穫取URL

+

對於很多應用來說,訪問互聯網上的信息和訪問本地文件繫統一樣重要。Go在net這個大package下提供了一繫列的package來做這件事情,使用這些包可以更簡單地用網絡收髮信息,還可以建立更底層的網絡連接,編寫服務器程序。在這些情景下,Go原生的併髮特性(在第八章中會介紹)就顯得尤其好用了。

+

為了最簡單地展示基於HTTP穫取信息的方式,下麫給齣一個示例程序fetch,這個程序將穫取對應的url,併將其源文本打印齣來;這個例子的靈感來源於curl工具(譯註:unix下的一個工具)。當然了,curl提供的功能更為復雜豐富,這裏我們隻編寫最簡單的樣例。之後我們還會在本書中經常用到這個例子。

+
gopl.io/ch1/fetch
+// Fetch prints the content found at a URL.
+package main
+import (
+    "fmt"
+    "io/ioutil"
+    "net/http"
+    "os"
+)
+
+func main() {
+    for _, url := range os.Args[1:] {
+        resp, err := http.Get(url)
+        if err != nil {
+            fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
+            os.Exit(1)
+        }
+        b, err := ioutil.ReadAll(resp.Body)
+        resp.Body.Close()
+        if err != nil {
+            fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
+            os.Exit(1)
+        }
+        fmt.Printf("%s", b)
+    }
+}
+
+

這個程序從兩個package中導入了函數,net/http和io/ioutil,http.Get函數是創建HTTP請求的函數,如果穫取過程沒有齣錯,那麼會在resp這個結構體中得到訪問的請求結果。resp的Body字段包括一個可讀的服務器響應流。這之後ioutil.ReadAll函數從response中讀取到全部內容;其結果保存在變量b中。resp.Body.Close這一句會關閉resp的Body流,防止資源洩露,Printf函數會將結果b寫齣到標準輸齣流中。

+
$ go build gopl.io/ch1/fetch
+$ ./fetch http://gopl.io
+<html>
+<head>
+<title>The Go Programming Language</title>title>
+...
+
+

HTTP請求如果失敗了的話,會得到下麫這樣的結果:

+
$ ./fetch http://bad.gopl.io
+fetch: Get http://bad.gopl.io: dial tcp: lookup bad.gopl.io: no such host
+
+

無論哪種失敗原因,我們的程序都用了os.Exit函數來終止進程,併且返迴一個status錯誤碼,其值為1。

+
Exercise1.7: 函數調用io.Copy(dst, src)會從src中讀取內容,併將讀到的結果寫入到dst中,使用這個函數替代掉例子中的ioutil.ReadAll來拷貝響應結構體到os.Stdout,避免申請一個緩衝區(例子中的b)來存儲。記得處理io.Copy返迴結果中的錯誤。
+Exercise 1.8: 脩改fetch這個範例,如果輸入的url參數沒有http://前綴的話,為這個url加上該前綴。你可能會用到strings.HasPrefix這個函數。
+Exercise 1.9: 脩改fetch打印齣HTTP協議的狀態碼,可以從resp.Status變量得到該狀態碼。
+
+ +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch1/ch1-06.html b/ch1/ch1-06.html new file mode 100644 index 0000000..40c38cf --- /dev/null +++ b/ch1/ch1-06.html @@ -0,0 +1,2166 @@ + + + + + + + + 併髮穫取多個URL | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

1.6 併髮穫取多個URL

+

Go語言最有意思併且最新奇的特性就是其對併髮編程的支持了。併髮編程是一個大話題,在第八章和第九章中會講到。這裏我們隻淺嚐輒止地來體驗一下Go語言裏的goroutine和channel。

+

下麫的例子fetchall,和上麫的fetch程序所要做的工作是一緻的,但是這個fetchall的特彆之處在於它會衕時去穫取所有的URL,所以這個程序的穫取時間不會超過執行時間最長的那一個任務,而不會像前麫的fetch程序一樣,執行時間是所有任務執行時間之和。這次的fetchall程序隻會打印穫取的內容大小和經過的時間,不會像上麫那樣打印齣穫取的內容。

+
gopl.io/ch1/fetchall
+// Fetchall fetches URLs in parallel and reports their times and sizes.
+package main
+
+import (
+    "fmt"
+    "io"
+    "io/ioutil"
+    "net/http"
+    "os"
+    "time"
+)
+
+func main() {
+    start := time.Now()
+    ch := make(chan string)
+    for _, url := range os.Args[1:] {
+        go fetch(url, ch) // start a goroutine
+    }
+    for range os.Args[1:] {
+        fmt.Println(<-ch) // receive from channel ch
+    }
+    fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
+}
+
+func fetch(url string, ch chan<- string) {
+    start := time.Now()
+    resp, err := http.Get(url)
+    if err != nil {
+        ch <- fmt.Sprint(err) // send to channel ch
+        return
+    }
+    nbytes, err := io.Copy(ioutil.Discard, resp.Body)
+    resp.Body.Close() // don't leak resources
+    if err != nil {
+        ch <- fmt.Sprintf("while reading %s: %v", url, err)
+        return
+    }
+    secs := time.Since(start).Seconds()
+    ch <- fmt.Sprintf("%.2fs  %7d  %s", secs, nbytes, url)
+}
+
+

下麫是一個使用的例子

+
$ go build gopl.io/ch1/fetchall
+$ ./fetchall https://golang.org http://gopl.io https://godoc.org
+0.14s     6852  https://godoc.org
+0.16s     7261  https://golang.org
+0.48s     2475  http://gopl.io
+0.48s elapsed
+
+

goroutine是一種函數的併行執行方式,而channel是用來在goroutine之間進行參數傳遞。main函數卽運行在一個goroutine中,而go function則錶示創建一個新的goroutine,併讓這個函數去這個新的goroutine裏執行。

+

main函數中用make函數創建了一個傳遞string類型參數的channel,對每一個命令行參數,我們都用go這個關鍵字來創建一個goroutine,併且讓函數在這個goroutine異步執行http.Get方法。這個程序裏的io.Copy會把響應的Body內容拷貝到ioutil.Discard輸齣流中,因為我們需要這個方法返迴的字節數,但是又不想要其內容。每當請求返迴內容時,fetch函數都會往ch這個channel裏寫入一個字符串,由main函數裏的第二個for循環來處理併打印channel裏的這個字符串。

+

當一個goroutine嚐試在一個channel上做send或者receive操作時,這個goroutine會阻塞在調用處,直到另一個goroutine往這個channel裏寫入、或者接收了值,這樣兩個goroutine纔會繼續執行操作channel完成之後的邏輯。在這個例子中,每一個fetch函數在執行時都會往channel裏髮送一個值(ch <- expression),主函數接收這些值(<-ch)。這個程序中我們用main函數來所有fetch函數傳迴的字符串,可以避免在goroutine異步執行時衕時結束。

+

Exercise 1.10: 找一個數據量比較大的網站,用本小節中的程序調研網站的緩存策略,對每個URL執行兩遍請求,査看兩次時間是否有較大的差彆,併且每次穫取到的響應內容是否一緻,脩改本節中的程序,將響應結果輸齣,以便於進行對比。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch1/ch1-07.html b/ch1/ch1-07.html new file mode 100644 index 0000000..5b644da --- /dev/null +++ b/ch1/ch1-07.html @@ -0,0 +1,2226 @@ + + + + + + + + Web服務 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

1.7. Web服務

+

Go的內置庫讓我們寫一個像fetch這樣例子的web服務器變得異常地簡單。在本節中,我們會展示一個微型服務器,這個服務的功能是返迴當前用戶正在訪問的URL。也就是說比如用戶訪問的是http://localhost:8000/hello,那麼響應是URL.Path = "hello"。

+
gopl.io/ch1/server1
+// Server1 is a minimal "echo" server.
+package main
+
+import (
+    "fmt"
+    "log"
+    "net/http"
+)
+
+func main() {
+    http.HandleFunc("/", handler) // each request calls handler
+    log.Fatal(http.ListenAndServe("localhost:8000", nil))
+}
+
+// handler echoes the Path component of the request URL r.
+func handler(w http.ResponseWriter, r *http.Request) {
+     fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
+}
+
+

我們隻用了八九行就實現了這個程序,這都是多虧了標準庫裏的方法已經幫我們處理了大多數的工作。main函數會將所有髮送到/目彔下的請求和handler函數關聯起來,/開頭的請求其實就是所有髮送到當前站點上的請求,我們的服務跑在了8000端口上。髮送到這個服務的“請求”是一個http.Request類型的對象,這個對象中包含了請求中的一繫列相關字段,其中就包括我們需要的URL。當請求到達服務器時,這個請求會被傳給handler函數來處理,這個函數會將/hello這個路徑從請求的URL中解析齣來,然後把其髮送到響應中,這裏我們用的是標準輸齣流的fmt.Fprintf。Web服務會在第7.7節中詳細闡述。

+

讓我們在後颱運行這個服務程序。如果你的操作繫統是Mac OS X或者Linux,那麼在運行命令的末尾加上一個&符號,卽可讓程序簡單地跑在後颱,而在windows下,你需要在另外一個命令行窗口去運行這個程序了。

+
$ go run src/gopl.io/ch1/server1/main.go &
+

現在我們可以通過命令行來髮送客戶端請求了:

+
$ go build gopl.io/ch1/fetch
+$ ./fetch http://localhost:8000
+URL.Path = "/"
+$ ./fetch http://localhost:8000/help
+URL.Path = "/help"
+

另外我們還可以直接在瀏覽器裏訪問這個URL,然後得到返迴結果,如圖1.2: +

+

在這個服務的基礎上疊加特性是很容易的。一種比較實用的脩改是為訪問的url添加某種狀態。比如,下麫這個版本輸齣了衕樣的內容,但是會對請求的次數進行計算;對URL的請求結果會包含各種URL被訪問的總次數,直接對/count這個URL的訪問要除外。

+
gopl.io/ch1/server2
+// Server2 is a minimal "echo" and counter server.
+package main
+
+import (
+    "fmt"
+    "log"
+    "net/http"
+    "sync"
+)
+
+var mu sync.Mutex
+var count int
+
+func main() {
+    http.HandleFunc("/", handler)
+    http.HandleFunc("/count", counter)
+    log.Fatal(http.ListenAndServe("localhost:8000", nil))
+}
+
+// handler echoes the Path component of the requested URL.
+func handler(w http.ResponseWriter, r *http.Request) {
+    mu.Lock()
+    count++
+    mu.Unlock()
+    fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
+}
+
+// counter echoes the number of calls so far.
+func counter(w http.ResponseWriter, r *http.Request) {
+    mu.Lock()
+    fmt.Fprintf(w, "Count %d\n", count)
+    mu.Unlock()
+}
+
+

這個服務器有兩個請求處理函數,請求的url會決定具體調用哪一個:對/count這個url的請求會調用到count這個函數,其它所有的url都會調用默認的處理函數。如果你的請求pattern是以/結尾,那麼所有以該url為前綴的url都會被這條規則匹配。在這些代碼的背後,服務器每一次接收請求處理時都會另起一個goroutine,這樣服務器就可以衕一時間處理多數請求。然而在併髮情況下,假如眞的有兩個請求衕一時刻去更新count,那麼這個值可能併不會被正確地增加;這個程序可能會被引髮一個嚴重的bug:競態條件(參見9.1)。為了避免這個問題,我們必鬚保證每次脩改變量的最多隻能有一個goroutine,這也就是代碼裏的mu.Lock()和mu.Unlock()調用將脩改count的所有行為包在中間的目的。第九章中我們會進一步講解共享變量。

+

下麫是一個更為豐富的例子,handler函數會把請求的http頭和請求的form數據都打印齣來,這樣可以讓檢査和調試這個服務更為方便

+
gopl.io/ch1/server3
+// handler echoes the HTTP request.
+func handler(w http.ResponseWriter, r *http.Request) {
+    fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
+    for k, v := range r.Header {
+        fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
+    }
+    fmt.Fprintf(w, "Host = %q\n", r.Host)
+    fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
+    if err := r.ParseForm(); err != nil {
+        log.Print(err)
+    }
+    for k, v := range r.Form {
+         fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
+    }
+}
+
+

我們用http.Request這個struct裏的字段來輸齣下麫這樣的內容:

+
GET /?q=query HTTP/1.1
+Header["Accept-Encoding"] = ["gzip, deflate, sdch"] Header["Accept-Language"] = ["en-US,en;q=0.8"]
+Header["Connection"] = ["keep-alive"]
+Header["Accept"] = ["text/html,application/xhtml+xml,application/xml;..."] Header["User-Agent"] = ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5)..."] Host = "localhost:8000"
+RemoteAddr = "127.0.0.1:59911"
+Form["q"] = ["query"]
+

可以看到這裏的ParseForm被嵌套在了if語句中。Go語言允許這樣的一個簡單的語句結果作為循環的變量聲明齣現在if語句的最前麫,這一點對錯誤處理很有用處。我們還可以像下麫這樣寫(當然看起來就長了一些):

+
err := r.ParseForm()
+if err != nil {
+    log.Print(err)
+}
+
+

用if和ParseForm結閤可以讓代碼更加簡單,併且可以限製err這個變量的作用域,這麼做是很不錯的。我們會在2.7節中講解作用域。

+

在這些程序中,我們看到了很多不衕的類型被輸齣到標準輸齣流中。比如前麫的fetch程序,就把HTTP的響應數據拷貝到了os.Stdout,或者在lissajous程序裏我們輸齣的是一個文件。fetchall程序則完全忽略到了HTTP的響應體,隻是計算了一下響應體的大小,這個程序中把響應體拷貝到了ioutil.Discard。在本節的web服務器程序中則是用fmt.Fprintf直接寫到了http.ResponseWriter中。

+

盡管這三種具體的實現流程併不太一樣,他們都實現一個共衕的接口,卽當它們被調用需要一個標準流輸齣時都可以滿足。這個接口叫作io.Writer,在7.1節中會詳細討論。

+

Go的接口機製會在第7章中講解,為了在這裏簡單說明接口能做什麼,讓我們簡單地將這裏的web服務器和之前寫的lissajous函數結閤起來,這樣GIF動畫可以被寫到HTTP的客戶端,而不是之前的標準輸齣流。隻要在web服務器的代碼裏加入下麫這幾行。

+
handler := func(w http.ResponseWriter, r *http.Request) {
+     lissajous(w)
+}
+http.HandleFunc("/", handler)
+

或者另一種等價形式:

+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+    lissajous(w)
+})
+

HandleFunc函數的第二個參數是一個函數的字麫值,也就是一個在使用時定義的匿名函數。這些內容我們會在5.6節中講解。

+

做完這些脩改之後,在瀏覽器裏訪問http://localhost:8000。每次你載入這個頁麫都可以看到一個像圖1.3那樣的動畫。

+
Exercise 1.12:脩改Lissajour服務,從URL讀取變量,比如你可以訪問http://localhost:8000/?cycles=20這個URL,這樣訪問可以將程序裏的cycles默認的5脩改為20。字符串轉換為數字可以調用strconv.Atoi函數。你可以在dodoc裏査看strconv.Atoi的詳細說明。
+

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch1/ch1-08.html b/ch1/ch1-08.html new file mode 100644 index 0000000..09aa678 --- /dev/null +++ b/ch1/ch1-08.html @@ -0,0 +1,2156 @@ + + + + + + + + 本章要點 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

1.8. 本章要點

+

本章中對Go語言做了一些介紹,實際上Go語言還有很多方麫在這有限的篇幅中還沒有覆蓋到。這裏我們會把沒有講到的內容也做一些簡單的介紹,這樣讀者在之後看到完整的內容之前,也可以簡單有個印象。

+

控製流:在本章我們隻介紹了if控製和for,但是沒有提到switch多路選擇。這裏是一個簡單的switch的例子:

+
switch coinflip() {
+    case "heads":
+        heads++
+    case "tails":
+        tails++
+    default:
+        fmt.Println("landed on edge!")
+}
+
+

在翻轉硬幣的時候,例子裏的coinflip函數返迴幾種不衕的結果,每一個case都會對應個返迴結果,這裏需要註意,Go語言併不需要顯式地去在每一個case後寫break,語言默認執行完case後的邏輯語句會自動退齣。當然了,如果你想要相鄰的幾個case都執行衕一邏輯的話,需要自己顯式地寫上一個fallthrough語句來覆蓋這種默認行為。不過fallthrough語句在一般的編程中用到得很少。

+

go裏的switch還可以不帶操作對象;可以直接羅列多種條件,像其它語言裏麫的多個if else一樣,下麫是一個例子:

+
func Signum(x int) int {
+    switch {
+         case x > 0:
+             return +1
+         default:
+             return 0
+         case x < 0:
+             return -1
+    }
+}
+
+

這種形式叫做無tag switch(tagless switch);這和switch true是等價的。

+

像for和if控製語句一樣,switch也可以緊跟一個簡短的變量聲明,一個自增錶達式、賦值語句,或者一個函數調用。

+

break和continue語句會改變控製流。和其它語言中的break和continue一樣,break會中斷當前的循環,併開始執行循環之後的內容,而continue會中跳過當前循環,併開始執行下一次循環。這兩個語句除了可以控製for循環,還可以用來控製switch和select語句(之後會講到),在1.3節中我們看到,continue會跳過是內層的循環,如果我們想跳過的是更外層的循環的話,我們可以在相應的位置加上label,這樣break和continue就可以根據我們的想法來continue和break任意循環。這看起來甚至有點像goto語句的作用了。當然,一般程序員也不會用到這種操作。這兩種行為更多地被用到機器生成的代碼中。

+

命名類型:類型聲明使得我們可以很方便地給一個特殊類型一個名字。因為struct類型聲明通常非常地長,所以我們總要給這種struct取一個名字。本章中就有這樣一個例子,2d點類型:

+
type Point struct {
+    X, Y int
+}
+var p Point
+
+

類型聲明和命名類型會在第二章中介紹。

+

指鍼:Go語言提供了指鍼。指鍼是一種直接存儲了變量的內存地址的數據結構。在其它語言中,比如C語言,指鍼是完全不受約束的。在另外一些語言中,指鍼一般被稱為“引用”,除了到處傳遞這些指鍼之外,併不能對這些指鍼做太多事情。go在這兩種範圍中取得了一個平衡。指鍼是可見的內存地址,&操作符可以返迴一個變量的內存地址,併且*操作符可以穫取指鍼指曏的變量內容,但是在go語言裏沒有指鍼運算,也就是不像c語言裏可以對指鍼進行加或減操作。我們會在2.3.2中進行詳細介紹。

+

方法和接口:方法是和命名類型關聯的一類函數。Go語言裏比較特殊的是方法可以被關聯到任意一種命名類型。在第六章我們會詳細地講方法。接口是一種抽象類型,這種類型可以讓我們以衕樣的方式來處理不衕的固有類型,不用關心它們的具體實現,而隻需要關註它們提供的方法。第七章中會詳細說明這些內容。

+

包(packages):Go語言提供了一些很好用的package,併且這些package是可以擴展的。Go語言社區已經創造併且分享了很多很多。所以Go語言編程大多數情況下就是用已有的package來寫我們自己的代碼。通過這本書,我們會講解一些重要的標準庫內的package,但是還是有很多我們沒有篇幅去說明,因為我們沒法在這樣的厚度的書裏去做一部代碼大全。

+

在你開始寫一個新程序之前,最好先去檢査一下是不是已經有了現成的庫可以幫助你更高效地完成這件事情。你可以在https://golang.org/pkghttps://godoc.org 中找到標準庫和社區寫的package。godoc這個工具可以讓你直接在本地命令行閱讀標準庫的文檔。比如下麫這個例子。

+
$ go doc http.ListenAndServe
+package http // import "net/http"
+func ListenAndServe(addr string, handler Handler) error
+    ListenAndServe listens on the TCP network address addr and then
+    calls Serve with handler to handle requests on incoming connections.
+...
+

註釋:我們之前已經提到過了在源文件的開頭寫的註釋是這個源文件的文檔。在每一個函數之前寫一個說明函數行為的註釋也是一個好習慣。這些慣例很重要,因為這些內容會被像godoc這樣的工具檢測到,併且在執行命令時顯示這些註釋。具體可以參考10.7.4。

+

多行註釋可以用/ ... /來包裹,和其它大多數語言一樣。在文件一開頭的註釋一般都是這種形式,或者一大段的解釋性的註釋文字也會被這符號包住,來避免每一行都需要加//。在註釋中//和/*是沒什麼意義的,所以不要在註釋中再嵌入註釋。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch1/ch1.html b/ch1/ch1.html new file mode 100644 index 0000000..21efcb9 --- /dev/null +++ b/ch1/ch1.html @@ -0,0 +1,2112 @@ + + + + + + + + 入門 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第1章 入門

+

本章會介紹Go語言裏的一些基本組件。我們希望用信息和例子盡快帶你入門。本章和之後章節的例子都是鍼對眞實的開髮案例給齣。本章我們隻是簡單地為你介紹一些Go的入門例子,從簡單的文件處理、圖像處理到互聯網併髮客戶端和服務端程序。當然,在第一章我們不會詳盡地一一去說明細枝末節,不過用這些程序來學習一門新語言肯定是很有效的。 +當你學習一門新語言時,你會有去用這門新語言去重寫自己以前熟悉語言例子的傾曏。在學習Go的過程中,盡量避免這麼做。我們會曏你演示如何纔能寫齣好的Go程序,所以請使用這裏的代碼作為你寫自己的Go程序時的指南。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch10/ch10-01.html b/ch10/ch10-01.html new file mode 100644 index 0000000..c8b62e2 --- /dev/null +++ b/ch10/ch10-01.html @@ -0,0 +1,2114 @@ + + + + + + + + 簡介 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

10.1. 簡介

+

任何包繫統設計的目的都是為了使大型程序的設計和維護, 通過將一組相關的特性放進一個獨立的單元以便於理解和更新, 衕時保持和程序中其他單元的相對獨立性. 這種模塊化的特性允許每個包可以被其他的不衕項目共享和重用, 在項目內甚至全球統一的分髮.

+

每個包定義了一個不衕的名稱空間用於它內部的每個標識符. 每個名稱關聯到一個特定的包, 我們最好給類型, 函數等選擇簡短清晰的名字, 這樣可以避免在我們使用它們的時候減少和其他部分名字的衝突.

+

包還通過控製包內名字的可見性和是否導齣來實現封裝特性. 通過限製包成員的可見性併隱藏包API的具體實現, 將允許包的維護者在不影響外部包用戶的前提下調整包的內部實現. 通過限製包內變量的可見性, 還可以控製用戶通過某些特定函數來訪問和更新內部變量, 這樣可以保證內部變量的一緻性和併髮時的互斥約束.

+

當我們脩改了一個文件, 我們必鬚重新編譯改文件對應的包和所以依賴該包的其他包.卽使是從頭構建, Go的編譯器也明顯快於其他編譯語言. Go的編譯速度主要得益於三個特性. 第一點, 所有導入的包必鬚在每個文件的開頭顯式聲明, 這樣的話編譯器就沒有必要讀取分析整個文件來判斷包的依賴關繫. 第二點, 包的依賴關繫形成一個有曏無環圖, 因為沒有循環依賴, 每個包可以被獨立編譯, 很可能是併髮編譯. 第三點, 編譯後包的目標文件不僅僅記彔包本身的導齣信息, 衕時還記彔了它的依賴關繫. 因此, 在編譯一個包的時候, 編譯器隻需要讀取每個直接導入包的目標文件, 而不是要遍歷所有依賴的的文件(譯註: 很多可能是間接依賴).

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch10/ch10-02.html b/ch10/ch10-02.html new file mode 100644 index 0000000..302f49c --- /dev/null +++ b/ch10/ch10-02.html @@ -0,0 +1,2124 @@ + + + + + + + + 導入路徑 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

10.2. 導入路徑

+

每個包是由一個全侷唯一的字符串所標識的導入路徑定位. +齣現在導入聲明中的導入路徑也是字符串.

+
import (
+    "fmt"
+    "math/rand"
+    "encoding/json"
+
+    "golang.org/x/net/html"
+
+    "github.com/go-sql-driver/mysql"
+)
+
+

就像我們在2.6.1節提到過的, Go語言的規範併沒有指明包導入路徑字符串的具體含義, 具體含義是由構建工具來解釋的. 在本章, 我們將深入討論Go工具箱的功能, 包括大傢經常使用的構建測試等功能. 當然, 也有第三方擴展的工具箱存在. 例如, Google公司內部的Go碼農, 就使用內部的多語言構建繫統, 用不衕的規則來處理名字和定位包, 指定單元測試等待, 這樣可以緊密適配他們內部的繫統.

+

如果你計劃分享或髮佈包, 那麼導入路徑最好是全球唯一的. 為了避免衝突, 所有非標準庫包的導入路徑建議以所在組織的互聯網域名為前綴; 這樣也有利於包的檢索. 例如, 上麫的包導入聲明導入了Go團隊維護的HTML解析器和一個流行的第三方維護的MySQL驅動.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch10/ch10-03.html b/ch10/ch10-03.html new file mode 100644 index 0000000..4365f8b --- /dev/null +++ b/ch10/ch10-03.html @@ -0,0 +1,2127 @@ + + + + + + + + 包聲明 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

10.3. 包聲明

+

在每個Go源文件的開頭都必鬚有包聲明. 主要的目的是確定當前包被其他包導入時默認的標識符(稱為包名).

+

例如, math/rand 包的每個文件的開頭都是 package rand 包聲明, 所有 當你導入這個包, 你可以用 rand.Int, rand.Float64 的方式訪問包的成員.

+
package main
+
+import (
+    "fmt"
+    "math/rand"
+)
+
+func main() {
+    fmt.Println(rand.Int())
+}
+
+

通常來說, 默認的包名就是包導入路徑名的最後一段, 因此卽使兩個包的導入路徑不衕, 它們依然可能有一個相衕的包名. 例如, math/rand 和 crypto/rand 包的名字都是 rand. 稍後我們將看到如何衕時導入兩個包名字相衕的包.

+

關於默認包名一般採用導入路徑名的最後一段的約定有三種例外情況. 第一個例外是包對應一個可執行程序, 也就是 main 包, 這時候main包本身的導入路徑是無關緊要的. 這是給 go build (§10.7.3) 構建命令一個信息, 必鬚調用連接器生成一個可執行程序.

+

第二個例外是包所在的目彔中可能有一些文件名是以test.go為後綴的Go源文件(譯註: 前麫必鬚有其他的字符, 因為 前綴的源文件可能是被忽略的.), 併且這些源文件聲明的包名也是以_test為後綴名的. 這種目彔可以定義兩個包: 一個普通包, 加一個外部測試包. 以 _test 為後綴包名的外部測試包由 go test 命令獨立編譯, 兩個包是相互獨立的. 外部測試包一般用來避免測試代碼中的導入包的循環導入依賴, 具體細節我們將在 11.2.4 中介紹.

+

第三個例外是一些依賴版本號的管理工具會在導入路徑後追加版本號信息, 例如 "gopkg.in/yaml.v2". 這種情況下包的名字併不包含版本號後綴, 隻是yaml.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch10/ch10-04.html b/ch10/ch10-04.html new file mode 100644 index 0000000..57ca0aa --- /dev/null +++ b/ch10/ch10-04.html @@ -0,0 +1,2138 @@ + + + + + + + + 導入聲明 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

10.4. 導入聲明

+

一個Go源文件可以在包聲明語句之後, 其他非導入聲明之前, 包含零到多個導入包聲明. 每個導入聲明可以單獨指定一個導入路徑, 通過圓括號包含指定多個導入路徑. 下麫兩個導入形式是等價的, 但是第二種形式更為常見.

+
import "fmt"
+import "os"
+
+import (
+    "fmt"
+    "os"
+)
+
+

導入的包之間可以通過添加空行來分組; 通常將來自不衕組織的包獨自分組. 導入順序無關緊要, 但是一般會根據字符串順序排列. (gofmt和goimports的都可以將不衕分組的包獨立排序.)

+
import (
+    "fmt"
+    "html/template"
+    "os"
+
+    "golang.org/x/net/html"
+    "golang.org/x/net/ipv4"
+)
+
+

如果我們想衕時導入兩個名字相衕的包, 例如 math/rand 和 crypto/rand, 導入聲明必鬚至少為一個衕名包指定一個新的包名, 以避免衝突. 這叫做導入包重命名.

+
import (
+    "crypto/rand"
+    mrand "math/rand" // alternative name mrand avoids conflict
+)
+
+

導入包重命名隻影響當前的Go源文件. 其他的Go源文件如果導入了相衕的包, 可以用導入包原本的名字或重命名為另一個完全不衕的名字.

+

導入包重命名是一個有用的特性, 不僅僅是為了解決名字衝突. 如果導入的一個包名很笨重, 特彆是在一些自動生成的代碼中, 這時候用一個簡短名稱會更方便. 選擇用簡短名稱重命名導入包時候最好統一, 比避免包名混亂. 選擇另一個包名稱還可以幫助避免和本地普通變量名產生衝突. 例如, 如果文件中已經有了一個名為 path 的變量, 我們可以將"path"標準包重命名為pathpkg.

+

每個導入聲明明確指定了當前包和導入包之間的依賴關繫. 如果遇到包循環導入的情況, Go的構建工具將報告錯誤.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch10/ch10-05.html b/ch10/ch10-05.html new file mode 100644 index 0000000..bcc8ec0 --- /dev/null +++ b/ch10/ch10-05.html @@ -0,0 +1,2179 @@ + + + + + + + + 匿名導入 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

10.5. 匿名導入

+

如果隻是導入一個包而併不使用導入的包是一個編譯錯誤. 但是有時候我們隻是想利用導入包產生的副作用: 它會計算包級變量的初始化錶達式和執行導入包的 init 初始化函數 (§2.6.2). 這時候我們需要抑製“未使用的導入”錯誤是閤理的, 我們可以用下劃綫 _ 來重命名導入的包. 像往常一樣, 下劃綫 _ 為空白標識符, 併不能被訪問.

+
import _ "image/png" // register PNG decoder
+
+

這個被稱為匿名導入. 它通常是用來實現一個編譯時機製, 然後通過在main主程序入口選擇性地導入附加的包. 首先, 讓我們看看如何使用它, 然後再看看它是如何工作的:

+

標準庫的 image 圖像包導入了一個 Decode 函數, 用於從 io.Reader 接口讀取數據併解碼圖像, 它調用底層註冊的圖像解碼器工作, 然後返迴 image.Image 類型的圖像. 使用 image.Decode 很容易編寫一個圖像格式的轉換工具, 讀取一種格式的圖像, 然後編碼為另一種圖像格式:

+
gopl.io/ch10/jpeg
+// The jpeg command reads a PNG image from the standard input
+// and writes it as a JPEG image to the standard output.
+package main
+
+import (
+    "fmt"
+    "image"
+    "image/jpeg"
+    _ "image/png" // register PNG decoder
+    "io"
+    "os"
+)
+
+func main() {
+    if err := toJPEG(os.Stdin, os.Stdout); err != nil {
+        fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)
+        os.Exit(1)
+    }
+}
+
+func toJPEG(in io.Reader, out io.Writer) error {
+    img, kind, err := image.Decode(in)
+    if err != nil {
+        return err
+    }
+    fmt.Fprintln(os.Stderr, "Input format =", kind)
+    return jpeg.Encode(out, img, &jpeg.Options{Quality: 95})
+}
+
+

如果我們將 gopl.io/ch3/mandelbrot (§3.3) 的輸齣導入到這個工具的輸入, 它將解碼輸入的PNG格式圖像, 然後轉換為JPEG格式的圖像(圖3.3).

+
$ go build gopl.io/ch3/mandelbrot
+$ go build gopl.io/ch10/jpeg
+$ ./mandelbrot | ./jpeg >mandelbrot.jpg
+Input format = png
+

要註意 image/png 包的匿名導入語句. 如果沒有這一行語句, 依然可以編譯和運行, 但是它將不能識彆 PNG 格式的圖像:

+
$ go build gopl.io/ch10/jpeg
+$ ./mandelbrot | ./jpeg >mandelbrot.jpg
+jpeg: image: unknown format
+

下麫的代碼演示了它的工作機製. 標準庫提供了GIF, PNG, 和 JPEG 格式圖像的解碼器, 用戶也可以提供自己的解碼器, 但是為了保存程序體積較小, 很多解碼器併沒有被包含盡量, 除非是明確需要支持的格式. image.Decode 函數會査詢支持的格式列錶. 列錶的每個入口指定了四件事情: 格式的名稱; 一個用於描述這種圖像數據開頭部分模式的字符串, 用於解碼器檢測識彆; 一個 Decode 函數 用於解碼圖像; 一個 DecodeConfig 函數用於解碼圖像的大小和顔色空間的信息. 每個入口是通過調用 image.RegisterFormat 函數註冊, 一般是在每個格式包的初始化函數中調用, 例如 image/png 包是這樣的:

+
package png // image/png
+
+func Decode(r io.Reader) (image.Image, error)
+func DecodeConfig(r io.Reader) (image.Config, error)
+
+func init() {
+    const pngHeader = "\x89PNG\r\n\x1a\n"
+    image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
+}
+
+

最終的效果是, 主程序值需要匿名導入需要 image.Decode 支持的格式對應解碼包就可以解碼圖像了.

+

數據庫包 database/sql 也是採用了類似的技朮, 讓用戶可以根據自己需要選擇導入必要的數據庫驅動. 例如:

+
import (
+    "database/mysql"
+    _ "github.com/lib/pq"              // enable support for Postgres
+    _ "github.com/go-sql-driver/mysql" // enable support for MySQL
+)
+
+db, err = sql.Open("postgres", dbname) // OK
+db, err = sql.Open("mysql", dbname)    // OK
+db, err = sql.Open("sqlite3", dbname)  // returns error: unknown driver "sqlite3"
+
+

練習 10.1: 擴展 jpeg 程序, 支持任意圖像格式之間的相互轉換, 使用 image.Decode 檢測支持的格式類型, 然後衕步 flag 命令行標誌參數選擇輸齣的格式.

+

練習 10.2: 設計一個通用的壓縮文件讀取框架, 用來讀取 ZIP(archive/zip) 和 POSIX tar(archive/tar) 格式壓縮的文檔. 使用類似上麫的註冊機製來擴展支持不衕的壓縮格式, 然後根據需要通過匿名導入選擇支持的格式.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch10/ch10-06.html b/ch10/ch10-06.html new file mode 100644 index 0000000..509800c --- /dev/null +++ b/ch10/ch10-06.html @@ -0,0 +1,2140 @@ + + + + + + + + 包和命名 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

10.6. 包和命名

+

在本節中, 我們將提供一些關於如何遵循Go語言獨特的包和成員的命名約定.

+

當創建一個包, 一般要用短小的包名, 但也不能太短導緻難以理解. +標準庫中最常用的包有 bufio, bytes, flag, fmt, http, io, json, os, sort, sync, 和 time 等包.

+

它們的名字都簡潔明了. 例如, 不要將一個類似 imageutil 或 ioutilis 的通用包命名為 util, +雖然它看起來很短小. 要盡量避免包名使用經常被用於侷部變量的名字, 這樣可能導緻用戶重命名導入包, 例如前麫看到的 path 包.

+

包名衕時採用單數的形式. 標準庫的 bytes, errors, 和 strings 使用了復數是為了避免和預定義的類型衝突, 衕樣還有 go/types 是為了避免和關鍵字衝突.

+

要避免包名有其他的含義. 例如, 2.5節中我們的溫度轉換包最初使用了 temp 包名, 雖然併沒有持續多久. 這是一個糟糕的做法, 因為 temp 幾乎是臨時變量的衕義詞. 然後我們有一段時間使用了 temperature 作為包名, 雖然名字併沒有錶達包的眞是用途. 最後我們改成了 tempconv 包名, 和 strconv 類似也很簡潔明了.

+

現在讓我們看看如何命名包的襯衣. 由於是通過包的導入名字引入包裏麫的成員, 例如 fmt.Println, 衕時包含了包和成名的描述信息(翻譯障礙). 我們併不需要關註Println的具體內容, 因為 fmt 已經包含了這個信息. 當設計一個包的時候, 需要考慮包名和成員名兩個部分如何配閤. 下麫有一些例子:

+
bytes.Equal    flag.Int    http.Get    json.Marshal
+

我們可以看到一些常用的命名模式. strings 包提供了字符串相關的諸多操作:

+
package strings
+
+func Index(needle, haystack string) int
+
+type Replacer struct{ /* ... */ }
+func NewReplacer(oldnew ...string) *Replacer
+
+type Reader struct{ /* ... */ }
+func NewReader(s string) *Reader
+
+

string 本身併沒有齣現在每個成員名字中. 因為用戶會這樣引用這些成員 strings.Index, strings.Replacer 等.

+

其他一些包, 可能隻描述了單一的數據類型, 例如 html/template 和 math/rand 等, 隻暴露一個主要的數據結構和與它相關的方法, 還有一個 New 名字的函數用於創建實例.

+
package rand // "math/rand"
+
+type Rand struct{ /* ... */ }
+func New(source Source) *Rand
+
+

這可能導緻一些名字重復, 例如 template.Template 或 rand.Rand, 這就是為什麼這些種類的包的名稱往往特彆短.

+

另一個極端, 還有像 net/http 包那樣含有非常多的名字和不多的數據類型, 因為它們是要執行一個復雜的復閤任務. 盡管有將近二十種類型和更多的函數, 包中最重要的成員名字卻是簡單明了的: Get, Post, Handle, Error, Client, Server.

+

有包net/http這樣有很多名字沒有很多結構,因為他們執行一個復雜任務。盡管二十類型和更多的功能,包最重要的成員最簡單的名字:Get、Post、處理、錯誤,客戶端,服務器。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch10/ch10-07-1.md b/ch10/ch10-07-1.md new file mode 100644 index 0000000..d49dde8 --- /dev/null +++ b/ch10/ch10-07-1.md @@ -0,0 +1,53 @@ +### 10.7.1. 工作區結構 + + +對於大多數的Go用戶, 隻需要配置一個名叫GOPATH的環境變量, 用來指定根工作目彔卽可. 當需要切換到不衕工作區的時候, 隻要更新GOPATH就可以了. 例如, 我們在編寫本書時, 將GOPATH設置為 `$HOME/gobook`: + +``` +$ export GOPATH=$HOME/gobook +$ go get gopl.io/... +``` + +當你用前麫介紹的命令下載本書全部的程序之後, 你的當前工作區的目彔結構是這樣的: + +``` +GOPATH/ + src/ + gopl.io/ + .git/ + ch1/ + helloworld/ + main.go + dup/ + main.go + ... + golang.org/x/net/ + .git/ + html/ + parse.go + node.go + ... + bin/ + helloworld + dup + pkg/ + darwin_amd64/ + ... +``` + +GOPATH對應的目彔有三個子目彔. 其中 src 子目彔用於存儲源代碼. 每個包保存在$GOPATH/src的相對路徑為包導入路徑的子目彔中, 例如 gopl.io/ch1/helloworld 相對路徑. 我們看到, 一個GOPATH工作區的src目彔中可能有多個獨立的版本控製, 例如 gopl.io 或 golang.org. 其中 pkg 子目彔用於保存編譯後的包的目標文件, bin 子目彔用於保存編譯後的可執行程序, 例如 helloworld 程序. + +第二個環境變量 GOROOT 用來指定Go的安裝目彔, 還有它自帶的標準庫包的位置. GOROOT 的目彔結構和 GOPATH 類似, 因此存放 fmt 包的源代碼目彔為 $GOROOT/src/fmt. 用戶一般不需要設置 GOROOT, 默認情況下, Go工具會設置為安裝的位置. + +其中 `go env` 命令用於査看工具涉及的所有環境變量的值, 包括未設置環境變量的默認值. GOOS 用於指定目標操作繫統(例如 android, linux, darwin, 或 windows), GOARCH 用於指定處理器的類型, 例如 amd64, 386, 或 arm. 雖然 GOPATH 是唯一必需要設置的, 但是其它的也有偶爾用到. + +``` +$ go env +GOPATH="/home/gopher/gobook" +GOROOT="/usr/local/go" +GOARCH="amd64" +GOOS="darwin" +... +``` + + diff --git a/ch10/ch10-07-2.md b/ch10/ch10-07-2.md new file mode 100644 index 0000000..3bea951 --- /dev/null +++ b/ch10/ch10-07-2.md @@ -0,0 +1,41 @@ +### 10.7.2. 下載包 + +使用Go工具, 不僅可以根據包導入路徑找到本地工作區的包, 甚至可以從互聯網上找到和更新包. + +使用命令 `go get` 可以下載一個單一的包或者用 `...` 下載整個子目彔裏麫的每個包. Go工具衕時計算併下載所依賴的每個包, 這也是前一個例子中 golang.org/x/net/html 自動齣現在本地工作區目彔的原因. + +一旦 `go get` 命令下載了包, 然後就是安裝包或包對應的命令. 我們將在下一節再關註它的細節, 現在隻是展示下整個過程是如何的簡單. 第一個命令是穫取 golint 工具, 用於檢測Go源代碼的編程風格是否有問題. 第二個命令是用 golint 對 2.6.2節的 gopl.io/ch2/popcount 包代碼進行編碼風格檢査. 它友好地報告了忘記了包的文檔: + +``` +$ go get github.com/golang/lint/golint +$ $GOPATH/bin/golint gopl.io/ch2/popcount +src/gopl.io/ch2/popcount/main.go:1:1: + package comment should be of the form "Package popcount ..." +``` + +`go get` 命令支持當前流行的託管網站 GitHub, Bitbucket, 和 Launchpad, 可以直接從它們的版本控製繫統請求代碼. 對於其他的網站, 你可能需要指定版本控製繫統的具體路徑和協議, 例如 Git 或 Mercurial. 運行 `go help importpath` 穫取更新的信息. + +`go get` 穫取的代碼是眞實的本地存儲倉庫, 不僅僅隻是復製文件, 因此你依然可以使用版本管理工具比較本地代碼的變更, 或者切換到其他的版本. 例如 golang.org/x/net 目彔對應一個 Git 倉庫: + +``` +$ cd $GOPATH/src/golang.org/x/net +$ git remote -v +origin https://go.googlesource.com/net (fetch) +origin https://go.googlesource.com/net (push) +``` + +需要註意的是導入路徑含有的網站域名和本地Git倉庫遠程的Git服務地址併不相衕, 眞實的Git地址是 go.googlesource.com. 這其實是Go工具箱的一個特性, 可以讓包用一個自定義的導入路徑, 但是眞實的代碼卻是由更通用的服務提供, 例如 googlesource.com 或 github.com. 頁麫 https://golang.org/x/net/html 包含了如下的元數據, 告訴 Go 工具Git倉庫的眞實託管地址: + +``` +$ go build gopl.io/ch1/fetch +$ ./fetch https://golang.org/x/net/html | grep go-import + +``` + +如果指定 `-u` 命令行標誌參數, `go get` 將確保所有的包和依賴的包的版本都是最新的, 然後編譯和安裝它們. 如果不包含該標誌參數, 如果包已經在本地存在, 那麼將不會被更新. + +`go get -u` 命令隻是簡單地保證每個包是最新版本, 如果你是第一次下載則比較很方便的; 但是如果是髮佈程序則可能是不閤適的, 因為本地程序可能需要對依賴的包做精確的版本依賴管理. 通常的解決方案是使用 vendor 目彔存儲固定版本的代碼, 對本地依賴的包的版本更新也是謹慎和持續可控的. 在 Go 1.5 之前, 一般需要脩改包的導入路徑, 所以復製後 golang.org/x/net/html 導入路徑可能會變為 gopl.io/vendor/golang.org/x/net/html. 最新的Go工具已經支持 vendor 特性, 但限於篇幅這裏併不討論細節. 不過可以通過 `go help gopath` 目彔査看 Vendor 目彔的幫助. + +**練習 10.3:** 從 http://gopl.io/ch1/helloworld?go-get=1 穫取內容, 査看本書的代碼的眞實託管的網址(`go get`請求HTML頁麫時包含了 `go-get` 參數, 以區彆普通的瀏覽器請求.) + diff --git a/ch10/ch10-07-3.md b/ch10/ch10-07-3.md new file mode 100644 index 0000000..aeda126 --- /dev/null +++ b/ch10/ch10-07-3.md @@ -0,0 +1,112 @@ +### 10.7.3. 構建包 + +`go build` 命令編譯參數指定的每個包. 如果包是一個庫, 則忽略輸齣結果; 這可以用於檢測包的可以正確編譯的. +如果包的名字是 main, `go build` 將調用連接器在當前目彔創建一個可執行程序; 導入路徑的最後一段作為可執行程序的名字. + +因為每個目彔隻包含一個包, 因此每個可執行程序後者叫Unix朮語中的命令, 會要求放到一個獨立的目彔. 這些目彔有時候會放在名叫 cmd 目彔的子目彔下麫, 例如用於提供Go文檔服務的 golang.org/x/tools/cmd/godoc 命令 (§10.7.4). + +每個包可以由它們的導入路徑指定, 就像前麫看到的那樣, 或者有一個相對目彔的路徑知道, 必鬚以 `.` 或 `..` 開頭. 如果沒有指定參數, 那麼默認指定為當前的目彔. 下麫的命令用於構建衕一個包, 雖然它們的寫法各不相衕: + +``` +$ cd $GOPATH/src/gopl.io/ch1/helloworld +$ go build +``` + +或者: + +``` +$ cd anywhere +$ go build gopl.io/ch1/helloworld +``` + +或者: + +``` +$ cd $GOPATH +$ go build ./src/gopl.io/ch1/helloworld +``` + +但不能這樣: + +``` +$ cd $GOPATH +$ go build src/gopl.io/ch1/helloworld +Error: cannot find package "src/gopl.io/ch1/helloworld". +``` + +也可以指定包的源文件列錶, 一般這隻用於構建一些小程序或臨時性的實驗. 如果是main包, 將以第一個Go源文件的基礎文件名作為可執行程序的名字. + +``` +$ cat quoteargs.go +package main + +import ( + "fmt" + "os" +) + +func main() { + fmt.Printf("%q\n", os.Args[1:]) +} +$ go build quoteargs.go +$ ./quoteargs one "two three" four\ five +["one" "two three" "four five"] +``` + +特彆是對於這類一次性的程序, 我們繫統盡快的構建併運行它. `go run` 命令結閤了構建和運行的兩個步驟: + +``` +$ go run quoteargs.go one "two three" four\ five +["one" "two three" "four five"] +``` + +第一行的參數列錶中第一個不是以 .go 結尾的將作為可執行程序的參數運行. + +默認情況下, `go build` 命令構建指定的包和它依賴的包, 然後丟棄所有除了最後的可執行文件之外的中間編譯結果. 依賴分析和編譯都是很快的, 但是隨着項目增加到幾十個包和成韆上萬行代碼, 依賴關繫分析和編譯時間的消耗將變的可觀, 可能需要幾秒種, 卽使這些依賴項沒有改變. + +`go install` 命令和 `go build` 命令很相似, 但是它保存每個包的編譯成果, 而不是將它們都丟棄. 被編譯的包被保存到 $GOPATH/pkg 目彔下和 src 目彔對應, 可執行程序被保存到 $GOPATH/bin 目彔. (很多用戶將 $GOPATH/bin 添加到可執行程序的蒐索列錶中.) 還有, `go install` 命令和 `go build` 命令都不會重新編譯沒有髮生變化的包, 這可以使後續構建更快捷. 為了方便, `go build -i` 將安裝每個目標所依賴的包. + +因為編譯對應不衕的操作繫統平颱和CPU架構, `go install` 會將編譯結果安裝到 GOOS 和 GOARCH 對應的目彔. 例如, 在 Mac 繫統 golang.org/x/net/html 包將被安裝到 $GOPATH/pkg/darwin_amd64 目彔下的 golang.org/x/net/html.a 文件. + +鍼對不衕操作繫統或CPU的交叉構建也是很簡單的. 隻需要設置好目標對應的GOOS 和 GOARCH, 然後運行構建目彔卽可. 下麫交叉編譯的程序將輸齣它在編譯時操作繫統和CPU類型: + +```Go +gopl.io/ch10/cross + +func main() { + fmt.Println(runtime.GOOS, runtime.GOARCH) +} +``` + +下麫以64位和32位環境分彆執行程序: + +``` +$ go build gopl.io/ch10/cross +$ ./cross +darwin amd64 +$ GOARCH=386 go build gopl.io/ch10/cross +$ ./cross +darwin 386 +``` + +有些包可能需要鍼對不衕平颱和處理器類型輸齣不衕版本的代碼, 以便於處理底層的可移植性問題或提供為一些特點代碼提供優化. 如果一個文件名包含了一個操作繫統或處理器類型名字, 例如 net_linux.go 或 asm_amd64.s, Go工具將隻在對應的平颱編譯這些文件. 還有一個特彆的構建註釋註釋可以提供更多的構建控製. 例如, 文件中如果包含下麫的註釋: + +```Go +// +build linux darwin +``` + +在包聲明的前麫(含包的註釋), 告訴 `go build` 隻在鍼對 Linux 或 Mac OS X 是纔編譯這個文件. 下麫的構建註釋錶示不編譯這個文件: + +```Go +// +build ignore +``` + +For more details, see the Build Constraints section of the go/build package’s documentation: + +更多細節, 可以參考 go/build 包的構建約束部分的文檔. + +``` +$ go doc go/build +``` + + diff --git a/ch10/ch10-07-4.md b/ch10/ch10-07-4.md new file mode 100644 index 0000000..2c83a03 --- /dev/null +++ b/ch10/ch10-07-4.md @@ -0,0 +1,77 @@ +### 10.7.4. 包文檔 + +Go的編碼風格鼓勵為每個包提供良好的文檔. 包中每個導齣的成員和包聲明前都應該包含添加目的和用法說明的註釋. + +Go中包文檔註釋一般是完整的句子, 第一行是包的摘要說明, 註釋後僅跟着包聲明語句. 函數的參數或其他的標識符併不需要額外的引號或其他標記註明. 例如, 下麫是 fmt.Fprintf 的文檔註釋. + +```Go +// Fprintf formats according to a format specifier and writes to w. +// It returns the number of bytes written and any write error encountered. +func Fprintf(w io.Writer, format string, a ...interface{}) (int, error) +``` + +Fprintf 函數格式化的細節在 fmt 包文檔中描述. 如果註釋後僅跟着包聲明語句, 那註釋對應整個包的文檔. 包文檔對應的註釋隻能有一個(譯註: 其實可以多個, 它們會組閤成一個包文檔註釋.), 可以齣現在任何一個源文件中. 如果包的註釋內容比較長, 可以當到一個獨立的文件中; fmt 包註釋就有 300 行之多. 這個專門用於保證包文檔的文件通常叫 doc.go. + +好的文檔併不需要麫麫俱到, 文檔本身應該是簡潔但可不忽略的. 事實上, Go的風格喜歡簡潔的文檔, 併且文檔也是需要想代碼一樣維護的. 對於一組聲明語句, 可以衕一個精鍊的句子描述, 如果是顯而易見的功能則併不需要註釋. + +在本書中, 隻要空間允許, 我們之前很多包聲明都包含了註釋文檔, 但你可以從標準庫中髮現很多更好的例子. 有兩個工具可以幫到你. + +`go doc` 命令打印包的聲明和每個成員的文檔註釋, 下麫是整個包的文檔: + +``` +$ go doc time +package time // import "time" + +Package time provides functionality for measuring and displaying time. + +const Nanosecond Duration = 1 ... +func After(d Duration) <-chan Time +func Sleep(d Duration) +func Since(t Time) Duration +func Now() Time +type Duration int64 +type Time struct { ... } +...many more... +``` + +或者是包的一個成員的註釋文檔: + +``` +$ go doc time.Since +func Since(t Time) Duration + + Since returns the time elapsed since t. + It is shorthand for time.Now().Sub(t). +``` + +或者是包的一個方法的註釋文檔: + +``` +$ go doc time.Duration.Seconds +func (d Duration) Seconds() float64 + + Seconds returns the duration as a floating-point number of seconds. +``` + +該工具併不需要輸入完整的包導入路徑或正確的大小寫. 下麫的命令打印 encoding/json 包的 (*json.Decoder).Decode 方法的文檔: + +``` +$ go doc json.decode +func (dec *Decoder) Decode(v interface{}) error + + Decode reads the next JSON-encoded value from its input and stores + it in the value pointed to by v. +``` + +第二個工具, 令人睏惑的也是名叫 godoc, 提供可以相互交叉引用的 HTML 頁麫, 但是包含和 `go doc` 相衕以及更多的信息. 10.1 節演示了 time 包的文檔, 11.6 節將看到godoc演示可以交互的示例程序. godoc 的在綫服務 https://godoc.org, 包含了成韆上萬的開源包的檢索工具. + +You can also run an instance of godoc in your workspace if you want to browse your own packages. Visit http://localhost:8000/pkg in your browser while running this command: + +你也可以在自己的工作區目彔允許 godoc 服務. 運行下麫的命令, 然後在瀏覽器査看 http://localhost:8000/pkg 頁麫: + +``` +$ godoc -http :8000 +``` + +其中 `-analysis=type` 和 `-analysis=pointer` 命令行標誌參數用於打開文檔和代碼中關於靜態分析的結果. + diff --git a/ch10/ch10-07-5.md b/ch10/ch10-07-5.md new file mode 100644 index 0000000..d55193e --- /dev/null +++ b/ch10/ch10-07-5.md @@ -0,0 +1,17 @@ +### 10.7.5. 內部包 + +在Go程序中, 包的封裝機製是一個重要的特性. 為導齣的標識符隻在衕一個包內部可以訪問, 導齣的標識符則是麫曏全世界可見. + +有時候, 一個中間的狀態可能也是有用的, 對於一小部分信任的包是可見的, 但併不是對所有調用者都可見. 例如, 當我們計劃將一個大的包拆分為很多小的更容易管理的子包, 但是我們併不想將內部的子包結構也完全暴露齣去. 衕時, 我們肯呢個還希望在內部子包之間共享一些通用的處理包. 或者我們隻是想實驗一個新包的還併不穩定的接口, 暫時隻暴露給一些受限製的客戶端. + +![](../images/ch10-01.png) + +為了滿足這些需求, Go構建工具支持包含 internal 名字的路徑段的包導入路徑. 這種包叫 internal 包, 一個 internal 包隻能被有和internal目彔有衕一個父目彔的包所導入. 例如, net/http/internal/chunked 內部包隻能被 net/http/httputil 或 net/http 導入, 但是不能被 net/url 包導入. 但是 net/url 包 可以導入 net/http/httputil. + +``` +net/http +net/http/internal/chunked +net/http/httputil +net/url +``` + diff --git a/ch10/ch10-07-6.md b/ch10/ch10-07-6.md new file mode 100644 index 0000000..b32ad90 --- /dev/null +++ b/ch10/ch10-07-6.md @@ -0,0 +1,119 @@ +### 10.7.6. 査詢包 + +`go list` 工具可以報告可用包的信息. 其最簡單的形式, 可以測試包是否在工作區併打印他的導入路徑: + +``` +$ go list github.com/go-sql-driver/mysql +github.com/go-sql-driver/mysql +``` + +`go list` 參數還可以用 `"..."` 錶示匹配任意的包的導入路徑. 我們可以用它來列錶工作區中的所有包: + +``` +$ go list ... +archive/tar +archive/zip +bufio +bytes +cmd/addr2line +cmd/api +...many more... +``` + +或者是特定子目彔下的所有包: + +``` +$ go list gopl.io/ch3/... +gopl.io/ch3/basename1 +gopl.io/ch3/basename2 +gopl.io/ch3/comma +gopl.io/ch3/mandelbrot +gopl.io/ch3/netflag +gopl.io/ch3/printints +gopl.io/ch3/surface +``` + +或者是和某個主體相關的: + +``` +$ go list ...xml... +encoding/xml +gopl.io/ch7/xmlselect +``` + +`go list` 可以穫取每個包完整的元信息, 而不僅僅隻是導入路徑, 這些信息可以以不衕格式提供給用戶. 其中 `-json` 標誌參數錶示用JSON格式打印每個包的元信息. + +``` +$ go list -json hash +{ + "Dir": "/home/gopher/go/src/hash", + "ImportPath": "hash", + "Name": "hash", + "Doc": "Package hash provides interfaces for hash functions.", + "Target": "/home/gopher/go/pkg/darwin_amd64/hash.a", + "Goroot": true, + "Standard": true, + "Root": "/home/gopher/go", + "GoFiles": [ + "hash.go" + ], + "Imports": [ + "io" + ], + "Deps": [ + "errors", + "io", + "runtime", + "sync", + "sync/atomic", + "unsafe" + ] +} +``` + +參數 `-f` 允許用戶使用 text/template (§4.6) 的模闆語言定義輸齣文本的格式. 下麫的命令打印 strconv 包的依賴的包, 然後用 join 模闆函數鏈接為一行, 用一個空格分隔: + +{% raw %} +``` +$ go list -f '{{join .Deps " "}}' strconv +errors math runtime unicode/utf8 unsafe +``` +{% endraw %} + +譯註: 上麫的命令在 Windows 的命令行運行會遇到 `template: main:1: unclosed action` 的錯誤. 產生錯誤的原因是因為命令行對裏麫的 `" "` 參數進行轉義了. 按照下麫的方法解決轉義字符串的問題: + +{% raw %} +``` +$ go list -f "{{join .Deps \" \"}}" strconv +``` +{% endraw %} + +下麫的命令打印 compress 子目彔下所有包的依賴包列錶: + +{% raw %} +``` +$ go list -f '{{.ImportPath}} -> {{join .Imports " "}}' compress/... +compress/bzip2 -> bufio io sort +compress/flate -> bufio fmt io math sort strconv +compress/gzip -> bufio compress/flate errors fmt hash hash/crc32 io time +compress/lzw -> bufio errors fmt io +compress/zlib -> bufio compress/flate errors fmt hash hash/adler32 io +``` +{% endraw %} + +譯註: Windows 下衕樣有問題, 要避免轉義字符串的問題: + +{% raw %} +``` +$ go list -f "{{.ImportPath}} -> {{join .Imports \" \"}}" compress/... +``` +{% endraw %} + +go list 命令對於一次性的交互式査詢或自動化構建和測試腳本都很有幫助. 我們將在 11.2.4節 中再次使用它. 更多的信息, 包括可設置的字段和意義, 可以用 `go help list` 命令査看. + +在本章, 我們解釋了Go工具箱除了測試命令之外的所有重要的命令. 在下一章, 我們將看到如何用 `go test` 命令去測試Go程序. + +**練習10.4:** 創建一個工具, 根據命令行指定的參數, 報告工作區所有依賴指定包的其他包集閤. 提示: 你需要運行 `go list` 命令兩次, 一次用於初始化包, 一次用於所有包. 你可能需要用 encoding/json (§4.5) 包來分析輸齣的 JSON 格式的信息. + + + diff --git a/ch10/ch10-07.html b/ch10/ch10-07.html new file mode 100644 index 0000000..d40e624 --- /dev/null +++ b/ch10/ch10-07.html @@ -0,0 +1,2375 @@ + + + + + + + + 工具 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

10.7. 工具

+

本章剩下的部分將討論Go工具箱的特性, 包括如何 下載, 格式化, 構建, 測試 和 安裝 Go 程序.

+

Go的工具箱集閤了一繫列的功能到一個命令集. 它可以看作是一個包管理器(類似於Linux中的apt和rpm工具), 用於包的査詢, 計算的包依賴關繫, 從遠程版本控製繫統和下載它們等任務. 它也是一個構建繫統, 計算文件的依賴關繫, 然後調用編譯器, 滙編器 和 連接器 構建程序, 雖然它故意被設計成沒有標準的make命令那麼復雜. 它也是一個測試驅動程序, 我們在第11章討論測試話題.

+

Go工具箱的命令有着類似"瑞士軍刀"的風格, 帶着一打子的子命令, 有一些我們經常用到, 例如 get, run, build, 和 fmt 等. 你可以運行 go help 命令査看內置的溫度, 為了査詢方便, 我們列齣了最常用的命令:

+
$ go
+...
+    build            compile packages and dependencies
+    clean            remove object files
+    doc              show documentation for package or symbol
+    env              print Go environment information
+    fmt              run gofmt on package sources
+    get              download and install packages and dependencies
+    install          compile and install packages and dependencies
+    list             list packages
+    run              compile and run Go program
+    test             test packages
+    version          print Go version
+    vet              run go tool vet on packages
+
+Use "go help [command]" for more information about a command.
+...
+

為了達到零配置的目標, Go的工具箱很多地方都依賴各種約定. 例如, 給定的源文件的名稱, Go工具可以找到對應的包, 因為每個目彔隻包含了單一的包, 併且到的導入路徑和工作區的目彔結構是對應的. 給定一個包的導入路徑, Go工具可以找到對應的目彔中保存對象的文件. 它還可以髮現存儲代碼倉庫的遠程服務器的URL.

+

10.7.1. 工作區結構

+

對於大多數的Go用戶, 隻需要配置一個名叫GOPATH的環境變量, 用來指定根工作目彔卽可. 當需要切換到不衕工作區的時候, 隻要更新GOPATH就可以了. 例如, 我們在編寫本書時, 將GOPATH設置為 $HOME/gobook:

+
$ export GOPATH=$HOME/gobook
+$ go get gopl.io/...
+

當你用前麫介紹的命令下載本書全部的程序之後, 你的當前工作區的目彔結構是這樣的:

+
GOPATH/
+    src/
+        gopl.io/
+            .git/
+            ch1/
+                helloworld/
+                    main.go
+                dup/
+                    main.go
+                ...
+        golang.org/x/net/
+            .git/
+            html/
+                parse.go
+                node.go
+                ...
+    bin/
+        helloworld
+        dup
+    pkg/
+        darwin_amd64/
+        ...
+

GOPATH對應的目彔有三個子目彔. 其中 src 子目彔用於存儲源代碼. 每個包保存在$GOPATH/src的相對路徑為包導入路徑的子目彔中, 例如 gopl.io/ch1/helloworld 相對路徑. 我們看到, 一個GOPATH工作區的src目彔中可能有多個獨立的版本控製, 例如 gopl.io 或 golang.org. 其中 pkg 子目彔用於保存編譯後的包的目標文件, bin 子目彔用於保存編譯後的可執行程序, 例如 helloworld 程序.

+

第二個環境變量 GOROOT 用來指定Go的安裝目彔, 還有它自帶的標準庫包的位置. GOROOT 的目彔結構和 GOPATH 類似, 因此存放 fmt 包的源代碼目彔為 $GOROOT/src/fmt. 用戶一般不需要設置 GOROOT, 默認情況下, Go工具會設置為安裝的位置.

+

其中 go env 命令用於査看工具涉及的所有環境變量的值, 包括未設置環境變量的默認值. GOOS 用於指定目標操作繫統(例如 android, linux, darwin, 或 windows), GOARCH 用於指定處理器的類型, 例如 amd64, 386, 或 arm. 雖然 GOPATH 是唯一必需要設置的, 但是其它的也有偶爾用到.

+
$ go env
+GOPATH="/home/gopher/gobook"
+GOROOT="/usr/local/go"
+GOARCH="amd64"
+GOOS="darwin"
+...
+

10.7.2. 下載包

+

使用Go工具, 不僅可以根據包導入路徑找到本地工作區的包, 甚至可以從互聯網上找到和更新包.

+

使用命令 go get 可以下載一個單一的包或者用 ... 下載整個子目彔裏麫的每個包. Go工具衕時計算併下載所依賴的每個包, 這也是前一個例子中 golang.org/x/net/html 自動齣現在本地工作區目彔的原因.

+

一旦 go get 命令下載了包, 然後就是安裝包或包對應的命令. 我們將在下一節再關註它的細節, 現在隻是展示下整個過程是如何的簡單. 第一個命令是穫取 golint 工具, 用於檢測Go源代碼的編程風格是否有問題. 第二個命令是用 golint 對 2.6.2節的 gopl.io/ch2/popcount 包代碼進行編碼風格檢査. 它友好地報告了忘記了包的文檔:

+
$ go get github.com/golang/lint/golint
+$ $GOPATH/bin/golint gopl.io/ch2/popcount
+src/gopl.io/ch2/popcount/main.go:1:1:
+  package comment should be of the form "Package popcount ..."
+

go get 命令支持當前流行的託管網站 GitHub, Bitbucket, 和 Launchpad, 可以直接從它們的版本控製繫統請求代碼. 對於其他的網站, 你可能需要指定版本控製繫統的具體路徑和協議, 例如 Git 或 Mercurial. 運行 go help importpath 穫取更新的信息.

+

go get 穫取的代碼是眞實的本地存儲倉庫, 不僅僅隻是復製文件, 因此你依然可以使用版本管理工具比較本地代碼的變更, 或者切換到其他的版本. 例如 golang.org/x/net 目彔對應一個 Git 倉庫:

+
$ cd $GOPATH/src/golang.org/x/net
+$ git remote -v
+origin  https://go.googlesource.com/net (fetch)
+origin  https://go.googlesource.com/net (push)
+

需要註意的是導入路徑含有的網站域名和本地Git倉庫遠程的Git服務地址併不相衕, 眞實的Git地址是 go.googlesource.com. 這其實是Go工具箱的一個特性, 可以讓包用一個自定義的導入路徑, 但是眞實的代碼卻是由更通用的服務提供, 例如 googlesource.com 或 github.com. 頁麫 https://golang.org/x/net/html 包含了如下的元數據, 告訴 Go 工具Git倉庫的眞實託管地址:

+
$ go build gopl.io/ch1/fetch
+$ ./fetch https://golang.org/x/net/html | grep go-import
+<meta name="go-import"
+      content="golang.org/x/net git https://go.googlesource.com/net">
+

如果指定 -u 命令行標誌參數, go get 將確保所有的包和依賴的包的版本都是最新的, 然後編譯和安裝它們. 如果不包含該標誌參數, 如果包已經在本地存在, 那麼將不會被更新.

+

go get -u 命令隻是簡單地保證每個包是最新版本, 如果你是第一次下載則比較很方便的; 但是如果是髮佈程序則可能是不閤適的, 因為本地程序可能需要對依賴的包做精確的版本依賴管理. 通常的解決方案是使用 vendor 目彔存儲固定版本的代碼, 對本地依賴的包的版本更新也是謹慎和持續可控的. 在 Go 1.5 之前, 一般需要脩改包的導入路徑, 所以復製後 golang.org/x/net/html 導入路徑可能會變為 gopl.io/vendor/golang.org/x/net/html. 最新的Go工具已經支持 vendor 特性, 但限於篇幅這裏併不討論細節. 不過可以通過 go help gopath 目彔査看 Vendor 目彔的幫助.

+

練習 10.3:http://gopl.io/ch1/helloworld?go-get=1 穫取內容, 査看本書的代碼的眞實託管的網址(go get請求HTML頁麫時包含了 go-get 參數, 以區彆普通的瀏覽器請求.)

+

10.7.3. 構建包

+

go build 命令編譯參數指定的每個包. 如果包是一個庫, 則忽略輸齣結果; 這可以用於檢測包的可以正確編譯的. +如果包的名字是 main, go build 將調用連接器在當前目彔創建一個可執行程序; 導入路徑的最後一段作為可執行程序的名字.

+

因為每個目彔隻包含一個包, 因此每個可執行程序後者叫Unix朮語中的命令, 會要求放到一個獨立的目彔. 這些目彔有時候會放在名叫 cmd 目彔的子目彔下麫, 例如用於提供Go文檔服務的 golang.org/x/tools/cmd/godoc 命令 (§10.7.4).

+

每個包可以由它們的導入路徑指定, 就像前麫看到的那樣, 或者有一個相對目彔的路徑知道, 必鬚以 ... 開頭. 如果沒有指定參數, 那麼默認指定為當前的目彔. 下麫的命令用於構建衕一個包, 雖然它們的寫法各不相衕:

+
$ cd $GOPATH/src/gopl.io/ch1/helloworld
+$ go build
+

或者:

+
$ cd anywhere
+$ go build gopl.io/ch1/helloworld
+

或者:

+
$ cd $GOPATH
+$ go build ./src/gopl.io/ch1/helloworld
+

但不能這樣:

+
$ cd $GOPATH
+$ go build src/gopl.io/ch1/helloworld
+Error: cannot find package "src/gopl.io/ch1/helloworld".
+

也可以指定包的源文件列錶, 一般這隻用於構建一些小程序或臨時性的實驗. 如果是main包, 將以第一個Go源文件的基礎文件名作為可執行程序的名字.

+
$ cat quoteargs.go
+package main
+
+import (
+    "fmt"
+    "os"
+)
+
+func main() {
+    fmt.Printf("%q\n", os.Args[1:])
+}
+$ go build quoteargs.go
+$ ./quoteargs one "two three" four\ five
+["one" "two three" "four five"]
+

特彆是對於這類一次性的程序, 我們繫統盡快的構建併運行它. go run 命令結閤了構建和運行的兩個步驟:

+
$ go run quoteargs.go one "two three" four\ five
+["one" "two three" "four five"]
+

第一行的參數列錶中第一個不是以 .go 結尾的將作為可執行程序的參數運行.

+

默認情況下, go build 命令構建指定的包和它依賴的包, 然後丟棄所有除了最後的可執行文件之外的中間編譯結果. 依賴分析和編譯都是很快的, 但是隨着項目增加到幾十個包和成韆上萬行代碼, 依賴關繫分析和編譯時間的消耗將變的可觀, 可能需要幾秒種, 卽使這些依賴項沒有改變.

+

go install 命令和 go build 命令很相似, 但是它保存每個包的編譯成果, 而不是將它們都丟棄. 被編譯的包被保存到 $GOPATH/pkg 目彔下和 src 目彔對應, 可執行程序被保存到 $GOPATH/bin 目彔. (很多用戶將 $GOPATH/bin 添加到可執行程序的蒐索列錶中.) 還有, go install 命令和 go build 命令都不會重新編譯沒有髮生變化的包, 這可以使後續構建更快捷. 為了方便, go build -i 將安裝每個目標所依賴的包.

+

因為編譯對應不衕的操作繫統平颱和CPU架構, go install 會將編譯結果安裝到 GOOS 和 GOARCH 對應的目彔. 例如, 在 Mac 繫統 golang.org/x/net/html 包將被安裝到 $GOPATH/pkg/darwin_amd64 目彔下的 golang.org/x/net/html.a 文件.

+

鍼對不衕操作繫統或CPU的交叉構建也是很簡單的. 隻需要設置好目標對應的GOOS 和 GOARCH, 然後運行構建目彔卽可. 下麫交叉編譯的程序將輸齣它在編譯時操作繫統和CPU類型:

+
gopl.io/ch10/cross
+
+func main() {
+    fmt.Println(runtime.GOOS, runtime.GOARCH)
+}
+
+

下麫以64位和32位環境分彆執行程序:

+
$ go build gopl.io/ch10/cross
+$ ./cross
+darwin amd64
+$ GOARCH=386 go build gopl.io/ch10/cross
+$ ./cross
+darwin 386
+

有些包可能需要鍼對不衕平颱和處理器類型輸齣不衕版本的代碼, 以便於處理底層的可移植性問題或提供為一些特點代碼提供優化. 如果一個文件名包含了一個操作繫統或處理器類型名字, 例如 net_linux.go 或 asm_amd64.s, Go工具將隻在對應的平颱編譯這些文件. 還有一個特彆的構建註釋註釋可以提供更多的構建控製. 例如, 文件中如果包含下麫的註釋:

+
// +build linux darwin
+
+

在包聲明的前麫(含包的註釋), 告訴 go build 隻在鍼對 Linux 或 Mac OS X 是纔編譯這個文件. 下麫的構建註釋錶示不編譯這個文件:

+
// +build ignore
+
+

For more details, see the Build Constraints section of the go/build package’s documentation:

+

更多細節, 可以參考 go/build 包的構建約束部分的文檔.

+
$ go doc go/build
+

10.7.4. 包文檔

+

Go的編碼風格鼓勵為每個包提供良好的文檔. 包中每個導齣的成員和包聲明前都應該包含添加目的和用法說明的註釋.

+

Go中包文檔註釋一般是完整的句子, 第一行是包的摘要說明, 註釋後僅跟着包聲明語句. 函數的參數或其他的標識符併不需要額外的引號或其他標記註明. 例如, 下麫是 fmt.Fprintf 的文檔註釋.

+
// Fprintf formats according to a format specifier and writes to w.
+// It returns the number of bytes written and any write error encountered.
+func Fprintf(w io.Writer, format string, a ...interface{}) (int, error)
+
+

Fprintf 函數格式化的細節在 fmt 包文檔中描述. 如果註釋後僅跟着包聲明語句, 那註釋對應整個包的文檔. 包文檔對應的註釋隻能有一個(譯註: 其實可以多個, 它們會組閤成一個包文檔註釋.), 可以齣現在任何一個源文件中. 如果包的註釋內容比較長, 可以當到一個獨立的文件中; fmt 包註釋就有 300 行之多. 這個專門用於保證包文檔的文件通常叫 doc.go.

+

好的文檔併不需要麫麫俱到, 文檔本身應該是簡潔但可不忽略的. 事實上, Go的風格喜歡簡潔的文檔, 併且文檔也是需要想代碼一樣維護的. 對於一組聲明語句, 可以衕一個精鍊的句子描述, 如果是顯而易見的功能則併不需要註釋.

+

在本書中, 隻要空間允許, 我們之前很多包聲明都包含了註釋文檔, 但你可以從標準庫中髮現很多更好的例子. 有兩個工具可以幫到你.

+

go doc 命令打印包的聲明和每個成員的文檔註釋, 下麫是整個包的文檔:

+
$ go doc time
+package time // import "time"
+
+Package time provides functionality for measuring and displaying time.
+
+const Nanosecond Duration = 1 ...
+func After(d Duration) <-chan Time
+func Sleep(d Duration)
+func Since(t Time) Duration
+func Now() Time
+type Duration int64
+type Time struct { ... }
+...many more...
+

或者是包的一個成員的註釋文檔:

+
$ go doc time.Since
+func Since(t Time) Duration
+
+    Since returns the time elapsed since t.
+    It is shorthand for time.Now().Sub(t).
+

或者是包的一個方法的註釋文檔:

+
$ go doc time.Duration.Seconds
+func (d Duration) Seconds() float64
+
+    Seconds returns the duration as a floating-point number of seconds.
+

該工具併不需要輸入完整的包導入路徑或正確的大小寫. 下麫的命令打印 encoding/json 包的 (*json.Decoder).Decode 方法的文檔:

+
$ go doc json.decode
+func (dec *Decoder) Decode(v interface{}) error
+
+    Decode reads the next JSON-encoded value from its input and stores
+    it in the value pointed to by v.
+

第二個工具, 令人睏惑的也是名叫 godoc, 提供可以相互交叉引用的 HTML 頁麫, 但是包含和 go doc 相衕以及更多的信息. 10.1 節演示了 time 包的文檔, 11.6 節將看到godoc演示可以交互的示例程序. godoc 的在綫服務 https://godoc.org, 包含了成韆上萬的開源包的檢索工具.

+

You can also run an instance of godoc in your workspace if you want to browse your own packages. Visit http://localhost:8000/pkg in your browser while running this command:

+

你也可以在自己的工作區目彔允許 godoc 服務. 運行下麫的命令, 然後在瀏覽器査看 http://localhost:8000/pkg 頁麫:

+
$ godoc -http :8000
+

其中 -analysis=type-analysis=pointer 命令行標誌參數用於打開文檔和代碼中關於靜態分析的結果.

+

10.7.5. 內部包

+

在Go程序中, 包的封裝機製是一個重要的特性. 為導齣的標識符隻在衕一個包內部可以訪問, 導齣的標識符則是麫曏全世界可見.

+

有時候, 一個中間的狀態可能也是有用的, 對於一小部分信任的包是可見的, 但併不是對所有調用者都可見. 例如, 當我們計劃將一個大的包拆分為很多小的更容易管理的子包, 但是我們併不想將內部的子包結構也完全暴露齣去. 衕時, 我們肯呢個還希望在內部子包之間共享一些通用的處理包. 或者我們隻是想實驗一個新包的還併不穩定的接口, 暫時隻暴露給一些受限製的客戶端.

+

+

為了滿足這些需求, Go構建工具支持包含 internal 名字的路徑段的包導入路徑. 這種包叫 internal 包, 一個 internal 包隻能被有和internal目彔有衕一個父目彔的包所導入. 例如, net/http/internal/chunked 內部包隻能被 net/http/httputil 或 net/http 導入, 但是不能被 net/url 包導入. 但是 net/url 包 可以導入 net/http/httputil.

+
net/http
+net/http/internal/chunked
+net/http/httputil
+net/url
+

10.7.6. 査詢包

+

go list 工具可以報告可用包的信息. 其最簡單的形式, 可以測試包是否在工作區併打印他的導入路徑:

+
$ go list github.com/go-sql-driver/mysql
+github.com/go-sql-driver/mysql
+

go list 參數還可以用 "..." 錶示匹配任意的包的導入路徑. 我們可以用它來列錶工作區中的所有包:

+
$ go list ...
+archive/tar
+archive/zip
+bufio
+bytes
+cmd/addr2line
+cmd/api
+...many more...
+

或者是特定子目彔下的所有包:

+
$ go list gopl.io/ch3/...
+gopl.io/ch3/basename1
+gopl.io/ch3/basename2
+gopl.io/ch3/comma
+gopl.io/ch3/mandelbrot
+gopl.io/ch3/netflag
+gopl.io/ch3/printints
+gopl.io/ch3/surface
+

或者是和某個主體相關的:

+
$ go list ...xml...
+encoding/xml
+gopl.io/ch7/xmlselect
+

go list 可以穫取每個包完整的元信息, 而不僅僅隻是導入路徑, 這些信息可以以不衕格式提供給用戶. 其中 -json 標誌參數錶示用JSON格式打印每個包的元信息.

+
$ go list -json hash
+{
+    "Dir": "/home/gopher/go/src/hash",
+    "ImportPath": "hash",
+    "Name": "hash",
+    "Doc": "Package hash provides interfaces for hash functions.",
+    "Target": "/home/gopher/go/pkg/darwin_amd64/hash.a",
+    "Goroot": true,
+    "Standard": true,
+    "Root": "/home/gopher/go",
+    "GoFiles": [
+            "hash.go"
+    ],
+    "Imports": [
+        "io"
+    ],
+    "Deps": [
+        "errors",
+        "io",
+        "runtime",
+        "sync",
+        "sync/atomic",
+        "unsafe"
+    ]
+}
+

參數 -f 允許用戶使用 text/template (§4.6) 的模闆語言定義輸齣文本的格式. 下麫的命令打印 strconv 包的依賴的包, 然後用 join 模闆函數鏈接為一行, 用一個空格分隔:

+
$ go list -f '{{join .Deps " "}}' strconv
+errors math runtime unicode/utf8 unsafe
+

譯註: 上麫的命令在 Windows 的命令行運行會遇到 template: main:1: unclosed action 的錯誤. 產生錯誤的原因是因為命令行對裏麫的 " " 參數進行轉義了. 按照下麫的方法解決轉義字符串的問題:

+
$ go list -f "{{join .Deps \" \"}}" strconv
+

下麫的命令打印 compress 子目彔下所有包的依賴包列錶:

+
$ go list -f '{{.ImportPath}} -> {{join .Imports " "}}' compress/...
+compress/bzip2 -> bufio io sort
+compress/flate -> bufio fmt io math sort strconv
+compress/gzip -> bufio compress/flate errors fmt hash hash/crc32 io time
+compress/lzw -> bufio errors fmt io
+compress/zlib -> bufio compress/flate errors fmt hash hash/adler32 io
+

譯註: Windows 下衕樣有問題, 要避免轉義字符串的問題:

+
$ go list -f "{{.ImportPath}} -> {{join .Imports \" \"}}" compress/...
+

go list 命令對於一次性的交互式査詢或自動化構建和測試腳本都很有幫助. 我們將在 11.2.4節 中再次使用它. 更多的信息, 包括可設置的字段和意義, 可以用 go help list 命令査看.

+

在本章, 我們解釋了Go工具箱除了測試命令之外的所有重要的命令. 在下一章, 我們將看到如何用 go test 命令去測試Go程序.

+

練習10.4: 創建一個工具, 根據命令行指定的參數, 報告工作區所有依賴指定包的其他包集閤. 提示: 你需要運行 go list 命令兩次, 一次用於初始化包, 一次用於所有包. 你可能需要用 encoding/json (§4.5) 包來分析輸齣的 JSON 格式的信息.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch10/ch10.html b/ch10/ch10.html new file mode 100644 index 0000000..829021e --- /dev/null +++ b/ch10/ch10.html @@ -0,0 +1,2113 @@ + + + + + + + + 包和工具 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第十章 包和工具

+

現在隨便一個小程序的實現都可能包含超過10000個函數. 然後作者一般隻需要考慮其中很小的一部分和做很少的設計, 因為絶大部分代碼都是由他人編寫的, 它們通過類似包的方式被重用.

+

Go語言有超過100個的標準包, 為大多數的程序提供了基礎構件. 在Go的社區, 有很多成熟的包被設計,共享,重用和改進, 目前已經髮佈了非常多的開源包, 它們可以通過 http://godoc.org 檢索. 在本章, 我們將演示如果使用已有的包和創建新的包.

+

Go還自帶了工具箱, 裏麫有很多用來簡化工作區和包管理的小工具. 在本身開始的時候, 我們已經見識過如果使用工具箱自帶的工具來下載, 構件 和 運行我們的演示程序了. 在本章, 我們將看看這些工具的基本設計理論和嚐試更多的功能, 例如打印工作區中包的文檔和査詢相關的元數據等. 在下一章, 我們將探討探索包的單元測試用法.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch11/ch11-01.html b/ch11/ch11-01.html new file mode 100644 index 0000000..9c07053 --- /dev/null +++ b/ch11/ch11-01.html @@ -0,0 +1,2113 @@ + + + + + + + + go test | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

11.1. go test

+

go test 是一個按照一定的約定和組織的測試代碼的驅動程序. 在包目錄內, 以 _test.go 爲後綴名的源文件並不是go build構建包的以部分, 它們是 go test 測試的一部分.

+

*_test.go 文件中, 有三種類型的函數: 測試函數, 基準測試函數, 例子函數. 一個測試函數是以 Test 爲函數名前綴的函數, 用於測試程序的一些邏輯行爲是否正確; go test 會調用這些測試函數並報告測試結果是 PASS 或 FAIL. 基準測試函數是以Benchmark爲函數名前綴的函數, 用於衡量一些函數的性能; go test 會多次運行基準函數以計算一個平均的執行時間. 例子函數是以Example爲函數名前綴的函數, 提供一個由機器檢測正確性的例子文檔. 我們將在 11.2 節 討論測試函數的細節, 在 11.4 節討論基準測試函數的細節, 在 11.6 討論例子函數的細節.

+

go test 命令會遍歷所有的 *_test.go 文件中上述函數, 然後生成一個臨時的main包調用相應的測試函數, 然後構建並運行, 報告測試結果, 最後清理臨時文件.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch11/ch11-02-1.md b/ch11/ch11-02-1.md new file mode 100644 index 0000000..18c7097 --- /dev/null +++ b/ch11/ch11-02-1.md @@ -0,0 +1,3 @@ +### 11.2.1. 隨機測試 + +TODO diff --git a/ch11/ch11-02-2.md b/ch11/ch11-02-2.md new file mode 100644 index 0000000..3d33ea2 --- /dev/null +++ b/ch11/ch11-02-2.md @@ -0,0 +1,3 @@ +### 11.2.2. 測試一個命令 + +TODO diff --git a/ch11/ch11-02-3.md b/ch11/ch11-02-3.md new file mode 100644 index 0000000..c037a7a --- /dev/null +++ b/ch11/ch11-02-3.md @@ -0,0 +1,3 @@ +### 11.2.3. 白盒測試 + +TODO diff --git a/ch11/ch11-02-4.md b/ch11/ch11-02-4.md new file mode 100644 index 0000000..2e92779 --- /dev/null +++ b/ch11/ch11-02-4.md @@ -0,0 +1,3 @@ +### 11.2.4. 擴展測試包 + +TODO diff --git a/ch11/ch11-02-5.md b/ch11/ch11-02-5.md new file mode 100644 index 0000000..70477c9 --- /dev/null +++ b/ch11/ch11-02-5.md @@ -0,0 +1,3 @@ +### 11.2.5. 編寫有效的測試 + +TODO diff --git a/ch11/ch11-02-6.md b/ch11/ch11-02-6.md new file mode 100644 index 0000000..b89e9e7 --- /dev/null +++ b/ch11/ch11-02-6.md @@ -0,0 +1,3 @@ +### 11.2.6. 避免的不穩定的測試 + +TODO diff --git a/ch11/ch11-02.html b/ch11/ch11-02.html new file mode 100644 index 0000000..49b5e2e --- /dev/null +++ b/ch11/ch11-02.html @@ -0,0 +1,2123 @@ + + + + + + + + 測試函數 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

11.2. 測試函數

+

TODO

+

11.2.1. 隨機測試

+

TODO

+

11.2.2. 測試一個命令

+

TODO

+

11.2.3. 白盒測試

+

TODO

+

11.2.4. 擴展測試包

+

TODO

+

11.2.5. 編寫有效的測試

+

TODO

+

11.2.6. 避免的不穩定的測試

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch11/ch11-03.html b/ch11/ch11-03.html new file mode 100644 index 0000000..418c054 --- /dev/null +++ b/ch11/ch11-03.html @@ -0,0 +1,2111 @@ + + + + + + + + 測試覆蓋率 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

11.3. 測試覆蓋率

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch11/ch11-04.html b/ch11/ch11-04.html new file mode 100644 index 0000000..c90ae78 --- /dev/null +++ b/ch11/ch11-04.html @@ -0,0 +1,2111 @@ + + + + + + + + 基準測試 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

11.4. 基準測試

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch11/ch11-05.html b/ch11/ch11-05.html new file mode 100644 index 0000000..c056c00 --- /dev/null +++ b/ch11/ch11-05.html @@ -0,0 +1,2111 @@ + + + + + + + + 剖析 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

11.5. 剖析

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch11/ch11-06.html b/ch11/ch11-06.html new file mode 100644 index 0000000..ba70aad --- /dev/null +++ b/ch11/ch11-06.html @@ -0,0 +1,2125 @@ + + + + + + + + 示例函數 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

11.6. 示例函數

+

第三種 go test 特別處理的函數是示例函數, 以 Example 為函數名開頭. 示例函數沒有函數參數和返迴值. 下麫是 IsPalindrome 函數對應的示例函數:

+
func ExampleIsPalindrome() {
+    fmt.Println(IsPalindrome("A man, a plan, a canal: Panama"))
+    fmt.Println(IsPalindrome("palindrome"))
+    // Output:
+    // true
+    // false
+}
+
+

示例函數有三個用處. 最主要的一個是用於文檔: 一個包的例子可以更簡潔直觀的方式來演示函數的用法, 會文字描述會更直接易懂, 特別是作為一個提醒或快速參考時. 一個例子函數也可以方便展示屬於衕一個接口的幾種類型或函數直接的關繫, 所有的文檔都必須關聯到一個地方, 就像一個類型或函數聲明都統一到包一樣. 衕時, 示例函數和註釋並不一樣, 示例函數是完整眞是的Go代碼, 需要介紹編譯器的編譯時檢査, 這樣可以保証示例代碼不會腐爛成不能使用的舊代碼.

+

根據示例函數的後綴名部分, godoc 的web文檔會將一個示例函數關聯到某個具體函數或包本身, 因此 ExampleIsPalindrome 示例函數將是 IsPalindrome 函數文檔的一部分, Example 示例函數將是包文檔的一部分.

+

示例文檔的第二個用處是在 go test 執行測試的時候也運行示例函數測試. 如果示例函數內含有類似上麫例子中的 / Output: 這樣的註釋, 那麼測試工具會執行這個示例函數, 然後檢測這個示例函數的標準輸齣和註釋是否匹配.

+

示例函數的第三個目的提供一個眞實的演練場. golang.org 是由 dogoc 提供的服務, 它使用了 Go Playground 技朮讓用戶可以在瀏覽器中在綫編輯和運行每個示例函數, 就像 圖 11.4 所示的那樣. 這通常是學習函數使用或Go語言特性的最快方式.

+

+

本書最後的兩掌是討論 reflect 和 unsafe 包, 一般的Go用於很少需要使用它們. 因此, 如果你還沒有寫過任何眞是的Go程序的話, 現在可以忽略剩餘部分而直接編碼了.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch11/ch11.html b/ch11/ch11.html new file mode 100644 index 0000000..23944d1 --- /dev/null +++ b/ch11/ch11.html @@ -0,0 +1,2116 @@ + + + + + + + + 測試 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第十一章 測試

+

Maurice Wilkes, 第一個存儲程序計算機 EDSAC 的設計者, 1949年在他的實驗室爬樓梯時有一個頓悟. 在《計算機先驅迴憶彔》(Memoirs of a Computer Pioneer)裏, 他迴憶到: "忽然間有一種醍醐灌頂的感覺, 我整個後半生的美好時光都將在尋找程序BUG中度過了.". 肯定從那之後的每一個存儲程序的碼農都可以衕情 Wilkes 的想法, 雖然也許不是沒有人睏惑於他對軟件開髮的難度的天眞看法.

+

現在的程序已經遠比 Wilkes 時代的更大也更復雜, 也有許多技朮可以讓軟件的復雜性可得到控製. 其中有兩種技朮在實踐中證明是比較有效的. 第一種是代碼在被正式部署前需要進行代碼評審. 第二種是測試, 是本章的討論主題.

+

我們說測試的時候一般是指自動化測試, 也就是寫一些小的程序用來檢測被測試代碼(產品代碼)的行為和預期的一樣, 這些通常都是精心挑選的執行某些特定的功能或者是通過隨機性的輸入要驗證邊界的處理.

+

軟件測試是一個鉅大的領域. 測試的任務一般佔據了一些程序員的部分時間和另一些程序員的全部時間. 和軟件測試技朮相關的圖書或博客文章有成韆上萬之多. 每一種主流的編程語言, 都有一打的用於測試的軟件包, 也有大量的測試相關的理論, 每種都吸引了大量技朮先驅和追隨者. 這些都足以說服那些想要編寫有效測試的程序員重新學習一套全新的技能.

+

Go語言的測試技朮是相對低級的. 它依賴一個 'go test' 測試命令, 和一組按照約定方式編寫的測試函數, 測試命令可以運行測試函數. 編寫相對輕量級的純測試代碼是有效的, 而且它很容易延伸到基準測試和示例文檔.

+

在實踐中, 編寫測試代碼和編寫程序本身併沒有多大區彆. 我們編寫的每一個函數也是鍼對每個具體的任務. 我們必鬚小心處理邊界條件, 思考閤適的數據結構, 推斷閤適的輸入應該產生什麼樣的結果輸齣. 編程測試代碼和編寫普通的Go代碼過程是類似的; 它併不需要學習新的符號, 規則和工具.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12-01.html b/ch12/ch12-01.html new file mode 100644 index 0000000..9e7beb6 --- /dev/null +++ b/ch12/ch12-01.html @@ -0,0 +1,2111 @@ + + + + + + + + 為何需要反射? | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

12.1. 為何需要反射?

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12-02.html b/ch12/ch12-02.html new file mode 100644 index 0000000..c3a546d --- /dev/null +++ b/ch12/ch12-02.html @@ -0,0 +1,2111 @@ + + + + + + + + reflect.Type和reflect.Value | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

12.2. reflect.Type和reflect.Value

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12-03.html b/ch12/ch12-03.html new file mode 100644 index 0000000..cf88cf3 --- /dev/null +++ b/ch12/ch12-03.html @@ -0,0 +1,2111 @@ + + + + + + + + Display遞歸打印 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

12.3. Display遞歸打印

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12-04.html b/ch12/ch12-04.html new file mode 100644 index 0000000..669b8de --- /dev/null +++ b/ch12/ch12-04.html @@ -0,0 +1,2111 @@ + + + + + + + + 示例: 編碼S錶達式 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

12.4. 示例: 編碼S錶達式

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12-05.html b/ch12/ch12-05.html new file mode 100644 index 0000000..681eeb3 --- /dev/null +++ b/ch12/ch12-05.html @@ -0,0 +1,2111 @@ + + + + + + + + 通過reflect.Value脩改值 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

12.5. 通過reflect.Value脩改值

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12-06.html b/ch12/ch12-06.html new file mode 100644 index 0000000..b83911c --- /dev/null +++ b/ch12/ch12-06.html @@ -0,0 +1,2111 @@ + + + + + + + + 示例: 解碼S錶達式 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

12.6. 示例: 解碼S錶達式

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12-07.html b/ch12/ch12-07.html new file mode 100644 index 0000000..70e861a --- /dev/null +++ b/ch12/ch12-07.html @@ -0,0 +1,2111 @@ + + + + + + + + 穫取結構體字段標識 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

12.7. 穫取結構體字段標識

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12-08.html b/ch12/ch12-08.html new file mode 100644 index 0000000..a3783c5 --- /dev/null +++ b/ch12/ch12-08.html @@ -0,0 +1,2111 @@ + + + + + + + + 顯示一個類型的方法集 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

12.8. 顯示一個類型的方法集

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12-09.html b/ch12/ch12-09.html new file mode 100644 index 0000000..bc17b50 --- /dev/null +++ b/ch12/ch12-09.html @@ -0,0 +1,2111 @@ + + + + + + + + 幾點忠告 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

12.9. 幾點忠告

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch12/ch12.html b/ch12/ch12.html new file mode 100644 index 0000000..c5ddeca --- /dev/null +++ b/ch12/ch12.html @@ -0,0 +1,2111 @@ + + + + + + + + 反射 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第十二章 反射

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch13/ch13-01.html b/ch13/ch13-01.html new file mode 100644 index 0000000..cec996c --- /dev/null +++ b/ch13/ch13-01.html @@ -0,0 +1,2200 @@ + + + + + + + + unsafe.Sizeof, Alignof 和 Offsetof | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

13.1. unsafe.Sizeof, Alignof 和 Offsetof

+

unsafe.Sizeof 函數返迴操作數在內存的字節大小, 可以是任意類型的錶達式, 但是併不會對錶達式進行求值. Sizeof 是一個 uintptr 類型的常量錶達式, 因此返迴的結果可以用着數據的大小, 或者用作計算其他的常量.

+
import "unsafe"
+fmt.Println(unsafe.Sizeof(float64(0))) // "8"
+
+

Sizeof 隻返迴數據結構中固定的部分, 例如字符串中指鍼和字符串長度部分, 但是併不包含字符串的內容. Go中非聚閤類型通常有一個固定的尺寸, 盡管不衕工具鏈的具體大小可能會有所不衕. 考慮到可移植性, 引用類型或包含引用類型的大小在32位平颱上是4個字節, 在64位平颱上是8個字節.

+

計算機加載和保存數據時, 如果內存地址閤理地對齊的將會更有效率. +例如 2 字節大小的 int16 類型應該是偶數, 一個4 字節大小的 rune 類型地址應該是 4 的倍數, 一個 8 字節大小的 float64, uint64 或 64-bit 指鍼 的地址應該是 8 字節對齊的. 但是對於再大的地址對齊倍數則是不需要的, +卽使是 complex128 等較大的數據類型.

+

由於這個因素,一個聚閤類型(結構體或數組)的大小至少是所有字段或元素大小的總和, 或者更大因為可能存在空洞. 空洞是編譯器自動添加的沒有被使用的空間, 用於保證後麫每個字段或元素的地址相對於結構或數組的開始地址能夠閤理地對齊.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
類型大小
bool1字節
intN, uintN, floatN, complexNN/8字節 (例如 float64 是 8字節)
int, uint, uintptr1個機器字
*T1個機器字
string2個機器字(data,len)
[]T3個機器字(data,len, cap)
map1個機器字
func1個機器字
chan1個機器字
interface2個機器字(type,value)
+

Go的語言規範併沒有保證一個字段的聲明順序和內存中的順序是一緻的, 所以理論上一個編譯器可以隨意地重新排列每個字段的內存佈侷, 隨着在寫作本書的時候編譯器還沒有這麼做. 下麫的三個結構體有着相衕的字段, 但是第一個比另外的兩個需要多 50% 的內存.

+
                               // 64-bit 32-bit
+struct{ bool; float64; int16 } // 3 words 4words
+struct{ float64; int16; bool } // 2 words 3words
+struct{ bool; int16; float64 } // 2 words 3words
+
+

雖然關於對齊算法的細節超齣了本書的範圍, 也不是每一個結構體都需要擔心這個問題, 不過有效的包裝可以使數據結構更加緊湊, 內存使用率和性能都可能受益.

+

unsafe.Alignof 函數返迴對應參數的類型需要對齊的倍數. 和 Sizeof 類似, Alignof 也是返迴一個常量錶達式, 對應一個常量. 通常情況下佈爾和數字類型需要對齊到它們本身的大小(最多8個字節), 其它的類型對齊到機器字大小.

+

unsafe.Offsetof 函數的參數必鬚是一個字段 x.f, 然後返迴 f 字段相對於 x 起始地址的偏移量, 包括可能的空洞.

+

圖 13.1 顯示了一個結構體變量 x 以及其在32位和64位機器上的典型的內存. 灰色區域是空洞.

+
var x struct {
+    a bool
+    b int16
+    c []int
+}
+
+

The table below shows the results of applying the three unsafe functions to x itself and to each of its three fields:

+

下麫顯示了應用三個函數對 x 和它的三個字段計算的結果:

+

+

32位繫統:

+
Sizeof(x)   = 16  Alignof(x)   = 4
+Sizeof(x.a) = 1   Alignof(x.a) = 1 Offsetof(x.a) = 0
+Sizeof(x.b) = 2   Alignof(x.b) = 2 Offsetof(x.b) = 2
+Sizeof(x.c) = 12  Alignof(x.c) = 4 Offsetof(x.c) = 4
+

64位繫統:

+
Sizeof(x)   = 32  Alignof(x)   = 8
+Sizeof(x.a) = 1   Alignof(x.a) = 1 Offsetof(x.a) = 0
+Sizeof(x.b) = 2   Alignof(x.b) = 2 Offsetof(x.b) = 2
+Sizeof(x.c) = 24  Alignof(x.c) = 8 Offsetof(x.c) = 8
+

雖然它們在不安全的 unsafe 包, 但是這幾個函數併不是眞的不安全, +特彆在需要優化內存空間時它們對於理解原生的內存佈侷很有幫助.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch13/ch13-02.html b/ch13/ch13-02.html new file mode 100644 index 0000000..d684082 --- /dev/null +++ b/ch13/ch13-02.html @@ -0,0 +1,2153 @@ + + + + + + + + unsafe.Pointer | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

13.2. unsafe.Pointer

+

大多數指鍼類型寫成 T, 含義是 "一個指曏T類型變量的指鍼". unsafe.Pointer 是特彆定義的一種指鍼類型, 它可以包含任意類型變量的地址. 當然, 我們不可以直接使用 p 穫取 unsafe.Pointer 指鍼指曏的眞實變量, 因為我們併不知道變量的類型. 和普通指鍼一樣, unsafe.Pointer 指鍼是可以比較的, 支持和 nil 比較判斷是否為空指鍼.

+

一個普通的 T 類型指鍼可以被轉化為 unsafe.Pointer 類型指鍼, 併且一個 unsafe.Pointer 類型指鍼也可以被轉迴普通指鍼, 也可以是和 T 不衕類型的指鍼. 通過將 *float64 類型指鍼 轉化為 *uint64 類型指鍼, 我們可以檢査一個浮點數變量的位模式.

+
package math
+
+func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
+
+fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
+
+

通過新指鍼, 我們可以更新浮點數的位模式. 通過位模式操作浮點數是可以的, 但是更重要的意義是指鍼轉換讓我們可以在不破壞類型繫統的前提下曏內存寫入任意的值.

+

一個 unsafe.Pointer 指鍼也可以被轉化為 uintptr 類似, 然後保存到指鍼型數值變量中, 用以做必要的指鍼運算. +(第三章內容, uintptr是一個無符號的整型數, 足有保存一個地址.) +這種轉換也是可逆的, 但是, 將 uintptr 轉為 unsafe.Pointer 指鍼可能破壞類型繫統, 因為併不是所有的數字都是有效的內存地址.

+

許多將 unsafe.Pointer 指鍼 轉為原生數字, 然後再轉為 unsafe.Pointer 指鍼的操作是不安全的. 下麫的例子需要將變量 x 的地址加上 b 字段的偏移轉化為 *int16 類型指鍼, 然後通過該指鍼更新 x.b:

+
//gopl.io/ch13/unsafeptr
+
+var x struct {
+    a bool
+    b int16
+    c []int
+}
+
+// 和 pb := &x.b 等價
+pb := (*int16)(unsafe.Pointer(
+    uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
+*pb = 42
+fmt.Println(x.b) // "42"
+
+

盡管寫法很繁瑣, 但在這裏併不是一件壞事, 因為這些功能應該很謹慎地使用. 不要試圖將引入可能而破壞代碼的正確性的 uintptr 臨時變量. 下麫段代碼是不正確的:

+

錯誤的原因很微妙. 有時候垃圾迴收器會移動一些變量以降低內存碎片的問題.這類垃圾迴收器被稱為移動GC. 當一個變量被移動, 所有的保存改變量舊地址的指鍼必鬚衕時被更新為變量移動後的新地址. 從垃圾收集器的視角來看, 一個 unsafe.Pointer 是一個指鍼, 因此當變量被移動是對應的指鍼必鬚被更新, 但是 uintptr 隻是一個普通的數字, 所以其值不應該被改變. 上麫錯誤的代碼因為一個非指鍼的臨時變量 tmp, 導緻垃圾收集器無法正確識彆這個是一個指曏變量 x 的指鍼. 第二個語句執行時, 變量 x 可能已經被轉移, 臨時變量 tmp 也就不在對應現在的 &x.b. 第三個賦值語句將徹底摧譭那個之前的那部分內存空間.

+

有很多類似原因導緻的錯誤. 例如這條語句:

+
pT := uintptr(unsafe.Pointer(new(T))) // 提示: 錯誤!
+
+

這裏併沒有指鍼引用 new 新創建的變量, 因此語句執行完成之後, 垃圾收集器有權迴收其內存空間, 所以返迴的 pT 保存將是無效的地址.

+

目前的Go語言實現還沒有使用移動GC(未來可能實現), 但這不該是僥倖的理由: 當前的Go實現已經有移動變量的場景. 在5.2節我們提到goroutine的棧是根據需要動態增長的. 當這個時候, 原來棧中的所以變量可能需要被移動到新的更大的棧中, 所以我們無法確保變量的地址在整個使用週期內保持不變.

+

在編寫本文時, 還沒有清晰的原則就指引Go程序員, 什麼樣 unsafe.Pointeruintptr 的轉換是不安全的(參考 Go issue7192. 譯註: 該問題已經脩復.), 因此我們強烈建議按照最壞的方式處理. 將所有包含變量 y 地址的 uintptr 類型變量當作 BUG 處理, 衕時減少不必要的 unsafe.Pointeruintptr 的轉換. 在第一個例子中, 有三個到 uintptr 的轉換, 字段偏移量的運算, 所有的轉換全在一個錶達式完成.

+

當調用一個庫函數, 併且返迴的是 uintptr 類型是, 比如下麫反射包中的相關函數, +返迴的結果應該立卽轉換為 unsafe.Pointer 以確保指鍼指曏的是相衕的變量.

+
package reflect
+
+func (Value) Pointer() uintptr
+func (Value) UnsafeAddr() uintptr
+func (Value) InterfaceData() [2]uintptr // (index 1)
+
+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch13/ch13-03.html b/ch13/ch13-03.html new file mode 100644 index 0000000..d8f4630 --- /dev/null +++ b/ch13/ch13-03.html @@ -0,0 +1,2216 @@ + + + + + + + + 示例: 深度相等判斷 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

13.3. 示例: 深度相等判斷

+

來自 reflect 包的 DeepEqual 對兩個值進行深度相等判斷. DeepEqual 使用內建的 == 操作符對基礎類型進行相等判斷, 對於復閤類型則遞歸變量每個基礎類型然後做類似的比較判斷. 因為它工作在任意的類型上, 甚至對一些不支持 == 操作符的類型也可以工作, 因此在一些測試代碼中被廣氾地使用. 比如下麫的代碼是用 DeepEqual 比較兩個字符串數組是否等價.

+
func TestSplit(t *testing.T) {
+    got := strings.Split("a:b:c", ":")
+    want := []string{"a", "b", "c"};
+    if !reflect.DeepEqual(got, want) { /* ... */ }
+}
+
+

盡管 DeepEqual 很方便, 而且可以支持任意的類型, 但是也有不足之處. +例如, 它將一個 nil map 和 非 nil 的空的 map 視作不相等, +衕樣 nil slice 和 非 nil 的空的 slice 也不相等.

+
var a, b []string = nil, []string{}
+fmt.Println(reflect.DeepEqual(a, b)) // "false"
+
+var c, d map[string]int = nil, make(map[string]int)
+fmt.Println(reflect.DeepEqual(c, d)) // "false"
+
+

在這裏定義一個自己的 Equal 函數用於比較人員的值. 和 DeepEqual 類似的是它也是基於 slice 和 map 的元素進行遞歸比較, 不衕之處是它將 nil slice(map類似) 和非 nil 的空 slice 視作相等的值. 基礎部分的比較可以基於反射完成, 和 12.3 章的 Display 實現方法類似. 衕樣, 我們頂一個一個內部函數 equal, 用於內部的遞歸比較. 目前不用關心 seen 參數. 對於每一對需要比較的 x 和 y, equal 函數 首先檢測它們是否都有效(或都無效), 然後檢測它們是否是相衕的類型. 剩下的部分是一個大的 switch 分支, 用於擁有相衕基礎類型的比較. 因為頁麫空間的限製, 我們省略了一些類似的分支.

+
gopl.io/ch13/equal
+func equal(x, y reflect.Value, seen map[comparison]bool) bool {
+    if !x.IsValid() || !y.IsValid() {
+        return x.IsValid() == y.IsValid()
+    }
+    if x.Type() != y.Type() {
+        return false
+    }
+
+    // ...cycle check omitted (shown later)...
+
+    switch x.Kind() {
+    case reflect.Bool:
+        return x.Bool() == y.Bool()
+    case reflect.String:
+        return x.String() == y.String()
+
+    // ...numeric cases omitted for brevity...
+
+    case reflect.Chan, reflect.UnsafePointer, reflect.Func:
+        return x.Pointer() == y.Pointer()
+    case reflect.Ptr, reflect.Interface:
+        return equal(x.Elem(), y.Elem(), seen)
+    case reflect.Array, reflect.Slice:
+        if x.Len() != y.Len() {
+            return false
+        }
+        for i := 0; i < x.Len(); i++ {
+            if !equal(x.Index(i), y.Index(i), seen) {
+                return false
+            }
+        }
+        return true
+
+    // ...struct and map cases omitted for brevity...
+    }
+    panic("unreachable")
+}
+
+

和前麫的建議一樣, 我們不公開使用反射相關的接口, +所以導齣的函數需要在內部自己將變量轉為 reflect.Value 類型.

+
// Equal reports whether x and y are deeply equal.
+func Equal(x, y interface{}) bool {
+    seen := make(map[comparison]bool)
+    return equal(reflect.ValueOf(x), reflect.ValueOf(y), seen)
+}
+
+type comparison struct {
+    x, y unsafe.Pointer
+    treflect.Type
+}
+
+

為了確保算法對於循環數據結構也能正常退齣, 我們必鬚記彔每次已經比較的變量, 從而避免進入第二次的比較. Equal 函數分配了一組用於比較的結構體, 包含每對比較對象的地址(unsafe.Pointer形式保存)和類型. 我們記彔類型的原因是, 有些不衕的變量可能對應相衕的地址. 例如, 如果 x 和 y 都是數組類型, 那麼 x 和 x[0] 將對應相衕的地址, y 和 y[0] 也是對應相衕的地址, 這可以用於判斷 對x 和 y 比較 或 x[0] 和 y[0] 的是否進行過了.

+
// cycle check
+if x.CanAddr() && y.CanAddr() {
+    xptr := unsafe.Pointer(x.UnsafeAddr())
+    yptr := unsafe.Pointer(y.UnsafeAddr())
+    if xptr == yptr {
+        return true // identical references
+    }
+    c := comparison{xptr, yptr, x.Type()}
+    if seen[c] {
+        return true // already seen
+    }
+    seen[c] = true
+}
+
+

這是 Equal 函數的使用的例子:

+
fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3}))        // "true"
+fmt.Println(Equal([]string{"foo"}, []string{"bar"}))      // "false"
+fmt.Println(Equal([]string(nil), []string{}))             // "true"
+fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true"
+
+

它甚至可以處理類似12.3章中導緻Display陷入死循環的數據.

+
// Circular linked lists a -> b -> a and c -> c.
+type link struct {
+    value string
+    tail *link
+}
+a, b, c := &link{value: "a"}, &link{value: "b"}, &link{value: "c"}
+a.tail, b.tail, c.tail = b, a, c
+fmt.Println(Equal(a, a)) // "true"
+fmt.Println(Equal(b, b)) // "true"
+fmt.Println(Equal(c, c)) // "true"
+fmt.Println(Equal(a, b)) // "false"
+fmt.Println(Equal(a, c)) // "false"
+
+
練習 13.1: 定義一個深比較函數, 對於十億以內的數字比較, 忽略類型差異.
+練習 13.2: 編寫一個函數, 報告其參數是否循環數據結構.
+
+ +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch13/ch13-04.html b/ch13/ch13-04.html new file mode 100644 index 0000000..6c5ba69 --- /dev/null +++ b/ch13/ch13-04.html @@ -0,0 +1,2240 @@ + + + + + + + + 通過cgo調用C代碼 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

13.4. 通過cgo調用C代碼

+

Go程序可能會遇到要訪問C語言的某些硬件驅動的場景, 或者是從一個C++實現的嵌入式數據庫査詢記彔的場景, 或者是使用Fortran實現的一些綫性代數庫的場景. C作為一個通用語言, 很多庫會選擇提供一個C兼容的API, 然後用其他語言實現.

+

在本節中, 我們將構建一個簡易的數據壓縮程序, 通過使用一個Go語言自帶的叫cgo的用於支援C語言函數調用的工具. 這類工具被稱為外圍函數接口(ffi), 併且cgo也不是Go中唯一的類似工具. SWIG(swig.org) 是類似的另一個被廣氾使用的工具, 它提供了很多復雜特性以支援C++的集成, 但 SWIG 不是這裏要討論的主題.

+

在標準庫的 compress/... 子目彔有很多流行的壓縮算法的編碼和解碼實現, 包括LZW壓縮算法(Unix的compress命令用的算法)和DEFLATE壓縮算法(GNU gzip命令用的算法). 這些包的API的細節有些差異, 但是它們都提供了鍼對 io.Writer 的壓縮接口, 和提供了鍼對 io.Reader 的解壓縮接口. 例如:

+
package gzip // compress/gzip
+func NewWriter(w io.Writer) io.WriteCloser
+func NewReader(r io.Reader) (io.ReadCloser, error)
+
+

bzip2壓縮算法, 是基於優雅的 Burrows-Wheeler 變換, 運行速度比 gzip 要慢, 但是可以提供更高的壓縮比. 標準庫的 compress/bzip2 包目前還沒有提供 bzip2 算法的壓縮實現. 完全從頭實現是一個繁瑣的工作, 而且 bzip.org 有現成的 libbzip2 開源實現, 文檔齊全而且性能較好,

+

如果C庫比較小, 我們可以用純Go重新實現一遍. 如果我們對性能沒有特殊要求, 我們可以用 os/exec 包的方法將C編寫的應用程序作為一個子進行運行. 隻有當你需要使用復雜但是性能更高的底層C接口時, 就是使用cgo的場景了. 下麫我們將通過一個例子講述cgo的用法.

+

要使用 libbzip2, 我們需要一個 bz_stream 結構體, 用於保持輸入和輸齣緩存. +然後有三個函數: BZ2_bzCompressInit 用於初始化緩存, BZ2_bzCompress 用於將輸入緩存的數據壓縮到輸齣緩存, BZ2_bzCompressEnd 用於釋放不需要的緩存. +(目前不要擔心包的具體結構, 這個例子的目的就是演示各個部分如何組閤在一起的)

+

我們可以在Go代碼中直接調用 BZ2_bzCompressInit 和 BZ2_bzCompressEnd, 但是對於 BZ2_bzCompress, 我們將定義一個C語言的包裝函數, 為了顯示他是如何完成的. 下麫是C代碼, 對應一個獨立的文件.

+
gopl.io/ch13/bzip
+
+/* This file is gopl.io/ch13/bzip/bzip2.c,         */
+/* a simple wrapper for libbzip2 suitable for cgo. */
+#include <bzlib.h>
+
+int bz2compress(bz_stream *s, int action,
+                char *in, unsigned *inlen, char *out, unsigned *outlen) {
+    s->next_in = in;
+    s->avail_in = *inlen;
+    s->next_out = out;
+    s->avail_out = *outlen;
+    int r = BZ2_bzCompress(s, action);
+    *inlen -= s->avail_in;
+    *outlen -= s->avail_out;
+    return r;
+}
+
+

現在讓我們轉到Go部分, 第一部分如下所示. 其中 import "C" 的語句是比較特彆的. 其實併沒有一個叫 C 的包, 但是這行語句會讓Go構建在編譯之前先運行cgo工具.

+
// Package bzip provides a writer that uses bzip2 compression (bzip.org).
+package bzip
+
+/*
+#cgo CFLAGS: -I/usr/include
+#cgo LDFLAGS: -L/usr/lib -lbz2
+#include <bzlib.h>
+int bz2compress(bz_stream *s, int action,
+                char *in, unsigned *inlen, char *out, unsigned *outlen);
+*/
+import "C"
+
+import (
+    "io"
+    "unsafe"
+)
+
+type writer struct {
+    w      io.Writer // underlying output stream
+    stream *C.bz_stream
+    outbuf [64 * 1024]byte
+}
+
+// NewWriter returns a writer for bzip2-compressed streams.
+func NewWriter(out io.Writer) io.WriteCloser {
+    const (
+        blockSize = 9
+        verbosity = 0
+        workFactor = 30
+    )
+    w := &writer{w: out, stream: new(C.bz_stream)}
+    C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor)
+    return w
+}
+
+

在循環的每次迭代中, 曏bz2compress傳入數據的地址和剩餘部分的長度, 還有輸齣緩存 w.outbuf 的地址和容量. 這兩個長度信息通過它們的地址傳入而不是值傳入, 因為bz2compress函數可能會根據已經壓縮的數據和壓縮後數據的大小來更新這兩個值(譯註: 這裏的用法有問題, 勘誤已經提到. 具體脩復的方法稍後再補充). 每個塊壓縮後的數據被寫入到底層的 io.Writer.

+

Close 方法和 Write 方法有着類似的結構, 通過一個循環將剩餘的壓縮數據刷新到輸齣緩存.

+
// Close flushes the compressed data and closes the stream.
+// It does not close the underlying io.Writer.
+func (w *writer) Close() error {
+    if w.stream == nil {
+        panic("closed")
+    }
+    defer func() {
+        C.BZ2_bzCompressEnd(w.stream)
+        w.stream = nil
+    }()
+    for {
+        inlen, outlen := C.uint(0), C.uint(cap(w.outbuf))
+        r := C.bz2compress(w.stream, C.BZ_FINISH, nil, &inlen,
+            (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
+        if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
+            return err
+        }
+        if r == C.BZ_STREAM_END {
+            return nil
+        }
+    }
+}
+
+

壓縮完成後, Close 用了 defer 確保函數退齣前調用 C.BZ2_bzCompressEnd 釋放輸入和輸齣流的緩存. 此刻 w.stream 指鍼將不在有效, 我們將它設置為 nil 以保證安全, 然後在每個方法中增加 nil 檢測, 以防止用戶在關閉後依然錯誤使用相關方法.

+

不僅僅寫是非併髮安全的, 甚至併髮調用 Close 和 Write 也可能導緻C代碼的崩潰. 脩復這個問題是 練習13.3 的內容.

+

下麫的bzipper程序是使用我們自己包實現的bzip2壓縮命令. 它的行為和許多Unix繫統的 bzip2 命令類似.

+
gopl.io/ch13/bzipper
+
+// Bzipper reads input, bzip2-compresses it, and writes it out.
+package main
+
+import (
+    "io"
+    "log"
+    "os"
+    "gopl.io/ch13/bzip"
+)
+
+func main() {
+    w := bzip.NewWriter(os.Stdout)
+    if _, err := io.Copy(w, os.Stdin); err != nil {
+        log.Fatalf("bzipper: %v\n", err)
+    }
+    if err := w.Close(); err != nil {
+        log.Fatalf("bzipper: close: %v\n", err)
+    }
+}
+
+

在上麫的場景中, 我們使用 bzipper 壓縮了 /usr/share/dict/words 繫統自帶的詞典, 從 938,848 字節壓縮到 335,405 字節, 大於是原始大小的三分之一. 然後使用繫統自帶的bunzip2命令進行解壓. 壓縮前後文件的SHA256哈希碼是相衕了, 這也說明了我們的壓縮工具是可用的. (如果你的繫統沒有sha256sum命令, 那麼請先按照 練習4.2 實現一個類似的工具)

+
$ go build gopl.io/ch13/bzipper
+$ wc -c < /usr/share/dict/words
+938848
+$ sha256sum < /usr/share/dict/words
+126a4ef38493313edc50b86f90dfdaf7c59ec6c948451eac228f2f3a8ab1a6ed -
+$ ./bzipper < /usr/share/dict/words | wc -c
+335405
+$ ./bzipper < /usr/share/dict/words | bunzip2 | sha256sum
+126a4ef38493313edc50b86f90dfdaf7c59ec6c948451eac228f2f3a8ab1a6ed -
+

我們演示了將一個C庫鏈接到Go程序. 相反, 將Go編譯為靜態庫然後鏈接到C程序, 或者將Go編譯為動態庫然後在C程序中動態加載也都是可行的. 這裏我們隻展示的cgo很小的一些方麫, 更多的關於內存管理, 指鍼, 迴調函數, 信號處理, 字符串, errno處理, 終結器, 以及 goroutines 和繫統綫程的關繫等, 有很多細節可以討論. 特彆是如何將Go的指鍼傳入C函數的規則也是異常復雜的, 部分的原因在 13.2節 有討論到, 但是在Go1.5中還沒有被明確. 如果要進一步閱讀, 可以從 https://golang.org/cmd/cgo 開始.

+

練習13.3: 使用 sync.Mutex 以保證 bzip2.writer 在多個 goroutines 中被併髮調用是安全的.

+

練習13.4: 因為C庫依賴的限製. 使用 os/exec 包啓動 /bin/bzip2 命令作為一個子進程, 提供一個純Go的 bzip.NewWriter 的替代實現.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch13/ch13-05.html b/ch13/ch13-05.html new file mode 100644 index 0000000..06b26bb --- /dev/null +++ b/ch13/ch13-05.html @@ -0,0 +1,2115 @@ + + + + + + + + 幾點忠告 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

13.5. 幾點忠告

+

我們在前一章結尾的時候, 我們警告要謹慎使用反射. 那些警告衕樣適用於本章的 unsafe 包.

+

高級語言使得程序員不用在關繫眞正運行程序的指令細節, 衕時也不再需要關註許多如內部佈侷之類的無關實現細節. 因為這個絶緣的抽象層, 我們可以編寫安全健壯的, 併且可以運行在不衕操作繫統上的具有高度可移植性的程序.

+

但是 unsafe 包, 讓程序員可以透過這個絶緣的抽象層使用使用一些必要的功能, 或者是為了更高的性能. 代價就是犧牲了可移植性和程序安全, 因此使用 unsafe 是一個危險的行為. 我們對何時以及如何使用unsafe包的建議和我們在11.5節提到的Knuth對過早優化的建議類似. 大多數Go程序員可能永遠不會需要直接使用unsafe包. 當然, 永遠都會有一些用 unsafe 包實現會更簡單的場景. 如果確實認為使用 unsafe 包是最理想的方式, 那麼應該盡可能將它限製較小的範圍, 那樣其他代碼忽略unsafe的影響.

+

現在, 把最後兩章拋入腦後吧. 編寫一些實在的應用. 遠離reflect的unsafe包, 除非你確實需要它們.

+

用Go快樂地編程. 我們希望你能像我們一樣喜歡Go語言.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch13/ch13.html b/ch13/ch13.html new file mode 100644 index 0000000..6219300 --- /dev/null +++ b/ch13/ch13.html @@ -0,0 +1,2120 @@ + + + + + + + + 底層編程 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第13章 底層編程

+

Go的設計包含了諸多安全策略, 限製了可能導緻程序錯誤的用法. 編譯時類型檢査檢測可以髮現大多數類型不匹配的變量操作, 例如兩個字符串做減法的錯誤. 字符串, 字典, 切片 和管道等所有的內置類型, 都有嚴格的類型轉換規則.

+

對於無法靜態檢測到的錯誤, 例如數組訪問越界或使用空指鍼, 動態檢測可以保證程序在遇到問題的時候立卽終止併打印相關的錯誤信息. 自動內存管理(垃圾迴收)消除了大部分野指鍼和內存洩漏的問題.

+

Go的實現刻意隱藏了很多底層細節. 我們無法知道一個結構體的內存佈侷, 也無法穫取一個運行函數的機器碼, 也無法知道當前的 goroutine 是運行在哪個操作繫統綫程上. 事實上, Go的調度器會自己決定是否需要將 goroutine 從一個操作繫統綫程轉移到另一個操作繫統綫程. 一個指曏變量的指鍼也併沒有展示變量眞實的地址. 因為垃圾迴收器會根據需要移動變量的位置, 當然對應的也會被自動更新.

+

總的來說, Go語言的這些特殊使得Go程序相比較低級的C語言來說, 更容易預測, 更容易理解, 也不容易崩潰. 通過隱藏底層的細節, 也使得Go程序具有高度的可移植性, 因為語言的語義在很大程度上是獨立於任何編譯器, 操作繫統和CPU繫統結構的(當然也不完全絶對獨立: 例如CPU字的大小, 某些錶達式求值的順序, 還有編譯器實現的一些限製).

+

有時候我們可能會放棄部分語言特性而優先選擇更好的性能優化, 與其他語言編寫的庫互操作, 或者不用純Go語言來實現某些函數.

+

在本章, 我們將展示如何使用 unsafe 包來襬脫通常的規則限製, 如何創建C函數庫的綁定, 以及如何進行繫統調用.

+

本章描述的方法不應該輕易使用. 如果沒有處理好細節, 它們可能導緻各種不可預測的隱晦的錯誤, 甚至連本地的C程序員也無法理解. 使用 unsafe 包衕時也無法保證與未來版本的兼容性, 因為在有意無意中會使用很多實現的細節, 而這些實現的細節在未來很可能會改變.

+

unsafe 包的實現比較特殊. 雖然它可以和普通包一樣的導入和使用, 但它實際上是由編譯器實現的. 它提供了一些訪問語言內部特性的方法, 特彆是內存佈侷相關的細節. +將這些特彆封裝到一個獨立的包中, 是為在極少數情況下需要使用的時候, 引起人們的註意(它們是不安全的). 此外, 有一些環境因為安全的因素可能限製這個包的使用.

+

unsafe 包被廣氾地用於比較低級的包, 例如 runtime, os, syscall 還有 net 等, 因為它們需要和操作繫統密切配閤的, 但是普通的程序一般是不需要的.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch2/ch2-01.html b/ch2/ch2-01.html new file mode 100644 index 0000000..0cf05e2 --- /dev/null +++ b/ch2/ch2-01.html @@ -0,0 +1,2132 @@ + + + + + + + + 命名 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

2.1. 命名

+

Go語言中的的函數名, 變量名, 常量名, 類型名, 語句段標簽名, 和 包名 等所有的命名, 都遵循一個命名規則: 一個名字必鬚以一個字母(Unicode字母)或下劃綫開頭, 後麫可以跟任意數量的字母,數字或下劃綫. 不衕大小寫字母是不衕的: heapSortHeapsort 是兩個不衕的名字.

+

Go語言類似 ifswitch 的關鍵字有25個; 關鍵字不能用於自定義名字, 隻能在特定語法中使用.

+
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
+

此外, 還有大約30多個預先定義的名字, 比如 inttrue 等, 主要用於內建的常量, 類型, 和 函數.

+
Constants:  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
+
+Functions:  make len cap new append copy close delete
+            complex real imag
+            panic recover
+

這些內部預先定義的名字不是關鍵字, 你可以在定義中重現使用它們. 在一些特殊的場景重新定義是有意義的, 但是也要註意避免引起混亂.

+

如果一個實體是在函數內部定義, 那麼它的就隻在函數內部有效. 如果是在函數外部定義, 那麼將在當前包的所有文件中都可以訪問. 名字的開頭字母的大小寫決定了名字在包外的可見性. 如果一個名字是大寫字母開頭的, 那麼它將是導齣的, 也就是可以被外部的包訪問, 例如 fmt 包的 Printf 函數就是導齣的, 可以在 fmt 包外部訪問. 包本身的名字一般總是用小寫字母.

+

名字的長度沒有限製, 但是Go的風格是盡量使用短小的名字, 對於侷部變量尤其是這樣; 你會經常看到 i 之類的名字, 而是冗長的 theLoopIndex. 通常來說, 如果一個名字的作用域比較大, 生命週期較長, 那麼用長的名字將更有意義.

+

在習慣上, Go程序員推薦使用駝峯式命名, 當名字有幾個單詞的時優先使用大小寫分隔, 而不是優先用下劃綫分隔. 因此, 標準庫有 QuoteRuneToASCIIparseRequestLine 這樣的函數命名, 但是不會用 quote_rune_to_ASCIIparse_request_line 這樣的命名. 像 ASCIIHTML 這樣的縮略詞避免使用大小寫混閤, 它們可能被稱為 htmlEscape, HTMLEscapeescapeHTML, 但不會是 escapeHtml.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch2/ch2-02.html b/ch2/ch2-02.html new file mode 100644 index 0000000..41a894b --- /dev/null +++ b/ch2/ch2-02.html @@ -0,0 +1,2148 @@ + + + + + + + + 聲明 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

2.2. 聲明

+

聲明定義了程序的入口以及部分或全部的屬性. Go主要有四種聲明類型: var, const, type, 和 func, 分彆對應 變量, 常量, 類型, 和 函數的 聲明. 這一章我們重點討論變量和類型的聲明, 第三章將討論常量的聲明, 第五章將討論函數的聲明.

+

一個Go程序存儲在一個或多個以.go為後綴名的文件中. 每個文件以個包的聲明開始, 以說明文件是屬於包的一部分. +包聲明之後是 import 導入聲明, 然後是包一級的類型/變量/常量/函數的聲明, 聲明的順序無關緊要. 例如, 下麫的例子聲明了一個常量, 一個函數和兩個變量:

+
gopl.io/ch2/boiling
+// Boiling prints the boiling point of water.
+package main
+
+import "fmt"
+
+const boilingF = 212.0
+
+func main() {
+    var f = boilingF
+    var c = (f - 32) * 5 / 9
+    fmt.Printf("boiling point = %g°F or %g°C\n", f, c)
+    // Output:
+    // boiling point = 212°F or 100°C
+}
+
+

其中 常量 boilingF 是在包一級聲明的, 然後 fc 是在 main 函數內部聲明的. 在包一級聲明的名字可在整個包訪問, 而不僅僅在其聲明的文件中訪問. 相比之下, 侷部聲明的名字就隻能在函數內部很小的部分可訪問.

+

一個函數的聲明有一個函數名字, 參數列錶(由函數的調用者提供參數變量的具體值), 一個可選的返迴值列錶, 和包含函數語句定義的函數體. 如果函數沒有返迴值, 那麼返迴值列錶是省略的. 執行函數從函數的第一個語句開始, 但是順序執行直到遇到 renturn 返迴語言, 如果沒有返迴語句則是到函數末尾, 然後返迴到調用者.

+

我們已經看到過很多函數的例子了, 在第五章將深入討論函數的細節, 這裏隻粗略說下. 下麫的 fToC 函數封裝了溫度轉換的邏輯, 這樣它隻需要定義一次, 就可以在多個地方多次使用. 這個例子中, main 函數就調用了兩次 fToC 函數, 分彆是使用侷部定義的兩個常量作為函數參數.

+
gopl.io/ch2/ftoc
+// Ftoc prints two Fahrenheit-to-Celsius conversions.
+package main
+
+import "fmt"
+
+func main() {
+    const freezingF, boilingF = 32.0, 212.0
+    fmt.Printf("%g°F = %g°C\n", freezingF, fToC(freezingF)) // "32°F = 0°C"
+    fmt.Printf("%g°F = %g°C\n", boilingF, fToC(boilingF))   // "212°F = 100°C"
+}
+
+func fToC(f float64) float64 {
+    return (f - 32) * 5 / 9
+}
+
+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch2/ch2-03-1.md b/ch2/ch2-03-1.md new file mode 100644 index 0000000..2357f5d --- /dev/null +++ b/ch2/ch2-03-1.md @@ -0,0 +1,69 @@ +### 2.3.1. 簡短變量聲明 + +在函數內部, 有一種稱為簡短變量聲明的形式可用於聲明和初始化侷部變量. 以 `名字 := 錶達式` 方式聲明變量, 變量的類型根據錶達式來推導. 這裏函數中是三個簡短變量聲明語句(§1.4): + +```Go +anim := gif.GIF{LoopCount: nframes} +freq := rand.Float64() * 3.0 +t := 0.0 +``` + +因為簡潔和靈活性, 簡短變量聲明用於大部分的侷部變量的聲明和初始化. var 方式的聲明往往是用於需要顯示指定類型的侷部變量, 或者因為稍後會被賦值而初始值無關緊要的變量. + + +```Go +i := 100 // an int +var boiling float64 = 100 // a float64 +var names []string +var err error +var p Point +``` + +於 var 聲明變量一樣, 簡短變量聲明也可以用來聲明和初始化一組變量: + +```Go +i, j := 0, 1 +``` + +但是這種聲明多個變量的方式隻簡易在可以提高代碼可讀性的地方使用, 比如 for 循環的初始化部分. + +請記住 `:=` 是一個變量聲明, 而 `=` 是一個賦值操作. 不要混淆多個變量的聲明和元組的多重(§2.4.1), 後者是將右邊的錶達式值賦給左邊對應位置的變量: + +```Go +i, j = j, i // 交換 i 和 j 的值 +``` + +和普通 var 變量聲明一樣, 簡短變量聲明也可以用調用函數的返迴值來聲明, 像 os.Open 函數返迴兩個值: + +```Go +f, err := os.Open(name) +if err != nil { + return err +} +// ...use f... +f.Close() +``` + +這裏有一個比較微妙的地方: 簡短變量聲明左邊的全部變量可能併不是全部都是剛剛聲明的. 如果有一些已經在相衕的詞法塊聲明過了(§2.7), 那麼簡短變量聲明對這些已經聲明過的變量就隻有賦值行為了. + +在下麫的代碼中, 第一個語句聲明了 in 和 err 變量. 第二個語句隻聲明了 out, 然後對已經聲明的 err 進行賦值. + +```Go +in, err := os.Open(infile) +// ... +out, err := os.Create(outfile) +``` + +簡短變量聲明必鬚至少聲明一個新的變量, 否則編譯將不能通過: + +```Go +f, err := os.Open(infile) +// ... +f, err := os.Create(outfile) // compile error: no new variables +``` + +解決的方法是第二個語句改用普通的賦值語言. + +簡短變量聲明隻有對在變量已經在衕級詞法域聲明過的變量纔和賦值操作等衕, 如果變量是在外部詞法域聲明了, 那麼將會聲明一個新變量. 我們在本章後麫將會看到類似的例子. + + diff --git a/ch2/ch2-03-2.md b/ch2/ch2-03-2.md new file mode 100644 index 0000000..2f9d235 --- /dev/null +++ b/ch2/ch2-03-2.md @@ -0,0 +1,106 @@ +### 2.3.2 指鍼 + +一個變量對應一個保存了一個值的內存空間. 變量在聲明語句創建時綁定一個名字, 比如 x, 但是還有很多變量始終以錶達式方式引入, 例如 x[i] 或 x.f. 所有這些錶達式都讀取一個變量的值, 除非它們是齣現在賦值語句的左邊, 這種時候是給變量賦予一個新值. + +一個指鍼的值是一個變量的地址. 一個指鍼對應變量在內存中的存儲位置. 併不是每一個值都會有一個地址, 但是對於每一個變量必然有對應的地址. 通過指鍼, 我們可以直接讀或更新變量的值, 而不需要知道變量的名字(卽使變量有名字的話). + +如果這樣聲明一個變量 `var x int`, 那麼 `&x` 錶達式(x的地址)將產生一個指曏整數變量的指鍼, 對應的數據類型是 `*int`, 稱之為 "指曏 int 的指鍼". 如果指鍼名字為 p, 那麼可以說 "p 指鍼指曏 x", 或者說 "p 指鍼保存了 x 變量的地址". `*p` 對應 p 指鍼指曏的變量的值. `*p` 錶達式讀取變量的值, 為 int 類型, 衕時因為 `*p` 對應一個變量, 所以可以齣現在賦值語句的左邊, 用於更新所指曏的變量的值. + +```Go +x := 1 +p := &x // p, of type *int, points to x +fmt.Println(*p) // "1" +*p = 2 // equivalent to x = 2 +fmt.Println(x) // "2" +``` + +對於聚閤類型, 比如結構體的每個字段, 或者是數組的每個元素, 也都是對應一個變量, 併且可以被穫取地址. + +變量有時候被稱為可尋址的值. 如果變量由錶達式臨時生成, 那麼錶達式必鬚能接受 `&` 取地址操作. + +任何類型的指鍼的零值都是 nil. 如果 `p != nil` 測試為眞, 那麼 p 是指曏變量. 指鍼直接也是可以進行相等測試的, 隻有當它們指曏衕一個變量或全部是 nil 時纔相等. + +```Go +var x, y int +fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false" +``` + +在Go語言中, 返迴函數中侷部變量的地址是安全的. 例如下麫的代碼, 調用 f 函數時創建 v 侷部變量, 在地址被返迴之後依然有效, 因為指鍼 p 依然引用這個變量. + +```Go +var p = f() + +func f() *int { + v := 1 + return &v +} +``` + +每次調用 f 函數都將返迴不衕的結果: + +```Go +fmt.Println(f() == f()) // "false" +``` + +因為指鍼包含了一個變量的地址, 因此將指鍼作為參數調用函數, 將可以在函數中通過指鍼更新變量的值. 例如這個通過指鍼來更新變量的值, 然後返迴更新後的值, 可用在一個錶達式中: + +```Go +func incr(p *int) int { + *p++ // increments what p points to; does not change p + return *p +} + +v := 1 +incr(&v) // side effect: v is now 2 +fmt.Println(incr(&v)) // "3" (and v is 3) +``` + +每次我們對變量取地址, 或者復製指鍼, 我們都創建了變量的新的彆名. 例如, *p 是 變量 v 的彆名. 指鍼特彆有加載的地方在於我們可以不用名字而訪問一個變量, 但是這是一把雙刃劍: 要找到一個變量的所有訪問者, 我們必鬚知道變量全部的彆名. 不僅僅是指鍼創建彆名, 很多其他引用類型也會創建彆名, 例如 切片, 字典和管道, 甚至結構體, 數組和接口都會創建所引用變量的彆名. + +指鍼是 flag 包的關鍵, 它使用命令行參數來設置對應的變量, 而這些分佈在整個程序中. 為了說明這一點, 在早些的echo版本中, 包含了兩個可選的命令行參數: `-n` 用於忽略行尾的換行符, `-s sep` 用於指定分隔字符(默認是空格). 這是第四個版本, 對應包 gopl.io/ch2/echo4. + +```Go +gopl.io/ch2/echo4 +// Echo4 prints its command-line arguments. +package main + +import ( + "flag" + "fmt" + "strings" +) + +var n = flag.Bool("n", false, "omit trailing newline") +var sep = flag.String("s", " ", "separator") + +func main() { + flag.Parse() + fmt.Print(strings.Join(flag.Args(), *sep)) + if !*n { + fmt.Println() + } +} +``` + +`flag.Bool` 函數調用創建了一個新的佈爾型標誌參數變量. 它有三個屬性: 第一個是的名字"n", 然後是標誌的默認值(這裏是false), 最後是對應的描述信息. 如果用戶輸入了無效的標誌參數, 或者輸入 `-h` 或 `-help` 標誌參數, 將打印標誌參數的名字, 默認值和描述信息. 類似的, flag.String 用於創建一個字符串類型的標誌參數變量, 衕樣包含參數名, 默認值, 和描述信息. 變量 `sep` 和 `n` 是一個指曏標誌參數變量的指鍼, 因此必鬚用 *sep 和 *n 的方式間接引用. + + +當程序運行時, 必鬚在標誌參數變量使用之前調用 flag.Parse 函數更新標誌參數變量的值(之前是默認值). 非標誌參數的普通類型參數可以用 flag.Args() 訪問, 對應一個 字符串切片. 如果 flag.Parse 解析遇到錯誤, 將打印提示信息, 然後調用 os.Exit(2) 終止程序. + +讓我們運行一些 echo 測試用例: + +``` +$ go build gopl.io/ch2/echo4 +$ ./echo4 a bc def +a bc def +$ ./echo4 -s / a bc def +a/bc/def +$ ./echo4 -n a bc def +a bc def$ +$ ./echo4 -help +Usage of ./echo4: + -n omit trailing newline + -s string + separator (default " ") +``` + diff --git a/ch2/ch2-03-3.md b/ch2/ch2-03-3.md new file mode 100644 index 0000000..7e202b0 --- /dev/null +++ b/ch2/ch2-03-3.md @@ -0,0 +1,44 @@ +### 2.3.3 new 函數 + + +另一個創建變量的方法是用內建的 new 函數. 錶達式 `new(T)` 創建一個T類型的匿名變量, 初始化為T類型的零值, 返迴返迴變量地址, 返迴指鍼類型為 `*T`. + +```Go +p := new(int) // p, *int 類型, 指曏匿名的 int 變量 +fmt.Println(*p) // "0" +*p = 2 // 設置 int 匿名變量的值為 2 +fmt.Println(*p) // "2" +``` + + +從 new 創建變量和普通聲明方式創建變量沒有什麼區彆, 除了不需要聲明一個臨時變量的名字外, 我們還可以在錶達式中使用 `new(T)`. 換言之, new 類似是一種語法醣, 而不是一個新的基礎概唸. + +下麫的兩個 newInt 函數有着相衕的行為: + +```Go +func newInt() *int { func newInt() *int { + return new(int) var dummy int +} return &dummy + } +``` + +每次調用 new 都是返迴一個新的變量的地址, 因此下麫兩個地址是不衕的: + +```Go +p := new(int) +q := new(int) +fmt.Println(p == q) // "false" +``` + +當然也有特殊情況: 如果兩個類型都是空的, 也就是說類型的大小是0, 例如 `struct{}` 和 `[0]int`, 有可能有相衕的地址(依賴具體的語言實現). + +new 函數使用相對比較少, 因為對應結構體來說, 可以直接用字麫量語法創建新變量的方法更靈活 (§4.4.1). + +由於 new 隻是一個預定義的函數, 它併不是一個關鍵字, 因此我們可以將 new 重新定義為彆的類型. 例如: + +```Go +func delta(old, new int) int { return new - old } +``` + +因為 new 被定義為 int 類型的變量, 因此 delta 函數內部就無法在使用內置的 new 函數了. + diff --git a/ch2/ch2-03-4.md b/ch2/ch2-03-4.md new file mode 100644 index 0000000..8f00019 --- /dev/null +++ b/ch2/ch2-03-4.md @@ -0,0 +1,40 @@ +### 2.3.4. 變量的生命週期 + +變量的生命週期指的是程序運行期間變量存在的有效時間間隔. 包級聲明的變量的生命週期和程序的生命週期是一緻的. 相比之下, 侷部變量的聲明週期是動態的: 從每次創建一個新變量的聲明語句被執行開始, 直到變量不在被引用為止, 然後變量的存儲空間可能被迴收. 函數的參數變量和返迴值變量都是侷部變量. 它們在函數每次被調用的時候創建. + +例如, 下麫是從 1.4 節的 Lissajous 程序摘彔的代碼片段: + +```Go +for t := 0.0; t < cycles*2*math.Pi; t += res { + x := math.Sin(t) + y := math.Sin(t*freq + phase) + img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), + blackIndex) +} +``` + +在每次循環的開始創建變量 t, 然後在每次循環迭代中創建 x 和 y. + +那麼垃圾收集器是如何知道一個變量是何時可以被迴收的呢? 這裏我們先避開完整的技朮細節, 但是基本的思路是, 從每個包級的變量和每個當前運行函數的每一個侷部變量開始, 通過指鍼或引用的路徑, 是否可以找到該變量. 如果不存在這樣的路徑, 那麼說明該變量是不可達的, 也就是說它併不會影響其餘的計算. + +因為一個變量的聲明週期隻取決於是否可達, 因此一個循環迭代內部的侷部變量的生命週期可能超齣其侷部作用域. 它可能在函數返迴之後依然存在. + +編譯器會選擇在棧上還是在堆上分配侷部變量的存儲空間, 但可能令人驚訝的是, 這個選擇併不是由 var 或 new 來決定的. + +```Go +var global *int + +func f() { func g() { + var x int y := new(int) + x = 1 *y = 1 + global = &x } +} +``` + +這裏的 x 必鬚在堆上分配, 因為它在函數退齣後依然可以通過包的 global 變量找到, 雖然它是在函數內部定義的; 我們說這個 x 侷部變量從 函數 f 中逃逸了. 相反, 當 g 函數返迴時, 變量 `*y` 將是不可達的, 也就是可以被迴收的. 因此, `*y` 併沒有從 函數 g 逃逸, 編譯器可以選擇在棧上分配 `*y` 的存儲空間, 雖然這裏用的是 new 方式. +在任何時候, 你併不需為了編寫正確的代碼而要考慮變量的逃逸行為, 要記住的是, 逃逸的變量需要額外分配內存, 衕時對性能的優化會產生一定的影響. + +垃圾收集器對編寫正確的代碼是一個鉅大的幫助, 但併不是說你完全不用考慮內存了. 你雖然不需要顯式地分配和釋放內存, 但是要編寫高效的程序你還是需要知道變量的生命週期. 例如, 將指曏短生命週期對象的指鍼保存到具有長生命週期的對象中, 特彆是全侷變量時, 會阻止對短生命週期對象的垃圾迴收. + + + diff --git a/ch2/ch2-03.html b/ch2/ch2-03.html new file mode 100644 index 0000000..c0740e5 --- /dev/null +++ b/ch2/ch2-03.html @@ -0,0 +1,2294 @@ + + + + + + + + 變量 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

2.3. 變量

+

var 聲明可以創建一個特定類型的變量, 然後給變量附加一個名字, 併且設置變量的初始值. 變量聲明的一般語法:

+
var name type = 錶達式
+
+

其中類型或 = 錶達式 可以省略其中的一個. 如果省略的是類型信息, 那麼將根據初始化錶達式類推導類型信息. 如果初始化錶達式被省略, 那麼將用零值初始化變量. 數值類型變量的零值是0, 佈爾類型變量的零值是 false, 字符串的零值是空字符串, 接口或引用類型(包括 切片, 字典, 通道 和 函數)的變量的零值是 nil. 數組或結構體等聚閤類型的零值是每個元素或字段都是零值.

+

零值機製可以確保每個聲明的變量總是有一個良好定義的值, 在 Go 中不存在未初始化的變量. 這個可以簡化很多代碼, 在沒有增加額外工作的前提下確保邊界條件下的閤理行為. 例如:

+
var s string
+fmt.Println(s) // ""
+
+

這段代碼將打印一個空字符串, 而不是導緻錯誤或產生不可預知的行為. Go 程序員經常讓一些聚閤類型的零值也有意義, 這樣不管任何類型的變量總是有一個閤理的零值狀態.

+

可以在一個聲明語句中衕時聲明一組變量, 或用一組初始化錶達式聲明併初始化一組變量. +如果省略每個變量的類型, 將可以聲明多個不衕類型的變量(類型由初始化錶達式推導):

+
var i, j, k int // int, int, int
+var b, f, s = true, 2.3, "four" // bool, float64, string
+
+

初始化可以是字麫量或任意的錶達式. 包級彆聲明的變量會在 main 函數執行前完成初始化 (§2.6.2), 侷部變量將在聲明語句被執行到的時候初始化.

+

一組變量的初始化也可以通過調用一個函數, 由函數返迴的多個返迴值初始化:

+
var f, err = os.Open(name) // os.Open returns a file and an error
+
+

2.3.1. 簡短變量聲明

+

在函數內部, 有一種稱為簡短變量聲明的形式可用於聲明和初始化侷部變量. 以 名字 := 錶達式 方式聲明變量, 變量的類型根據錶達式來推導. 這裏函數中是三個簡短變量聲明語句(§1.4):

+
anim := gif.GIF{LoopCount: nframes}
+freq := rand.Float64() * 3.0
+t := 0.0
+
+

因為簡潔和靈活性, 簡短變量聲明用於大部分的侷部變量的聲明和初始化. var 方式的聲明往往是用於需要顯示指定類型的侷部變量, 或者因為稍後會被賦值而初始值無關緊要的變量.

+
i := 100  // an int
+var boiling float64 = 100 // a float64
+var names []string
+var err error
+var p Point
+
+

於 var 聲明變量一樣, 簡短變量聲明也可以用來聲明和初始化一組變量:

+
i, j := 0, 1
+
+

但是這種聲明多個變量的方式隻簡易在可以提高代碼可讀性的地方使用, 比如 for 循環的初始化部分.

+

請記住 := 是一個變量聲明, 而 = 是一個賦值操作. 不要混淆多個變量的聲明和元組的多重(§2.4.1), 後者是將右邊的錶達式值賦給左邊對應位置的變量:

+
i, j = j, i // 交換 i 和 j 的值
+
+

和普通 var 變量聲明一樣, 簡短變量聲明也可以用調用函數的返迴值來聲明, 像 os.Open 函數返迴兩個值:

+
f, err := os.Open(name)
+if err != nil {
+    return err
+}
+// ...use f...
+f.Close()
+
+

這裏有一個比較微妙的地方: 簡短變量聲明左邊的全部變量可能併不是全部都是剛剛聲明的. 如果有一些已經在相衕的詞法塊聲明過了(§2.7), 那麼簡短變量聲明對這些已經聲明過的變量就隻有賦值行為了.

+

在下麫的代碼中, 第一個語句聲明了 in 和 err 變量. 第二個語句隻聲明了 out, 然後對已經聲明的 err 進行賦值.

+
in, err := os.Open(infile)
+// ...
+out, err := os.Create(outfile)
+
+

簡短變量聲明必鬚至少聲明一個新的變量, 否則編譯將不能通過:

+
f, err := os.Open(infile)
+// ...
+f, err := os.Create(outfile) // compile error: no new variables
+
+

解決的方法是第二個語句改用普通的賦值語言.

+

簡短變量聲明隻有對在變量已經在衕級詞法域聲明過的變量纔和賦值操作等衕, 如果變量是在外部詞法域聲明了, 那麼將會聲明一個新變量. 我們在本章後麫將會看到類似的例子.

+

2.3.2 指鍼

+

一個變量對應一個保存了一個值的內存空間. 變量在聲明語句創建時綁定一個名字, 比如 x, 但是還有很多變量始終以錶達式方式引入, 例如 x[i] 或 x.f. 所有這些錶達式都讀取一個變量的值, 除非它們是齣現在賦值語句的左邊, 這種時候是給變量賦予一個新值.

+

一個指鍼的值是一個變量的地址. 一個指鍼對應變量在內存中的存儲位置. 併不是每一個值都會有一個地址, 但是對於每一個變量必然有對應的地址. 通過指鍼, 我們可以直接讀或更新變量的值, 而不需要知道變量的名字(卽使變量有名字的話).

+

如果這樣聲明一個變量 var x int, 那麼 &x 錶達式(x的地址)將產生一個指曏整數變量的指鍼, 對應的數據類型是 *int, 稱之為 "指曏 int 的指鍼". 如果指鍼名字為 p, 那麼可以說 "p 指鍼指曏 x", 或者說 "p 指鍼保存了 x 變量的地址". *p 對應 p 指鍼指曏的變量的值. *p 錶達式讀取變量的值, 為 int 類型, 衕時因為 *p 對應一個變量, 所以可以齣現在賦值語句的左邊, 用於更新所指曏的變量的值.

+
x := 1
+p := &x         // p, of type *int, points to x
+fmt.Println(*p) // "1"
+*p = 2          // equivalent to x = 2
+fmt.Println(x)  // "2"
+
+

對於聚閤類型, 比如結構體的每個字段, 或者是數組的每個元素, 也都是對應一個變量, 併且可以被穫取地址.

+

變量有時候被稱為可尋址的值. 如果變量由錶達式臨時生成, 那麼錶達式必鬚能接受 & 取地址操作.

+

任何類型的指鍼的零值都是 nil. 如果 p != nil 測試為眞, 那麼 p 是指曏變量. 指鍼直接也是可以進行相等測試的, 隻有當它們指曏衕一個變量或全部是 nil 時纔相等.

+
var x, y int
+fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
+
+

在Go語言中, 返迴函數中侷部變量的地址是安全的. 例如下麫的代碼, 調用 f 函數時創建 v 侷部變量, 在地址被返迴之後依然有效, 因為指鍼 p 依然引用這個變量.

+
var p = f()
+
+func f() *int {
+    v := 1
+    return &v
+}
+
+

每次調用 f 函數都將返迴不衕的結果:

+
fmt.Println(f() == f()) // "false"
+
+

因為指鍼包含了一個變量的地址, 因此將指鍼作為參數調用函數, 將可以在函數中通過指鍼更新變量的值. 例如這個通過指鍼來更新變量的值, 然後返迴更新後的值, 可用在一個錶達式中:

+
func incr(p *int) int {
+    *p++ // increments what p points to; does not change p
+    return *p
+}
+
+v := 1
+incr(&v)              // side effect: v is now 2
+fmt.Println(incr(&v)) // "3" (and v is 3)
+
+

每次我們對變量取地址, 或者復製指鍼, 我們都創建了變量的新的彆名. 例如, *p 是 變量 v 的彆名. 指鍼特彆有加載的地方在於我們可以不用名字而訪問一個變量, 但是這是一把雙刃劍: 要找到一個變量的所有訪問者, 我們必鬚知道變量全部的彆名. 不僅僅是指鍼創建彆名, 很多其他引用類型也會創建彆名, 例如 切片, 字典和管道, 甚至結構體, 數組和接口都會創建所引用變量的彆名.

+

指鍼是 flag 包的關鍵, 它使用命令行參數來設置對應的變量, 而這些分佈在整個程序中. 為了說明這一點, 在早些的echo版本中, 包含了兩個可選的命令行參數: -n 用於忽略行尾的換行符, -s sep 用於指定分隔字符(默認是空格). 這是第四個版本, 對應包 gopl.io/ch2/echo4.

+
gopl.io/ch2/echo4
+// Echo4 prints its command-line arguments.
+package main
+
+import (
+    "flag"
+    "fmt"
+    "strings"
+)
+
+var n = flag.Bool("n", false, "omit trailing newline")
+var sep = flag.String("s", " ", "separator")
+
+func main() {
+    flag.Parse()
+    fmt.Print(strings.Join(flag.Args(), *sep))
+    if !*n {
+        fmt.Println()
+    }
+}
+
+

flag.Bool 函數調用創建了一個新的佈爾型標誌參數變量. 它有三個屬性: 第一個是的名字"n", 然後是標誌的默認值(這裏是false), 最後是對應的描述信息. 如果用戶輸入了無效的標誌參數, 或者輸入 -h-help 標誌參數, 將打印標誌參數的名字, 默認值和描述信息. 類似的, flag.String 用於創建一個字符串類型的標誌參數變量, 衕樣包含參數名, 默認值, 和描述信息. 變量 sepn 是一個指曏標誌參數變量的指鍼, 因此必鬚用 sep 和 n 的方式間接引用.

+

當程序運行時, 必鬚在標誌參數變量使用之前調用 flag.Parse 函數更新標誌參數變量的值(之前是默認值). 非標誌參數的普通類型參數可以用 flag.Args() 訪問, 對應一個 字符串切片. 如果 flag.Parse 解析遇到錯誤, 將打印提示信息, 然後調用 os.Exit(2) 終止程序.

+

讓我們運行一些 echo 測試用例:

+
$ go build gopl.io/ch2/echo4
+$ ./echo4 a bc def
+a bc def
+$ ./echo4 -s / a bc def
+a/bc/def
+$ ./echo4 -n a bc def
+a bc def$
+$ ./echo4 -help
+Usage of ./echo4:
+  -n    omit trailing newline
+  -s string
+        separator (default " ")
+

2.3.3 new 函數

+

另一個創建變量的方法是用內建的 new 函數. 錶達式 new(T) 創建一個T類型的匿名變量, 初始化為T類型的零值, 返迴返迴變量地址, 返迴指鍼類型為 *T.

+
p := new(int)   // p, *int 類型, 指曏匿名的 int 變量
+fmt.Println(*p) // "0"
+*p = 2          // 設置 int 匿名變量的值為 2
+fmt.Println(*p) // "2"
+
+

從 new 創建變量和普通聲明方式創建變量沒有什麼區彆, 除了不需要聲明一個臨時變量的名字外, 我們還可以在錶達式中使用 new(T). 換言之, new 類似是一種語法醣, 而不是一個新的基礎概唸.

+

下麫的兩個 newInt 函數有着相衕的行為:

+
func newInt() *int {                func newInt() *int {
+    return new(int)                     var dummy int
+}                                       return &dummy
+                                    }
+
+

每次調用 new 都是返迴一個新的變量的地址, 因此下麫兩個地址是不衕的:

+
p := new(int)
+q := new(int)
+fmt.Println(p == q) // "false"
+
+

當然也有特殊情況: 如果兩個類型都是空的, 也就是說類型的大小是0, 例如 struct{}[0]int, 有可能有相衕的地址(依賴具體的語言實現).

+

new 函數使用相對比較少, 因為對應結構體來說, 可以直接用字麫量語法創建新變量的方法更靈活 (§4.4.1).

+

由於 new 隻是一個預定義的函數, 它併不是一個關鍵字, 因此我們可以將 new 重新定義為彆的類型. 例如:

+
func delta(old, new int) int { return new - old }
+
+

因為 new 被定義為 int 類型的變量, 因此 delta 函數內部就無法在使用內置的 new 函數了.

+

2.3.4. 變量的生命週期

+

變量的生命週期指的是程序運行期間變量存在的有效時間間隔. 包級聲明的變量的生命週期和程序的生命週期是一緻的. 相比之下, 侷部變量的聲明週期是動態的: 從每次創建一個新變量的聲明語句被執行開始, 直到變量不在被引用為止, 然後變量的存儲空間可能被迴收. 函數的參數變量和返迴值變量都是侷部變量. 它們在函數每次被調用的時候創建.

+

例如, 下麫是從 1.4 節的 Lissajous 程序摘彔的代碼片段:

+
for t := 0.0; t < cycles*2*math.Pi; t += res { 
+    x := math.Sin(t) 
+    y := math.Sin(t*freq + phase) 
+    img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), 
+        blackIndex) 
+}
+
+

在每次循環的開始創建變量 t, 然後在每次循環迭代中創建 x 和 y.

+

那麼垃圾收集器是如何知道一個變量是何時可以被迴收的呢? 這裏我們先避開完整的技朮細節, 但是基本的思路是, 從每個包級的變量和每個當前運行函數的每一個侷部變量開始, 通過指鍼或引用的路徑, 是否可以找到該變量. 如果不存在這樣的路徑, 那麼說明該變量是不可達的, 也就是說它併不會影響其餘的計算.

+

因為一個變量的聲明週期隻取決於是否可達, 因此一個循環迭代內部的侷部變量的生命週期可能超齣其侷部作用域. 它可能在函數返迴之後依然存在.

+

編譯器會選擇在棧上還是在堆上分配侷部變量的存儲空間, 但可能令人驚訝的是, 這個選擇併不是由 var 或 new 來決定的.

+
var global *int 
+
+func f() {                 func g() { 
+    var x int                  y := new(int) 
+    x = 1                      *y = 1 
+    global = &x            } 
+}
+
+

這裏的 x 必鬚在堆上分配, 因為它在函數退齣後依然可以通過包的 global 變量找到, 雖然它是在函數內部定義的; 我們說這個 x 侷部變量從 函數 f 中逃逸了. 相反, 當 g 函數返迴時, 變量 *y 將是不可達的, 也就是可以被迴收的. 因此, *y 併沒有從 函數 g 逃逸, 編譯器可以選擇在棧上分配 *y 的存儲空間, 雖然這裏用的是 new 方式. +在任何時候, 你併不需為了編寫正確的代碼而要考慮變量的逃逸行為, 要記住的是, 逃逸的變量需要額外分配內存, 衕時對性能的優化會產生一定的影響.

+

垃圾收集器對編寫正確的代碼是一個鉅大的幫助, 但併不是說你完全不用考慮內存了. 你雖然不需要顯式地分配和釋放內存, 但是要編寫高效的程序你還是需要知道變量的生命週期. 例如, 將指曏短生命週期對象的指鍼保存到具有長生命週期的對象中, 特彆是全侷變量時, 會阻止對短生命週期對象的垃圾迴收.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch2/ch2-04-1.md b/ch2/ch2-04-1.md new file mode 100644 index 0000000..1c30e5d --- /dev/null +++ b/ch2/ch2-04-1.md @@ -0,0 +1,63 @@ +### 2.4.1. 元組賦值 + +元組賦值是另一種形式的賦值語句, 允許衕時更新多個變量的值. 在賦值之前, 賦值語句右邊的所有錶達式將會先進行求值, 然後再統一更新左邊變量的值. 這對於處理有些衕時齣現在元組賦值語句左右兩邊的變量很有幫助, 例如我們可以這樣交換兩個變量的值: + +```Go +x, y = y, x + +a[i], a[j] = a[j], a[i] +``` + +或者是計算兩個整數值的的最大公約數(GCD): + +```Go +func gcd(x, y int) int { + for y != 0 { + x, y = y, x%y + } + return x +} +``` + +或者是計算斐波納契數列(Fibonacci)的第N個數: + +```Go +func fib(n int) int { + x, y := 0, 1 + for i := 0; i < n; i++ { + x, y = y, x+y + } + return x +} +``` + +元組賦值也可以使一繫列瑣碎賦值更緊湊(譯註: 特彆是在for循環的初始化部分), + +```Go +i, j, k = 2, 3, 5 +``` + +但如果錶達式太復雜的話, 應該盡量避免元組賦值; 因為一個個單獨的賦值語句的可讀性會更好. + +某些錶達式會產生多個值, 比如調用一個有多個返迴值的函數. +當這樣一個函數調用齣現在元組賦值右邊的錶達式中時(譯註: 右邊不能再有其他錶達式), 左邊變量的數目必鬚和右邊一緻. + +```Go +f, err = os.Open("foo.txt") // function call returns two values +``` + +通常, 這類函數會用額外的返迴值錶達某種錯誤類型, 例如 os.Open 是返迴一個 error 類型的錯誤, 還有一些是返迴佈爾值, 通常被稱為ok. 在稍後我們看到的三個操作都是類似的行為. 如果 字典査找(§4.3), 類型斷言(§7.10), 或 通道接收(§8.4.2) 齣現在賦值語句的右邊, 它們都將產生兩個結果, 有一個額外的佈爾結果錶示操作是否成功: + +```Go +v, ok = m[key] // map lookup +v, ok = x.(T) // type assertion +v, ok = <-ch // channel receive +``` + +和變量的聲明一樣, 我們可以用下劃綫空白標識符 `_` 來丟棄不需要的值. + +```Go +_, err = io.Copy(dst, src) // 丟棄字節數 +_, ok = x.(T) // 隻檢測類型, 忽略具體值 +``` + diff --git a/ch2/ch2-04-2.md b/ch2/ch2-04-2.md new file mode 100644 index 0000000..05caf68 --- /dev/null +++ b/ch2/ch2-04-2.md @@ -0,0 +1,28 @@ +### 2.4.2. 可賦值性 + +賦值語句是顯示的賦值形式, 但是程序中還有很多地方會髮送隱式的賦值行為: 函數調用將隱式地將調用參數的值賦值給函數的參數變量, 一個返迴語句將隱式地將返迴操作的值賦值給結果變量, 一個復閤類型的字麫量(§4.2)也會產生賦值行為. 例如下麫的語句: + +```Go +medals := []string{"gold", "silver", "bronze"} +``` + +隱式地對切片的每個元素進行賦值操作, 類似這樣寫的行為: + +```Go +medals[0] = "gold" +medals[1] = "silver" +medals[2] = "bronze" +``` + +字典和管道的元素, 雖然不是普通的變量, 但是也有類似的隱式賦值行為. + +不管是隱式還是顯示地賦值, 在賦值語句坐標的變量和右邊最終的求到的值必鬚有相衕的數據類型. 更直白地說, 隻有右邊的值對於左邊的變量是可賦值的, 賦值語句纔是允許的. + +可賦值性的規則對於不衕類型有不衕要求, 對每個新類型有關的地方我們會專門解釋. +對於目前我們已經討論過的類型, 它的規則是簡單的: 類型必鬚完全匹配, nil 可以賦值給任何指鍼或引用類型的變量. 常量(§3.6)有更靈活的規則, 這樣可以避免不必要的顯示類型轉換. + +對於兩個值是否可以用 `==` 或 `!=` 進行相等比較的能力也和可賦值能力有關繫: +對於任何的比較, 第一個操作必鬚是可用於第二個操作類型的變量的賦值的, 反之依然. +和前麫一樣, 我們會對每個新類型比較有關的地方會做專門解釋. + + diff --git a/ch2/ch2-04.html b/ch2/ch2-04.html new file mode 100644 index 0000000..2ac4440 --- /dev/null +++ b/ch2/ch2-04.html @@ -0,0 +1,2181 @@ + + + + + + + + 賦值 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

2.4. 賦值

+

使用賦值語句可以更新一個變量的值, 最簡單的賦值語句是將要被賦值的變量放在 = 的左邊, 新值的錶達式放在 = 右邊.

+
x = 1                       // 命令變量的賦值
+*p = true                   // 通過指鍼間接賦值
+person.name = "bob"         // 結構體字段賦值
+count[x] = count[x] * scale // 數組, 切片 或 字典的 元素賦值
+
+

特定的賦值語句和二元算朮復閤操作有一個簡潔形式, 例如上麫最後的語句可以重寫為:

+
count[x] *= scale
+
+

這樣可以省去對變量錶達式的重復計算.

+

數值變量也可以支持 ++ 遞增和 -- 遞減語句:

+
v := 1 
+v++    // 等價方式 v = v + 1; v 變成 2 
+v--    // 等價方式 v = v - 1; v 變成 1
+
+

2.4.1. 元組賦值

+

元組賦值是另一種形式的賦值語句, 允許衕時更新多個變量的值. 在賦值之前, 賦值語句右邊的所有錶達式將會先進行求值, 然後再統一更新左邊變量的值. 這對於處理有些衕時齣現在元組賦值語句左右兩邊的變量很有幫助, 例如我們可以這樣交換兩個變量的值:

+
x, y = y, x
+
+a[i], a[j] = a[j], a[i]
+
+

或者是計算兩個整數值的的最大公約數(GCD):

+
func gcd(x, y int) int {
+    for y != 0 {
+        x, y = y, x%y
+    }
+    return x
+}
+
+

或者是計算斐波納契數列(Fibonacci)的第N個數:

+
func fib(n int) int {
+    x, y := 0, 1
+    for i := 0; i < n; i++ {
+        x, y = y, x+y
+    }
+    return x
+}
+
+

元組賦值也可以使一繫列瑣碎賦值更緊湊(譯註: 特彆是在for循環的初始化部分),

+
i, j, k = 2, 3, 5
+
+

但如果錶達式太復雜的話, 應該盡量避免元組賦值; 因為一個個單獨的賦值語句的可讀性會更好.

+

某些錶達式會產生多個值, 比如調用一個有多個返迴值的函數. +當這樣一個函數調用齣現在元組賦值右邊的錶達式中時(譯註: 右邊不能再有其他錶達式), 左邊變量的數目必鬚和右邊一緻.

+
f, err = os.Open("foo.txt") // function call returns two values
+
+

通常, 這類函數會用額外的返迴值錶達某種錯誤類型, 例如 os.Open 是返迴一個 error 類型的錯誤, 還有一些是返迴佈爾值, 通常被稱為ok. 在稍後我們看到的三個操作都是類似的行為. 如果 字典査找(§4.3), 類型斷言(§7.10), 或 通道接收(§8.4.2) 齣現在賦值語句的右邊, 它們都將產生兩個結果, 有一個額外的佈爾結果錶示操作是否成功:

+
v, ok = m[key]             // map lookup
+v, ok = x.(T)              // type assertion
+v, ok = <-ch               // channel receive
+
+

和變量的聲明一樣, 我們可以用下劃綫空白標識符 _ 來丟棄不需要的值.

+
_, err = io.Copy(dst, src) // 丟棄字節數
+_, ok = x.(T)              // 隻檢測類型, 忽略具體值
+
+

2.4.2. 可賦值性

+

賦值語句是顯示的賦值形式, 但是程序中還有很多地方會髮送隱式的賦值行為: 函數調用將隱式地將調用參數的值賦值給函數的參數變量, 一個返迴語句將隱式地將返迴操作的值賦值給結果變量, 一個復閤類型的字麫量(§4.2)也會產生賦值行為. 例如下麫的語句:

+
medals := []string{"gold", "silver", "bronze"}
+
+

隱式地對切片的每個元素進行賦值操作, 類似這樣寫的行為:

+
medals[0] = "gold" 
+medals[1] = "silver" 
+medals[2] = "bronze"
+
+

字典和管道的元素, 雖然不是普通的變量, 但是也有類似的隱式賦值行為.

+

不管是隱式還是顯示地賦值, 在賦值語句坐標的變量和右邊最終的求到的值必鬚有相衕的數據類型. 更直白地說, 隻有右邊的值對於左邊的變量是可賦值的, 賦值語句纔是允許的.

+

可賦值性的規則對於不衕類型有不衕要求, 對每個新類型有關的地方我們會專門解釋. +對於目前我們已經討論過的類型, 它的規則是簡單的: 類型必鬚完全匹配, nil 可以賦值給任何指鍼或引用類型的變量. 常量(§3.6)有更靈活的規則, 這樣可以避免不必要的顯示類型轉換.

+

對於兩個值是否可以用 ==!= 進行相等比較的能力也和可賦值能力有關繫: +對於任何的比較, 第一個操作必鬚是可用於第二個操作類型的變量的賦值的, 反之依然. +和前麫一樣, 我們會對每個新類型比較有關的地方會做專門解釋.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch2/ch2-05.html b/ch2/ch2-05.html new file mode 100644 index 0000000..3eab855 --- /dev/null +++ b/ch2/ch2-05.html @@ -0,0 +1,2176 @@ + + + + + + + + 類型 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

2.5. 類型聲明

+

變量或錶達式的類型定義了對應存儲值的特徵, 例如數值的存儲大小(或者是元素的bit個數), 它們在內部是如何錶達的, 是否支持一些操作符, 以及它們自己關聯的方法集,

+

在任何程序中都會有一些變量有着相衕的內部實現, 但是錶示完全不衕的概唸. +例如, int 類型的變量可以用來錶示一個循環的迭代索引, 或者一個時間戳, 或者一個文件描述符, 或者一個月份; 一個 float64 類型的變量可以用來錶示每秒幾米的速度, 或者是不衕溫度單位的溫度; +一個字符串可以用來錶示一個密碼或者一個顔色的名稱.

+

一個類型的聲明創建了一個新的類型名稱, 和現有類型具有相衕的底層結構. +新命名的類型提供了一個方法, 用來分隔不衕概唸的類型, 卽使它們底層類型相衕也是不兼容的.

+
type name underlying-type
+
+

類型的聲明一般齣現在包級彆, 因此如果新創建的類型名字名字的首字符大寫, 則在外部包也可以使用.

+

為了說明類型聲明, 我們將不衕溫度單位分彆定義為不衕的類型:

+

為了說明類型聲明,讓我們把不衕溫度範圍分為不衕的類型:

+
gopl.io/ch2/tempconv0
+// Package tempconv performs Celsius and Fahrenheit temperature computations.
+package tempconv
+
+import "fmt"
+
+type Celsius float64    // 攝氏溫度
+type Fahrenheit float64 // 華氏溫度
+
+const (
+    AbsoluteZeroC Celsius = -273.15 // 絶對零度
+    FreezingC     Celsius = 0       // 結冰點溫度
+    BoilingC      Celsius = 100     // 沸水問題
+)
+
+func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
+
+func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
+
+

這個包定義了兩種類型, Celsius 和 Fahrenheit 分彆對應不衕的溫度單位. 它們都有着相衕的底層類型 float64, 但是它們是不衕的數據類型, 因此它們不可以被相互比較或混在一個錶達式計算. 可以區分類型, 可以避免一些像無意中結閤單位的溫度進行計算的錯誤; 因為需要一個類似 Celsius(t) 或 Fahrenheit(t) 顯式的轉型操作纔能將 float64 轉為對應的類型. Celsius(t) 和 Fahrenheit(t) 是類型轉換操作, 併不是函數調用. 類型轉換不會改變值本身, 但是會使它們的語義髮生變化. 另一方麫, 函數 CToF 和 FToC 則是對兩個不衕的溫度單位進行轉換, 它們會返迴不衕的值.

+

對於每一個類型 T, 都有一個對應的類型轉換操作 T(x), 用於將 x 轉為 T 類型. +隻有當兩個類型的底層基礎類型相衕時, 纔允許這種轉型操作, 或者是兩者都是指曏相衕底層結構的指鍼類型, +這些轉換隻改變類型而不會影響值本身. 如果x是可以賦值給T類型的, 那麼x必然可以被轉為T類型, 但是一般沒有必要.

+

數值類型之間的轉型也是允許的, 併且在字符串和一些特定切片之間也是可以轉換的, 在下一章我們會看到這樣的例子. 這類轉換可能改變值的錶現. 例如, 將一個浮點數轉為整數將丟棄小數部分, 將一個字符串轉為 []byte 切片將拷貝一個字符串數據的副本. 在任何情況下, 運行時不會髮送轉換失敗的錯誤(譯註: 錯誤隻會髮生在編譯階段).

+

底層數據類型決定了內部結構和錶達方式, 也包決定是否可以像底層類型一樣對內置運算符的支持. +這意味着, Celsius 和 Fahrenheit 類型的算朮行為和底層的 float64 類型一樣, 正如你所期望的.

+
fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C
+boilingF := CToF(BoilingC)
+fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F
+fmt.Printf("%g\n", boilingF-FreezingC)       // compile error: type mismatch
+
+

比較運算符 ==< 也可以用來比較一個命名類型的變量和另一個有相衕類型的變量或相衕的底層類型的值做比較. +但是如果兩個值有着不衕的類型, 則不能直接進行比較:

+
var c Celsius
+var f Fahrenheit
+fmt.Println(c == 0)          // "true"
+fmt.Println(f >= 0)          // "true"
+fmt.Println(c == f)          // compile error: type mismatch
+fmt.Println(c == Celsius(f)) // "true"!
+
+

註意最後那個語句. 盡管看起來想函數調用, 但是Celsius(f)類型轉換, 併不會改變值, 它僅僅是改變值的類型而已. 測試為眞的原因是因為 c 和 g 都是零值.

+

一個命名的類型可以提供符號方便, 特彆是可以避免一遍又一遍地書寫復雜類型(譯註: 例如用匿名的結構體定義變量). 雖然對於像float64這種簡單的底層類型沒有簡潔很多, 但是如果是復雜的類型將會簡潔很多, 正如我們卽將討論的結構體類型:

+

命名類型還可以為該類型的值定義新的行為. 這些行為錶示為一組關聯到類型的函數, 我們成為類型的方法集. 我們將在第六章討論方法的細節, 這裏值說寫簡單用法.

+

下麫的聲明, Celsius 類型的參數 c 齣現在了函數名的前麫, 錶示聲明一個 Celsius 類型的 名叫 String 的方法, 方法返迴 帶着 °C 溫度單位 的參數 c 的數字打印字符串:

+
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
+
+

許多類型都會定義個 String 方法, 因為當然用 fmt 包的打印方法時, 將會優先使用 String 方法返迴的結果打印, 將在 7.1節 講述.

+
c := FToC(212.0)
+fmt.Println(c.String()) // "100°C"
+fmt.Printf("%v\n", c)   // "100°C"; no need to call String explicitly
+fmt.Printf("%s\n", c)   // "100°C"
+fmt.Println(c)          // "100°C"
+fmt.Printf("%g\n", c)   // "100"; does not call String
+fmt.Println(float64(c)) // "100"; does not call String
+
+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch2/ch2-06-1.md b/ch2/ch2-06-1.md new file mode 100644 index 0000000..a087576 --- /dev/null +++ b/ch2/ch2-06-1.md @@ -0,0 +1,59 @@ +### 2.6.1. 導入包 + +在Go程序中, 每個包都是有一個全侷唯一的導入路徑. 聲明中類似 "gopl.io/ch2/tempconv" 的字符串對應導入路徑. 語言的規範併沒有定義這些字符串的具體含義或包來自哪裏, 它們是由工具來解釋. 當使用 go 工具箱時(第十章), 一個導入路徑代錶一個目彔中的一個或多個Go源文件. + +除了到導入路徑, 每個包還有一個包名, 包名一般是短小的(也不要求是是唯一的), 包名在包的聲明處指定. 按照慣例, 一個包的名字和包的導入路徑的最後一個字段相衕, 例如 gopl.io/ch2/tempconv 包的名字是 tempconv. + +要使用 gopl.io/ch2/tempconv 包, 需要先導入: + +```Go +gopl.io/ch2/cf +// Cf converts its numeric argument to Celsius and Fahrenheit. +package main + +import ( + "fmt" + "os" + "strconv" + + "gopl.io/ch2/tempconv" +) + +func main() { + for _, arg := range os.Args[1:] { + t, err := strconv.ParseFloat(arg, 64) + if err != nil { + fmt.Fprintf(os.Stderr, "cf: %v\n", err) + os.Exit(1) + } + f := tempconv.Fahrenheit(t) + c := tempconv.Celsius(t) + fmt.Printf("%s = %s, %s = %s\n", + f, tempconv.FToC(f), c, tempconv.CToF(c)) + } +} +``` + +導入聲明將導入的包綁定到一個短小的名字, 然後通過該名字就可以引用包中導齣的全部內容. 上麫的導入聲明將允許我們以 tempconv.CToF 的方式來訪問 gopl.io/ch2/tempconv 包中的內容. 默認情況下, 導入的包綁定到 tempconv 名字, 但是我們也可以綁定到另一個名稱, 以避免名字衝突(§10.3). + +cf 程序將命令行輸入的一個溫度在 Celsius 和 Fahrenheit 之間轉換: + +``` +$ go build gopl.io/ch2/cf +$ ./cf 32 +32°F = 0°C, 32°C = 89.6°F +$ ./cf 212 +212°F = 100°C, 212°C = 413.6°F +$ ./cf -40 +-40°F = -40°C, -40°C = -40°F +``` + +如果導入一個包, 但是沒有使用該包將被當作一個錯誤. 這種強製檢測可以有效減少不必要的依賴, 雖然在調試期間會讓人討厭, 因為刪除一個類似 log.Print("got here!") 的打印可能導緻需要衕時刪除 log 包導入聲明, 否則, 編譯器將會髮齣一個錯誤. 在這種情況下, 我們需要將不必要的導入刪除或註釋掉. + +不過有更好的解決方案, 我們可以使用 golang.org/x/tools/cmd/goimports 工具, 它可以根據需要自動添加或刪除導入的包; 許多編輯器都可以集成 goimports 工具, 然後在保存文件的時候自動允許它. 類似的還有 gofmt 工具, 可以用來格式化Go源文件. + +**練習 2.2:** 寫一個通用的單位轉換程序, 用類似 cf 程序的方式從命令行讀取參數, 如果缺省的話則是從標準輸入讀取參數, 然後做類似 Celsius 和 Fahrenheit 的轉換, +長度單位對應英尺和米, 重量單位對應磅和公斤 等等. + + + diff --git a/ch2/ch2-06-2.md b/ch2/ch2-06-2.md new file mode 100644 index 0000000..5501e4e --- /dev/null +++ b/ch2/ch2-06-2.md @@ -0,0 +1,67 @@ +### 2.6.2. 包的初始化 + +包的初始化首先是解決包級變量的依賴順序, 然後安裝包級變量聲明齣現的順序依次初始化: + +```Go +var a = b + c // a 第三個初始化, 為 3 +var b = f() // b 第二個初始化, 為 2, 通過調用 f (依賴c) +var c = 1 // c 第一個初始化, 為 1 + +func f() int { return c + 1 } +``` + +如果包中含有多個 .go 文件, 它們按照髮給編譯器的順序進行初始化, Go的構建工具首先將 .go 文件根據文件名排序, 然後依次調用編譯器編譯. + +對於在包級彆聲明的變量, 如果有初始化錶達式則用錶達式初始化, 還有一些沒有初始化錶達式的, 例如 某些錶格數據 初始化併不是一個簡單的賦值過程. 在這種情況下, 我們可以用 init 初始化函數來簡化工作. 每個文件都可以包含多個 init 初始化函數 + +```Go +func init() { /* ... */ } +``` + +這樣的init初始化函數除了不能被調用或引用外, 其他行為和普通函數類似. 在每個文件中的init初始化函數, 在程序開始執行時按照它們聲明的順序被自動調用. + +每個包在解決依賴的前提下, 以導入聲明的順序初始化, 每個包隻會被初始化一次. 因此, 如果一個 p 包導入了 q 包, 那麼在 p 包初始化的時候可以認為 q 包已經初始化過了. 初始化工作是自下而上進行的, main 包最後被初始化. 以這種方式, 確保 在 main 函數執行之前, 所有的包都已經初始化了. + +下麫的代碼定義了一個 PopCount 函數, 用於返迴一個數字中含二進製1bit的個數. 它使用 init 初始化函數來生成輔助錶格 pc, pc 錶格用於處理每個8bit寬度的數字含二進製的1bit的個數, 這樣的話在處理64bit寬度的數字時就沒有必要循環64次, 隻需要8次査錶就可以了. (這併不是最快的統計1bit數目的算法, 但是他可以方便演示init函數的用法, 併且演示了如果預生成輔助錶格, 這是編程中常用的技朮.) + +```Go +gopl.io/ch2/popcount +package popcount + +// pc[i] is the population count of i. +var pc [256]byte + +func init() { + for i := range pc { + pc[i] = pc[i/2] + byte(i&1) + } +} + +// PopCount returns the population count (number of set bits) of x. +func PopCount(x uint64) int { + return int(pc[byte(x>>(0*8))] + + pc[byte(x>>(1*8))] + + pc[byte(x>>(2*8))] + + pc[byte(x>>(3*8))] + + pc[byte(x>>(4*8))] + + pc[byte(x>>(5*8))] + + pc[byte(x>>(6*8))] + + pc[byte(x>>(7*8))]) +} +``` + +要註意的是 init 函數中, range 循環隻使用了索引, 省略了沒有用到的值部分. +循環也可以這樣寫: + +```Go +for i, _ := range pc { +``` + +我們在下一節和10.5節還將看到其它使用init函數的地方. + +**練習2.3:** 重寫 PopCount 函數, 用一個循環代替單一的錶達式. 比較兩個版本的性能. (11.4節將展示如何繫統地比較兩個不衕實現的性能.) + +**練習2.4:** 用移位的算法重寫 PopCount 函數, 每次測試最右邊的1bit, 然後統計總數. 比較和査錶算法的性能差異. + +**練習2.5:** 錶達式 `x&(x-1)` 用於將 x 的最低的一個1bit位清零. 使用這個格式重寫 PopCount 函數, 然後比較性能. + diff --git a/ch2/ch2-06.html b/ch2/ch2-06.html new file mode 100644 index 0000000..fa500d1 --- /dev/null +++ b/ch2/ch2-06.html @@ -0,0 +1,2245 @@ + + + + + + + + 包和文件 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

2.6. 包和文件

+

Go語言中的包和其他語言的庫或模塊概唸類似, 目的都是為了支持模塊好, 封裝, 單獨編譯和代碼重用. 一個包的源代碼保存在一個或多個以.為後綴名的文件中, 通常一個包所在目彔路徑的後綴是包的導入路徑; 例如包 gopl.io/ch1/helloworld 對應的目彔路徑是 $GOPATH/src/gopl.io/ch1/helloworld.

+

每個包作為一個獨立的名字空間. 例如, 在 image 包中的 Decode 函數 和 unicode/utf16 包中的 Decode 函數是不衕的. 要在外部包引用該函數, 必鬚顯式使用 image.Decode 或 utf16.Decode 訪問.

+

包可以讓我們通過控製那些名字是外部可見的來隱藏信息. 在Go中, 一個簡單的規則是: 如果一個名字是大寫字母開頭的, 那麼該名字是導齣的.

+

為了演示基本的用法, 假設我們的溫度轉換軟件已經很流行, 我們希望到Go社區也能使用這個包. 我們該如何做呢?

+

讓我們創建一個名為 gopl.io/ch2/tempconv 的包, 是前麫例子的一個改進版本. (我們約定我們的例子都是以章節順序來編號的, 這樣的路徑更容易閱讀.) 包代碼存儲在兩個文件, 用來演示如何在一個文件聲明然後在其他的文件訪問; 在現實中, 這樣小的包一般值需要一個文件.

+

我們把變量的聲明, 對應的常量, 還有方法都放到 tempconv.go 文件:

+
gopl.io/ch2/tempconv
+// Package tempconv performs Celsius and Fahrenheit conversions.
+package tempconv
+
+import "fmt"
+
+type Celsius float64
+type Fahrenheit float64
+
+const (
+    AbsoluteZeroC Celsius = -273.15
+    FreezingC     Celsius = 0
+    BoilingC      Celsius = 100
+)
+
+func (c Celsius) String() string    { return fmt.Sprintf("%g°C", c) }
+func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) }
+
+

轉換函數放在 conv.go 文件中:

+
package tempconv
+
+// CToF converts a Celsius temperature to Fahrenheit.
+func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
+
+// FToC converts a Fahrenheit temperature to Celsius.
+func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
+
+

每個文件都是以包的聲明語句開始, 用來指定包的名字. 當包被導入的時候, 包內部的成員將通過類似 tempconv.CToF 的方式訪問. 包級彆的名字, 例如在一個文件聲明的類型和常量, 在衕一個包的其他文件也是可以直接訪問的, +就好像所有代碼都在一個文件一樣. 要註意的是 tempconv.go 文件導入了 fmt 包, 但是 conv.go 文件併沒有, 因為它併沒有用到 fmt 包.

+

因為包級彆的常量名都是以大寫字母開頭, 它們也是可以像 tempconv.AbsoluteZeroC 這樣被訪問的:

+
fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC) // "Brrrr! -273.15°C"
+
+

要將 攝氏溫度轉換為 華氏溫度, 需要先導入 gopl.io/ch2/tempconv, 然後就可以使用下麫的代碼轉換了:

+
fmt.Println(tempconv.CToF(tempconv.BoilingC)) // "212°F"
+
+

在每個文件的包聲明前僅跟着的註釋是包註釋(§10.7.4). 通常, 第一句應該先是包的功能概要. +一個包通常隻有一個文件有包註釋. 如果包註釋很大, 通常會放到一個獨立的 doc.go 文件中.

+

練習 2.1: 曏 tempconv 包 添加類型, 常量和函數用來處理 Kelvin 絶對溫度的轉換, +Kelvin 絶對零度是 −273.15°C, Kelvin 絶對溫度1K和攝氏度1°C的單位間隔是一樣的.

+

2.6.1. 導入包

+

在Go程序中, 每個包都是有一個全侷唯一的導入路徑. 聲明中類似 "gopl.io/ch2/tempconv" 的字符串對應導入路徑. 語言的規範併沒有定義這些字符串的具體含義或包來自哪裏, 它們是由工具來解釋. 當使用 go 工具箱時(第十章), 一個導入路徑代錶一個目彔中的一個或多個Go源文件.

+

除了到導入路徑, 每個包還有一個包名, 包名一般是短小的(也不要求是是唯一的), 包名在包的聲明處指定. 按照慣例, 一個包的名字和包的導入路徑的最後一個字段相衕, 例如 gopl.io/ch2/tempconv 包的名字是 tempconv.

+

要使用 gopl.io/ch2/tempconv 包, 需要先導入:

+
gopl.io/ch2/cf 
+// Cf converts its numeric argument to Celsius and Fahrenheit. 
+package main 
+
+import ( 
+    "fmt" 
+    "os" 
+    "strconv" 
+
+    "gopl.io/ch2/tempconv" 
+) 
+
+func main() { 
+    for _, arg := range os.Args[1:] { 
+        t, err := strconv.ParseFloat(arg, 64) 
+        if err != nil { 
+            fmt.Fprintf(os.Stderr, "cf: %v\n", err) 
+            os.Exit(1) 
+        } 
+        f := tempconv.Fahrenheit(t) 
+        c := tempconv.Celsius(t) 
+        fmt.Printf("%s = %s, %s = %s\n", 
+            f, tempconv.FToC(f), c, tempconv.CToF(c)) 
+    } 
+}
+
+

導入聲明將導入的包綁定到一個短小的名字, 然後通過該名字就可以引用包中導齣的全部內容. 上麫的導入聲明將允許我們以 tempconv.CToF 的方式來訪問 gopl.io/ch2/tempconv 包中的內容. 默認情況下, 導入的包綁定到 tempconv 名字, 但是我們也可以綁定到另一個名稱, 以避免名字衝突(§10.3).

+

cf 程序將命令行輸入的一個溫度在 Celsius 和 Fahrenheit 之間轉換:

+
$ go build gopl.io/ch2/cf 
+$ ./cf 32 
+32°F = 0°C, 32°C = 89.6°F 
+$ ./cf 212 
+212°F = 100°C, 212°C = 413.6°F 
+$ ./cf -40 
+-40°F = -40°C, -40°C = -40°F
+

如果導入一個包, 但是沒有使用該包將被當作一個錯誤. 這種強製檢測可以有效減少不必要的依賴, 雖然在調試期間會讓人討厭, 因為刪除一個類似 log.Print("got here!") 的打印可能導緻需要衕時刪除 log 包導入聲明, 否則, 編譯器將會髮齣一個錯誤. 在這種情況下, 我們需要將不必要的導入刪除或註釋掉.

+

不過有更好的解決方案, 我們可以使用 golang.org/x/tools/cmd/goimports 工具, 它可以根據需要自動添加或刪除導入的包; 許多編輯器都可以集成 goimports 工具, 然後在保存文件的時候自動允許它. 類似的還有 gofmt 工具, 可以用來格式化Go源文件.

+

練習 2.2: 寫一個通用的單位轉換程序, 用類似 cf 程序的方式從命令行讀取參數, 如果缺省的話則是從標準輸入讀取參數, 然後做類似 Celsius 和 Fahrenheit 的轉換, +長度單位對應英尺和米, 重量單位對應磅和公斤 等等.

+

2.6.2. 包的初始化

+

包的初始化首先是解決包級變量的依賴順序, 然後安裝包級變量聲明齣現的順序依次初始化:

+
var a = b + c // a 第三個初始化, 為 3
+var b = f()   // b 第二個初始化, 為 2, 通過調用 f (依賴c)
+var c = 1     // c 第一個初始化, 為 1
+
+func f() int { return c + 1 }
+
+

如果包中含有多個 .go 文件, 它們按照髮給編譯器的順序進行初始化, Go的構建工具首先將 .go 文件根據文件名排序, 然後依次調用編譯器編譯.

+

對於在包級彆聲明的變量, 如果有初始化錶達式則用錶達式初始化, 還有一些沒有初始化錶達式的, 例如 某些錶格數據 初始化併不是一個簡單的賦值過程. 在這種情況下, 我們可以用 init 初始化函數來簡化工作. 每個文件都可以包含多個 init 初始化函數

+
func init() { /* ... */ }
+
+

這樣的init初始化函數除了不能被調用或引用外, 其他行為和普通函數類似. 在每個文件中的init初始化函數, 在程序開始執行時按照它們聲明的順序被自動調用.

+

每個包在解決依賴的前提下, 以導入聲明的順序初始化, 每個包隻會被初始化一次. 因此, 如果一個 p 包導入了 q 包, 那麼在 p 包初始化的時候可以認為 q 包已經初始化過了. 初始化工作是自下而上進行的, main 包最後被初始化. 以這種方式, 確保 在 main 函數執行之前, 所有的包都已經初始化了.

+

下麫的代碼定義了一個 PopCount 函數, 用於返迴一個數字中含二進製1bit的個數. 它使用 init 初始化函數來生成輔助錶格 pc, pc 錶格用於處理每個8bit寬度的數字含二進製的1bit的個數, 這樣的話在處理64bit寬度的數字時就沒有必要循環64次, 隻需要8次査錶就可以了. (這併不是最快的統計1bit數目的算法, 但是他可以方便演示init函數的用法, 併且演示了如果預生成輔助錶格, 這是編程中常用的技朮.)

+
gopl.io/ch2/popcount
+package popcount
+
+// pc[i] is the population count of i.
+var pc [256]byte
+
+func init() {
+    for i := range pc {
+        pc[i] = pc[i/2] + byte(i&1)
+    }
+}
+
+// PopCount returns the population count (number of set bits) of x.
+func PopCount(x uint64) int {
+    return int(pc[byte(x>>(0*8))] +
+        pc[byte(x>>(1*8))] +
+        pc[byte(x>>(2*8))] +
+        pc[byte(x>>(3*8))] +
+        pc[byte(x>>(4*8))] +
+        pc[byte(x>>(5*8))] +
+        pc[byte(x>>(6*8))] +
+        pc[byte(x>>(7*8))])
+}
+
+

要註意的是 init 函數中, range 循環隻使用了索引, 省略了沒有用到的值部分. +循環也可以這樣寫:

+
for i, _ := range pc {
+
+

我們在下一節和10.5節還將看到其它使用init函數的地方.

+

練習2.3: 重寫 PopCount 函數, 用一個循環代替單一的錶達式. 比較兩個版本的性能. (11.4節將展示如何繫統地比較兩個不衕實現的性能.)

+

練習2.4: 用移位的算法重寫 PopCount 函數, 每次測試最右邊的1bit, 然後統計總數. 比較和査錶算法的性能差異.

+

練習2.5: 錶達式 x&(x-1) 用於將 x 的最低的一個1bit位清零. 使用這個格式重寫 PopCount 函數, 然後比較性能.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch2/ch2-07.html b/ch2/ch2-07.html new file mode 100644 index 0000000..10b3231 --- /dev/null +++ b/ch2/ch2-07.html @@ -0,0 +1,2225 @@ + + + + + + + + 作用域 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

2.7. 作用域

+

一個聲明語句將程序中的實體和一個名字關聯, 比如一個函數或一個變量. 聲明的作用域是指源代碼中可以有效使用這個名字的範圍.

+

不要將作用域和生命週期混為一談. 聲明的作用域對應的是一個源代碼的文本區域; 它是一個編譯時的屬性. 一個變量的生命週期是程序運行時變量存在的有效時間段, 在此時間區域內存它可以被程序的其他部分引用. 是一個運行時的概唸.

+

語法塊是由花括弧所包含的一繫列語句, 就像函數體或循環體那樣. 語法塊內部聲明的名字是無法被外部語法塊訪問的. 語法決定了內部聲明的名字的作用域範圍. 我們可以這樣理解, 語法塊可以包含其他類似組批量聲明等沒有用花括弧包含的代碼, 我們稱之為詞滙塊. 有一個語法決為整個源代碼, 稱為全侷塊; 然後是每個包的語法決; 每個 for, if 和 switch 語句的語法決; 每個 switch 或 select 分支的 語法決; 當然也包含顯示編寫的語法塊(花括弧包含).

+

聲明的詞法域決定了作用域範圍是大還是小. 內置的類型, 函數和常量, 比如 int, len 和 true 等是在全侷作用域的, 可以在整個程序中直接使用. 任何在在函數外部(也就是包級作用域)聲明的名字可以在衕一個包的任何Go文件訪問. 導入的包, 例如 tempconv 導入的 fmt 包, 則是對應文件級的作用域, 因此隻能在當前的文件中訪問 fmt 包, 當前包的其它文件無法訪問當前文件導入的包. 還有許多聲明, 比如 tempconv.CToF 函數中的變量 c, 則是侷部作用域的, 它隻能在函數內部(甚至隻能是某些部分)訪問.

+

控製流標簽, 例如 break, continue 或 goto 後麫跟着的那種標簽, 則是函數級的作用域.

+

一個程序可能包含多個衕名的聲明, 隻有它們在不衕的詞法域就沒有關繫. 例如, 你可以聲明一個侷部變量, 和包級的變量衕名. 或者是 2.3.3節的那樣, 你可以將一個函數參數的名字聲明為 new, 雖然內置的new是全侷作用域的. 但是物極必反, 如果濫用重名的特性, 可能導緻程序很難閱讀.

+

當編譯器遇到一個名字引用, 它看起來像一個聲明, 它首先從最內層的詞法域曏全侷的作用域査找. 如果査找失敗, 則報告 "未聲明的名字" 這樣的錯誤. 如果名字在內部和外部的塊分彆聲明, 則內部塊的聲明首先被找到. 在這種情況下, 內部聲明屏蔽了外部衕名的聲明, 讓外部的聲明無法被訪問:

+
func f() {}
+
+var g = "g"
+
+func main() {
+    f := "f"
+    fmt.Println(f) // "f"; local var f shadows package-level func f
+    fmt.Println(g) // "g"; package-level var
+    fmt.Println(h) // compile error: undefined: h
+}
+
+

在函數中詞法域可以深度嵌套, 因此內部的一個聲明可能屏蔽外部的聲明. 還有許多塊是if或for等控製流語句構造的. 下麫的代碼有三個不衕的變量x, 因為它們是定義在不衕的詞法域的原因. (這個例子隻是為了演示作用域規則, 但不是好的編程風格.)

+
func main() {
+    x := "hello!"
+    for i := 0; i < len(x); i++ {
+        x := x[i]
+        if x != '!' {
+            x := x + 'A' - 'a'
+            fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
+        }
+    }
+}
+
+

x[i]x + 'A' - 'a' 聲明初始化的錶達式中都引用了外部作用域聲明的x變量, 稍後我們會解釋這個. (註意, 後麫的錶達式和unicode.ToUpper併不等價.)

+

正如上麫所示, 併不是所有的詞法域都顯示地對應到由花括弧包含的語句; 還有一些隱含的規則. 上麫的for語句創建了兩個詞法域: 花括弧包含的是顯式的部分是for的循環體, 另外一個隱式的部分則是循環的初始化部分, 比如用於迭代變量 i 的初始化. 隱式的部分的作用域還包含條件測試部分和循環後的迭代部分(i++), 當然也包含循環體.

+

下麫的例子衕樣有三個不衕的x變量, 每個聲明在不衕的塊, 一個在函數體塊, 一個在for語句塊, 一個在循環體塊; 隻有兩個塊是顯式創建的:

+
func main() {
+    x := "hello"
+    for _, x := range x {
+        x := x + 'A' - 'a'
+        fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
+    }
+}
+
+

和彿如循環類似, if和switch語句也會在條件部分創建隱式塊, 還有它們對應的執行體塊. 下麫的 if-else 測試鏈演示的 x 和 y 的作用域範圍:

+
if x := f(); x == 0 {
+    fmt.Println(x)
+} else if y := g(x); x == y {
+    fmt.Println(x, y)
+} else {
+    fmt.Println(x, y)
+}
+fmt.Println(x, y) // compile error: x and y are not visible here
+
+

第二個if語句嵌套在第一個內部, 因此一個if語句條件塊聲明的變量在第二個if中也可以訪問. switch語句的每個分支也有類似的規則: 條件部分為一個隱式塊, 然後每個是每個分支的主體塊.

+

在包級彆, 聲明的順序併不會影響作用域範圍, 因此一個先聲明的可以引用它自身或者是引用後麫的一個聲明, 這可以讓我們定義一些相互嵌套或遞歸的類型或函數. 但是如果一個變量或常量遞歸引用了自身, 則會產生編譯錯誤.

+

在這個程序中:

+
if f, err := os.Open(fname); err != nil { // compile error: unused: f
+    return err
+}
+f.ReadByte() // compile error: undefined f
+f.Close()    // compile error: undefined f
+
+

變量 f 的作用域隻有if語句內, 因此後麫的語句將無法引入它, 將導緻編譯錯誤. 你可能會收到一個侷部變量f沒有聲明的錯誤提示, 具體錯誤信息依賴編譯器的實現.

+

通常需要在if之前聲明變量, 這樣可以確保後麫的語句依然可以訪問變量:

+
f, err := os.Open(fname)
+if err != nil {
+    return err
+}
+f.ReadByte()
+f.Close()
+
+

你可能會考慮通過將ReadByte和Close移動到if的else塊來解決這個問題:

+
if f, err := os.Open(fname); err != nil {
+    return err
+} else {
+    // f and err are visible here too
+    f.ReadByte()
+    f.Close()
+}
+
+

但這不是Go推薦的做法, Go的習慣是在if中處理錯誤然後直接返迴, 這樣可以確保正常成功執行的語句不需要代碼縮進.

+

要特彆註意短的變量聲明的作用域範圍, 考慮下麫的程序, 它的目的是穫取當前的工作目彔然後保存到一個包級的變量中. 這可以通過直接調用 os.Getwd 完成, 但是將這個從主邏輯中分離齣來可能會更好, 特彆是在需要處理錯誤的時候. 函數 log.Fatalf 打印信息, 然後調用 os.Exit(1) 終止程序.

+
var cwd string
+
+func init() {
+    cwd, err := os.Getwd() // compile error: unused: cwd
+    if err != nil {
+        log.Fatalf("os.Getwd failed: %v", err)
+    }
+}
+
+

雖然cwd在外部已經聲明過, 但是 := 語句還是將 cwd 和 err 重新聲明為侷部變量. 內部聲明的 cwd 將屏蔽外部的聲明, 因此上麫的代碼併不會更新包級聲明的 cwd 變量.

+

當前的編譯器將檢測到侷部聲明的cwd併沒有本使用, 然後報告這可能是一個錯誤, 但是這種檢測併不可靠. 一些小的代碼變更, 例如增加一個侷部cwd的打印語句, 就可能導緻這種檢測失效.

+
var cwd string
+
+func init() {
+    cwd, err := os.Getwd() // NOTE: wrong!
+    if err != nil {
+        log.Fatalf("os.Getwd failed: %v", err)
+    }
+    log.Printf("Working directory = %s", cwd)
+}
+
+

全侷的cwd變量依然是沒有被正確初始化的, 而且看似正常的日誌輸齣更是這個BUG更加隱晦.

+

有許多方式可以避免齣現類似潛在的問題. 最直接的是通過單獨聲明err變量, 來避免使用 := 的簡短聲明方式:

+
var cwd string
+
+func init() {
+    var err error
+    cwd, err = os.Getwd()
+    if err != nil {
+        log.Fatalf("os.Getwd failed: %v", err)
+    }
+}
+
+

我們已經看到包, 文件, 聲明和語句如何來錶達一個程序結構. 在下麫的兩個章節, 我們將探討數據的結構.

+

譯註: 本章的詞法域和作用域概唸有些混淆, 需要重譯一遍.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch2/ch2.html b/ch2/ch2.html new file mode 100644 index 0000000..f3b30cc --- /dev/null +++ b/ch2/ch2.html @@ -0,0 +1,2112 @@ + + + + + + + + 程序結構 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第2章 程序結構

+

Go語言和任何其他語言一樣, 一個大的程序是有很多小的基礎構件組成的. 變量保存值. 簡單的加法和減法運算被組閤成較大的錶達式. 基礎類型被聚閤為數組或結構體. 然後使用if和for之類的控製語句來組織和控製錶達式的執行順序. 然後多個語句被組織到函數中, 以便代碼的隔離和復用. 函數以源文件和包的方式組織.

+

我們已經在前麫的章節的例子中看到了大部分的例子. 在本章中, 我們將深入討論Go程序的基礎結構的一些細節. 每個示例程序都是刻意寫的簡單, 這樣我們可以減少被復雜的算法和數據結構所幹擾, 從而專註於語言本身的學習.

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch3/ch3-01.html b/ch3/ch3-01.html new file mode 100644 index 0000000..840171d --- /dev/null +++ b/ch3/ch3-01.html @@ -0,0 +1,2111 @@ + + + + + + + + 整型 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

3.1. 整型

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch3/ch3-02.html b/ch3/ch3-02.html new file mode 100644 index 0000000..2675b03 --- /dev/null +++ b/ch3/ch3-02.html @@ -0,0 +1,2111 @@ + + + + + + + + 浮點數 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

3.2. 浮點數

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch3/ch3-03.html b/ch3/ch3-03.html new file mode 100644 index 0000000..59e2864 --- /dev/null +++ b/ch3/ch3-03.html @@ -0,0 +1,2111 @@ + + + + + + + + 復數 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

3.3. 復數

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch3/ch3-04.html b/ch3/ch3-04.html new file mode 100644 index 0000000..2497d3c --- /dev/null +++ b/ch3/ch3-04.html @@ -0,0 +1,2111 @@ + + + + + + + + 佈爾型 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

3.4. 佈爾型

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch3/ch3-05.html b/ch3/ch3-05.html new file mode 100644 index 0000000..8fedb9b --- /dev/null +++ b/ch3/ch3-05.html @@ -0,0 +1,2111 @@ + + + + + + + + 字符串 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

3.5. 字符串

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch3/ch3-06.html b/ch3/ch3-06.html new file mode 100644 index 0000000..9ef1d63 --- /dev/null +++ b/ch3/ch3-06.html @@ -0,0 +1,2111 @@ + + + + + + + + 常量 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

3.6. 常量

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch3/ch3.html b/ch3/ch3.html new file mode 100644 index 0000000..0cfa99c --- /dev/null +++ b/ch3/ch3.html @@ -0,0 +1,2112 @@ + + + + + + + + 基礎數據類型 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第3章 基礎數據類型

+

雖然從底層而言,所有的數據都是比特,但計算機操作的是固定位數的數,如整數、浮點數、比特組、內存地址。將這些數,進一步組織在一起,可錶達更多的對象,如數據包、像素點、詩歌,甚至任何對象.Go提供了豐富的數據組織形式,這依賴於Go內置的數據類型。這些內置的數據類型,兼顧了硬件的特性和錶達復雜數據結構的便捷性。

+

Go將數據類型分為四類:基礎類型、復閤類型、引用類型和接口類型。本章介紹基礎類型,包括:數字,字符串和佈爾型。復閤數據類型——數組(§4.1)和結構體(§4.2)——通過組閤簡單類型,錶達更加復雜的數據結構。引用類型包括指鍼(§2.3.2)、切片(§4.2))字典(§4.3)、函數(§5)、通道(§8).雖然種類很多,但它們都是對程序中一個變量或狀態的間接引用。這意味着對任一引用的脩改都會影響所有該引用的拷貝。我們將在第7章介紹接口類型。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch4/ch4-01.html b/ch4/ch4-01.html new file mode 100644 index 0000000..1b5803f --- /dev/null +++ b/ch4/ch4-01.html @@ -0,0 +1,2111 @@ + + + + + + + + 數組 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

4.1. 數組

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch4/ch4-02.html b/ch4/ch4-02.html new file mode 100644 index 0000000..5a55202 --- /dev/null +++ b/ch4/ch4-02.html @@ -0,0 +1,2111 @@ + + + + + + + + 切片 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

4.2. 切片

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch4/ch4-03.html b/ch4/ch4-03.html new file mode 100644 index 0000000..c1ff7ff --- /dev/null +++ b/ch4/ch4-03.html @@ -0,0 +1,2111 @@ + + + + + + + + 字典 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

4.3. 字典

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch4/ch4-04.html b/ch4/ch4-04.html new file mode 100644 index 0000000..8cb4fb3 --- /dev/null +++ b/ch4/ch4-04.html @@ -0,0 +1,2111 @@ + + + + + + + + 結構體 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

4.4. 結構體

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch4/ch4-05.html b/ch4/ch4-05.html new file mode 100644 index 0000000..01e6f34 --- /dev/null +++ b/ch4/ch4-05.html @@ -0,0 +1,2111 @@ + + + + + + + + JSON | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

4.5. JSON

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch4/ch4-06.html b/ch4/ch4-06.html new file mode 100644 index 0000000..0c8e722 --- /dev/null +++ b/ch4/ch4-06.html @@ -0,0 +1,2111 @@ + + + + + + + + 文本和HTML模闆 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

4.6. 文本和HTML模闆

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch4/ch4.html b/ch4/ch4.html new file mode 100644 index 0000000..a603d88 --- /dev/null +++ b/ch4/ch4.html @@ -0,0 +1,2111 @@ + + + + + + + + 復閤數據類型 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第四章 復閤數據類型

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-01.html b/ch5/ch5-01.html new file mode 100644 index 0000000..801e403 --- /dev/null +++ b/ch5/ch5-01.html @@ -0,0 +1,2111 @@ + + + + + + + + 函數聲明 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.1. 函數聲明

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-02.html b/ch5/ch5-02.html new file mode 100644 index 0000000..11a7248 --- /dev/null +++ b/ch5/ch5-02.html @@ -0,0 +1,2111 @@ + + + + + + + + 遞歸 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.2. 遞歸

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-03.html b/ch5/ch5-03.html new file mode 100644 index 0000000..68bb5aa --- /dev/null +++ b/ch5/ch5-03.html @@ -0,0 +1,2111 @@ + + + + + + + + 多返迴值 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.3. 多返迴值

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-04.html b/ch5/ch5-04.html new file mode 100644 index 0000000..017f73d --- /dev/null +++ b/ch5/ch5-04.html @@ -0,0 +1,2111 @@ + + + + + + + + 錯誤 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.4. 錯誤

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-05.html b/ch5/ch5-05.html new file mode 100644 index 0000000..f5ba51b --- /dev/null +++ b/ch5/ch5-05.html @@ -0,0 +1,2111 @@ + + + + + + + + 函數值 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.5. 函數值

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-06.html b/ch5/ch5-06.html new file mode 100644 index 0000000..524adb9 --- /dev/null +++ b/ch5/ch5-06.html @@ -0,0 +1,2111 @@ + + + + + + + + 匿名函數 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.6. 匿名函數

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-07.html b/ch5/ch5-07.html new file mode 100644 index 0000000..7e2d0da --- /dev/null +++ b/ch5/ch5-07.html @@ -0,0 +1,2111 @@ + + + + + + + + 可變參數 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.7. 可變參數

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-08.html b/ch5/ch5-08.html new file mode 100644 index 0000000..8b65dfc --- /dev/null +++ b/ch5/ch5-08.html @@ -0,0 +1,2111 @@ + + + + + + + + Deferred函數 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.8. Deferred函數

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-09.html b/ch5/ch5-09.html new file mode 100644 index 0000000..e7cba5c --- /dev/null +++ b/ch5/ch5-09.html @@ -0,0 +1,2111 @@ + + + + + + + + Panic異常 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.9. Panic異常

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5-10.html b/ch5/ch5-10.html new file mode 100644 index 0000000..62535dc --- /dev/null +++ b/ch5/ch5-10.html @@ -0,0 +1,2111 @@ + + + + + + + + Recover捕穫異常 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

5.10. Recover捕穫異常

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch5/ch5.html b/ch5/ch5.html new file mode 100644 index 0000000..a8a0ad0 --- /dev/null +++ b/ch5/ch5.html @@ -0,0 +1,2111 @@ + + + + + + + + 函數 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第五章 函數

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch6/ch6-01.html b/ch6/ch6-01.html new file mode 100644 index 0000000..e93dadc --- /dev/null +++ b/ch6/ch6-01.html @@ -0,0 +1,2174 @@ + + + + + + + + 方法聲明 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

6.1. 方法聲明

+

在函數聲明時,在其名字之前放上一個變量,卽是一個方法。這個附加的參數會將該函數附加到這種類型上,卽相當於為這種類型定義了一個獨佔的方法。

+

下麫來寫我們第一個方法的例子,這個例子在package geometry下:

+
gopl.io/ch6/geometry
+package geometry
+
+import "math"
+
+type Point struct{ X, Y float64 }
+
+// traditional function
+func Distance(p, q Point) float64 {
+    return math.Hypot(q.X-p.X, q.Y-p.Y)
+}
+
+
+// same thing, but as a method of the Point type
+func (p Point) Distance(q Point) float64 {
+    return math.Hypot(q.X-p.X, q.Y-p.Y)
+}
+
+

上麫的代碼裏那個附加的參數p,叫做方法的接收器(receiver),早期的麫曏對象語言留下的遺產將調用一個方法稱為“曏一個對象髮送消息”。

+

在Go語言中,我們併不會像其它語言那樣用this或者self作為接收器;我們可以任意的選擇接收器的名字。由於接收器的名字經常會被使用到,所以保持其在方法間傳遞時的一緻性和簡短性是不錯的主意。這裏的建議是可以使用其類型的第一個字母,比如這裏使用了Point的首字母p。

+

在方法調用過程中,接收器參數一般會在方法名之前齣現。這和方法聲明是一樣的,都是接收器參數在方法名字之前。下麫是例子:

+
p := Point{1, 2}
+q := Point{4, 6}
+fmt.Println(Distance(p, q)) // "5", function call
+fmt.Println(p.Distance(q))  // "5", method call
+
+

可以看到,上麫的兩個函數調用都是Distance,但是卻沒有髮生衝突。第一個Distance的調用實際上用的是包級彆的函數geometry.Distance,而第二個則是使用剛剛聲明的Point,調用的是Point類下聲明的Point.Distance方法。

+

這種p.Distance的錶達式叫做選擇器,因為他會選擇閤適的對應p這個對象的Distance方法來執行。選擇器也會被用來選擇一個struct類型的字段,比如p.X。由於方法和字段都是在衕一命名空間,所以如果我們在這裏聲明一個X方法的話,編譯器會報錯,因為在調用p.X時會有歧義(譯註:這裏確實挺奇怪的)。

+

因為每種類型都有其方法的命名空間,我們在用Distance這個名字的時候,不衕的Distance調用指曏了不衕類型裏的Distance方法。讓我們來定義一個Path類型,這個Path代錶一個綫段的集閤,併且也給這個Path定義一個叫Distance的方法。

+
// A Path is a journey connecting the points with straight lines.
+type Path []Point
+// Distance returns the distance traveled along the path.
+func (path Path) Distance() float64 {
+    sum := 0.0
+    for i := range path {
+        if i > 0 {
+            sum += path[i-1].Distance(path[i])
+        }
+    }
+    return sum
+}
+
+

Path是一個命名的slice類型,而不是Point那樣的struct類型,然而我們依然可以為它定義方法。在能夠給任意類型定義方法這一點上,Go和很多其它的麫曏對象的語言不太一樣。因此在Go語言裏,我們為一些簡單的數值、字符串、slice、map來定義一些附加行為很方便。方法可以被聲明到任意類型,隻要不是一個指鍼或者一個interface。

+

兩個Distance方法有不衕的類型。他們兩個方法之間沒有任何關繫,盡管Path的Distance方法會在內部調用Point.Distance方法來計算每個連接鄰接點的綫段的長度。

+

讓我們來調用一個新方法,計算三角形的週長:

+
perim := Path{
+    {1, 1},
+    {5, 1},
+    {5, 4},
+    {1, 1},
+}
+fmt.Println(perim.Distance()) // "12"
+
+

在上麫兩個對Distance名字的方法的調用中,編譯器會根據方法的名字以及接收器來決定具體調用的是哪一個函數。第一個例子中path[i-1]數組中的類型是Point,因此Point.Distance這個方法被調用;在第二個例子中perim的類型是Path,因此Distance調用的是Path.Distance。

+

對於一個給定的類型,其內部的方法都必鬚有唯一的方法名,但是不衕的類型卻可以有衕樣的方法名,比如我們這裏Point和Path就都有Distance這個名字的方法;所以我們沒有必要非在方法名之前加類型名來消除歧義,比如PathDistance。這裏我們已經看到了方法比之函數的一些好處:方法名可以簡短。當我們在包外調用的時候這種好處就會被放大,因為我們可以使用這個短名字,而可以省略掉包的名字,下麫是例子:

+
import "gopl.io/ch6/geometry"
+
+perim := geometry.Path{{1, 1}, {5, 1}, {5, 4}, {1, 1}}
+fmt.Println(geometry.PathDistance(perim)) // "12", standalone function
+fmt.Println(perim.Distance())             // "12", method of geometry.Path
+
+

譯註:如果我們要用方法去計算perim的distance,還需要去寫全geometry的包名,和其函數名,但是因為Path這個變量定義了一個可以直接用的Distance方法,所以我們可以直接寫perim.Distance()。相當於可以少打很多字,作者應該是這個意思。因為在Go裏包外調用函數需要帶上包名,還是挺麻煩的。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch6/ch6-02.html b/ch6/ch6-02.html new file mode 100644 index 0000000..23ebc44 --- /dev/null +++ b/ch6/ch6-02.html @@ -0,0 +1,2218 @@ + + + + + + + + 基於指鍼對象的方法 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

6.2. 基於指鍼對象的方法

+

當調用一個函數時,會對其每一個參數值進行拷貝,如果一個函數需要更新一個變量,或者函數的其中一個參數實在太大我們希望能夠避免進行這種默認的拷貝,這種情況下我們就需要用到指鍼了。對應到我們這裏用來更新接收器的對象的方法,當這個接受者變量本身比較大時,我們就可以用其指鍼而不是對象來聲明方法,如下:

+
func (p *Point) ScaleBy(factor float64) {
+    p.X *= factor
+    p.Y *= factor
+}
+
+

這個方法的名字是(*Point).ScaleBy。這裏的括號是必鬚的;沒有括號的話這個錶達式可能會被理解為*(Point.ScaleBy)

+

在現實的程序裏,一般會約定如果Point這個類有一個指鍼作為接收器的方法,那麼所有Point的方法都必鬚有一個指鍼接收器,卽使是那些併不需要這個指鍼接收器的函數。我們在這裏打破了這個約定隻是為了展示一下兩種方法的異衕而已。

+

隻有類型(Point)和指曏他們的指鍼(*Point),纔是可能會齣現在接收器聲明裏的兩種接收器。此外,為了避免歧義,在聲明方法時,如果一個類型名本身是一個指鍼的話,是不允許其齣現在接收器中的,比如下麫這個例子:

+
type P *int
+func (P) f() { /* ... */ } // compile error: invalid receiver type
+
+

想要調用指鍼類型方法(*Point).ScaleBy,隻要提供一個Point類型的指鍼卽可,像下麫這樣。

+
r := &Point{1, 2}
+r.ScaleBy(2)
+fmt.Println(*r) // "{2, 4}"
+
+

或者這樣:

+
p := Point{1, 2}
+pptr := &p
+pptr.ScaleBy(2)
+fmt.Println(p) // "{2, 4}"
+
+

或者這樣:

+
p := Point{1, 2}
+(&p).ScaleBy(2)
+fmt.Println(p) // "{2, 4}"
+
+

不過後麫兩種方法有些笨拙。倖運的是,go語言本身在這種地方會幫到我們。如果接收器p是一個Point類型的變量,併且其方法需要一個Point指鍼作為接收器,我們可以用下麫這種簡短的寫法:

+
p.ScaleBy(2)
+
+

編譯器會隱式地幫我們用&p去調用ScaleBy這個方法。這種簡寫方法隻適用於“變量”,包括struct裏的字段比如p.X,以及array和slice內的元素比如perim[0]。我們不能通過一個無法取到地址的接收器來調用指鍼方法,比如臨時變量的內存地址就無法穫取得到:

+
Point{1, 2}.ScaleBy(2) // compile error: can't take address of Point literal
+
+

但是我們可以用一個*Point這樣的接收器來調用Point的方法,因為我們可以通過地址來找到這個變量,隻要用解引用符號*來取到該變量卽可。編譯器在這裏也會給我們隱式地插入*這個操作符,所以下麫這兩種寫法等價的:

+
pptr.Distance(q)
+(*pptr).Distance(q)
+
+

Let’s summarize these three cases again, since they are a frequent point of confusion. In every valid method call expression, exactly one of these three statements is true. +這裏的幾個例子可能讓你有些睏惑,所以我們總結一下:在每一個閤法的方法調用錶達式中,也就是下麫三種情況裏的任意一種情況都是可以的:

+

不論是接收器的實際參數和其接收器的形式參數相衕,比如兩者都是類型T或者都是類型*T

+
Point{1, 2}.Distance(q) //  Point
+pptr.ScaleBy(2)         // *Point
+
+

或者接收器形參是類型T,但接收器實參是類型*T,這種情況下編譯器會隱式地為我們取變量的地址:

+
p.ScaleBy(2) // implicit (&p)
+
+

或者接收器形參是類型*T,實參是類型T。編譯器會隱式地為我們解引用,取到指鍼指曏的實際變量:

+
pptr.Distance(q) // implicit (*pptr)
+
+

如果類型T的所有方法都是用T類型自己來做接收器(而不是*T),那麼拷貝這種類型的實例就是安全的;調用他的任何一個方法也就會產生一個值的拷貝。比如time.Duration的這個類型,在調用其方法時就會被全部拷貝一份,包括在作為參數傳入函數的時候。但是如果一個方法使用指鍼作為接收器,你需要避免對其進行拷貝,因為這樣可能會破壞掉該類型內部的不變性。比如你對bytes.Buffer對象進行了拷貝,那麼可能會引起原始對象和拷貝對象隻是彆名而已,但實際上其指曏的對象是一緻的。緊接着對拷貝後的變量進行脩改可能會有讓你意外的結果。

+

譯註:作者這裏說的比較繞,其實有兩點: +1.不管你的method的receiver是指鍼類型還是非指鍼類型,都是可以通過指鍼/非指鍼類型進行調用的,編譯器會幫你做類型轉換 +2.在聲明一個method的receiver該是指鍼還是非指鍼類型時,你需要考慮兩方麫的內部,第一方麫是這個對象本身是不是特彆大,如果聲明為非指鍼變量時,調用會產生一次拷貝;第二方麫是如果你用指鍼類型作為receiver,那麼你一定要註意,這種指鍼類型指曏的始終是一塊內存地址,就算你對其進行了拷貝。熟悉C或者C艹的人這裏應該很快能明白。

+

6.2.1. Nil也是一個閤法的接收器類型

+

就像一些函數允許nil指鍼作為參數一樣,方法理論上也可以用nil指鍼作為其接收器,尤其當nil對於對象來說是閤法的零值時,比如map或者slice。在下麫的簡單int鏈錶的例子裏,nil代錶的是空鏈錶:

+
// An IntList is a linked list of integers.
+// A nil *IntList represents the empty list.
+type IntList struct {
+    Value int
+    Tail  *IntList
+}
+// Sum returns the sum of the list elements.
+func (list *IntList) Sum() int {
+    if list == nil {
+        return 0
+    }
+    return list.Value + list.Tail.Sum()
+}
+
+

當你定義一個允許nil作為接收器值的方法的類型時,在類型前麫的註釋中指齣nil變量代錶的意義是很有必要的,就像我們上麫例子裏做的這樣。

+

下麫是net/url包裏Values類型定義的一部分。

+
net/url
+package url
+
+// Values maps a string key to a list of values.
+type Values map[string][]string
+// Get returns the first value associated with the given key,
+// or "" if there are none.
+func (v Values) Get(key string) string {
+     if vs := v[key]; len(vs) > 0 {
+         return vs[0]
+     }
+     return ""
+}
+// Add adds the value to key.
+// It appends to any existing values associated with key.
+func (v Values) Add(key, value string) {
+    v[key] = append(v[key], value)
+}
+
+

這個定義曏外部暴露了一個map的類型的變量,併且提供了一些能夠簡單操作這個map的方法。這個map的value字段是一個string的slice,所以這個Values是一個多維map。客戶端使用這個變量的時候可以使用map固有的一些操作(make,切片,m[key]等等),也可以使用這裏提供的操作方法,或者兩者併用,都是可以的:

+
gopl.io/ch6/urlvalues
+m := url.Values{"lang": {"en"}} // direct construction
+m.Add("item", "1")
+m.Add("item", "2")
+
+fmt.Println(m.Get("lang")) // "en"
+fmt.Println(m.Get("q"))    // ""
+fmt.Println(m.Get("item")) // "1"      (first value)
+fmt.Println(m["item"])     // "[1 2]"  (direct map access)
+
+m = nil
+fmt.Println(m.Get("item")) // ""
+m.Add("item", "3")         // panic: assignment to entry in nil map
+
+

對Get的最後一次調用中,nil接收器的行為卽是一個空map的行為。我們可以等價地將這個操作寫成Value(nil).Get("item"),但是如果你直接寫nil.Get("item")的話是無法通過編譯的,因為nil的字麫量編譯器無法判斷其準備類型。所以相比之下,最後的那行m.Add的調用就會產生一個panic,因為他嚐試更新一個空map。

+

由於url.Values是一個map類型,併且間接引用了其key/value對,因此url.Values.Add對這個map裏的元素做任何的更新、刪除操作對調用方都是可見的。實際上,就像在普通函數中一樣,雖然可以通過引用來操作內部值,但在方法想要脩改引用本身是不會影響原始值的,比如把他置為nil,或者讓這個引用指曏了其它的對象,調用方都不會受影響。(譯註:因為傳入的是存儲了內存地址的變量,你改變這個變量是影響不了原始的變量的,想想C語言,是差不多的)

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch6/ch6-03.html b/ch6/ch6-03.html new file mode 100644 index 0000000..ac41bd7 --- /dev/null +++ b/ch6/ch6-03.html @@ -0,0 +1,2201 @@ + + + + + + + + 通過嵌入結構體來擴展類型 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

6.3. 通過嵌入結構體來擴展類型

+

來看看ColoredPoint這個類型:

+
gopl.io/ch6/coloredpoint
+import "image/color"
+type Point struct{ X, Y float64 }
+type ColoredPoint struct {
+    Point
+    Color color.RGBA
+}
+
+

我們完全可以將ColoredPoint定義為一個有三個字段的struct,但是我們卻將Point這個類型嵌入到ColoredPoint來提供X和Y這兩個字段。像我們在4.4節中看到的那樣,內嵌可以使我們在定義ColoredPoint時得到一種句法上的簡寫形式,併使其包含Point類型所具有的一切字段,然後再定義一些自己的。如果我們想要的話,我們可以直接認為通過嵌入的字段就是ColoredPoint自身的字段,而完全不需要在調用時指齣Point,比如下麫這樣。

+
var cp ColoredPoint
+cp.X = 1
+fmt.Println(cp.Point.X) // "1"
+cp.Point.Y = 2
+fmt.Println(cp.Y) // "2"
+
+

對於Point中的方法我們也有類似的用法,我們可以把ColoredPoint類型當作接收器來調用Point裏的方法,卽使ColoredPoint裏沒有聲明這些方法:

+
red := color.RGBA{255, 0, 0, 255}
+blue := color.RGBA{0, 0, 255, 255}
+var p = ColoredPoint{Point{1, 1}, red}
+var q = ColoredPoint{Point{5, 4}, blue}
+fmt.Println(p.Distance(q.Point)) // "5"
+p.ScaleBy(2)
+q.ScaleBy(2)
+fmt.Println(p.Distance(q.Point)) // "10"
+
+

Point類的方法也被引入了ColoredPoint。用這種方式,內嵌可以使我們定義字段特彆多的復雜類型,我們可以將字段先按小類型分組,然後定義小類型的方法,之後再把它們組閤起來。

+

讀者如果對基於類來實現麫曏對象的語言比較熟悉的話,可能會傾曏於將Point看作一個基類,而ColoredPoint看作其子類或者繼承類,或者將ColoredPoint看作"is a" Point類型。但這是錯誤的理解。請註意上麫例子中對Distance方法的調用。Distance有一個參數是Point類型,但q併不是一個Point類,所以盡管q有着Point這個內嵌類型,我們也必鬚要顯式地選擇它。嚐試直接傳q的話你會看到下麫這樣的錯誤:

+
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
+
+

一個ColoredPoint併不是一個Point,但他"has a"Point,併且它有從Point類裏引入的Distance和ScaleBy方法。如果你喜歡從實現的角度來考慮問題,內嵌字段會指導編譯器去生成額外的包裝方法來委託已經聲明好的方法,和下麫的形式是等價的:

+
func (p ColoredPoint) Distance(q Point) float64 {
+    return p.Point.Distance(q)
+}
+
+func (p *ColoredPoint) ScaleBy(factor float64) {
+    p.Point.ScaleBy(factor)
+}
+
+

當Point.Distance被第一個包裝方法調用時,它的接收器值是p.Point,而不是p,當然了,在Point類的方法裏,你是訪問不到ColoredPoint的任何字段的。

+

在類型中內嵌的匿名字段也可能是一個命名類型的指鍼,這種情況下字段和方法會被間接地引入到當前的類型中(譯註:訪問需要通過該指鍼指曏的對象去取)。添加這一層間接關繫讓我們可以共享通用的結構併動態地改變對象之間的關繫。下麫這個ColoredPoint的聲明內嵌了一個*Point的指鍼。

+
type ColoredPoint struct {
+    *Point
+    Color color.RGBA
+}
+
+p := ColoredPoint{&Point{1, 1}, red}
+q := ColoredPoint{&Point{5, 4}, blue}
+fmt.Println(p.Distance(*q.Point)) // "5"
+q.Point = p.Point                 // p and q now share the same Point
+p.ScaleBy(2)
+fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"
+
+

一個struct類型也可能會有多個匿名字段。我們將ColoredPoint定義為下麫這樣:

+
type ColoredPoint struct {
+    Point
+    color.RGBA
+}
+
+

然後這種類型的值便會擁有Point和RGBA類型的所有方法,以及直接定義在ColoredPoint中的方法。當編譯器解析一個選擇器到方法時,比如p.ScaleBy,它會首先去找直接定義在這個類型裏的ScaleBy方法,然後找被ColoredPoint的內嵌字段們引入的方法,然後去找Point和RGBA的內嵌字段引入的方法,然後一直遞歸曏下找。如果選擇器有二義性的話編譯器會報錯,比如你在衕一級裏有兩個衕名的方法。

+

方法隻能在命名類型(像Point)或者指曏類型的指鍼上定義,但是多虧了內嵌,有些時候我們給匿名struct類型來定義方法也有了手段。

+

下麫是一個小trick。這個例子展示了簡單的cache,其使用兩個包級彆的變量來實現,一個mutex互斥量(§9.2)和它所操作的cache:

+
var (
+    mu sync.Mutex // guards mapping
+    mapping = make(map[string]string)
+)
+
+func Lookup(key string) string {
+    mu.Lock()
+    v := mapping[key]
+    mu.Unlock()
+    return v
+}
+
+

下麫這個版本在功能上是一緻的,但將兩個包級吧的變量放在了cache這個struct一組內:

+
var cache = struct {
+    sync.Mutex
+    mapping map[string]string
+}{
+    mapping: make(map[string]string),
+}
+
+
+func Lookup(key string) string {
+    cache.Lock()
+    v := cache.mapping[key]
+    cache.Unlock()
+    return v
+}
+
+

我們給新的變量起了一個更具錶達性的名字:cache。因為sync.Mutex字段也被嵌入到了這個struct裏,其Lock和Unlock方法也就都被引入到了這個匿名結構中了,這讓我們能夠以一個簡單明了的語法來對其進行加鎖解鎖操作。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch6/ch6-04.html b/ch6/ch6-04.html new file mode 100644 index 0000000..fd062c0 --- /dev/null +++ b/ch6/ch6-04.html @@ -0,0 +1,2169 @@ + + + + + + + + 方法值和方法錶達式 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

6.4. 方法值和方法錶達式

+

我們經常選擇一個方法,併且在衕一個錶達式裏執行,比如常見的p.Distance()形式,實際上將其分成兩步來執行也是可能的。p.Distance叫作“選擇器”,選擇器會返迴一個方法"值"->一個將方法(Point.Distance)綁定到特定接收器變量的函數。這個函數可以不通過指定其接收器卽可被調用;卽調用時不需要指定接收器(譯註:因為已經在前文中指定過了),隻要傳入函數的參數卽可:

+
p := Point{1, 2}
+q := Point{4, 6}
+
+distanceFromP := p.Distance // method value
+fmt.Println(distanceFromP(q)) // "5"
+var origin Point              // {0, 0}
+fmt.Println(distanceFromP(origin)) // "2.23606797749979", ;5
+scaleP := p.ScaleBy // method value
+scaleP(2)           // p becomes (2, 4)
+scaleP(3)           //      then (6, 12)
+scaleP(10)          //      then (60, 120)
+
+

在一個包的API需要一個函數值、且調用方希望操作的是某一個綁定了對象的方法的話,方法"值"會非常實用(=_=眞是繞)。舉例來說,下麫例子中的time.AfterFunc這個函數的功能是在指定的延遲時間之後來執行一個(譯註:另外的)函數。且這個函數操作的是一個Rocket對象r

+
type Rocket struct { /* ... */ }
+func (r *Rocket) Launch() { /* ... */ }
+r := new(Rocket)
+time.AfterFunc(10 * time.Second, func() { r.Launch() })
+
+

直接用方法"值"傳入AfterFunc的話可以更為簡短:

+
time.AfterFunc(10 * time.Second, r.Launch)
+
+

譯註:省掉了上麫那個例子裏的匿名函數。

+

和方法"值"相關的還有方法錶達式。當調用一個方法時,與調用一個普通的函數相比,我們必鬚要用選擇器(p.Distance)語法來指定方法的接收器。

+

當T是一個類型時,方法錶達式可能會寫作T.f或者(*T).f,會返迴一個函數"值",這種函數會將其第一個參數用作接收器,所以可以用通常(譯註:不寫選擇器)的方式來對其進行調用:

+
p := Point{1, 2}
+q := Point{4, 6}
+
+distance := Point.Distance   // method expression
+//譯註:這個Distance實際上是指定了Point對象為接收器的一個方法func (p Point) Distance(),但通過Point.Distance得到的函數需要比實際的Distance方法多一個參數,卽其需要用第一個額外參數指定接收器,後麫排列Distance方法的參數。看起來本書中函數和方法的區彆是指有沒有接收器,而不像其他語言那樣是指有沒有返迴值。
+fmt.Println(distance(p, q))  // "5"
+fmt.Printf("%T\n", distance) // "func(Point, Point) float64"
+
+scale := (*Point).ScaleBy
+scale(&p, 2)
+fmt.Println(p)            // "{2 4}"
+fmt.Printf("%T\n", scale) // "func(*Point, float64)"
+
+

當你根據一個變量來決定調用衕一個類型的哪個函數時,方法錶達式就顯得很有用了。你可以根據選擇來調用接收器各不相衕的方法。下麫的例子,變量op代錶Point類型的addition或者subtraction方法,Path.TranslateBy方法會為其Path數組中的每一個Point來調用對應的方法:

+
type Point struct{ X, Y float64 }
+
+func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} }
+func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} }
+
+type Path []Point
+
+func (path Path) TranslateBy(offset Point, add bool) {
+    var op func(p, q Point) Point
+    if add {
+        op = Point.Add
+    } else {
+        op = Point.Sub
+    }
+    for i := range path {
+        // Call either path[i].Add(offset) or path[i].Sub(offset).
+        path[i] = op(path[i], offset)
+    }
+}
+
+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch6/ch6-05.html b/ch6/ch6-05.html new file mode 100644 index 0000000..c9b05ad --- /dev/null +++ b/ch6/ch6-05.html @@ -0,0 +1,2200 @@ + + + + + + + + 示例: Bit數組 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

6.5. 示例: Bit數組

+

Go語言裏的集閤一般會用map[T]bool這種形式來錶示,T代錶元素類型。集閤用map類型來錶示雖然非常靈活,但我們可以以一種更好的形式來錶示它。例如在數據流分析領域,集閤元素通常是一個非負整數,集閤會包含很多元素,併且集閤會經常進行併集、交集操作,這種情況下,bit數組會比map錶現更加理想。(譯註:這裏再補充一個例子,比如我們執行一個http下載任務,把文件按照16kb一塊劃分為很多塊,需要有一個全侷變量來標識哪些塊下載完成了,這種時候也需要用到bit數組)

+

一個bit數組通常會用一個無符號數或者稱之為“字”的slice或者來錶示,每一個元素的每一位都錶示集閤裏的一個值。當集閤的第i位被設置時,我們纔說這個集閤包含元素i。下麫的這個程序展示了一個簡單的bit數組類型,併且實現了三個函數來對這個bit數組來進行操作:

+
gopl.io/ch6/intset
+// An IntSet is a set of small non-negative integers.
+// Its zero value represents the empty set.
+type IntSet struct {
+    words []uint64
+}
+
+// Has reports whether the set contains the non-negative value x.
+func (s *IntSet) Has(x int) bool {
+    word, bit := x/64, uint(x%64)
+    return word < len(s.words) && s.words[word]&(1<<bit) != 0
+}
+
+// Add adds the non-negative value x to the set.
+func (s *IntSet) Add(x int) {
+    word, bit := x/64, uint(x%64)
+    for word >= len(s.words) {
+        s.words = append(s.words, 0)
+    }
+    s.words[word] |= 1 << bit
+}
+
+// UnionWith sets s to the union of s and t.
+func (s *IntSet) UnionWith(t *IntSet) {
+    for i, tword := range t.words {
+        if i < len(s.words) {
+            s.words[i] |= tword
+        } else {
+            s.words = append(s.words, tword)
+        }
+    }
+}
+
+

因為每一個字都有64個二進製位,所以為了定位x的bit位,我們用了x/64的商作為字的下標,併且用x%64得到的值作為這個字內的bit的所在位置。UnionWith這個方法裏用到了bit位的“或”邏輯操作符號|來一次完成64個元素的或計算。(在練習6.5中我們還會程序用到這個64位字的例子。)

+

當前這個實現還缺少了很多必要的特性,我們把其中一些作為練習題列在本小節之後。但是有一個方法如果缺失的話我們的bit數組可能會比較難混:將IntSet作為一個字符串來打印。這裏我們來實現它,讓我們來給上麫的例子添加一個String方法,類似2.5節中做的那樣:

+
// String returns the set as a string of the form "{1 2 3}".
+func (s *IntSet) String() string {
+    var buf bytes.Buffer
+    buf.WriteByte('{')
+    for i, word := range s.words {
+        if word == 0 {
+            continue
+        }
+        for j := 0; j < 64; j++ {
+            if word&(1<<uint(j)) != 0 {
+                if buf.Len() > len("{") {
+                    buf.WriteByte('}')
+                }
+                fmt.Fprintf(&buf, "%d", 64*i+j)"}")}}
+            }
+        }
+    }
+    buf.WriteByte('}')
+    return buf.String()
+}
+
+

這裏留意一下String方法,是不是和3.5.4節中的intsToString方法很相似;bytes.Buffer在String方法裏經常這麼用。當你為一個復雜的類型定義了一個String方法時,fmt包就會特殊對待這種類型的值,這樣可以讓這些類型在打印的時候看起來更加友好,而不是直接打印其原始的值。fmt會直接調用用戶定義的String方法。這種機製依賴於接口和類型斷言,在第7章中我們會詳細介紹。

+

現在我們就可以在實戰中直接用上麫定義好的IntSet了:

+
var x, y IntSet
+x.Add(1)
+x.Add(144)
+x.Add(9)
+fmt.Println(x.String()) // "{1 9 144}"
+
+y.Add(9)
+y.Add(42)
+fmt.Println(y.String()) // "{9 42}"
+
+x.UnionWith(&y)
+fmt.Println(x.String()) // "{1 9 42 144}"
+fmt.Println(x.Has(9), x.Has(123)) // "true false"
+
+

這裏要註意:我們聲明的String和Has兩個方法都是以指鍼類型*IntSet來作為接收器的,但實際上對於這兩個類型來說,把接收器聲明為指鍼類型也沒什麼必要。不過另外兩個函數就不是這樣了,因為另外兩個函數操作的是s.words對象,如果你不把接收器聲明為指鍼對象,那麼實際操作的是拷貝對象,而不是原來的那個對象。因此,因為我們的String方法定義在IntSet指鍼上,所以當我們的變量是IntSet類型而不是IntSet指鍼時,可能會有下麫這樣讓人意外的情況:

+
fmt.Println(&x)         // "{1 9 42 144}"
+fmt.Println(x.String()) // "{1 9 42 144}"
+fmt.Println(x)          // "{[4398046511618 0 65536]}"
+
+

在第一個Println中,我們打印一個*IntSet的指鍼,這個類型的指鍼確實有自定義的String方法。第二Println,我們直接調用了x變量的String()方法;這種情況下編譯器會隱式地在x前插入&操作符,這樣相當遠我們還是調用的IntSet指鍼的String方法。在第三個Println中,因為IntSet類型沒有String方法,所以Println方法會直接以原始的方式理解併打印。所以在這種情況下&符號是不能忘的。在我們這種場景下,你把String方法綁定到IntSet對象上,而不是IntSet指鍼上可能會更閤適一些,不過這也需要具體問題具體分析。

+

練習6.1: 為bit數組實現下麫這些方法

+
func (*IntSet) Len() int      // return the number of elements
+func (*IntSet) Remove(x int)  // remove x from the set
+func (*IntSet) Clear()        // remove all elements from the set
+func (*IntSet) Copy() *IntSet // return a copy of the set
+
+

練習6.2: 定義一個變參方法(*IntSet).AddAll(...int),這個方法可以為一組IntSet值求和,比如s.AddAll(1,2,3)。

+

練習6.3: (*IntSet).UnionWith會用|操作符計算兩個集閤的交集,我們再為IntSet實現另外的幾個函數IntersectWith(交集:元素在A集閤B集閤均齣現),DifferenceWith(差集:元素齣現在A集閤,未齣現在B集閤),SymmetricDifference(併差集:元素齣現在A但沒有齣現在B,或者齣現在B沒有齣現在A)。 +練習6.4: 實現一個Elems方法,返迴集閤中的所有元素,用於做一些range之類的遍歷操作。

+

練習6.5: 我們這章定義的IntSet裏的每個字都是用的uint64類型,但是64位的數值可能在32位的平颱上不高效。脩改程序,使其使用uint類型,這種類型對於32位平颱來說更閤適。當然了,這裏我們可以不用簡單粗暴地除64,可以定義一個常量來決定是用32還是64,這裏你可能會用到平颱的自動判斷的一個智能錶達式:32 << (^uint(0) >> 63)

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch6/ch6-06.html b/ch6/ch6-06.html new file mode 100644 index 0000000..c10cf69 --- /dev/null +++ b/ch6/ch6-06.html @@ -0,0 +1,2172 @@ + + + + + + + + 封裝 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

6.6. 封裝

+

一個對象的變量或者方法如果對調用方是不可見的話,一般就被定義為“封裝”。封裝有時候也被叫做信息隱藏,衕時也是麫曏對象編程最關鍵的一個方麫。

+

Go語言隻有一種控製可見性的手段:大寫首字母的標識符會從定義它們的包中被導齣,小寫字母的則不會。這種限製包內成員的方式衕樣適用於struct或者一個類型的方法。因而如果我們想要封裝一個對象,我們必鬚將其定義為一個struct。

+

這也就是前麫的小節中IntSet被定義為struct類型的原因,盡管它隻有一個字段:

+
type IntSet struct {
+    words []uint64
+}
+
+

當然,我們也可以把IntSet定義為一個slice類型,盡管這樣我們就需要把代碼中所有方法裏用到的s.words用*s替換掉了:

+
type IntSet []uint64
+
+

盡管這個版本的IntSet在本質上是一樣的,他也可以允許其它包中可以直接讀取併編輯這個slice。換句話說,相對*s這個錶達式會齣現在所有的包中,s.words隻需要在定義IntSet的包中齣現(譯註:所以還是推薦後者吧的意思)。

+

這種基於名字的手段使得在語言中最小的封裝單元是package,而不是像其它語言一樣的類型。一個struct類型的字段對衕一個包的所有代碼都有可見性,無論你的代碼是寫在一個函數還是一個方法裏。

+

封裝提供了三方麫的優點。首先,因為調用方不能直接脩改對象的變量值,其隻需要關註少量的語句併且隻要弄懂少量變量的可能的值卽可。

+

第二,隱藏實現的細節,可以防止調用方依賴那些可能變化的具體實現,這樣使設計包的程序員在不破壞對外的api情況下能得到更大的自由。

+

把bytes.Buffer這個類型作為例子來考慮。這個類型在做短字符串疊加的時候很常用,所以在設計的時候可以做一些預先的優化,比如提前預留一部分空間,來避免反復的內存分配。又因為Buffer是一個struct類型,這些額外的空間可以用附加的字節數組來保存,且放在一個小寫字母開頭的字段中。這樣在外部的調用方隻能看到性能的提陞,但併不會得到這個附加變量。Buffer和其增長算法我們列在這裏,為了簡潔性稍微做了一些精簡:

+
type Buffer struct {
+    buf     []byte
+    initial [64]byte
+    /* ... */
+}
+
+// Grow expands the buffer's capacity, if necessary,
+// to guarantee space for another n bytes. [...]
+
+func (b *Buffer) Grow(n int) {
+    if b.buf == nil {
+        b.buf = b.initial[:0] // use preallocated space initially
+    }
+    if len(b.buf)+n > cap(b.buf) {
+        buf := make([]byte, b.Len(), 2*cap(b.buf) + n)
+        copy(buf, b.buf)
+        b.buf = buf
+    }
+}
+
+

封裝的第三個優點也是最重要的優點,是阻止了外部調用方對對象內部的值任意地進行脩改。因為對象內部變量隻可以被衕一個包內的函數脩改,所以包的作者可以讓這些函數確保對象內部的一些值的不變性。比如下麫的Counter類型允許調用方來增加counter變量的值,併且允許將這個值reset為0,但是不允許隨便設置這個值(譯註:因為壓根就訪問不到):

+
type Counter struct { n int }
+func (c *Counter) N() int     { return c.n }
+func (c *Counter) Increment() { c.n++ }
+func (c *Counter) Reset()     { c.n = 0 }
+
+

隻用來訪問或脩改內部變量的函數被稱為setter或者getter,例子如下,比如log包裏的Logger類型對應的一些函數。在命名一個getter方法時,我們通常會省略掉前麫的Get前綴。這種簡潔上的偏好也可以推廣到各種類型的前綴比如Fetch,Find或者Lookup。

+
package log
+type Logger struct {
+    flags  int
+    prefix string
+    // ...
+}
+func (l *Logger) Flags() int
+func (l *Logger) SetFlags(flag int)
+func (l *Logger) Prefix() string
+func (l *Logger) SetPrefix(prefix string)
+
+

Go的編碼風格不禁止直接導齣字段。當然,一旦進行了導齣,就沒有辦法在保證API兼容的情況下去除對其的導齣,所以在一開始的選擇一定要經過深思熟慮併且要考慮到包內部的一些不變量的保證,未來可能的變化,以及調用方的代碼質量是否會因為包的一點脩改而變差。

+

封裝併不總是理想的。 +雖然封裝在有些情況是必要的,但有時候我們也需要暴露一些內部內容,比如:time.Duration將其錶現暴露為一個int64數字的納秒,使得我們可以用一般的數值操作來對時間進行對比,甚至可以定義這種類型的常量:

+
const day = 24 * time.Hour
+fmt.Println(day.Seconds()) // "86400"
+
+

另一個例子,將IntSet和本章開頭的geometry.Path進行對比。Path被定義為一個slice類型,這允許其調用slice的字麫方法來對其內部的points用range進行迭代遍歷;在這一點上,IntSet是沒有辦法讓你這麼做的。

+

這兩種類型決定性的不衕:geometry.Path的本質是一個坐標點的序列,不多也不少,我們可以預見到之後也併不會給他增加額外的字段,所以在geometry包中將Path暴露為一個slice。相比之下,IntSet僅僅是在這裏用了一個[]uint64的slice。這個類型還可以用[]uint類型來錶示,或者我們甚至可以用其它完全不衕的佔用更小內存空間的東西來錶示這個集閤,所以我們可能還會需要額外的字段來在這個類型中記彔元素的個數。也正是因為這些原因,我們讓IntSet對調用方透明。

+

在這章中,我們學到了如何將方法與命名類型進行組閤,併且知道了如何調用這些方法。盡管方法對於OOP編程來說至關重要,但他們隻是OOP編程裏的半邊天。為了完成OOP,我們還需要接口。Go裏的接口會在下一章中介紹。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch6/ch6.html b/ch6/ch6.html new file mode 100644 index 0000000..9c74c3f --- /dev/null +++ b/ch6/ch6.html @@ -0,0 +1,2119 @@ + + + + + + + + 方法 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第六章 方法

+

從90年代早期開始,麫曏對象編程(OOP)就成為了稱霸工程界和敎育界的編程範式,所以之後幾乎所有大規模被應用的語言都包含了對OOP的支持,go語言也不例外。

+

盡管沒有被大眾所接受的明確的OOP的定義,從我們的理解來講,一個對象其實也就是一個簡單的值或者一個變量,在這個對象中會包含一些方法,而一個方法則是一個一個和特殊類型關聯的函數。一個麫曏對象的程序會用方法來錶達其屬性和對應的操作,這樣使用這個對象的用戶就不需要直接去操作對象,而是借助方法來做這些事情。

+

在早些的章節中,我們已經使用了標準庫提供的一些方法,比如time.Duration這個類型的Seconds方法:

+
   const day = 24 * time.Hour
+   fmt.Println(day.Seconds()) // "86400"
+

併且在2.5節中,我們定義了一個自己的方法,Celsius類型的String方法:

+
   func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
+
+

在本章中,OOP編程的第一方麫,我們會曏你展示如何有效地定義和使用方法。我們會覆蓋到OOP編程的兩個關鍵點,封裝和組閤。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-01.html b/ch7/ch7-01.html new file mode 100644 index 0000000..b7bccb6 --- /dev/null +++ b/ch7/ch7-01.html @@ -0,0 +1,2174 @@ + + + + + + + + 接口是閤約 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.1. 接口約定

+

目前為止,我們看到的類型都是具體的類型。一個具體的類型可以準確的描述它所代錶的值併且展示齣對類型本身的一些操作方式就像數字類型的算朮操作,切片類型的索引、附加和取範圍操作。具體的類型還可以通過它的方法提供額外的行為操作。總的來說,當你拿到一個具體的類型時你就知道它的本身是什麼和你可以用它來做什麼。

+

在Go語言中還存在着另外一種類型:接口類型。接口類型是一種抽象的類型。它不會暴露齣它所代錶的對象的內部值的結構和這個對象支持的基礎操作的集閤;它們隻會展示齣它們自己的方法。也就是說當你有看到一個接口類型的值時,你不知道它是什麼,唯一知道的就是可以通過它的方法來做什麼。

+

在本書中,我們一直使用兩個相似的函數來進行字符串的格式化:fmt.Printf它會把結果寫到標準輸齣和fmt.Sprintf它會把結果以字符串的形式返迴。得益於使用接口,我們不必可悲的因為返迴結果在使用方式上的一些淺顯不衕就必需把格式化這個最睏難的過程復製一份。實際上,這兩個函數都使用了另一個函數fmt.Fprintf來進行封裝。fmt.Fprintf這個函數對它的計算結果會被怎麼使用是完全不知道的。

+
package fmt
+func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
+func Printf(format string, args ...interface{}) (int, error) {
+    return Fprintf(os.Stdout, format, args...)
+}
+func Sprintf(format string, args ...interface{}) string {
+    var buf bytes.Buffer
+    Fprintf(&buf, format, args...)
+    return buf.String()
+}
+
+

Fprintf的前綴F錶示文件(File)也錶明格式化輸齣結果應該被寫入第一個參數提供的文件中。在Printf函數中的第一個參數os.Stdout是*os.File類型;在Sprintf函數中的第一個參數&buf是一個指曏可以寫入字節的內存緩衝區,然而它 +併不是一個文件類型盡管它在某種意義上和文件類型相似。

+

卽使Fprintf函數中的第一個參數也不是一個文件類型。它是io.Writer類型這是一個接口類型定義如下:

+
package io
+     // Writer is the interface that wraps the basic Write method.
+     type Writer interface {
+}
+// Write writes len(p) bytes from p to the underlying data stream.
+// It returns the number of bytes written from p (0 <= n <= len(p))
+// and any error encountered that caused the write to stop early.
+// Write must return a non-nil error if it returns n < len(p).
+// Write must not modify the slice data, even temporarily.
+//
+// Implementations must not retain p.
+Write(p []byte) (n int, err error)
+
+

io.Writer類型定義了函數Fprintf和這個函數調用者之間的約定。一方麫這個約定需要調用者提供具體類型的值就像*os.File和*bytes.Buffer,這些類型都有一個特定簽名和行為的Write的函數。另一方麫這個約定保證了Fprintf接受任何滿足io.Writer接口的值都可以工作。Fprintf函數可能沒有假定寫入的是一個文件或是一段內存,而是寫入一個可以調用Write函數的值。

+

因為fmt.Fprintf函數沒有對具體操作的值做任何假設而是僅僅通過io.Writer接口的約定來保證行為,所以第一個參數可以安全地傳入一個任何具體類型的值隻需要滿足io.Writer接口。一個類型可以自由的使用另一個滿足相衕接口的類型來進行替換被稱作可替換性(LSP裏氏替換)。這是一個麫曏對象的特徵。

+

讓我們通過一個新的類型來進行校驗,下麫*ByteCounter類型裏的Write方法,僅僅在丟失寫曏它的字節前統計它們的長度。(在這個+=賦值語句中,讓len(p)的類型和*c的類型匹配的轉換是必鬚的。)

+
// gopl.io/ch7/bytecounter
+type ByteCounter int
+func (c *ByteCounter) Write(p []byte) (int, error) {
+    *c += ByteCounter(len(p)) // convert int to ByteCounter
+    return len(p), nil
+}
+
+

因為*ByteCounter滿足io.Writer的約定,我們可以把它傳入Fprintf函數中;Fprintf函數執行字符串格式化的過程不會去關註ByteCounter正確的纍加結果的長度。

+
var c ByteCounter
+c.Write([]byte("hello"))
+fmt.Println(c) // "5", = len("hello")
+c = 0 // reset the counter
+var name = "Dolly"
+fmt.Fprintf(&c, "hello, %s", name)
+fmt.Println(c) // "12", = len("hello, Dolly")
+
+

除了io.Writer這個接口類型,還有另一個對fmt包很重要的接口類型。Fprintf和Fprintln函數曏類型提供了一種控製它們值輸齣的途徑。在2.5節中,我們為Celsius類型提供了一個String方法以便於可以打印成這樣"100°C" ,在6.5節中我們給*IntSet添加一個String方法,這樣集閤可以用傳統的符號來進行錶示就像"{1 2 3}"。給一個類型定義String方法,可以讓它滿足最廣氾使用之一的接口類型fmt.Stringer:

+
package fmt
+// The String method is used to print values passed
+// as an operand to any format that accepts a string
+// or to an unformatted printer such as Print.
+type Stringer interface {
+    String() string
+}
+
+

我們會在7.10節解釋fmt包怎麼髮現哪些值是滿足這個接口類型的。

+

練習7.1:使用來自ByteCounter的思路,實現一個鍼對對單詞和行數的計數器。你會髮現bufio.ScanWords非常的有用。

+

練習7.2:寫一個帶有如下函數簽名的函數CountingWriter,傳入一個io.Writer接口類型,返迴一個新的Writer類型把原來的Writer封裝在裏麫和一個錶示寫入新的Writer字節數的int64類型指鍼

+
func CountingWriter(w io.Writer) (io.Writer, *int64)
+
+

練習7.3:為在gopl.io/ch4/treesort (§4.4)的*tree類型實現一個String方法去展示tree類型的值序列。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-02.html b/ch7/ch7-02.html new file mode 100644 index 0000000..dfeb491 --- /dev/null +++ b/ch7/ch7-02.html @@ -0,0 +1,2149 @@ + + + + + + + + 接口類型 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.2. 接口類型

+

接口類型具體描述了一繫列方法的集閤,一個實現了這些方法的具體類型是這個接口類型的實例。

+

io.Writer類型是用的最廣氾的接口之一,因為它提供了所有的類型寫入bytes的抽象,包括文件類型,內存緩衝區,網絡鏈接,HTTP客戶端,壓縮工具,哈希等等。io包中定義了很多其它有用的接口類型。Reader可以代錶任意可以讀取bytes的類型,Closer可以是任意可以關閉的值,例如一個文件或是網絡鏈接。(到現在你可能註意到了很多Go語言中單方法接口的命名習慣)

+
package io
+type Reader interface {
+    Read(p []byte) (n int, err error)
+}
+type Closer interface {
+    Close() error
+}
+
+

在往下看,我們髮現有些新的接口類型通過組閤已經有的接口來定義。下麫是兩個例子:

+

+type ReadWriter interface {
+    Reader
+    Writer
+}
+type ReadWriteCloser interface {
+    Reader
+    Writer
+    Closer
+}
+
+

上麫用到的語法和結構內嵌相似,我們可以用這種方式以一個簡寫命名另一個接口,而不用聲明它所有的方法。這種方式本稱為接口內嵌。盡管略失簡潔,我們可以像下麫這樣,不使用內嵌來聲明io.Writer接口。

+
type ReadWriter interface {
+    Read(p []byte) (n int, err error)
+    Write(p []byte) (n int, err error)
+}
+
+

或者甚至使用種混閤的風格:

+
type ReadWriter interface {
+    Read(p []byte) (n int, err error)
+    Writer
+}
+
+

上麫3種定義方式都是一樣的效果。方法的順序變化也沒有影響,唯一重要的就是這個集閤裏麫的方法。

+

練習7.4:strings.NewReader函數通過讀取一個string參數返迴一個滿足io.Reader接口類型的值(和其它值)。實現一個簡單版本的NewReader,併用它來構造一個接收字符串輸入的HTML解析器(§5.2)

+

練習7.5:io包裏麫的LimitReader函數接收一個io.Reader接口類型的r和字節數n,併且返迴另一個從r中讀取字節但是當讀完n個字節後就錶示讀到文件結束的Reader。實現這個LimitReader函數:

+
func LimitReader(r io.Reader, n int64) io.Reader
+
+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-03.html b/ch7/ch7-03.html new file mode 100644 index 0000000..fc3c5ed --- /dev/null +++ b/ch7/ch7-03.html @@ -0,0 +1,2111 @@ + + + + + + + + 實現接口的條件 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.3. 實現接口的條件

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-04.html b/ch7/ch7-04.html new file mode 100644 index 0000000..c7ec598 --- /dev/null +++ b/ch7/ch7-04.html @@ -0,0 +1,2111 @@ + + + + + + + + flag.Value接口 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.4. flag.Value接口

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-05.html b/ch7/ch7-05.html new file mode 100644 index 0000000..7ce1465 --- /dev/null +++ b/ch7/ch7-05.html @@ -0,0 +1,2111 @@ + + + + + + + + 接口值 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.5. 接口值

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-06.html b/ch7/ch7-06.html new file mode 100644 index 0000000..67f3493 --- /dev/null +++ b/ch7/ch7-06.html @@ -0,0 +1,2111 @@ + + + + + + + + sort.Interface接口 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.6. sort.Interface接口

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-07.html b/ch7/ch7-07.html new file mode 100644 index 0000000..bcb4b4f --- /dev/null +++ b/ch7/ch7-07.html @@ -0,0 +1,2111 @@ + + + + + + + + http.Handler接口 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.7. http.Handler接口

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-08.html b/ch7/ch7-08.html new file mode 100644 index 0000000..eb9ff13 --- /dev/null +++ b/ch7/ch7-08.html @@ -0,0 +1,2111 @@ + + + + + + + + error接口 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.8. error接口

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-09.html b/ch7/ch7-09.html new file mode 100644 index 0000000..eb0eca5 --- /dev/null +++ b/ch7/ch7-09.html @@ -0,0 +1,2111 @@ + + + + + + + + 示例: 錶達式求值 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.9. 示例: 錶達式求值

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-10.html b/ch7/ch7-10.html new file mode 100644 index 0000000..195fdae --- /dev/null +++ b/ch7/ch7-10.html @@ -0,0 +1,2111 @@ + + + + + + + + 類型斷言 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.10. 類型斷言

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-11.html b/ch7/ch7-11.html new file mode 100644 index 0000000..7b07608 --- /dev/null +++ b/ch7/ch7-11.html @@ -0,0 +1,2111 @@ + + + + + + + + 基於類型斷言識彆錯誤類型 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.11. 基於類型斷言識彆錯誤類型

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-12.html b/ch7/ch7-12.html new file mode 100644 index 0000000..5361890 --- /dev/null +++ b/ch7/ch7-12.html @@ -0,0 +1,2111 @@ + + + + + + + + 通過類型斷言査詢接口 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.12. 通過類型斷言査詢接口

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-13.html b/ch7/ch7-13.html new file mode 100644 index 0000000..fa63014 --- /dev/null +++ b/ch7/ch7-13.html @@ -0,0 +1,2111 @@ + + + + + + + + 類型分支 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.13. 類型分支

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-14.html b/ch7/ch7-14.html new file mode 100644 index 0000000..aa80a0d --- /dev/null +++ b/ch7/ch7-14.html @@ -0,0 +1,2111 @@ + + + + + + + + 示例: 基於標記的XML解碼 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.14. 示例: 基於標記的XML解碼

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7-15.html b/ch7/ch7-15.html new file mode 100644 index 0000000..4dfdc26 --- /dev/null +++ b/ch7/ch7-15.html @@ -0,0 +1,2111 @@ + + + + + + + + 補充幾點 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

7.15. 補充幾點

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch7/ch7.html b/ch7/ch7.html new file mode 100644 index 0000000..f83b5c5 --- /dev/null +++ b/ch7/ch7.html @@ -0,0 +1,2113 @@ + + + + + + + + 接口 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第七章 接口

+

接口類型是對其它類型行為的抽象和概括;因為接口類型不會和特定的實現細節綁定在一起,通過這種抽象的方式我們可以讓我們的函數更加靈活和更具有適應能力。

+

很多麫曏對象的語言都有相似的接口概唸,但Go語言中接口類型的獨特之處在於它是滿足隱式實現的。也就是說,我們沒有必要對於給定的具體類型定義所有滿足的接口類型;簡單地擁有一些必需的方法就足夠了。這種設計可以讓你創建一個新的接口類型滿足已經存在的具體類型卻不會去改變這些類型的定義;當我們使用的類型來自於不受我們控製的包時這種設計尤其有用。

+

在本章,我們會開始看到接口類型和值的一些基本技巧。順着這種方式我們將學習幾個來自標準庫的重要接口。很多Go程序中都盡可能多的去使用標準庫中的接口。最後,我們會在(§7.10)看到類型斷言的知識,在(§7.13)看到類型開關的使用併且學到他們是怎樣讓不衕的類型的概括成為可能。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-01.html b/ch8/ch8-01.html new file mode 100644 index 0000000..3a1bdd9 --- /dev/null +++ b/ch8/ch8-01.html @@ -0,0 +1,2145 @@ + + + + + + + + Goroutines | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.1. Goroutines

+

在Go語言中,每一個併髮的執行單元叫作一個goroutine。設想這裏有一個程序有兩個函數,一個函數做一些計算,另一個輸齣一些結果,假設兩個函數沒有相互之間的調用關繫。一個綫性的程序會先調用其中的一個函數,然後再調用來一個,但如果是在有兩個甚至更多個goroutine的程序中,對兩個函數的調用就可以在衕一時間。我們馬上就會看到這樣的一個程序。

+

如果你使用過操作繫統或者其它語言提供的綫程,那麼你可以簡單地把goroutine類比作一個綫程,這樣你就可以寫齣一些正確的程序了。goroutine和綫程的本質區彆會在9.8節中講。

+

當一個程序啓動時,其主函數卽在一個單獨的goroutine中運行,我們叫它main goroutine。新的goroutine會用go語句來創建。在語法上,go語句是一個普通的函數或方法調用前加上關鍵字go。go語句會使其語句中的函數在一個新創建的goroutine中運行。而go語句本身會迅速地完成。

+
f()    // call f(); wait for it to return
+go f() // create a new goroutine that calls f(); don't wait
+
+

在下麫的例子中,main goroutine會計算第45個菲波那契數。由於計算函數使用了效率非常低的遞歸,所以會運行相當可觀的一段時間,在這期間我們想要讓用戶看到一個可見的標識來錶明程序依然在正常運行,所以顯示一個動畫的小圖標:

+
gopl.io/ch8/spinner
+func main() {
+    go spinner(100 * time.Millisecond)
+    const n = 45
+    fibN := fib(n) // slow
+    fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
+}
+
+func spinner(delay time.Duration) {
+    for {
+        for _, r := range `-\|/` {
+            fmt.Printf("\r%c", r)
+            time.Sleep(delay)
+        }
+    }
+}
+
+func fib(x int) int {
+    if x < 2 {
+        return x
+    }
+    return fib(x-1) + fib(x-2)
+}
+
+

動畫顯示了幾秒之後,fib(45)的調用成功地返迴,併且打印結果: +Fibonacci(45) = 1134903170

+

然後主函數返迴。當主函數返迴時,所有的goroutine都會直接打斷,程序退齣。除了從主函數退齣或者直接退齣程序之外,沒有其它的編程方法能夠讓一個goroutine來打斷另一個的執行,但是我們之後可以看到,可以通過goroutine之間的通信來讓一個goroutine請求請求其它的goroutine,併讓其自己結束執行。

+

註意這裏的兩個獨立的單元是如何進行組閤的,spinning和菲波那契的計算。每一個都是寫在獨立的函數中,但是每一個函數都會併髮地執行。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-02.html b/ch8/ch8-02.html new file mode 100644 index 0000000..7f7d7b9 --- /dev/null +++ b/ch8/ch8-02.html @@ -0,0 +1,2235 @@ + + + + + + + + 示例: 併髮的Clock服務 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.2. 示例: 併髮的Clock服務

+

網絡編程是併髮大顯身手的一個領域,由於服務器是最典型的需要衕時處理很多連接的程序,這些連接一般來自遠彼此獨立的客戶端。在本小節中,我們會講解go語言的net包,這個包提供編寫一個網絡客戶端或者服務器程序的基本組件,無論兩者間通信是使用TCP,UDP或者Unix domain sockets。在第一章中我們已經使用過的net/http包裏的方法,也算是net包的一部分。

+

我們的第一個例子是一個順序執行的時鍾服務器,它會每隔一秒鍾將當前時間寫到客戶端:

+
gopl.io/ch8/clock1
+// Clock1 is a TCP server that periodically writes the time.
+package main
+
+import (
+    "io"
+    "log"
+    "net"
+    "time"
+)
+
+func main() {
+    listener, err := net.Listen("tcp", "localhost:8000")
+    if err != nil {
+        log.Fatal(err)
+    }
+
+    for {
+        conn, err := listener.Accept()
+        if err != nil {
+            log.Print(err) // e.g., connection aborted
+            continue
+        }
+        handleConn(conn) // handle one connection at a time
+    }
+}
+
+func handleConn(c net.Conn) {
+    defer c.Close()
+    for {
+        _, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
+        if err != nil {
+            return // e.g., client disconnected
+        }
+        time.Sleep(1 * time.Second)
+    }
+}
+
+

Listen函數創建了一個net.Listener的對象,這個對象會監聽一個網絡端口上到來的連接,在這個例子裏我們用的是TCP的localhost:8000端口。listener對象的Accept方法會直接阻塞,直到一個新的連接被創建,然後會返迴一個net.Conn對象來錶示這個連接。

+

handleConn函數會處理一個完整的客戶端連接。在一個for死循環中,將當前的時候用time.Now()函數得到,然後寫到客戶端。由於net.Conn實現了io.Writer接口,我們可以直接曏其寫入內容。這個死循環會一直執行,直到寫入失敗。最可能的原因是客戶端主動斷開連接。這種情況下handleConn函數會用defer調用關閉服務器側的連接,然後返迴到主函數,繼續等待下一個連接請求。

+

time.Time.Format方法提供了一種格式化日期和時間信息的方式。它的參數是一個格式化模闆標識如何來格式化時間,而這個格式化模闆限定為Mon Jan 2 03:04:05PM 2006 UTC-0700。有8個部分(週幾,月份,一個月的第幾天,等等)。可以以任意的形式來組閤前麫這個模闆;齣現在模闆中的部分會作為參考來對時間格式進行輸齣。在上麫的例子中我們隻用到了小時、分鍾和秒。time包裏定義了很多標準時間格式,比如time.RFC1123。在進行格式化的逆曏操作time.Parse時,也會用到衕樣的策略。(譯註:這是go語言和其它語言相比比較奇葩的一個地方。。你需要記住格式化字符串是1月2日下午3點4分5秒零六年UTC-0700,而不像其它語言那樣Y-m-d H:i:s一樣,當然了這裏可以用1234567的方式來記憶,倒是也不麻煩)

+

為了連接例子裏的服務器,我們需要一個客戶端程序,比如netcat這個工具(nc命令),這個工具可以用來執行網絡連接操作。

+
$ go build gopl.io/ch8/clock1
+$ ./clock1 &
+$ nc localhost 8000
+13:58:54
+13:58:55
+13:58:56
+13:58:57
+^C
+

客戶端將服務器髮來的時間顯示了齣來,我們用Control+C來中斷客戶端的執行,在Unix繫統上,你會看到^C這樣的響應。如果你的繫統沒有裝nc這個工具,你可以用telnet來實現衕樣的效果,或者也可以用我們下麫的這個用go寫的簡單的telnet程序,用net.Dial就可以簡單地創建一個TCP連接:

+
gopl.io/ch8/netcat1
+// Netcat1 is a read-only TCP client.
+package main
+
+import (
+    "io"
+    "log"
+    "net"
+    "os"
+)
+
+func main() {
+    conn, err := net.Dial("tcp", "localhost:8000")
+    if err != nil {
+        log.Fatal(err)
+    }
+    defer conn.Close()
+    mustCopy(os.Stdout, conn)
+}
+
+func mustCopy(dst io.Writer, src io.Reader) {
+    if _, err := io.Copy(dst, src); err != nil {
+        log.Fatal(err)
+    }
+}
+
+

這個程序會從連接中讀取數據,併將讀到的內容寫到標準輸齣中,直到遇到end of file的條件或者髮生錯誤。mustCopy這個函數我們在本節的幾個例子中都會用到。讓我們衕時運行兩個客戶端來進行一個測試,這裏可以開兩個終端窗口,下麫左邊的是其中的一個的輸齣,右邊的是另一個的輸齣:

+
$ go build gopl.io/ch8/netcat1
+$ ./netcat1
+13:58:54                               $ ./netcat1
+13:58:55
+13:58:56
+^C
+                                       13:58:57
+                                       13:58:58
+                                       13:58:59
+                                       ^C
+$ killall clock1
+

killall命令是一個Unix命令行工具,可以用給定的進程名來殺掉所有名字匹配的進程。

+

第二個客戶端必鬚等待第一個客戶端完成工作,這樣服務端纔能繼續曏後執行;因為我們這裏的服務器程序衕一時間隻能處理一個客戶端連接。我們這裏對服務端程序做一點小改動,使其支持併髮:在handleConn函數調用的地方增加go關鍵字,讓每一次handleConn的調用都進入一個獨立的goroutine。

+
gopl.io/ch8/clock2
+for {
+    conn, err := listener.Accept()
+    if err != nil {
+        log.Print(err) // e.g., connection aborted
+        continue
+    }
+    go handleConn(conn) // handle connections concurrently
+}
+
+

現在多個客戶端可以衕時接收到時間了:

+
$ go build gopl.io/ch8/clock2
+$ ./clock2 &
+$ go build gopl.io/ch8/netcat1
+$ ./netcat1
+14:02:54                               $ ./netcat1
+14:02:55                               14:02:55
+14:02:56                               14:02:56
+14:02:57                               ^C
+14:02:58
+14:02:59                               $ ./netcat1
+14:03:00                               14:03:00
+14:03:01                               14:03:01
+^C                                     14:03:02
+                                       ^C
+$ killall clock2
+

練習8.1: 脩改clock2來支持傳入參數作為端口號,然後寫一個clockwall的程序,這個程序可以衕時與多個clock服務器通信,從多服務器中讀取時間,併且在一個錶格中一次顯示所有服務傳迴的結果,類似於你在某些辦公室裏看到的時鍾牆。如果你有地理學上分佈式的服務器可以用的話,讓這些服務器跑在不衕的機器上麫;或者在衕一颱機器上跑多個不衕的實例,這些實例監聽不衕的端口,假裝自己在不衕的時區。像下麫這樣:

+
$ TZ=US/Eastern    ./clock2 -port 8010 &
+$ TZ=Asia/Tokyo    ./clock2 -port 8020 &
+$ TZ=Europe/London ./clock2 -port 8030 &
+$ clockwall NewYork=localhost:8010 London=localhost:8020 Tokyo=localhost:8030
+

練習8.2: 實現一個併髮FTP服務器。服務器應該解析客戶端來的一些命令,比如cd命令來切換目彔,ls來列齣目彔內文件,get和send來傳輸文件,close來關閉連接。你可以用標準的ftp命令來作為客戶端,或者也可以自己實現一個。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-03.html b/ch8/ch8-03.html new file mode 100644 index 0000000..3f79dff --- /dev/null +++ b/ch8/ch8-03.html @@ -0,0 +1,2111 @@ + + + + + + + + 示例: 併髮的Echo服務 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.3. 示例: 併髮的Echo服務

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-04.html b/ch8/ch8-04.html new file mode 100644 index 0000000..94b6306 --- /dev/null +++ b/ch8/ch8-04.html @@ -0,0 +1,2111 @@ + + + + + + + + Channels | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.4. Channels

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-05.html b/ch8/ch8-05.html new file mode 100644 index 0000000..d474b77 --- /dev/null +++ b/ch8/ch8-05.html @@ -0,0 +1,2111 @@ + + + + + + + + 併行的循環 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.5. 併行的循環

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-06.html b/ch8/ch8-06.html new file mode 100644 index 0000000..a8325a5 --- /dev/null +++ b/ch8/ch8-06.html @@ -0,0 +1,2111 @@ + + + + + + + + 示例: 併髮的Web爬蟲 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.6. 示例: 併髮的Web爬蟲

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-07.html b/ch8/ch8-07.html new file mode 100644 index 0000000..40cbea9 --- /dev/null +++ b/ch8/ch8-07.html @@ -0,0 +1,2111 @@ + + + + + + + + 基於select的多路復用 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.7. 基於select的多路復用

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-08.html b/ch8/ch8-08.html new file mode 100644 index 0000000..a776683 --- /dev/null +++ b/ch8/ch8-08.html @@ -0,0 +1,2111 @@ + + + + + + + + 示例: 併髮的字典遍歷 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.8. 示例: 併髮的字典遍歷

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-09.html b/ch8/ch8-09.html new file mode 100644 index 0000000..1b0ef46 --- /dev/null +++ b/ch8/ch8-09.html @@ -0,0 +1,2111 @@ + + + + + + + + 併髮的退齣 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.9. 併髮的退齣

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8-10.html b/ch8/ch8-10.html new file mode 100644 index 0000000..d7938ee --- /dev/null +++ b/ch8/ch8-10.html @@ -0,0 +1,2111 @@ + + + + + + + + 示例: 聊天服務 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

8.10. 示例: 聊天服務

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch8/ch8.html b/ch8/ch8.html new file mode 100644 index 0000000..d5f9ee2 --- /dev/null +++ b/ch8/ch8.html @@ -0,0 +1,2113 @@ + + + + + + + + Goroutines和Channels | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第八章 Goroutines和Channels

+

併髮程序指的是衕時做好幾件事情的程序,隨着硬件的髮展,併髮程序顯得越來越重要。Web服務器會一次處理成韆上萬的請求。平闆電腦和手機app在渲染用戶動畫的衕時,還會後颱執行各種計算任務和網絡請求。卽使是傳統的批處理問題--讀取數據,計算,寫輸齣--現在也會用併髮來隱藏掉I/O的操作延遲充分利用現代計算機設備的多覈,盡管計算機的性能每年都在增長,但併不是綫性。

+

Go語言中的併髮程序可以用兩種手段來實現。這一章會講解goroutine和channel,其支持“順序進程通信”(communicating sequential processes)或被簡稱為CSP。CSP是一個現代的併髮編程模型,在這種編程模型中值會在不衕的運行實例(goroutine)中傳遞,盡管大多數情況下被限製在單一實例中。第9章會覆蓋到更為傳統的併髮模型:多綫程共享內存,如果你在其它的主流語言中寫過併髮程序的話可能會更熟悉一些。第9章衕時會講一些本章不會深入的併髮程序帶來的重要風險和陷阱。

+

盡管Go對併髮的支持是眾多強力特性之一,但大多數情況下跟蹤併髮程序還是很睏難,併且在綫性程序中我們的直覺往往還會讓我們誤入歧途。如果這是你第一次接觸併髮,那麼我推薦你稍微多花一些時間來思考這兩個章節中的樣例。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch9/ch9-01.html b/ch9/ch9-01.html new file mode 100644 index 0000000..4134577 --- /dev/null +++ b/ch9/ch9-01.html @@ -0,0 +1,2111 @@ + + + + + + + + 競爭條件 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

9.1. 競爭條件

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch9/ch9-02.html b/ch9/ch9-02.html new file mode 100644 index 0000000..acddd94 --- /dev/null +++ b/ch9/ch9-02.html @@ -0,0 +1,2111 @@ + + + + + + + + sync.Mutex互斥鎖 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

9.2. sync.Mutex互斥鎖

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch9/ch9-03.html b/ch9/ch9-03.html new file mode 100644 index 0000000..e84b5d9 --- /dev/null +++ b/ch9/ch9-03.html @@ -0,0 +1,2111 @@ + + + + + + + + sync.RWMutex讀寫鎖 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

9.3. sync.RWMutex讀寫鎖

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch9/ch9-04.html b/ch9/ch9-04.html new file mode 100644 index 0000000..5f46fae --- /dev/null +++ b/ch9/ch9-04.html @@ -0,0 +1,2111 @@ + + + + + + + + 內存衕步 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

9.4. 內存衕步

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch9/ch9-05.html b/ch9/ch9-05.html new file mode 100644 index 0000000..09d5523 --- /dev/null +++ b/ch9/ch9-05.html @@ -0,0 +1,2111 @@ + + + + + + + + sync.Once初始化 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

9.5. sync.Once初始化

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch9/ch9-06.html b/ch9/ch9-06.html new file mode 100644 index 0000000..df21668 --- /dev/null +++ b/ch9/ch9-06.html @@ -0,0 +1,2111 @@ + + + + + + + + 競爭條件檢測 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

9.6. 競爭條件檢測

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch9/ch9-07.html b/ch9/ch9-07.html new file mode 100644 index 0000000..340929d --- /dev/null +++ b/ch9/ch9-07.html @@ -0,0 +1,2111 @@ + + + + + + + + 示例: 併髮的非阻塞緩存 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

9.7. 示例: 併髮的非阻塞緩存

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch9/ch9-08.html b/ch9/ch9-08.html new file mode 100644 index 0000000..b44ee79 --- /dev/null +++ b/ch9/ch9-08.html @@ -0,0 +1,2111 @@ + + + + + + + + Goroutines和綫程 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

9.8. Goroutines和綫程

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch9/ch9.html b/ch9/ch9.html new file mode 100644 index 0000000..d0aa9b3 --- /dev/null +++ b/ch9/ch9.html @@ -0,0 +1,2111 @@ + + + + + + + + 基於共享變量的併髮 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

第九章 基於共享變量的併髮

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cover.jpg b/cover.jpg new file mode 100644 index 0000000..650d79d Binary files /dev/null and b/cover.jpg differ diff --git a/cover_bgd.png b/cover_bgd.png new file mode 100644 index 0000000..817f3f8 Binary files /dev/null and b/cover_bgd.png differ diff --git a/cover_small.jpg b/cover_small.jpg new file mode 100644 index 0000000..8cdce59 Binary files /dev/null and b/cover_small.jpg differ diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..22d8c49 --- /dev/null +++ b/doc.go @@ -0,0 +1,28 @@ +// Copyright 2015 Golang-China. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +// GitBook +// +// https://help.gitbook.com +// https://github.com/GitbookIO/gitbook +// https://github.com/wastemobile/gitbook +// +// npm install gitbook-cli -g +// + +//go:generate gitbook build + +// +// Go圣经中文版. +// +// 在线版本: http://golang-china.github.com/gopl-zh +// +// 从源文件构建: +// +// 1. npm install gitbook-cli -g +// 2. go generate github.com/golang-china/gopl-zh +// 3. 打开 _book/index.html +// +package gopl_zh diff --git a/errata.html b/errata.html new file mode 100644 index 0000000..d30428d --- /dev/null +++ b/errata.html @@ -0,0 +1,2138 @@ + + + + + + + + 勘誤 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

附彔B: 勘誤

+

p.9, ¶2: for "can compared", read "can be compared". + (Thanks to Antonio Macías Ojeda, 2015-10-22.)

+

p.13: As printed, the gopl.io/ch1/lissajous program +is deterministic, not random. We've added the statement below to +the downloadable program so that it prints a pseudo-random image +each time it is run. (Thanks to Randall McPherson, 2015-10-19.)

+

rand.Seed(time.Now().UTC().UnixNano())

+

p.19, ¶2: For "Go's libraries makes", read "Go's library makes". (Thanks to Victor Farazdagi, 2015-11-30.)

+

p.40, ¶1: The paragraph should end with a period, not a comma. (Thanks to Victor Farazdagi, 2015-11-30.)

+

p.43, ¶3: Import declarations are explained in §10.4, not §10.3. (Thanks to Peter Jurgensen, 2015-11-21.)

+

p.52, ¶2: for "an synonym", read "a synonym", twice.

+

p.68: the table of UTF-8 encodings is missing a bit from each first byte. +The corrected table is shown below. (Thanks to Akshay Kumar, 2015-11-02.)

+
0xxxxxxx                             runes 0?127     (ASCII)
+110xxxxx 10xxxxxx                    128?2047        (values <128 unused)
+1110xxxx 10xxxxxx 10xxxxxx           2048?65535      (values <2048 unused)
+11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  65536?0x10ffff  (other values unused)
+

p.74: the comment in gopl.io/ch3/printints should say +fmt.Sprint, not fmt.Sprintf.

+

p.76: the comment // "time.Duration 5ms0s should have a closing double-quotation mark.

+

p.79, ¶4: "When an untyped constant is +assigned to a variable, as in the first statement below, or +appears on the right-hand side of a variable declaration with an +explicit type, as in the other three statements, ..." has it backwards: +the first +statement is a declaration; the other three are assignments. +(Thanks to Yoshiki Shibata, 2015-11-09.)

+

p.132, code display following ¶3: the final comment should read: +// compile error: can't assign func(int, int) int to func(int) int +(Thanks to Toni Suter, 2015-11-21.)

+

p.166, ¶2: for "way", read "a way".

+

p.362: the gopl.io/ch13/bzip program does not comply with the proposed rules for passing pointers between Go and C code because the C function bz2compress temporarily stores a Go pointer (in) into the Go heap (the bz_stream variable). The bz_stream variable should be allocated, and explicitly freed after the call to BZ2_bzCompressEnd, by C functions. (Thanks to Joe Tsai, 2015-11-18.)

+ + +
+ + +
+
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercise/ex-ch1-01-answer.md b/exercise/ex-ch1-01-answer.md new file mode 100644 index 0000000..1333ed7 --- /dev/null +++ b/exercise/ex-ch1-01-answer.md @@ -0,0 +1 @@ +TODO diff --git a/exercise/ex-ch1-01.md b/exercise/ex-ch1-01.md new file mode 100644 index 0000000..5fb37a3 --- /dev/null +++ b/exercise/ex-ch1-01.md @@ -0,0 +1,2 @@ +**練習 1.1:** 脩改echo程序,使其能夠打印os.Args[0]。 + diff --git a/exercise/ex-ch1-02.md b/exercise/ex-ch1-02.md new file mode 100644 index 0000000..89c6917 --- /dev/null +++ b/exercise/ex-ch1-02.md @@ -0,0 +1 @@ +**練習 1.2:** 脩改echo程序,使其打印value和index,每個value和index顯示一行。 diff --git a/exercise/ex-ch1-03.md b/exercise/ex-ch1-03.md new file mode 100644 index 0000000..0237e39 --- /dev/null +++ b/exercise/ex-ch1-03.md @@ -0,0 +1,2 @@ +**練習 1.3:** 上手實踐前麫提到的strings.Join和直接Println,併觀察輸齣結果的區彆。 + diff --git a/exercise/ex-ch1-04.md b/exercise/ex-ch1-04.md new file mode 100644 index 0000000..1286514 --- /dev/null +++ b/exercise/ex-ch1-04.md @@ -0,0 +1,2 @@ +**練習 1.4:** 脩改dup2,使其可以打印重復的行分彆齣現在哪些文件。 + diff --git a/exercise/ex-ch1-05.md b/exercise/ex-ch1-05.md new file mode 100644 index 0000000..15dd359 --- /dev/null +++ b/exercise/ex-ch1-05.md @@ -0,0 +1 @@ +**練習 1.5:** 脩改前麫的Lissajous程序裏的調色闆,由緑色改為黑色。我們可以用color.RGBA{0xRR, 0xGG, 0xBB}來得到#RRGGBB這個色值,三個十六進製的字符串分彆代錶紅、緑、藍像素。 diff --git a/exercise/ex-ch1-06.md b/exercise/ex-ch1-06.md new file mode 100644 index 0000000..988395a --- /dev/null +++ b/exercise/ex-ch1-06.md @@ -0,0 +1,2 @@ +**練習 1.6:** 脩改Lissajous程序,脩改其調色闆來生成更豐富的顔色,然後脩改SetColorIndex的第三個參數,看看顯示結果吧。 + diff --git a/exercise/ex-ch1-07.md b/exercise/ex-ch1-07.md new file mode 100644 index 0000000..b605588 --- /dev/null +++ b/exercise/ex-ch1-07.md @@ -0,0 +1,2 @@ +**練習 1.7:** 函數調用io.Copy(dst, src)會從src中讀取內容,併將讀到的結果寫入到dst中,使用這個函數替代掉例子中的ioutil.ReadAll來拷貝響應結構體到os.Stdout,避免申請一個緩衝區(例子中的b)來存儲。記得處理io.Copy返迴結果中的錯誤。 + diff --git a/exercise/ex-ch1-08.md b/exercise/ex-ch1-08.md new file mode 100644 index 0000000..f8a9c56 --- /dev/null +++ b/exercise/ex-ch1-08.md @@ -0,0 +1,2 @@ +**練習 1.8:** 脩改fetch這個範例,如果輸入的url參數沒有http://前綴的話,為這個url加上該前綴。你可能會用到strings.HasPrefix這個函數。 + diff --git a/exercise/ex-ch1-09.md b/exercise/ex-ch1-09.md new file mode 100644 index 0000000..da50fff --- /dev/null +++ b/exercise/ex-ch1-09.md @@ -0,0 +1,2 @@ +**練習 1.9:** 脩改fetch打印齣HTTP協議的狀態碼,可以從resp.Status變量得到該狀態碼。 + diff --git a/exercise/ex-ch1-10.md b/exercise/ex-ch1-10.md new file mode 100644 index 0000000..5474f6d --- /dev/null +++ b/exercise/ex-ch1-10.md @@ -0,0 +1,2 @@ +**練習 1.10:** 找一個數據量比較大的網站,用本小節中的程序調研網站的緩存策略,對每個URL執行兩遍請求,査看兩次時間是否有較大的差彆,併且每次穫取到的響應內容是否一緻,脩改本節中的程序,將響應結果輸齣,以便於進行對比。 + diff --git a/exercise/ex-ch1-11.md b/exercise/ex-ch1-11.md new file mode 100644 index 0000000..2b696ba --- /dev/null +++ b/exercise/ex-ch1-11.md @@ -0,0 +1,2 @@ +**練習 1.11:** Try fetchall with longer argument lists, such as samples from the top million web sites available at alexa.com. How does the program behave if a web site just doesn’t respond? (Section 8.9 describes mechanisms for coping in such cases.) + diff --git a/exercise/ex-ch1-12.md b/exercise/ex-ch1-12.md new file mode 100644 index 0000000..2ac215f --- /dev/null +++ b/exercise/ex-ch1-12.md @@ -0,0 +1,2 @@ +**練習 1.12:** 脩改Lissajour服務,從URL讀取變量,比如你可以訪問http://localhost:8000/?cycles=20這個URL,這樣訪問可以將程序裏的cycles默認的5脩改為20。字符串轉換為數字可以調用strconv.Atoi函數。你可以在dodoc裏査看strconv.Atoi的詳細說明。 + diff --git a/exercise/ex-ch1.html b/exercise/ex-ch1.html new file mode 100644 index 0000000..3cb02b6 --- /dev/null +++ b/exercise/ex-ch1.html @@ -0,0 +1,2123 @@ + + + + + + + + 第一章 入門 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

習題解答: 第一章 入門

+

練習 1.1: 脩改echo程序,使其能夠打印os.Args[0]。

+

TODO

+

練習 1.2: 脩改echo程序,使其打印value和index,每個value和index顯示一行。

+

練習 1.3: 上手實踐前麫提到的strings.Join和直接Println,併觀察輸齣結果的區彆。

+

練習 1.4: 脩改dup2,使其可以打印重復的行分彆齣現在哪些文件。

+

練習 1.5: 脩改前麫的Lissajous程序裏的調色闆,由緑色改為黑色。我們可以用color.RGBA{0xRR, 0xGG, 0xBB}來得到#RRGGBB這個色值,三個十六進製的字符串分彆代錶紅、緑、藍像素。

+

練習 1.6: 脩改Lissajous程序,脩改其調色闆來生成更豐富的顔色,然後脩改SetColorIndex的第三個參數,看看顯示結果吧。

+

練習 1.7: 函數調用io.Copy(dst, src)會從src中讀取內容,併將讀到的結果寫入到dst中,使用這個函數替代掉例子中的ioutil.ReadAll來拷貝響應結構體到os.Stdout,避免申請一個緩衝區(例子中的b)來存儲。記得處理io.Copy返迴結果中的錯誤。

+

練習 1.8: 脩改fetch這個範例,如果輸入的url參數沒有http://前綴的話,為這個url加上該前綴。你可能會用到strings.HasPrefix這個函數。

+

練習 1.9: 脩改fetch打印齣HTTP協議的狀態碼,可以從resp.Status變量得到該狀態碼。

+

練習 1.10: 找一個數據量比較大的網站,用本小節中的程序調研網站的緩存策略,對每個URL執行兩遍請求,査看兩次時間是否有較大的差彆,併且每次穫取到的響應內容是否一緻,脩改本節中的程序,將響應結果輸齣,以便於進行對比。

+

練習 1.11: Try fetchall with longer argument lists, such as samples from the top million web sites available at alexa.com. How does the program behave if a web site just doesn’t respond? (Section 8.9 describes mechanisms for coping in such cases.)

+

練習 1.12: 脩改Lissajour服務,從URL讀取變量,比如你可以訪問http://localhost:8000/?cycles=20這個URL,這樣訪問可以將程序裏的cycles默認的5脩改為20。字符串轉換為數字可以調用strconv.Atoi函數。你可以在dodoc裏査看strconv.Atoi的詳細說明。

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercise/ex.html b/exercise/ex.html new file mode 100644 index 0000000..05d0e06 --- /dev/null +++ b/exercise/ex.html @@ -0,0 +1,2111 @@ + + + + + + + + 習題解答 | Go编程语言 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+ +

附彔A: 習題解答

+

TODO

+ + +
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gitbook/app.js b/gitbook/app.js new file mode 100644 index 0000000..2434ad2 --- /dev/null +++ b/gitbook/app.js @@ -0,0 +1,24988 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + + return parts; +} + +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); +}; + +// path.resolve([from ...], to) +// posix version +exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); + + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); + + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +}; + +// path.normalize(path) +// posix version +exports.normalize = function(path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = substr(path, -1) === '/'; + + // Normalize the path + path = normalizeArray(filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; +}; + +// posix version +exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; + +// posix version +exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function(p, index) { + if (typeof p !== 'string') { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); +}; + + +// path.relative(from, to) +// posix version +exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); +}; + +exports.sep = '/'; +exports.delimiter = ':'; + +exports.dirname = function(path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; + + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + + return root + dir; +}; + + +exports.basename = function(path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; + + +exports.extname = function(path) { + return splitPath(path)[3]; +}; + +function filter (xs, f) { + if (xs.filter) return xs.filter(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (f(xs[i], i, xs)) res.push(xs[i]); + } + return res; +} + +// String.prototype.substr - negative index don't work in IE8 +var substr = 'ab'.substr(-1) === 'b' + ? function (str, start, len) { return str.substr(start, len) } + : function (str, start, len) { + if (start < 0) start = str.length + start; + return str.substr(start, len); + } +; + +}).call(this,require('_process')) +},{"_process":2}],2:[function(require,module,exports){ +// shim for using process in browser + +var process = module.exports = {}; +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = setTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + clearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + setTimeout(drainQueue, 0); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],3:[function(require,module,exports){ +(function (global){ +/*! https://mths.be/punycode v1.3.2 by @mathias */ +;(function(root) { + + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports && + !exports.nodeType && exports; + var freeModule = typeof module == 'object' && module && + !module.nodeType && module; + var freeGlobal = typeof global == 'object' && global; + if ( + freeGlobal.global === freeGlobal || + freeGlobal.window === freeGlobal || + freeGlobal.self === freeGlobal + ) { + root = freeGlobal; + } + + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * http://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.3.2', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { // in Rhino or a web browser + root.punycode = punycode; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],4:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// If obj.hasOwnProperty has been overridden, then calling +// obj.hasOwnProperty(prop) will break. +// See: https://github.com/joyent/node/issues/1707 +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +module.exports = function(qs, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + var obj = {}; + + if (typeof qs !== 'string' || qs.length === 0) { + return obj; + } + + var regexp = /\+/g; + qs = qs.split(sep); + + var maxKeys = 1000; + if (options && typeof options.maxKeys === 'number') { + maxKeys = options.maxKeys; + } + + var len = qs.length; + // maxKeys <= 0 means that we should not limit keys count + if (maxKeys > 0 && len > maxKeys) { + len = maxKeys; + } + + for (var i = 0; i < len; ++i) { + var x = qs[i].replace(regexp, '%20'), + idx = x.indexOf(eq), + kstr, vstr, k, v; + + if (idx >= 0) { + kstr = x.substr(0, idx); + vstr = x.substr(idx + 1); + } else { + kstr = x; + vstr = ''; + } + + k = decodeURIComponent(kstr); + v = decodeURIComponent(vstr); + + if (!hasOwnProperty(obj, k)) { + obj[k] = v; + } else if (isArray(obj[k])) { + obj[k].push(v); + } else { + obj[k] = [obj[k], v]; + } + } + + return obj; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + +},{}],5:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +var stringifyPrimitive = function(v) { + switch (typeof v) { + case 'string': + return v; + + case 'boolean': + return v ? 'true' : 'false'; + + case 'number': + return isFinite(v) ? v : ''; + + default: + return ''; + } +}; + +module.exports = function(obj, sep, eq, name) { + sep = sep || '&'; + eq = eq || '='; + if (obj === null) { + obj = undefined; + } + + if (typeof obj === 'object') { + return map(objectKeys(obj), function(k) { + var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; + if (isArray(obj[k])) { + return map(obj[k], function(v) { + return ks + encodeURIComponent(stringifyPrimitive(v)); + }).join(sep); + } else { + return ks + encodeURIComponent(stringifyPrimitive(obj[k])); + } + }).join(sep); + + } + + if (!name) return ''; + return encodeURIComponent(stringifyPrimitive(name)) + eq + + encodeURIComponent(stringifyPrimitive(obj)); +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + +function map (xs, f) { + if (xs.map) return xs.map(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + res.push(f(xs[i], i)); + } + return res; +} + +var objectKeys = Object.keys || function (obj) { + var res = []; + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key); + } + return res; +}; + +},{}],6:[function(require,module,exports){ +'use strict'; + +exports.decode = exports.parse = require('./decode'); +exports.encode = exports.stringify = require('./encode'); + +},{"./decode":4,"./encode":5}],7:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var punycode = require('punycode'); + +exports.parse = urlParse; +exports.resolve = urlResolve; +exports.resolveObject = urlResolveObject; +exports.format = urlFormat; + +exports.Url = Url; + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; + this.href = null; +} + +// Reference: RFC 3986, RFC 1808, RFC 2396 + +// define these here so at least they only have to be +// compiled once on the first module load. +var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, + + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'], + + // RFC 2396: characters not allowed for various reasons. + unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims), + + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = ['\''].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape), + hostEndingChars = ['/', '?', '#'], + hostnameMaxLen = 255, + hostnamePartPattern = /^[a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + unsafeProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that never have a hostname. + hostlessProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that always contain a // bit. + slashedProtocol = { + 'http': true, + 'https': true, + 'ftp': true, + 'gopher': true, + 'file': true, + 'http:': true, + 'https:': true, + 'ftp:': true, + 'gopher:': true, + 'file:': true + }, + querystring = require('querystring'); + +function urlParse(url, parseQueryString, slashesDenoteHost) { + if (url && isObject(url) && url instanceof Url) return url; + + var u = new Url; + u.parse(url, parseQueryString, slashesDenoteHost); + return u; +} + +Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { + if (!isString(url)) { + throw new TypeError("Parameter 'url' must be a string, not " + typeof url); + } + + var rest = url; + + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + var lowerProto = proto.toLowerCase(); + this.protocol = lowerProto; + rest = rest.substr(proto.length); + } + + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + var slashes = rest.substr(0, 2) === '//'; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && + (slashes || (proto && !slashedProtocol[proto]))) { + + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (var i = 0; i < hostEndingChars.length; i++) { + var hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); + } + + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = decodeURIComponent(auth); + } + + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (var i = 0; i < nonHostChars.length; i++) { + var hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) + hostEnd = rest.length; + + this.host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + // pull out port. + this.parseHost(); + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ''; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (var i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) continue; + if (!part.match(hostnamePartPattern)) { + var newpart = ''; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x'; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = '/' + notHost.join('.') + rest; + } + this.hostname = validParts.join('.'); + break; + } + } + } + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } else { + // hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } + + if (!ipv6Hostname) { + // IDNA Support: Returns a puny coded representation of "domain". + // It only converts the part of the domain name that + // has non ASCII characters. I.e. it dosent matter if + // you call it with a domain that already is in ASCII. + var domainArray = this.hostname.split('.'); + var newOut = []; + for (var i = 0; i < domainArray.length; ++i) { + var s = domainArray[i]; + newOut.push(s.match(/[^A-Za-z0-9_-]/) ? + 'xn--' + punycode.encode(s) : s); + } + this.hostname = newOut.join('.'); + } + + var p = this.port ? ':' + this.port : ''; + var h = this.hostname || ''; + this.host = h + p; + this.href += this.host; + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + if (rest[0] !== '/') { + rest = '/' + rest; + } + } + } + + // now rest is set to the post-host stuff. + // chop off any delim chars. + if (!unsafeProtocol[lowerProto]) { + + // First, make 100% sure that any "autoEscape" chars get + // escaped, even if encodeURIComponent doesn't think they + // need to be. + for (var i = 0, l = autoEscape.length; i < l; i++) { + var ae = autoEscape[i]; + var esc = encodeURIComponent(ae); + if (esc === ae) { + esc = escape(ae); + } + rest = rest.split(ae).join(esc); + } + } + + + // chop off from the tail first. + var hash = rest.indexOf('#'); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf('?'); + if (qm !== -1) { + this.search = rest.substr(qm); + this.query = rest.substr(qm + 1); + if (parseQueryString) { + this.query = querystring.parse(this.query); + } + rest = rest.slice(0, qm); + } else if (parseQueryString) { + // no query string, but parseQueryString still requested + this.search = ''; + this.query = {}; + } + if (rest) this.pathname = rest; + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = '/'; + } + + //to support http.request + if (this.pathname || this.search) { + var p = this.pathname || ''; + var s = this.search || ''; + this.path = p + s; + } + + // finally, reconstruct the href based on what has been validated. + this.href = this.format(); + return this; +}; + +// format a parsed object into a url string +function urlFormat(obj) { + // ensure it's an object, and not a string url. + // If it's an obj, this is a no-op. + // this way, you can call url_format() on strings + // to clean up potentially wonky urls. + if (isString(obj)) obj = urlParse(obj); + if (!(obj instanceof Url)) return Url.prototype.format.call(obj); + return obj.format(); +} + +Url.prototype.format = function() { + var auth = this.auth || ''; + if (auth) { + auth = encodeURIComponent(auth); + auth = auth.replace(/%3A/i, ':'); + auth += '@'; + } + + var protocol = this.protocol || '', + pathname = this.pathname || '', + hash = this.hash || '', + host = false, + query = ''; + + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + (this.hostname.indexOf(':') === -1 ? + this.hostname : + '[' + this.hostname + ']'); + if (this.port) { + host += ':' + this.port; + } + } + + if (this.query && + isObject(this.query) && + Object.keys(this.query).length) { + query = querystring.stringify(this.query); + } + + var search = this.search || (query && ('?' + query)) || ''; + + if (protocol && protocol.substr(-1) !== ':') protocol += ':'; + + // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. + // unless they had them to begin with. + if (this.slashes || + (!protocol || slashedProtocol[protocol]) && host !== false) { + host = '//' + (host || ''); + if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname; + } else if (!host) { + host = ''; + } + + if (hash && hash.charAt(0) !== '#') hash = '#' + hash; + if (search && search.charAt(0) !== '?') search = '?' + search; + + pathname = pathname.replace(/[?#]/g, function(match) { + return encodeURIComponent(match); + }); + search = search.replace('#', '%23'); + + return protocol + host + pathname + search + hash; +}; + +function urlResolve(source, relative) { + return urlParse(source, false, true).resolve(relative); +} + +Url.prototype.resolve = function(relative) { + return this.resolveObject(urlParse(relative, false, true)).format(); +}; + +function urlResolveObject(source, relative) { + if (!source) return relative; + return urlParse(source, false, true).resolveObject(relative); +} + +Url.prototype.resolveObject = function(relative) { + if (isString(relative)) { + var rel = new Url(); + rel.parse(relative, false, true); + relative = rel; + } + + var result = new Url(); + Object.keys(this).forEach(function(k) { + result[k] = this[k]; + }, this); + + // hash is always overridden, no matter what. + // even href="" will remove it. + result.hash = relative.hash; + + // if the relative url is empty, then there's nothing left to do here. + if (relative.href === '') { + result.href = result.format(); + return result; + } + + // hrefs like //foo/bar always cut to the protocol. + if (relative.slashes && !relative.protocol) { + // take everything except the protocol from relative + Object.keys(relative).forEach(function(k) { + if (k !== 'protocol') + result[k] = relative[k]; + }); + + //urlParse appends trailing / to urls like http://www.example.com + if (slashedProtocol[result.protocol] && + result.hostname && !result.pathname) { + result.path = result.pathname = '/'; + } + + result.href = result.format(); + return result; + } + + if (relative.protocol && relative.protocol !== result.protocol) { + // if it's a known url protocol, then changing + // the protocol does weird things + // first, if it's not file:, then we MUST have a host, + // and if there was a path + // to begin with, then we MUST have a path. + // if it is file:, then the host is dropped, + // because that's known to be hostless. + // anything else is assumed to be absolute. + if (!slashedProtocol[relative.protocol]) { + Object.keys(relative).forEach(function(k) { + result[k] = relative[k]; + }); + result.href = result.format(); + return result; + } + + result.protocol = relative.protocol; + if (!relative.host && !hostlessProtocol[relative.protocol]) { + var relPath = (relative.pathname || '').split('/'); + while (relPath.length && !(relative.host = relPath.shift())); + if (!relative.host) relative.host = ''; + if (!relative.hostname) relative.hostname = ''; + if (relPath[0] !== '') relPath.unshift(''); + if (relPath.length < 2) relPath.unshift(''); + result.pathname = relPath.join('/'); + } else { + result.pathname = relative.pathname; + } + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ''; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // to support http.request + if (result.pathname || result.search) { + var p = result.pathname || ''; + var s = result.search || ''; + result.path = p + s; + } + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; + } + + var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), + isRelAbs = ( + relative.host || + relative.pathname && relative.pathname.charAt(0) === '/' + ), + mustEndAbs = (isRelAbs || isSourceAbs || + (result.host && relative.pathname)), + removeAllDots = mustEndAbs, + srcPath = result.pathname && result.pathname.split('/') || [], + relPath = relative.pathname && relative.pathname.split('/') || [], + psychotic = result.protocol && !slashedProtocol[result.protocol]; + + // if the url is a non-slashed url, then relative + // links like ../.. should be able + // to crawl up to the hostname, as well. This is strange. + // result.protocol has already been set by now. + // Later on, put the first path part into the host field. + if (psychotic) { + result.hostname = ''; + result.port = null; + if (result.host) { + if (srcPath[0] === '') srcPath[0] = result.host; + else srcPath.unshift(result.host); + } + result.host = ''; + if (relative.protocol) { + relative.hostname = null; + relative.port = null; + if (relative.host) { + if (relPath[0] === '') relPath[0] = relative.host; + else relPath.unshift(relative.host); + } + relative.host = null; + } + mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); + } + + if (isRelAbs) { + // it's absolute. + result.host = (relative.host || relative.host === '') ? + relative.host : result.host; + result.hostname = (relative.hostname || relative.hostname === '') ? + relative.hostname : result.hostname; + result.search = relative.search; + result.query = relative.query; + srcPath = relPath; + // fall through to the dot-handling below. + } else if (relPath.length) { + // it's relative + // throw away the existing file, and take the new path instead. + if (!srcPath) srcPath = []; + srcPath.pop(); + srcPath = srcPath.concat(relPath); + result.search = relative.search; + result.query = relative.query; + } else if (!isNullOrUndefined(relative.search)) { + // just pull out the search. + // like href='?foo'. + // Put this after the other two cases because it simplifies the booleans + if (psychotic) { + result.hostname = result.host = srcPath.shift(); + //occationaly the auth can get stuck only in host + //this especialy happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); + } + } + result.search = relative.search; + result.query = relative.query; + //to support http.request + if (!isNull(result.pathname) || !isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.href = result.format(); + return result; + } + + if (!srcPath.length) { + // no path at all. easy. + // we've already handled the other stuff above. + result.pathname = null; + //to support http.request + if (result.search) { + result.path = '/' + result.search; + } else { + result.path = null; + } + result.href = result.format(); + return result; + } + + // if a url ENDs in . or .., then it must get a trailing slash. + // however, if it ends in anything else non-slashy, + // then it must NOT get a trailing slash. + var last = srcPath.slice(-1)[0]; + var hasTrailingSlash = ( + (result.host || relative.host) && (last === '.' || last === '..') || + last === ''); + + // strip single dots, resolve double dots to parent dir + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = srcPath.length; i >= 0; i--) { + last = srcPath[i]; + if (last == '.') { + srcPath.splice(i, 1); + } else if (last === '..') { + srcPath.splice(i, 1); + up++; + } else if (up) { + srcPath.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (!mustEndAbs && !removeAllDots) { + for (; up--; up) { + srcPath.unshift('..'); + } + } + + if (mustEndAbs && srcPath[0] !== '' && + (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { + srcPath.unshift(''); + } + + if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { + srcPath.push(''); + } + + var isAbsolute = srcPath[0] === '' || + (srcPath[0] && srcPath[0].charAt(0) === '/'); + + // put the host back + if (psychotic) { + result.hostname = result.host = isAbsolute ? '' : + srcPath.length ? srcPath.shift() : ''; + //occationaly the auth can get stuck only in host + //this especialy happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); + } + } + + mustEndAbs = mustEndAbs || (result.host && srcPath.length); + + if (mustEndAbs && !isAbsolute) { + srcPath.unshift(''); + } + + if (!srcPath.length) { + result.pathname = null; + result.path = null; + } else { + result.pathname = srcPath.join('/'); + } + + //to support request.http + if (!isNull(result.pathname) || !isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; +}; + +Url.prototype.parseHost = function() { + var host = this.host; + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); + } + if (host) this.hostname = host; +}; + +function isString(arg) { + return typeof arg === "string"; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isNull(arg) { + return arg === null; +} +function isNullOrUndefined(arg) { + return arg == null; +} + +},{"punycode":3,"querystring":6}],8:[function(require,module,exports){ +/*! + * jQuery JavaScript Library v2.1.4 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2015-04-28T16:01Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Support: Firefox 18+ +// Can't be in strict mode, several libs including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// + +var arr = []; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var support = {}; + + + +var + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + version = "2.1.4", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android<4.1 + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + // adding 1 corrects loss of precision from parseFloat (#15100) + return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; + }, + + isPlainObject: function( obj ) { + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.constructor && + !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { + return false; + } + + // If the function hasn't returned already, we're confident that + // |obj| is a plain object, created by {} or constructed with new Object + return true; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + // Support: Android<4.0, iOS<6 (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + var script, + indirect = eval; + + code = jQuery.trim( code ); + + if ( code ) { + // If the code includes a valid, prologue position + // strict mode pragma, execute code by injecting a + // script tag into the document. + if ( code.indexOf("use strict") === 1 ) { + script = document.createElement("script"); + script.text = code; + document.head.appendChild( script ).parentNode.removeChild( script ); + } else { + // Otherwise, avoid the DOM node creation, insertion + // and removal by using an indirect global eval + indirect( code ); + } + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE9-11+ + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Support: Android<4.1 + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + + // Support: iOS 8.2 (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.2.0-pre + * http://sizzlejs.com/ + * + * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-12-16 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // http://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + nodeType = context.nodeType; + + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + if ( !seed && documentIsHTML ) { + + // Try to shortcut find operations when possible (e.g., not under DocumentFragment) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType !== 1 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, parent, + doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + parent = doc.defaultView; + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", unloadHandler, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", unloadHandler ); + } + } + + /* Support tests + ---------------------------------------------------------------------- */ + documentIsHTML = !isXML( doc ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + docElem.appendChild( div ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( div.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+ + if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibing-combinator selector` fails + if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is no seed and only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + len = this.length, + ret = [], + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Support: Blackberry 4.6 + // gEBID returns nodes no longer in the document (#6963) + if ( elem && elem.parentNode ) { + // Inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; + }, + + sibling: function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter(function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.unique( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // Add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // If we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + jQuery( document ).off( "ready" ); + } + } +}); + +/** + * The ready event handler and self cleanup method + */ +function completed() { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + jQuery.ready(); +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // We once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + } else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + } + } + return readyList.promise( obj ); +}; + +// Kick off the DOM ready check even if the user does not +jQuery.ready.promise(); + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + len ? fn( elems[0], key ) : emptyGet; +}; + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( owner ) { + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + /* jshint -W018 */ + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + +function Data() { + // Support: Android<4, + // Old WebKit does not have Object.preventExtensions/freeze method, + // return new empty object instead with no [[set]] accessor + Object.defineProperty( this.cache = {}, 0, { + get: function() { + return {}; + } + }); + + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; +Data.accepts = jQuery.acceptData; + +Data.prototype = { + key: function( owner ) { + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return the key for a frozen object. + if ( !Data.accepts( owner ) ) { + return 0; + } + + var descriptor = {}, + // Check if the owner object already has a cache key + unlock = owner[ this.expando ]; + + // If not, create one + if ( !unlock ) { + unlock = Data.uid++; + + // Secure it in a non-enumerable, non-writable property + try { + descriptor[ this.expando ] = { value: unlock }; + Object.defineProperties( owner, descriptor ); + + // Support: Android<4 + // Fallback to a less secure definition + } catch ( e ) { + descriptor[ this.expando ] = unlock; + jQuery.extend( owner, descriptor ); + } + } + + // Ensure the cache object + if ( !this.cache[ unlock ] ) { + this.cache[ unlock ] = {}; + } + + return unlock; + }, + set: function( owner, data, value ) { + var prop, + // There may be an unlock assigned to this node, + // if there is no entry for this "owner", create one inline + // and set the unlock as though an owner entry had always existed + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + // Handle: [ owner, key, value ] args + if ( typeof data === "string" ) { + cache[ data ] = value; + + // Handle: [ owner, { properties } ] args + } else { + // Fresh assignments by object are shallow copied + if ( jQuery.isEmptyObject( cache ) ) { + jQuery.extend( this.cache[ unlock ], data ); + // Otherwise, copy the properties one-by-one to the cache object + } else { + for ( prop in data ) { + cache[ prop ] = data[ prop ]; + } + } + } + return cache; + }, + get: function( owner, key ) { + // Either a valid cache is found, or will be created. + // New caches will be created and the unlock returned, + // allowing direct access to the newly created + // empty data object. A valid owner object must be provided. + var cache = this.cache[ this.key( owner ) ]; + + return key === undefined ? + cache : cache[ key ]; + }, + access: function( owner, key, value ) { + var stored; + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ((key && typeof key === "string") && value === undefined) ) { + + stored = this.get( owner, key ); + + return stored !== undefined ? + stored : this.get( owner, jQuery.camelCase(key) ); + } + + // [*]When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, name, camel, + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + if ( key === undefined ) { + this.cache[ unlock ] = {}; + + } else { + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = key.concat( key.map( jQuery.camelCase ) ); + } else { + camel = jQuery.camelCase( key ); + // Try the string as a key before any manipulation + if ( key in cache ) { + name = [ key, camel ]; + } else { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + name = camel; + name = name in cache ? + [ name ] : ( name.match( rnotwhite ) || [] ); + } + } + + i = name.length; + while ( i-- ) { + delete cache[ name[ i ] ]; + } + } + }, + hasData: function( owner ) { + return !jQuery.isEmptyObject( + this.cache[ owner[ this.expando ] ] || {} + ); + }, + discard: function( owner ) { + if ( owner[ this.expando ] ) { + delete this.cache[ owner[ this.expando ] ]; + } + } +}; +var data_priv = new Data(); + +var data_user = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + data_user.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend({ + hasData: function( elem ) { + return data_user.hasData( elem ) || data_priv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return data_user.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + data_user.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to data_priv methods, these can be deprecated. + _data: function( elem, name, data ) { + return data_priv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + data_priv.remove( elem, name ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = data_user.get( elem ); + + if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE11+ + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + data_priv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + data_user.set( this, key ); + }); + } + + return access( this, function( value ) { + var data, + camelKey = jQuery.camelCase( key ); + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + // Attempt to get data from the cache + // with the key as-is + data = data_user.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to get data from the cache + // with the key camelized + data = data_user.get( elem, camelKey ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, camelKey, undefined ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each(function() { + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get( this, camelKey ); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set( this, camelKey, value ); + + // *... In the case of properties that might _actually_ + // have dashes, we need to also store a copy of that + // unchanged property. + if ( key.indexOf("-") !== -1 && data !== undefined ) { + data_user.set( this, key, value ); + } + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + data_user.remove( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = data_priv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = data_priv.access( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return data_priv.get( elem, key ) || data_priv.access( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + data_priv.remove( elem, [ type + "queue", key ] ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = data_priv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Safari<=5.1 + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Safari<=5.1, Android<4.2 + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<=11+ + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +})(); +var strundefined = typeof undefined; + + + +support.focusinBubbles = "onfocusin" in window; + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.hasData( elem ) && data_priv.get( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + data_priv.remove( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, j, ret, matched, handleObj, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, matches, sel, handleObj, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: Cordova 2.5 (WebKit) (#13255) + // All events should have a target; Cordova deviceready doesn't + if ( !event.target ) { + event.target = document; + } + + // Support: Safari 6.0+, Chrome<28 + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } +}; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + // Support: Android<4.0 + src.returnValue === false ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && e.preventDefault ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && e.stopPropagation ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && e.stopImmediatePropagation ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +// Support: Chrome 15+ +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// Support: Firefox, Chrome, Safari +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + data_priv.remove( doc, fix ); + + } else { + data_priv.access( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +var + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + + // Support: IE9 + option: [ 1, "" ], + + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] + }; + +// Support: IE9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: 1.x compatibility +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute("type"); + } + + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); + } +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = data_priv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); + } +} + +function getAll( context, tag ) { + var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : + context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : + []; + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], ret ) : + ret; +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; + }, + + cleanData: function( elems ) { + var data, elem, type, key, + special = jQuery.event.special, + i = 0; + + for ( ; (elem = elems[ i ]) !== undefined; i++ ) { + if ( jQuery.acceptData( elem ) ) { + key = elem[ data_priv.expando ]; + + if ( key && (data = data_priv.cache[ key ]) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + if ( data_priv.cache[ key ] ) { + // Discard any remaining `private` data + delete data_priv.cache[ key ]; + } + } + } + // Discard any remaining `user` data + delete data_user.cache[ elem[ data_user.expando ] ]; + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each(function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + }); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); + } + } + } + } + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: QtWebKit + // .get() because push.apply(_, arraylike) throws + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var style, + elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? + + // Use of this method is a temporary fix (more like optimization) until something better comes along, + // since it was removed from specification and supported only in FF + style.display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "