鍍金池/ 教程/ GO/ 4.4 變量
4.7 strings 和 strconv 包
13.6 啟動(dòng)外部命令和程序
?# 11.4 類(lèi)型判斷:type-switch
12.1 讀取用戶(hù)的輸入
10.6 方法
12.2 文件讀寫(xiě)
13 錯(cuò)誤處理與測(cè)試
9.3 鎖和 sync 包
12.3 文件拷貝
?# 11.7 第一個(gè)例子:使用 Sorter 接口排序
?# 11.5 測(cè)試一個(gè)值是否實(shí)現(xiàn)了某個(gè)接口
6.4 defer 和追蹤
12.10 XML 數(shù)據(jù)格式
13.10 性能調(diào)試:分析并優(yōu)化 Go 程序
?# 11.1 接口是什么
2.2 Go 環(huán)境變量
2.6 安裝目錄清單
2.5 在 Windows 上安裝 Go
11.11 Printf 和反射
1.2 語(yǔ)言的主要特性與發(fā)展的環(huán)境和影響因素
9.0 包(package)
7.4 切片重組(reslice)
13.2 運(yùn)行時(shí)異常和 panic
10.2 使用工廠(chǎng)方法創(chuàng)建結(jié)構(gòu)體實(shí)例
12.8 使用接口的實(shí)際例子:fmt.Fprintf
2.4 在 Mac OS X 上安裝 Go
3.8 Go 性能說(shuō)明
7.2 切片
8.0 Map
3.1 Go 開(kāi)發(fā)環(huán)境的基本要求
5.6 標(biāo)簽與 goto
6.10 使用閉包調(diào)試
9.5 自定義包和可見(jiàn)性
4.3 常量
?# 11.2 接口嵌套接口
6.5 內(nèi)置函數(shù)
前言
10.8 垃圾回收和 SetFinalizer
2.8 Go 解釋器
13.7 Go 中的單元測(cè)試和基準(zhǔn)測(cè)試
6.8 閉包
4.9 指針
13.1 錯(cuò)誤處理
10.1 結(jié)構(gòu)體定義
5.1 if-else 結(jié)構(gòu)
6.6 遞歸函數(shù)
9.9 通過(guò) Git 打包和安裝
2.7 Go 運(yùn)行時(shí)(runtime)
10.7 類(lèi)型的 String() 方法和格式化描述符
3.7 其它工具
9.6 為自定義包使用 godoc
11.12 接口與動(dòng)態(tài)類(lèi)型
13.3 從 panic 中恢復(fù)(Recover)
10.3 使用自定義包中的結(jié)構(gòu)體
11.14 結(jié)構(gòu)體、集合和高階函數(shù)
3.6 生成代碼文檔
9.2 regexp 包
4.1 文件名、關(guān)鍵字與標(biāo)識(shí)符
?# 11.6 使用方法集與接口
7.0 數(shù)組與切片
7.1 聲明和初始化
12.11 用 Gob 傳輸數(shù)據(jù)
5.5 Break 與 continue
1.1 起源與發(fā)展
?# 11 接口(Interfaces)與反射(reflection)
6.9 應(yīng)用閉包:將函數(shù)作為返回值
4.2 Go 程序的基本結(jié)構(gòu)和要素
8.6 將 map 的鍵值對(duì)調(diào)
6.11 計(jì)算函數(shù)執(zhí)行時(shí)間
5.0 控制結(jié)構(gòu)
10.5 匿名字段和內(nèi)嵌結(jié)構(gòu)體
4.6 字符串
3.0 編輯器、集成開(kāi)發(fā)環(huán)境與其它工具
13.8 測(cè)試的具體例子
7.6 字符串、數(shù)組和切片的應(yīng)用
8.4 map 類(lèi)型的切片
3.9 與其它語(yǔ)言進(jìn)行交互
7.3 For-range 結(jié)構(gòu)
9.7 使用 go install 安裝自定義包
6.0 函數(shù)
9.8 自定義包的目錄結(jié)構(gòu)、go install 和 go test
6.3 傳遞變長(zhǎng)參數(shù)
13.9 用(測(cè)試數(shù)據(jù))表驅(qū)動(dòng)測(cè)試
11.9 空接口
8.1 聲明、初始化和 make
6.2 函數(shù)參數(shù)與返回值
9.11 在 Go 程序中使用外部庫(kù)
3.3 調(diào)試器
4.5 基本類(lèi)型和運(yùn)算符
?# 11.8 第二個(gè)例子:讀和寫(xiě)
12.5 用 buffer 讀取文件
總結(jié):Go 中的面向?qū)ο?/span>
11.10 反射包
12.7 用 defer 關(guān)閉文件
9.4 精密計(jì)算和 big 包
4.4 變量
6.1 介紹
13.4 自定義包中的錯(cuò)誤處理和 panicking
12.4 從命令行讀取參數(shù)
9.10 Go 的外部包和項(xiàng)目
8.3 for-range 的配套用法
3.5 格式化代碼
10.4 帶標(biāo)簽的結(jié)構(gòu)體
7.5 切片的復(fù)制與追加
?# 11.3 類(lèi)型斷言:如何檢測(cè)和轉(zhuǎn)換接口變量的類(lèi)型
5.4 for 結(jié)構(gòu)
4.8 時(shí)間和日期
2.3 在 Linux 上安裝 Go
12 讀寫(xiě)數(shù)據(jù)
6.12 通過(guò)內(nèi)存緩存來(lái)提升性能
9.1 標(biāo)準(zhǔn)庫(kù)概述
12.6 用切片讀寫(xiě)文件
10 結(jié)構(gòu)(struct)與方法(method)
8.5 map 的排序
12.9 JSON 數(shù)據(jù)格式
13.5 一種用閉包處理錯(cuò)誤的模式
3.2 編輯器和集成開(kāi)發(fā)環(huán)境
12.12 Go 中的密碼學(xué)
5.2 測(cè)試多返回值函數(shù)的錯(cuò)誤
6.7 將函數(shù)作為參數(shù)
8.2 測(cè)試鍵值對(duì)是否存在及刪除元素
3.4 構(gòu)建并運(yùn)行 Go 程序
2.1 平臺(tái)與架構(gòu)
5.3 switch 結(jié)構(gòu)

4.4 變量

4.4.1 簡(jiǎn)介

聲明變量的一般形式是使用 var 關(guān)鍵字:var identifier type。

需要注意的是,Go 和許多編程語(yǔ)言不同,它在聲明變量時(shí)將變量的類(lèi)型放在變量的名稱(chēng)之后。Go 為什么要選擇這么做呢?

首先,它是為了避免像 C 語(yǔ)言中那樣含糊不清的聲明形式,例如:int* a, b;。在這個(gè)例子中,只有 a 是指針而 b 不是。如果你想要這兩個(gè)變量都是指針,則需要將它們分開(kāi)書(shū)寫(xiě)(你可以在 Go 語(yǔ)言的聲明語(yǔ)法 頁(yè)面找到有關(guān)于這個(gè)話(huà)題的更多討論)。

而在 Go 中,則可以很輕松地將它們都聲明為指針類(lèi)型:

var a, b *int

其次,這種語(yǔ)法能夠按照從左至右的順序閱讀,使得代碼更加容易理解。

示例:

var a int
var b bool
var str string

你也可以改寫(xiě)成這種形式:

var (
    a int
    b bool
    str string
)

這種因式分解關(guān)鍵字的寫(xiě)法一般用于聲明全局變量。

當(dāng)一個(gè)變量被聲明之后,系統(tǒng)自動(dòng)賦予它該類(lèi)型的零值:int 為 0,float 為 0.0,bool 為 false,string 為空字符串,指針為 nil。記住,所有的內(nèi)存在 Go 中都是經(jīng)過(guò)初始化的。

變量的命名規(guī)則遵循駱駝命名法,即首個(gè)單詞小寫(xiě),每個(gè)新單詞的首字母大寫(xiě),例如:numShipsstartDate。

但如果你的全局變量希望能夠被外部包所使用,則需要將首個(gè)單詞的首字母也大寫(xiě)(第 4.2 節(jié):可見(jiàn)性規(guī)則)。

