常量是一種特殊的變量,被初始化之後就無法再改變。
Go 語言中,常量的類型只能是布爾型,數字型(整型、浮點型和複數)和字符串型。
常量可以使用關鍵字 const 來定義,定義格式為 const variable [type] = value。
1const m string = "abc" // 顯示聲明
2const n = "xyz" // 隱式聲明
常量的值必須在編譯時能確定,給常量賦值時可以涉及表達式的計算,但計算值必須能在編譯時確定。
1const m = 2 / 3 // 正確
2const n = getValue() // 錯誤,編譯時自定義函數屬於未知,無法用於常量賦值,但可以使用內置函數,如 len()
常量也可以用於枚舉:
Unknown 表示未知性別,Female 表示女性,Male 表示男性。
1const (
2 Unknown = 0
3 Female = 1
4 Male = 2
5)
itoa 是一個特殊的常量,itoa 在 const 關鍵字出現時被重置為 0;const 中每多聲明一行常量,itos 會自動加 1( itoa 可理解成常量的行索引)。如:
1const (
2 a = itoa // a 的值為 0
3 b = itoa // b 的值為 1
4 c = itoa // c 的值為 2
5)
當聲明多行常量時,若不指定常量的值和類型,那麼該常量的類型和值與上一個常量相同。
1const (
2 a = 100
3 b // b 的值為 100
4)
1const (
2 a = itoa // a 的值為 0
3 b // b 的值為 1
4 c // c 的值為 2
5)
我們來看一個例子:
1package main
2
3import "fmt"
4
5func main() {
6 const (
7 a = iota // 0
8 b // 1
9 c // 2
10 d = "ha" // iota += 1
11 e // "ha" iota += 1
12 f = 100 // iota +=1
13 g // 100 iota += 1
14 h = iota // 7
15 i // 8
16 )
17
18 fmt.Println(a, b, c, d, e, f, g, h, i)
19}
運行結果為:
10 1 2 ha ha 100 100 7 8
我們在上一篇文章 Golang 入門筆記-02-Go 語言基本語法和結構 中已闡述了變量的定義方式和注意點。
2.2 值類型和引用類型基本數據類型 int,float,bool,string 以及數組和結構體都屬於值類型,值類型的變量直接存儲值,內存通常在棧中分配。
指針,slice,map 和 chan 等都屬於引用類型,引用類型的變量存儲的是地址,內存通常在堆中分配,比棧擁有更大的空間,通過 GC 進行回收。
Go 語言中可以通過 & 來獲取變量的內存地址,如獲取變量 i 的內存地址:&i。
若一個變量被引用,那麼當該變量發生變化時,該變量的引用都會指向被修改後的內容。
我們來看一個例子:
1package main
2
3import "fmt"
4
5func main() {
6 a := 1
7 c := &a // c 的類型為 *int,是變量 a 的引用
8 d := &a // d 的類型為 *int,是變量 a 的引用
9 fmt.Println("c = ", c, ", d = ", d)
10 fmt.Println("*c = ", *c, ", *d = ", *d)
11
12 a = 2 // 修改 a 的值,引用類型變量 c 與 d 會指向修改後的 a
13 fmt.Println("c = ", c, ", d = ", d)
14 fmt.Println("*c = ", *c, ", *d = ", *d)
15}
運行結果為:
1c = 0xc00000a0a0 , d = 0xc00000a0a0
2*c = 1 , *d = 1
3c = 0xc00000a0a0 , d = 0xc00000a0a0
4*c = 2 , *d = 2
定義 bool 類型的變量:
1var b1 bool = true
2var b2 = true
3b3 := false
bool 類型的值只能是 true 或 false。
可以通過運算符等於 == 或不等於 != 得到 bool 類型的值,如:
1var m = 6
2
3m == 5 // false
4m == 6 // true
5m != 5 // true
6m != 6 // false
還可以通過與邏輯運算符非 !,與 && ,或 || 結合得到 bool 類型的值。
Go 語言中,&& 和 || 具有快捷性質,當 && 和 || 左邊的表達式已經能夠決定整個表達式結果時(當 && 左邊值為 false 或 || 左邊值為 true 時),右邊的表達式不會被執行。因此,我們應儘量將複雜的表達式放在右邊,以減少運算量。
1var T = true
2var F = false
3
4!T // false
5!F // true
6T && T // true
7T && F // false
8F && T // false
9F && F // false
10T || T // true
11T || F // true
12F || T // true
13F || F // false
Go 語言中字符串的字節使用 UTF-8 編碼表示 Unicode 文本,所以 Go 語言字符串是變寬字符序列,這和其他語言(Java,Python)完全不同,後者為定寬字符序列。Go 語言這樣做,就不需要對 UTF-8 字符集文本進行編碼和解碼,節省了內存和空間。
Go 語言支持以下兩種形式定義字符串:解釋字符串
解釋字符串用雙引號包裹起來,如:
1package main
2
3import "fmt"
4
5func main() {
6 var s = "abc\nefg"
7
8 fmt.Println("s: ", s)
9}
運行結果為:
1s: abc
2efg
同時,以下這些字符串將被轉義:
\n:換行符
\r:回車符
\t:tab 鍵
\u 或 \U:Unicode 字符
\\:反斜槓
非解釋字符串
非解釋字符串用反引號包裹起來,與解釋字符串不同,非解釋字符串會按照用戶的輸入形式保存起來,且 \n,\r 等字符串不會被轉義, 如:
1package main
2
3import "fmt"
4
5func main() {
6 var s = `
7 Hello World!\n
8 I am a gopher!
9 `
10 fmt.Println("s: ", s)
11}
運行結果為:
1s:
2 Hello World!\n
3 I am a gopher!
Go 語言中的字符串和 C/C++ 不一樣,不以 \0 表示結尾,而是以長度限定。
長度為 0 的字符串是空字符串 ""。
可以通過 len() 來獲取字符串的長度,如:
1s := "abc"
2length := len(s) // 3
可以通過數組下標獲取字符串中的字符,如:
1s := "abc"
2s1 := s[0] // 'a'
3s2 := s[len(s)-1] // 'c'
可以通過 + 進行字符串拼接 :
1s1 := "Hello "
2s2 := "World"
3s3 := s1 + s2
4
5s4 := "He" + "llo "
6s4 += "World" // 等同於 s4 = s4 + "World"
7
8s5 := "Hello " + // 多行拼接 + 必須放在上一行
9 "World"
用 + 來拼接字符串效率並不是太高,後續我們會講到使用字節緩衝 bytes.Buffer 來進行字符串拼接。
3.1.3 整型Go 語言中比較常見的整型有 int,uint,uintptr,這三個類型的長度和計算機架構有關。
還有與計算機架構無關的整型,從命名上就可以看出:
整型
int8(-128 ~ 127)
int16(-32768 ~ 32767)
int32(-2,147,483,648 ~ 2,147,483,647)
int64(-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807)
無符號整型
uint8(0 ~ 255)
uint16(0 ~ 65,535)
uint32(0 ~ 4,294,967,295)
uint64(0 ~ 18,446,744,073,709,551,615)
可以通過前綴 0 來表示八進位數:0666;通過前綴 0x 表示十六進位數:0xEF;通過 e 來表示 10 的冪次方:1e2:100,3.14e20:3.14 x (10^20)。
注意 :
int 和 int64 不是同類型,以下代碼是錯誤的:
1var a int = 1
2var b int64 = 2
3a = b // a 與 b 屬於不同類型變量,無法賦值
聲明整型變量時,若不指定類型,默認為 int :
1var a = 1 // int
2b := 1 // int
短變量聲明中可以通過指定類型來定義變量:
1b := uint64(2) // uint64
Go 語言中沒有 float 類型,僅存在兩種浮點類型(遵循 IEEE-754 標準):
浮點型默認值為 0.0。
float32 精確到小數點後 7 位,float64 精確到小數點後 15 位。
對於精確度要求較高的運算,應使用 float64 類型。
3.1.5 複數Go 語言中,複數有兩種類型:
complex64(32 位實數和虛數)
complex128(64 位實數和虛數)
我們可以通過 a + bi 的方式來定義複數,a 表示實部,b 表示虛部,如:
1var c complex64 = 1 + 2i
可以通過 real(c) 和 imag(c) 函數來獲取複數變量 c 的實部和虛部。
複數和其他數據類型一樣,支持 == 和 != 的比較,比較時需注意複數精確度。若對性能沒有太高要求,建議使用精確度更高的 complex128 。
3.2 運算符3.2.1 位運算注意:位運算是對給定數所對應的二進位數進行運算。
二元運算符
按位與 &
當兩位都為 1 時,值才為 1,否則值為 0:
11 & 1 // 1
21 & 0 // 0
30 & 1 // 0
40 & 0 // 0
按位或 |
兩位中有一個為 1,值即為 1,否則,值為 0:
11 | 1 // 1
21 | 0 // 1
30 | 1 // 1
40 | 0 // 0
按位異或 ^
若兩位的值相同,值為 0,若不相同,值為 1:
11 ^ 1 // 0
21 ^ 0 // 1
30 ^ 1 // 1
40 ^ 0 // 0
按位置零 &^
p &^ q:當 q 為 0 時,則結果為 p;否則,結果為 0:
11 &^ 0 // 1
21 &^ 1 // 0
一元運算符
位左移 <<
用法:a << n,將 a 左移 n 位,右側部分用 0 填充。相當於 a 乘以 2 的 n 次方。
11 << 10 // 2^10 -> 1KB
21 << 20 // 2^20 -> 1MB
31 << 30 // 2^30 -> 1GB
位右移 >>
用法:a >> n,將 a 右移 n 位,左側部分用 0 填充。相當於 a 除以 2 的 n 次方。
18 >> 1 // 8/(2^1) -> 4
21024 >> 6 // 1024/(2^6) -> 16
邏輯非 !
! 運算符表示取反,當 T 為 true 時,!T 值為 false,反之則為 true。
邏輯與 &&
&& 運算符表示邏輯與,當 && 兩邊表達式都為 true 時,值才為 true,否則為 false。
邏輯或 ||
|| 運算符表示邏輯或,當 || 有一邊表達式為 true 時,值就為 true,僅當兩邊表達式都為 false,值才為 false。
3.2.3 算術運算符+ 加法
1a := 1
2b := 2
3c := a + b // 3
- 減法
1a := 2
2b := 1
3c := a - b // 1
* 乘法
1a := 1
2b := 2
3c := a * b // 2
/ 除法
1a := 2
2b := 1
3c := a / b // 2
% 求餘
1a := 6
2b := 5
3c := a % b // 1
++ 自增
變量自增 1。
1a := 1
2a++ // 2,相當於 a = a + 1
-- 自減
變量自減 1。
1a := 2
2a-- // 1,相當於 a = a - 1
注意:
Go 語言中,++ 和 -- 僅僅是語句,不能作為表達式,以下寫法是錯誤的:
1a := 1
2b := a++ // 錯誤,++ 不能作為表達式
運算符的優先級
1優先級 運算符
2 7 ^ !
3 6 * / % << >> & &^
4 5 + - | ^
5 4 == != < <= >= >
6 3 <-
7 2 &&
8 1 ||
Go 語言為我們提供了指針功能,但不能進行指針運算。Go 語言允許我們控制特定數據結構,分配數量和內存訪問,有利於構建強大的網絡應用。
指針在 Go 語言中被拆分為兩個概念:
4.1 指針地址和指針類型一個指針變量可以指向任意一個值的地址,它所指向的值的內存地址在 32 位和 64 位計算機上分別佔用 4 個字節和 8 個字節。(佔用字節大小與值大小無關)
當一個指針被定義後,若沒有被分配變量,則它的默認值為 nil(相當于于 C 語言中的 null)。
每一個變量都有一個地址,Go 語言中可以通過取址符號 & 來獲取一個變量的內存地址:
1a := 1
2p := &a // &a 表示取 a 的地址
我們通過一個例子來了解地址:
1package main
2
3import "fmt"
4
5func main() {
6 a := 1 // 定義 int 型變量 a
7 s := "abc" // 定義 string 型變量 s
8
9 fmt.Printf("%p %p", &a, &s) // 列印 a 和 s 的內存地址
10}
運行結果為:
10xc00000a0a0 0xc00003c1f0
任意變量都有地址,指針變量保存的就是地址。
4.2 獲取指針指向的值可以對指針使用 * 操作符來獲取指針指向的值,例如我們定義了指針變量 p,可以通過 *p 來獲取指針 p 指向的值:
1package main
2
3import "fmt"
4
5func main() {
6 a := 1 // 定義 int 型變量 a
7 p := &a // 定義指針變量 p,p 保存 a 的地址
8
9 fmt.Printf("p -> type %T\n", p) // p 的類型
10 fmt.Printf("p -> value %p\n", p) // p 的值
11 fmt.Printf("*p -> type %T\n", *p) // *p 的類型
12 fmt.Printf("*p -> value %v\n", *p) // *p 的值
13}
運行結果為:
1p -> type *int
2p -> value 0xc00000a0a0
3*p ->type int
4*p -> value 1
指針也可以修改值:
1package main
2
3import "fmt"
4
5func swap(a, b *int) {
6 t := *a // t 保存 a 指向的變量的值
7
8 *a = *b // 將 b 指向的變量的值賦值給 a 指向的變量
9
10 *b = t // 將 t 賦值給 b 指向的變量
11}
12
13func main() {
14 m, n := 1, 2
15
16 swap(&m, &n)
17
18 fmt.Println(m, n)
19}
運行結果為:
12 1
* 指針操作符作為右值時,如 t := *a ,表示取指針指向的變量的值;作為左值時,如 *b = t,表示指針指向的變量。
我們還可以通過 new 函數來創建指針變量,如:
1s := new(string)
2*s = "Hello World"
new() 函數可以創建一個對應類型的指針,創建時會分配內存,創建完成後指針指向默認值。
注意: