## 變量
~~~
var 變量名字 類型 = 表達式
~~~
其中“*類型*”或“*\= 表達式*”兩個部分可以省略其中的一個。如果省略的是類型信息,那么將根據初始化表達式來推導變量的類型信息。
**如果初始化表達式被省略,那么將用零值初始化該變量。 數值類型變量對應的零值是0,布爾類型變量對應的零值是false,字符串類型對應的零值是空字符串,接口或引用類型(包括slice、指針、map、chan和函數)變量對應的零值是nil。數組或結構體等聚合類型對應的零值是每個元素或字段都是對應該類型的零值。**
在包級別聲明的變量會在main入口函數執行前完成初始化(§2.6.2),局部變量將在聲明語句被執行到的時候完成初始化。
## 簡短變量聲明
**在函數內部**,有一種稱為簡短變量聲明語句的形式可用于聲明和初始化局部變量。它以“名字 := 表達式”形式聲明變量,變量的類型根據表達式來自動推導。
因為簡潔和靈活的特點,**簡短變量聲明被廣泛用于大部分的局部變量的聲明和初始化**。var形式的聲明語句往往是用于需要顯式指定變量類型地方,或者因為變量稍后會被重新賦值而初始值無關緊要的地方。
和var形式聲明語句一樣,簡短變量聲明語句也可以用來聲明和初始化一組變量:
~~~
i, j := 0, 1
~~~
請記住“:=”是一個變量聲明并賦值語句,而“=”是一個變量賦值操作。也不要混淆多個變量的聲明和元組的多重賦值,后者是將右邊各個的表達式值賦值給左邊對應位置的各個變量:
~~~
i, j = j, i // 交換 i 和 j 的值
~~~
**簡短變量聲明左邊的變量可能并不是全部都是剛剛聲明的。如果有一些已經在相同的詞法域聲明過了,那么簡短變量聲明語句對這些已經聲明過的變量就只有賦值行為了**。
**簡短變量聲明語句中必須至少要聲明一個新的變量。**
**簡短變量聲明語句只有對已經在同級詞法域聲明過的變量才和賦值操作語句等價,如果變量是在外部詞法域聲明的,那么簡短變量聲明語句將會在當前詞法域重新聲明一個新的變量。**
~~~
var name = "jack"
func main() {
name, age := info() //這里的name是當前詞法作用域中的一個新聲明的變量,和包級的name不是同一個變量,所以包級別name的值并未改變
fmt.Println(name) //milan
fmt.Println(age) //20
show() //jack
}
func info() (string, int) {
return "milan", 20
}
func show() {
fmt.Println(name)
}
~~~
代碼改成這樣:
~~~
var name = "jack"
func main() {
var age int
name, age = info()
fmt.Println(name)
fmt.Println(age)
show() //此時外層的name的值被賦值為"milan"
}
func info() (string, int) {
return "milan", 20
}
func show() {
fmt.Println(name)
}
~~~
## 指針
**指針的本質:表示內存地址的數據類型.**
**有很多變量始終以表達式方式引入,例如x\[i\]或x.f變量。所有這些表達式一般都是讀取一個變量的值。(這里的意思是如果我使用結構體的指針去"."一個字段名始終是訪問這個字段的值嘛?)**
**一個指針的值是另一個變量的地址。一個指針對應變量在內存中的存儲位置**。并不是每一個值都會有一個內存地址,但是對于每一個變量必然有對應的內存地址。通過指針,我們可以直接讀或更新對應變量的值,而不需要知道該變量的名字(如果變量有名字的話)。
如果用“var x int”聲明語句聲明一個x變量,那么&x表達式(取x變量的內存地址)將產生一個指向該整數變量的指針,指針對應的數據類型是`*int`,指針被稱之為“指向int類型的指針”。如果指針名字為p,那么可以說“p指針指向變量x”,或者說“p指針保存了x變量的內存地址”。同時`*p`表達式對應p指針指向的變量的值。一般`*p`表達式讀取指針指向的變量的值,這里為int類型的值,同時因為`*p`對應一個變量,所以該表達式也可以出現在賦值語句的左邊,表示更新指針所指向的變量的值。
對于聚合類型每個成員——比如結構體的每個字段、或者是數組的每個元素——也都是對應一個變量,因此可以被取地址。
~~~
user := &User{Name: "jack", Age: 20}
p := &user.Name //取結構體字段的指針
*p = "milan"
fmt.Println(user)
~~~
**任何類型的指針的零值都是nil。如果p指向某個有效變量,那么`p != nil`測試為真。指針之間也是可以進行相等測試的,只有當它們指向同一個變量或全部是nil時才相等。**
~~~
var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
~~~
**在Go語言中,返回函數中局部變量的地址也是安全的。**
~~~
func main() {
fmt.Println(show())
fmt.Println(show())
fmt.Println(show())
}
func show() *int {
n := 10
return &n
}
~~~
地址是不同的:
```
0xc000016098
0xc0000160b0
0xc0000160b8
```
每次我們對一個變量取地址,或者復制指針,我們都是為原變量創建了新的別名。例如,`*p`就是是 變量v的別名。指針特別有價值的地方在于我們可以不用名字而訪問一個變量,但是這是一把雙刃劍
**特別需要注意的是,只有當指針有值(不為nil)的情況下才可以進行操作**.
## 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函數類似是一種**語法糖**,而不是一個新的基礎概念。
new函數使用通常相對比較少,因為對于結構體來說,直接用字面量語法創建新變量的方法會更靈活
## 變量的生命周期
變量的生命周期指的是在程序運行期間變量有效存在的時間間隔。對于在包一級聲明的變量來說,它們的生命周期和整個程序的運行周期是一致的。而相比之下,局部變量的聲明周期則是動態的:每次從創建一個新變量的聲明語句開始,直到該變量不再被引用為止,然后變量的存儲空間可能被回收。函數的參數變量和返回值變量都是局部變量。它們在函數每次被調用的時候創建。
那么Go語言的自動垃圾收集器是如何知道一個變量是何時可以被回收的呢?這里我們可以避開完整的技術細節,基本的實現思路是,從每個包級的變量和每個當前運行函數的每一個局部變量開始,通過指針或引用的訪問路徑遍歷,是否可以找到該變量。如果不存在這樣的訪問路徑,那么說明該變量是不可達的,也就是說它是否存在并不會影響程序后續的計算結果。
因為一個變量的有效周期只取決于是否可達,因此一個循環迭代內部的局部變量的生命周期可能超出其局部作用域。同時,局部變量可能在函數返回之后依然存在。
編譯器會自動選擇在棧上還是在堆上分配局部變量的存儲空間,但可能令人驚訝的是,這個選擇并不是由用var還是new聲明變量的方式決定的。
~~~
var global *int
func f() {
var x int
x = 1
global = &x
}
func g() {
y := new(int)
*y = 1
}
~~~
函數里的x變量必須在堆上分配,因為它在函數退出后依然可以通過包一級的global變量找到,雖然它是在函數內部定義的;用Go語言的術語說,這個x局部變量從函數f中逃逸了。相反,當g函數返回時,變量`*y`將是不可達的,也就是說可以馬上被回收的。因此,`*y`并沒有從函數g中逃逸,編譯器可以選擇在棧上分配`*y`的存儲空間(譯注:也可以選擇在堆上分配,然后由Go語言的GC回收這個變量的內存空間),雖然這里用的是new方式。其實在任何時候,你并不需為了編寫正確的代碼而要考慮變量的逃逸行為,要記住的是,逃逸的變量需要額外分配內存,同時對性能的優化可能會產生細微的影響。
Go語言的自動垃圾收集器對編寫正確的代碼是一個巨大的幫助,但也并不是說你完全不用考慮內存了。你雖然不需要顯式地分配和釋放內存,但是要編寫高效的程序你依然需要了解變量的生命周期。例如,如果將指向短生命周期對象的指針保存到具有長生命周期的對象中,特別是保存到全局變量時,會阻止對短生命周期對象的垃圾回收(從而可能影響程序的性能)。
- 基本語法
- 申明變量
- 常量
- 數據類型
- 強制類型轉換
- 獲取命令行參數
- 指針
- 概述
- new函數
- 函數
- 概述
- 不定參數類型
- 有返回值
- 函數類型
- 回調函數
- 匿名函數和閉包
- 延遲調用defer
- 工程管理
- 工作區
- src,pkg和bin目錄
- 復合類型
- 概述
- 數組
- 概述
- 聲明并初始化
- 拷貝傳值
- slice
- 概述
- 創建切片
- 切片截取
- 切片和底層數組的關系
- slice常用方法
- 切片做函數參數
- map
- 概述
- map操作
- 結構體
- 概述
- 結構體初始化
- 結構體比較
- 結構體作為函數參數
- 結構體前加&
- 面向對象
- 概述
- 匿名組合
- 方法
- 值語義和引用語義
- 方法集
- 方法的繼承
- 方法重寫
- 方法值
- 接口
- 接口定義和實現
- 多態的表現
- 接口繼承
- 接口轉換
- 空接口
- 類型斷言
- 異常處理
- error接口
- panic
- recover
- 文本文件處理
- 字符串操作
- 正則表達式
- json處理
- 文件操作
- 標準設備文件操作
- 并發編程
- 概述
- 并發和并行
- go語言并發優勢
- goroutine
- goroutine概述
- 創建goroutine
- 主協程先退出
- runtime包
- Gosched
- Goexit
- GOMAXPROCE
- channel
- 多資源競爭
- channel類型
- 無緩沖channel
- 有緩沖channel
- 關閉channel
- 單向channel
- 單向channel特性
- 定時器
- Timer
- Ticker
- select
- select作用
- 超時
- sync
- 競爭狀態
- 網絡編程
- 網絡概述
- 網絡協議
- 分層模型
- 網絡分層架構
- 層與協議
- 每層協議的功能
- 鏈路層
- 網絡層
- 傳輸層
- 應用層
- socket編程
- 組合和繼承
- 注意事項
- 細節
- go語言實現隊列
- google工程師golang
- 基礎語法
- 內建容器
- 面向"對象"
- 依賴管理
- 面向接口
- 函數式編程
- 錯誤處理和資源管理
- 測試與性能調優
- goroutine
- channel
- golang問題集
- 斷言和類型轉換
- Go語言圣經
- 入門
- 程序結構
- 命名
- 聲明
- 變量
- 賦值
- 類型
- 包和文件
- 作用域
- 基礎數據類型
- 整數
- 浮點數
- 復數
- 布爾型
- 字符串
- 常量
- 復合數據類型
- 數組
- slice
- map
- 結構體
- json
- 文本和HTML模板
- 函數
- 函數聲明
- 錯誤
- 函數值
- 匿名函數
- defer
- panic
- recover
- 方法
- 方法聲明
- 指針對象的方法
- 封裝
- 接口
- 說明
- 接口是合約
- 實現接口的條件
- 接口值
- 類型斷言
- 通過類型斷言詢問行為
- 類型開關
- Goroutines和Channels
- 協程
- channels
- 無緩沖channel
- 串聯的channel
- 有緩沖channel
- 并發的循環
- select多路復用
- 并發的退出
- 并發問題的自我思考
- 基于共享變量的并發
- 競爭條件
- 互斥鎖
- 讀寫鎖
- 內存同步
- sync.Once
- 協程和線程
- 包和工具
- 測試
- 反射
- 什么是反射
- 為什么需要反射
- reflect.Type和reflect.Value
- 通過reflect.Value修改值
- 底層編程