一個(gè)變量(常量、類(lèi)型或函數(shù))在程序中都有一定的作用范圍,稱(chēng)之為作用域。如果一個(gè)變量在函數(shù)體外聲明,則被認(rèn)為是全局變量,可以在整個(gè)包甚至外部包(被導(dǎo)出后)使用,不管你聲明在哪個(gè)源文件里或在哪個(gè)源文件里調(diào)用該變量。

在函數(shù)體內(nèi)聲明的變量稱(chēng)之為局部變量,它們的作用域只在函數(shù)體內(nèi),參數(shù)和返回值變量也是局部變量。在第 5 章,我們將會(huì)學(xué)習(xí)到像 if 和 for 這些控制結(jié)構(gòu),而在這些結(jié)構(gòu)中聲明的變量的作用域只在相應(yīng)的代碼塊內(nèi)。一般情況下,局部變量的作用域可以通過(guò)代碼塊(用大括號(hào)括起來(lái)的部分)判斷。

盡管變量的標(biāo)識(shí)符必須是唯一的,但你可以在某個(gè)代碼塊的內(nèi)層代碼塊中使用相同名稱(chēng)的變量,則此時(shí)外部的同名變量將會(huì)暫時(shí)隱藏(結(jié)束內(nèi)部代碼塊的執(zhí)行后隱藏的外部同名變量又會(huì)出現(xiàn),而內(nèi)部同名變量則被釋放),你任何的操作都只會(huì)影響內(nèi)部代碼塊的局部變量。

變量可以編譯期間就被賦值,賦值給變量使用運(yùn)算符等號(hào) =,當(dāng)然你也可以在運(yùn)行時(shí)對(duì)變量進(jìn)行賦值操作。

示例:

a = 15
b = false

一般情況下,當(dāng)變量a和變量b之間類(lèi)型相同時(shí),才能進(jìn)行如a = b的賦值。

聲明與賦值(初始化)語(yǔ)句也可以組合起來(lái)。

示例:

var identifier [type] = value
var a int = 15
var i = 5
var b bool = false
var str string = "Go says hello to the world!"

但是 Go 編譯器的智商已經(jīng)高到可以根據(jù)變量的值來(lái)自動(dòng)推斷其類(lèi)型,這有點(diǎn)像 Ruby 和 Python 這類(lèi)動(dòng)態(tài)語(yǔ)言,只不過(guò)它們是在運(yùn)行時(shí)進(jìn)行推斷,而 Go 是在編譯時(shí)就已經(jīng)完成推斷過(guò)程。因此,你還可以使用下面的這些形式來(lái)聲明及初始化變量:

var a = 15
var b = false
var str = "Go says hello to the world!"

或:

var (
    a = 15
    b = false
    str = "Go says hello to the world!"
    numShips = 50
    city string
)

不過(guò)自動(dòng)推斷類(lèi)型并不是任何時(shí)候都適用的,當(dāng)你想要給變量的類(lèi)型并不是自動(dòng)推斷出的某種類(lèi)型時(shí),你還是需要顯式指定變量的類(lèi)型,例如:

var n int64 = 2

然而,var a 這種語(yǔ)法是不正確的,因?yàn)榫幾g器沒(méi)有任何可以用于自動(dòng)推斷類(lèi)型的依據(jù)。變量的類(lèi)型也可以在運(yùn)行時(shí)實(shí)現(xiàn)自動(dòng)推斷,例如:

var (
    HOME = os.Getenv("HOME")
    USER = os.Getenv("USER")
    GOROOT = os.Getenv("GOROOT")
)

這種寫(xiě)法主要用于聲明包級(jí)別的全局變量,當(dāng)你在函數(shù)體內(nèi)聲明局部變量時(shí),應(yīng)使用簡(jiǎn)短聲明語(yǔ)法 :=,例如:

a := 1

下面這個(gè)例子展示了如何通過(guò)runtime包在運(yùn)行時(shí)獲取所在的操作系統(tǒng)類(lèi)型,以及如何通過(guò) os 包中的函數(shù) os.Getenv() 來(lái)獲取環(huán)境變量中的值,并保存到 string 類(lèi)型的局部變量 path 中。

示例 4.5 goos.go

package main

import (
    "fmt"
   "runtime"
    "os"
)

func main() {
    var goos string = runtime.GOOS
    fmt.Printf("The operating system is: %s\n", goos)
    path := os.Getenv("PATH")
    fmt.Printf("Path is %s\n", path)
}

如果你在 Windows 下運(yùn)行這段代碼,則會(huì)輸出 The operating system is: windows 以及相應(yīng)的環(huán)境變量的值;如果你在 Linux 下運(yùn)行這段代碼,則會(huì)輸出 The operating system is: linux 以及相應(yīng)的的環(huán)境變量的值。

這里用到了 Printf 的格式化輸出的功能(第 4.4.3 節(jié))。

4.4.2 值類(lèi)型和引用類(lèi)型

程序中所用到的內(nèi)存在計(jì)算機(jī)中使用一堆箱子來(lái)表示(這也是人們?cè)谥v解它的時(shí)候的畫(huà)法),這些箱子被稱(chēng)為 “ 字 ”。根據(jù)不同的處理器以及操作系統(tǒng)類(lèi)型,所有的字都具有 32 位(4 字節(jié))或 64 位(8 字節(jié))的相同長(zhǎng)度;所有的字都使用相關(guān)的內(nèi)存地址來(lái)進(jìn)行表示(以十六進(jìn)制數(shù)表示)。

所有像 int、float、bool 和 string 這些基本類(lèi)型都屬于值類(lèi)型,使用這些類(lèi)型的變量直接指向存在內(nèi)存中的值:

http://wiki.jikexueyuan.com/project/the-way-to-go/images/4.4.2_fig4.1.jpg?raw=true" alt="" />

另外,像數(shù)組(第 7 章)和結(jié)構(gòu)(第 10 章)這些復(fù)合類(lèi)型也是值類(lèi)型。

當(dāng)使用等號(hào) = 將一個(gè)變量的值賦值給另一個(gè)變量時(shí),如:j = i,實(shí)際上是在內(nèi)存中將 i 的值進(jìn)行了拷貝:

http://wiki.jikexueyuan.com/project/the-way-to-go/images/4.4.2_fig4.2.jpg?raw=true" alt="" />

你可以通過(guò) &i 來(lái)獲取變量 i 的內(nèi)存地址(第 4.9 節(jié)),例如:0xf840000040(每次的地址都可能不一樣)。值類(lèi)型的變量的值存儲(chǔ)在棧中。

內(nèi)存地址會(huì)根據(jù)機(jī)器的不同而有所不同,甚至相同的程序在不同的機(jī)器上執(zhí)行后也會(huì)有不同的內(nèi)存地址。因?yàn)槊颗_(tái)機(jī)器可能有不同的存儲(chǔ)器布局,并且位置分配也可能不同。

更復(fù)雜的數(shù)據(jù)通常會(huì)需要使用多個(gè)字,這些數(shù)據(jù)一般使用引用類(lèi)型保存。

一個(gè)引用類(lèi)型的變量 r1 存儲(chǔ)的是 r1 的值所在的內(nèi)存地址(數(shù)字),或內(nèi)存地址中第一個(gè)字所在的位置。

http://wiki.jikexueyuan.com/project/the-way-to-go/images/4.4.2_fig4.3.jpg?raw=true" alt="" />

這個(gè)內(nèi)存地址被稱(chēng)之為指針(你可以從上圖中很清晰地看到,第 4.9 節(jié)將會(huì)詳細(xì)說(shuō)明),這個(gè)指針實(shí)際上也被存在另外的某一個(gè)字中。

同一個(gè)引用類(lèi)型的指針指向的多個(gè)字可以是在連續(xù)的內(nèi)存地址中(內(nèi)存布局是連續(xù)的),這也是計(jì)算效率最高的一種存儲(chǔ)形式;也可以將這些字分散存放在內(nèi)存中,每個(gè)字都指示了下一個(gè)字所在的內(nèi)存地址。

當(dāng)使用賦值語(yǔ)句 r2 = r1 時(shí),只有引用(地址)被復(fù)制。

如果 r1 的值被改變了,那么這個(gè)值的所有引用都會(huì)指向被修改后的內(nèi)容,在這個(gè)例子中,r2 也會(huì)受到影響。

