## 簡短變量申明規則
### 規則一: 多變量賦值可能會重新聲明
我們知道使用`:=`一次可以聲明多個變量,像下面這樣:
~~~go
field1, offset := nextField(str, 0)
~~~
上面代碼定義了兩個變量,并用函數返回值進行賦值。
如果這兩個變量中的一個再次出現在`:=`左側就會重新聲明。像下面這樣:
~~~go
field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)
~~~
offset被重新聲明。
重新聲明并沒有什么問題,它并沒有引入新的變量,只是把變量的值改變了,但要明白,這是Go提供的一個語法糖。
* 當`:=`左側存在新變量時(如field2),那么已聲明的變量(如offset)則會被重新聲明,不會有其他額外副作用。
* 當`:=`左側沒有新變量是不允許的,編譯會提示`no new variable on left side of :=`。
我們所說的重新聲明不會引入問題要滿足一個前提,變量聲明要在同一個作用域中出現。如果出現在不同的作用域,那很可能就創建了新的同名變量,同一函數不同作用域的同名變量往往不是預期做法,很容易引入缺陷
### 規則二:不能用于函數外部
簡短變量場景只能用于函數中,使用`:=`來聲明和初始化全局變量是行不通的。
比如,像下面這樣:
~~~go
package sugar
import fmt
rule := "Short variable declarations" // syntax error: non-declaration statement outside function body
~~~
這里的編譯錯誤提示`syntax error: non-declaration statement outside function body`,表示非聲明語句不能出現在函數外部。可以理解成`:=`實際上會拆分成兩個語句,即聲明和賦值。賦值語句不能出現在函數外部的
## 變量作用域問題
幾乎所有的工程師都了解變量作用域,但是由于`:=`使用過于頻繁的話,還是有可能掉進陷阱里。
下面代碼源自真實項目,但為了描述方便,也為了避免信息安全風險,簡化如下:
~~~go
func Redeclare() {
field, err:= nextField() // 1號err
if field == 1{
field, err:= nextField() // 2號err
newField, err := nextField() // 3號err
...
}
...
}
~~~
注意上面聲明的三個err變量。
2號err與1號err不屬于同一個作用域,`:=`聲明了新的變量,所以2號err與1號err屬于兩個變量。
2號err與3號err屬于同一個作用域,`:=`重新聲明了err但沒創建新的變量,所以2號err與3號err是同一個變量。
如果誤把2號err與1號err混淆,就很容易產生意想不到的錯誤
## 常見陷阱
#### 題目一
問:下面代碼輸出什么?
~~~go
func fun1() {
i := 0
i, j := 1, 2
fmt.Printf("i = %d, j = %d\n", i, j)
}
~~~
程序輸出如下:
~~~go
i = 1, j = 2
~~~
再進一步想一下,前一個語句中已經聲明了i, 為什么還可以再次聲明呢?
#### 題目二
問:下面代碼為什么不能通過編譯?
~~~go
func fun2(i int) {
i := 0
fmt.Println(i)
}
~~~
不能通過編譯原因是形參已經聲明了變量i,使用`:=`再次聲明是不允許的
#### 題目三
問:下面代碼輸出什么?
~~~go
func fun3() {
i, j := 0, 0
if true {
j, k := 1, 1
fmt.Printf("j = %d, k = %d\n", j, k)
}
fmt.Printf("i = %d, j = %d\n", i, j)
}
~~~
程序輸出如下:
~~~go
j = 1, k = 1
i = 0, j = 0
~~~
這里要注意的是,block`if`中聲明的j,與上面的j屬于不同的作用域
- 概述
- go語言基礎特性
- Go語言聲明
- Go項目構建及編譯
- go command
- 程序設計原則
- Go基礎
- 變量
- 常量
- iota
- 基本類型
- byte和rune類型
- 類型定義和類型別名
- 數組
- string
- 高效字符串連接
- string底層原理
- 運算符
- new
- make
- 指針
- 下劃線 & import
- 語法糖
- 簡短變量申明
- 流程控制
- ifelse
- switch
- select
- select實現原理
- select常見案例
- for
- range
- range實現原理
- 常見案例
- range陷阱
- Goto&Break&Continue
- Go函數
- 函數
- 可變參數函數
- 高階函數
- init函數和main函數
- 匿名函數
- 閉包
- 常用內置函數
- defer
- defer常見案例
- defer規則
- defer與函數返回值
- defer實現原理
- defer陷阱
- 數據結構
- slice
- slice內存布局
- slice&array
- slice底層實現
- slice陷阱
- map
- Map實現原理
- 集合
- List
- Set
- 線程安全數據結構
- sync.Map
- Concurrent Map
- 面向對象編程
- struct
- 匿名結構體&匿名字段
- 嵌套結構體
- 結構體的“繼承”
- struct tag
- 行為方法
- 方法與函數
- type Method Value & Method Expressions
- interface
- 類型斷言
- 多態
- 錯誤機制
- error
- 自定義錯誤
- panic&recover
- reflect
- reflect包
- 應用示例
- DeepEqual
- 反射-fillObjectField
- 反射-copyObject
- IO
- 讀取文件
- 寫文件
- bufio
- ioutil
- Go網絡編程
- tcp
- tcp粘包
- udp
- HTTP
- http服務
- httprouter
- webSocket
- go并發編程
- Goroutine
- thread vs goroutine
- Goroutine任務取消
- 通過channel廣播實現
- Context
- Goroutine調度機制
- goroutine調度器1.0
- GMP模型調度器
- 調度器竊取策略
- 調度器的生命周期
- 調度過程全解析
- channel
- 無緩沖的通道
- 緩沖信道
- 單向信道
- chan實現原理
- 共享內存并發機制
- mutex互斥鎖
- mutex
- mutex原理
- mutex模式
- RWLock
- 使用信道處理競態條件
- WaitGroup
- 工作池
- 并發任務
- once運行一次
- 僅需任意任務完成
- 所有任務完成
- 對象池
- 定時器Timer
- Timer
- Timer實現原理
- 周期性定時器Ticker
- Ticker對外接口
- ticker使用場景
- ticker實現原理
- ticker使用陷阱
- 包和依賴管理
- package
- 依賴管理
- 測試
- 單元測試
- 表格測試法
- Banchmark
- BDD
- 常用架構模式
- Pipe-filter pattern
- Micro Kernel
- JSON
- json-內置解析器
- easyjson
- 性能分析
- gc
- 工具類
- fmt
- Time
- builtin
- unsafe
- sync.pool
- atomic
- flag
- runtime
- strconv
- template