Go值類型和引用類型+作用域+空白標識符+常量
值類型和引用類型
所有像 int、float、bool 和 string 這些基本類型都屬於值類型,使用這些類型的變數直接指向存在記憶體中的值:

當使用等號 = 將一個變數的值賦值給另一個變數時,如:j = i,實際上是在記憶體中將 i 的值進行了拷貝:

你可以通過 &i 來獲取變數 i 的記憶體地址,例如:0xf840000040(每次的地址都可能不一樣)。
值類型變數的值存儲在堆中。
記憶體地址會根據機器的不同而有所不同,甚至相同的程式在不同的機器上執行後也會有不同的記憶體地址。因為每台機器可能有不同的存儲器布局,並且位置分配也可能不同。
更複雜的數據通常會需要使用多個字,這些數據一般使用引用類型保存。
一個引用類型的變數 r1 存儲的是 r1 的值所在的記憶體地址(數字),或記憶體地址中第一個字所在的位置。

這個記憶體地址稱之為指針,這個指針實際上也被存在另外的某一個值中。
同一個引用類型的指針指向的多個字可以是在連續的記憶體地址中(記憶體布局是連續的),這也是計算效率最高的一種存儲形式;也可以將這些字分散存放在記憶體中,每個字都指示了下一個字所在的記憶體地址。
當使用賦值語句 r2 = r1 時,只有引用(地址)被複制。
如果 r1 的值被改變了,那麼這個值的所有引用都會指向被修改後的內容,在這個例子中,r2 也會受到影響。
簡短形式,使用 := 賦值操作符
我們知道可以在變數的初始化時省略變數的類型而由系統自動推斷,聲明語句寫上 var 關鍵字其實是顯得有些多餘了,因此我們可以將它們簡寫為 a := 50 或 b := false。
a 和 b 的類型(int 和 bool)將由編譯器自動推斷。
這是使用變數的首選形式,但是它只能被用在函數體內,而不可以用於全局變數的聲明與賦值。使用操作符 := 可以高效地創建一個新的變數,稱之為初始化聲明。
注意事項
如果在相同的程式碼塊中,我們不可以再次對於相同名稱的變數使用初始化聲明,例如:a := 20 就是不被允許的,編譯器會提示錯誤 no new variables on left side of :=,但是 a = 20 是可以的,因為這是給相同的變數賦予一個新的值。
如果你在定義變數 a 之前使用它,則會得到編譯錯誤 undefined: a。
如果你聲明了一個局部變數卻沒有在相同的程式碼塊中使用它,同樣會得到編譯錯誤,例如下面這個例子當中的變數 a:
實例
package main
import “fmt”
func main() {
var a string = “abc”
fmt.Println(“hello, world”)
}
嘗試編譯這段程式碼將得到錯誤 a declared but not used。
此外,單純地給 a 賦值也是不夠的,這個值必須被使用,所以使用
fmt.Println(“hello, world”, a)
會移除錯誤。
但是全局變數是允許聲明但不使用的。 同一類型的多個變數可以聲明在同一行,如:
var a, b, c int
多變數可以在同一行進行賦值,如:
var a, b int var c string a, b, c = 5, 7, “abc”
上面這行假設了變數 a,b 和 c 都已經被聲明,否則的話應該這樣使用:
a, b, c := 5, 7, “abc”
右邊的這些值以相同的順序賦值給左邊的變數,所以 a 的值是 5, b 的值是 7,c 的值是 “abc”。
這被稱為 並行 或 同時 賦值。
如果你想要交換兩個變數的值,則可以簡單地使用 a, b = b, a,兩個變數的類型必須是相同。
空白標識符 _ 也被用於拋棄值,如值 5 在:_, b = 5, 7 中被拋棄。
_ 實際上是一個只寫變數,你不能得到它的值。這樣做是因為 Go 語言中你必須使用所有被聲明的變數,但有時你並不需要使用從一個函數得到的所有返回值。
並行賦值也被用於當一個函數返回多個返回值時,比如這裡的 val 和錯誤 err 是通過調用 Func1 函數同時得到:val, err = Func1(var1)。
Go語言變數的作用域
作用域為已聲明標識符所表示的常量、類型、變數、函數或包在源程式碼中的作用範圍。
Go 語言中變數可以在三個地方聲明:
函數內定義的變數稱為局部變數
函數外定義的變數稱為全局變數
函數定義中的變數稱為形式參數
接下來讓我們具體了解局部變數、全局變數和形式參數。
局部變數
在函數體內聲明的變數稱之為局部變數,它們的作用域只在函數體內,參數和返回值變數也是局部變數。
局部變數不是一直存在的,它只在定義它的函數被調用後存在,函數調用結束後這個局部變數就會被銷毀。
以下實例中 main() 函數使用了局部變數 a, b, c:
package main
import “fmt”
func main() {
/* 聲明局部變數 */
var a, b, c int
/* 初始化參數 */
a = 10
b = 20
c = a + b
fmt.Printf (“結果: a = %d, b = %d and c = %d\n”, a, b, c)
}
以上實例執行輸出結果為:
結果: a = 10, b = 20 and c = 30
全局變數
在函數體外聲明的變數稱之為全局變數,全局變數可以在整個包甚至外部包(被導出後)使用。
全局變數可以在任何函數中使用,以下實例演示了如何使用全局變數:
package main
import “fmt”
//聲明全局變數
var c int
func main() {
//聲明局部變數
var a, b int
//初始化參數
a = 3
b = 4
c = a + b
fmt.Printf(“a = %d, b = %d, c = %d\n”, a, b, c)
}
運行結果如下所示:
a = 3, b = 4, c = 7
Go 語言程式中全局變數與局部變數名稱可以相同,但是函數內的局部變數會被優先考慮。實例如下:
package main
import “fmt”
/* 聲明全局變數 */
var g int = 20
func main() {
/* 聲明局部變數 */
var g int = 10
fmt.Printf (“結果: g = %d\n”, g)
}
以上實例執行輸出結果為:
結果: g = 10
形式參數
在定義函數時函數名後面括弧中的變數叫做形式參數(簡稱形參)。形式參數只在函數調用時才會生效,函數調用結束後就會被銷毀,在函數未被調用時,函數的形參並不佔用實際的存儲單元,也沒有實際值。
形式參數會作為函數的局部變數來使用。實例如下:
package main
import “fmt”
/* 聲明全局變數 */
var a int = 20;
func main() {
/* main 函數中聲明局部變數 */
var a int = 10
var b int = 20
var c int = 0
fmt.Printf(“main()函數中 a = %d\n”, a);
c = sum( a, b);
fmt.Printf(“main()函數中 c = %d\n”, c);
}
/* 函數定義-兩數相加 */
func sum(a, b int) int {
fmt.Printf(“sum() 函數中 a = %d\n”, a);
fmt.Printf(“sum() 函數中 b = %d\n”, b);
return a + b;
}
以上實例執行輸出結果為:
main()函數中 a = 10
sum() 函數中 a = 10
sum() 函數中 b = 20
main()函數中 c = 30
Go語言「:=」
go語言中可以使用「:=」為一個新的變數完成聲明以及初始化的工作,如下例所示:
i := 1
等價於:
var i = 1
要注意語句中沒有變數類型,不是var i int = 1。
「:=」不能重新聲明一個已經聲明過的變數,如下例所示:
package main import “fmt” func main() { var i = 1 i := 2 fmt.Println(i) }
編譯結果:
C:/Go\bin\go.exe run C:/Work/go_work/Hello.go # command-line-arguments .\Hello.go:8: no new variables on left side of :=
錯誤的原因是變數被重複聲明了。
但使用「:=」為多個變數賦值時,如果引入了至少一個新的變數,編譯是可以通過的,如下例所示:
package main import “fmt” func main() { var i = 1 i, err := 2, false fmt.Println(i, err) }
編譯執行:
C:/Go\bin\go.exe run C:/Work/go_work/Hello.go 2 false
要注意這個時候,並沒有重新聲明「i」變數,只是為之前聲明的「i」變數賦值。
「:=」只能用在函數體中。它的一個重要用途是用在「if」,「for」和「switch」語句的初始化,使變數成為一個「臨時變數」,也就是變數的作用域僅限於這條語句。如下例所示:
package main import “fmt” func main() { for j := 3; j <= 5; j++ { fmt.Println(j) } fmt.Println(j) }
編譯結果:
C:/Go\bin\go.exe run C:/Work/go_work/Hello.go # command-line-arguments .\Hello.go:11: undefined: j
「j」的聲明作用域僅限於「for」語句,所以編譯第二個列印語句會出錯。
GO語言中的空白標識符
空白符在語法上是用_表示。
_ 實際上是一個只寫變數,被用於拋棄值,你不能得到它的值。
這樣做是因為 Go 語言中你必須使用所有被聲明的變數,但有時你並不需要使用從一個函數得到的所有返回值。如值 5 在:_, b = 5, 7 中被拋棄。
在函數返回值中被拋棄的案例如下:
package main
import “fmt”
func main() {
_, e, f := number()
fmt.Println(e, f)
}
func number()(int, int, string) {
a, b, c := 1, 2, “curry”
return a, b, c
}
輸出:
2 curry
Go 語言常量
常量是一個簡單值的標識符,在程式運行時,不會被修改的量。
常量中的數據類型只可以是布爾型、數字型(整數型、浮點型和複數)和字元串型。
常量的定義格式:
const identifier [type] = value
你可以省略類型說明符 [type],因為編譯器可以根據變數的值來推斷其類型。
- 顯式類型定義: const b string = “abc”
- 隱式類型定義: const b = “abc”
多個相同類型的聲明可以簡寫為:
const c_name1, c_name2 = value1, value2
以下實例演示了常量的應用:
實例
package main
import “fmt”
func main() {
const LENGTH int = 10
const WIDTH int = 5
var area int
const a, b, c = 1, false, “str” //多重賦值
area = LENGTH * WIDTH
fmt.Printf(“面積為 : %d”, area)
println()
println(a, b, c)
}
以上實例運行結果為:
面積為 : 50 1 false str
常量還可以用作枚舉:
const ( Unknown = 0 Female = 1 Male = 2 )
數字 0、1 和 2 分別代表未知性別、女性和男性。
常量可以用len(), cap(), unsafe.Sizeof()函數計算表達式的值。常量表達式中,函數必須是內置函數,否則編譯不過:
實例
package main
import “unsafe”
const (
a = “abc”
b = len(a)
c = unsafe.Sizeof(a)
)
func main(){
println(a, b, c)
}
以上實例運行結果為:
abc 3 16
iota
iota,特殊常量,可以認為是一個可以被編譯器修改的常量。
iota 在 const關鍵字出現時將被重置為 0(const 內部的第一行之前),const 中每新增一行常量聲明將使 iota 計數一次(iota 可理解為 const 語句塊中的行索引)。
iota 可以被用作枚舉值:
const ( a = iota b = iota c = iota )
第一個 iota 等於 0,每當 iota 在新的一行被使用時,它的值都會自動加 1;所以 a=0, b=1, c=2 可以簡寫為如下形式:
const ( a = iota b c )
iota 用法
實例
package main
import “fmt”
func main() {
const (
a = iota //0
b //1
c //2
d = “ha” //獨立值,iota += 1
e //”ha” iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢復計數
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
以上實例運行結果為:
0 1 2 ha ha 100 100 7 8
再看個有趣的的 iota 實例:
實例
package main
import “fmt”
const (
i=1<<iota
j=3<<iota
k
l
)
func main() {
fmt.Println(“i=”,i)
fmt.Println(“j=”,j)
fmt.Println(“k=”,k)
fmt.Println(“l=”,l)
}
以上實例運行結果為:
i= 1 j= 6 k= 12 l= 24
iota 表示從 0 開始自動加 1,所以 i=1<<0, j=3<<1(<< 表示左移的意思),即:i=1, j=6,這沒問題,關鍵在 k 和 l,從輸出結果看 k=3<<2,l=3<<3。
簡單表述:
- i=1:左移 0 位,不變仍為 1。
- j=3:左移 1 位,變為二進位 110,即 6。
- k=3:左移 2 位,變為二進位 1100,即 12。
- l=3:左移 3 位,變為二進位 11000,即 24。
註:<<n==*(2^n)。