在 Go 語(yǔ)言中,指針(第 4.9 節(jié))屬于引用類(lèi)型,其它的引用類(lèi)型還包括 slices(第 7 章),maps(第 8 章)和 channel(第 13 章)。被引用的變量會(huì)存儲(chǔ)在堆中,以便進(jìn)行垃圾回收,且比棧擁有更大的內(nèi)存空間。

4.4.3 打印

函數(shù) Printf 可以在 fmt 包外部使用,這是因?yàn)樗源髮?xiě)字母 P 開(kāi)頭,該函數(shù)主要用于打印輸出到控制臺(tái)。通常使用的格式化字符串作為第一個(gè)參數(shù):

func Printf(format string, list of variables to be printed)

在示例 4.5 中,格式化字符串為:"The operating system is: %s\n"。

這個(gè)格式化字符串可以含有一個(gè)或多個(gè)的格式化標(biāo)識(shí)符,例如:%..,其中 .. 可以被不同類(lèi)型所對(duì)應(yīng)的標(biāo)識(shí)符替換,如 %s 代表字符串標(biāo)識(shí)符、%v 代表使用類(lèi)型的默認(rèn)輸出格式的標(biāo)識(shí)符。這些標(biāo)識(shí)符所對(duì)應(yīng)的值從格式化字符串后的第一個(gè)逗號(hào)開(kāi)始按照相同順序添加,如果參數(shù)超過(guò) 1 個(gè)則同樣需要使用逗號(hào)分隔。使用這些占位符可以很好地控制格式化輸出的文本。

函數(shù) fmt.SprintfPrintf 的作用是完全相同的,不過(guò)前者將格式化后的字符串以返回值的形式返回給調(diào)用者,因此你可以在程序中使用包含變量的字符串,具體例子可以參見(jiàn)示例 15.4 simple_tcp_server.go。

函數(shù) fmt.Printfmt.Println 會(huì)自動(dòng)使用格式化標(biāo)識(shí)符 %v 對(duì)字符串進(jìn)行格式化,兩者都會(huì)在每個(gè)參數(shù)之間自動(dòng)增加空格,而后者還會(huì)在字符串的最后加上一個(gè)換行符。例如:

fmt.Print("Hello:", 23)

將輸出:Hello: 23。

4.4.4 簡(jiǎn)短形式,使用 := 賦值操作符

我們知道可以在變量的初始化時(shí)省略變量的類(lèi)型而由系統(tǒng)自動(dòng)推斷,而這個(gè)時(shí)候再在 Example 4.4.1 的最后一個(gè)聲明語(yǔ)句寫(xiě)上 var 關(guān)鍵字就顯得有些多余了,因此我們可以將它們簡(jiǎn)寫(xiě)為 a := 50b := false

a 和 b 的類(lèi)型(int 和 bool)將由編譯器自動(dòng)推斷。

這是使用變量的首選形式,但是它只能被用在函數(shù)體內(nèi),而不可以用于全局變量的聲明與賦值。使用操作符 := 可以高效地創(chuàng)建一個(gè)新的變量,稱(chēng)之為初始化聲明。

注意事項(xiàng)

如果在相同的代碼塊中,我們不可以再次對(duì)于相同名稱(chēng)的變量使用初始化聲明,例如:a := 20 就是不被允許的,編譯器會(huì)提示錯(cuò)誤 no new variables on left side of :=,但是 a = 20 是可以的,因?yàn)檫@是給相同的變量賦予一個(gè)新的值。

如果你在定義變量 a 之前使用它,則會(huì)得到編譯錯(cuò)誤 undefined: a

如果你聲明了一個(gè)局部變量卻沒(méi)有在相同的代碼塊中使用它,同樣會(huì)得到編譯錯(cuò)誤,例如下面這個(gè)例子當(dāng)中的變量 a:

func main() {
   var a string = "abc"
   fmt.Println("hello, world")
}

嘗試編譯這段代碼將得到錯(cuò)誤 a declared and not used。

此外,單純地給 a 賦值也是不夠的,這個(gè)值必須被使用,所以使用 fmt.Println("hello, world", a) 會(huì)移除錯(cuò)誤。

但是全局變量是允許聲明但不使用。

其他的簡(jiǎn)短形式為:

同一類(lèi)型的多個(gè)變量可以聲明在同一行,如:

var a, b, c int

(這是將類(lèi)型寫(xiě)在標(biāo)識(shí)符后面的一個(gè)重要原因)

多變量可以在同一行進(jìn)行賦值,如:

a, b, c = 5, 7, "abc"

上面這行假設(shè)了變量 a,b 和 c 都已經(jīng)被聲明,否則的話(huà)應(yīng)該這樣使用:

a, b, c := 5, 7, "abc"

右邊的這些值以相同的順序賦值給左邊的變量,所以 a 的值是 5, b 的值是 7,c 的值是 "abc"。

這被稱(chēng)為 并行同時(shí) 賦值。

如果你想要交換兩個(gè)變量的值,則可以簡(jiǎn)單地使用 a, b = b, a。

(在 Go 語(yǔ)言中,這樣省去了使用交換函數(shù)的必要)

空白標(biāo)識(shí)符 _ 也被用于拋棄值,如值 5 在:_, b = 5, 7 中被拋棄。

_ 實(shí)際上是一個(gè)只寫(xiě)變量,你不能得到它的值。這樣做是因?yàn)?Go 語(yǔ)言中你必須使用所有被聲明的變量,但有時(shí)你并不需要使用從一個(gè)函數(shù)得到的所有返回值。

并行賦值也被用于當(dāng)一個(gè)函數(shù)返回多個(gè)返回值時(shí),比如這里的 val 和錯(cuò)誤 err 是通過(guò)調(diào)用 Func1 函數(shù)同時(shí)得到:val, err = Func1(var1)。

4.4.5 init 函數(shù)

變量除了可以在全局聲明中初始化,也可以在 init 函數(shù)中初始化。這是一類(lèi)非常特殊的函數(shù),它不能夠被人為調(diào)用,而是在每個(gè)包完成初始化后自動(dòng)執(zhí)行,并且執(zhí)行優(yōu)先級(jí)比 main 函數(shù)高。

每個(gè)源文件都只能包含一個(gè) init 函數(shù)。初始化總是以單線(xiàn)程執(zhí)行,并且按照包的依賴(lài)關(guān)系順序執(zhí)行。

一個(gè)可能的用途是在開(kāi)始執(zhí)行程序之前對(duì)數(shù)據(jù)進(jìn)行檢驗(yàn)或修復(fù),以保證程序狀態(tài)的正確性。

示例 4.6 init.go:

package trans

import "math"

var Pi float64

func init() {
   Pi = 4 * math.Atan(1) // init() function computes Pi
}

在它的 init 函數(shù)中計(jì)算變量 Pi 的初始值。

示例 4.7 user_init.go 中導(dǎo)入了包 trans(需要init.go目錄為./trans/init.go)并且使用到了變量 Pi:

package main

import (
   "fmt"
   "./trans"
)

var twoPi = 2 * trans.Pi

func main() {
   fmt.Printf("2*Pi = %g\n", twoPi) // 2*Pi = 6.283185307179586
}

init 函數(shù)也經(jīng)常被用在當(dāng)一個(gè)程序開(kāi)始之前調(diào)用后臺(tái)執(zhí)行的 goroutine,如下面這個(gè)例子當(dāng)中的 backend()

func init() {
   // setup preparations
   go backend()
}

練習(xí) 推斷以下程序的輸出,并解釋你的答案,然后編譯并執(zhí)行它們。

練習(xí) 4.1 local_scope.go:

package main

var a = "G"

func main() {
   n()
   m()
   n()
}

func n() { print(a) }

func m() {
   a := "O"
   print(a)
}

練習(xí) 4.2 global_scope.go:

package main

var a = "G"

func main() {
   n()
   m()
   n()
}

func n() {
   print(a)
}

func m() {
   a = "O"
   print(a)
}

練習(xí) 4.3 function_calls_function.go

package main

var a string

func main() {
   a = "G"
   print(a)
   f1()
}

func f1() {
   a := "O"
   print(a)
   f2()
}

func f2() {
   print(a)
}

鏈接