加 Golang學習 QQ群共同學習進步成家立業工作 ^-^ 群號:96933959
變量&常量
變量
變量名由字母、數字、下划線組成,不能以數字開頭。
... var ( A int //默認為0 B string //默認為"" C bool //默認為false Name string = "suoning" Age = 18 // 自動識別類型 ) func main() { sex := "man" //等價於 var sex string = "man" var girlfriend string //聲明變量 girlfriend = "Dawn" //變量賦值 Name := "Nick" //正確 //girlfriend := "Jenny" //錯誤的!不能申明兩次 ... }
常量
常量使用const 修飾,代表永遠是只讀的,不能修改。
常量中的數據類型只可以是布爾型、數字型(整數型、浮點型和復數)和字符串型。
語法:const identifier [type] = value,其中type可以省略。
iota
iota,特殊常量,可以認為是一個可以被編譯器修改的常量。
在每一個const關鍵字出現時,被重置為0,然后再下一個const出現之前,每出現一次iota,其所代表的數字會自動增加1。
... const ( A = 'a' //97 B = iota //1 C //2 D = "nick" //"nick" iota += 1 E //"nick" iota += 1 F = 618 //618 iota +=1 G //618 iota +=1 H = iota //7 I //8 ) ...
基本數據類型
布爾型
布爾型的值只可以是常量 true 或者 false。
var a bool var a bool = true var a = false
數字類型
int:
- uint8(無符號 8 位整型 (0 到 255))
- uint16(無符號 16 位整型 (0 到 65535))
- uint32(無符號 32 位整型 (0 到 4294967295))
- uint64(無符號 64 位整型 (0 到 18446744073709551615))
- int8(有符號 8 位整型 (-128 到 127))
- int16(有符號 16 位整型 (-32768 到 32767))
- int32(有符號 32 位整型 (-2147483648 到 2147483647))
- int64(有符號 64 位整型 (-9223372036854775808 到 9223372036854775807))
float:
- float32(IEEE-754 32位浮點型數)
- float64(IEEE-754 64位浮點型數)
- complex64(32 位實數和虛數)
- complex128(64 位實數和虛數)
其它:
- byte(類似 uint8)
- rune(類似 int32)
- uint(32 或 64 位)
- int(與 uint 一樣大小)
- uintptr(無符號整型,用於存放一個指針)
類型轉換
package main func main() { var a int var b int32 var c int64 a = 15 //b = a + a // compiler error b = int32(a + a) // ok b = b + 5 // ok: 5 is a constant c = c + 5 // ok }
字符類型
存儲為ascii碼
var a byte = 'a' fmt.Println(a) //97
字符串類型
字符串表示兩種方式:
- 雙引號
- `` (反引號,不轉義)
string底層就是一個byte的數組
string本身是不可變的,因此要改變string中字符,需要如下操作:
str := "hello world" s := []byte(str) s[0] = 'o' str = string(s)
操作符
算術運算符
- + 相加
- - 相減
- * 相乘
- / 相除
- % 求余
- ++ 自增
- -- 自減
關系運算符
- == 檢查兩個值是否相等,如果相等返回 True 否則返回 False。
- != 檢查兩個值是否不相等,如果不相等返回 True 否則返回 False。
- > 檢查左邊值是否大於右邊值,如果是返回 True 否則返回 False。
- < 檢查左邊值是否小於右邊值,如果是返回 True 否則返回 False。
- >= 檢查左邊值是否大於等於右邊值,如果是返回 True 否則返回 False。
- <= 檢查左邊值是否小於等於右邊值,如果是返回 True 否則返回 False。
邏輯運算符
- && 邏輯 AND 運算符。 如果兩邊的操作數都是 True,則條件 True,否則為 False。
- || 邏輯 OR 運算符。 如果兩邊的操作數有一個 True,則條件 True,否則為 False。
- ! 邏輯 NOT 運算符。 如果條件為 True,則邏輯 NOT 條件 False,否則為 True。
位運算符
位運算符對整數在內存中的二進制位進行操作。
- & 按位與運算符"&"是雙目運算符。 其功能是參與運算的兩數各對應的二進位相與。
- | 按位或運算符"|"是雙目運算符。 其功能是參與運算的兩數各對應的二進位相或。
- ^ 按位異或運算符"^"是雙目運算符。 其功能是參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果為1。
- << 左移運算符"<<"是雙目運算符。左移n位就是乘以2的n次方。 其功能把"<<"左邊的運算數的各二進位全部左移若干位,由"<<"右邊的數指定移動的位數,高位丟棄,低位補0。
- >> 右移運算符">>"是雙目運算符。右移n位就是除以2的n次方。 其功能是把">>"左邊的運算數的各二進位全部右移若干位,">>"右邊的數指定移動的位數。
賦值運算符
- = 簡單的賦值運算符,將一個表達式的值賦給一個左值
- += 相加后再賦值 (C += A 等於 C = C + A)
- -= 相減后再賦值 (C -= A 等於 C = C - A)
- *= 相乘后再賦值 (C *= A 等於 C = C * A)
- /= 相除后再賦值 (C /= A 等於 C = C / A)
- %= 求余后再賦值 (C %= A 等於 C = C % A)
- <<= 左移后賦值 (C <<= 2 等於 C = C << 2)
- >>= 右移后賦值 (C >>= 2 等於 C = C >> 2)
- &= 按位與后賦值 (C &= 2 等於 C = C & 2)
- ^= 按位異或后賦值 (C ^= 2 等於 C = C ^ 2)
- |= 按位或后賦值 (C |= 2 等於 C = C | 2)
流程控制
If/else & for & range
注意 else if / else 位置
if condition1 { } else if condition2 { } else if condition3 { } else { }
for循環條件沒有小括號
for i := 0; i < 10; i++ { }
死循環
for true { } 可以簡寫為: for { }
range
for i, v := range str { }

package main import ( "fmt" ) func ran(str string) { for i, v := range str { fmt.Printf("index[%d] val[%c] len[%d]\n", i, v, len([]byte(string(v)))) } } func main() { ran("Love, 索寧") } 輸出結果: index[0] val[L] len[1] index[1] val[o] len[1] index[2] val[v] len[1] index[3] val[e] len[1] index[4] val[,] len[1] index[5] val[ ] len[1] index[6] val[索] len[3] index[9] val[寧] len[3]
switch case
Go里面switch默認相當於每個case最后帶有break,匹配成功后不會自動向下執行其他case,而是跳出整個switch。
switch var { case var1: case var2: case var3: default: }

func sw(num int) { switch num { case 1, 2, 3: fmt.Printf("%s in 1,2,3\n", num) case 4, 5, 6: fmt.Printf("%s in 4,5,6\n", num) fallthrough case 7, 8, 9: fmt.Printf("%s big 789\n", num) default: fmt.Printf("default...\n") } }

func sw2(num int) { switch { case num > 0 && num < 4: fmt.Printf("%s in 1,2,3\n", num) case num > 4 && num < 7: fmt.Printf("%s in 4,5,6\n", num) fallthrough case num > 7 && num < 10: fmt.Printf("%s big 789\n", num) default: fmt.Printf("default...\n") } }

func sw3() { switch num := 5; { case num > 0 && num < 4: fmt.Printf("%s in 1,2,3\n", num) case num > 4 && num < 7: fmt.Printf("%s in 4,5,6\n", num) fallthrough case num > 7 && num < 10: fmt.Printf("%s big 789\n", num) default: fmt.Printf("default...\n") } }
fallthrough
可以使用fallthrough強制執行后面的case代碼。

package main import "fmt" func main() { switch { case false: fmt.Println("The integer was <= 4") fallthrough case true: fmt.Println("The integer was <= 5") fallthrough case false: fmt.Println("The integer was <= 6") fallthrough case true: fmt.Println("The integer was <= 7") case false: fmt.Println("The integer was <= 8") fallthrough default: fmt.Println("default case") } } 運行結果: The integer was <= 5 The integer was <= 6 The integer was <= 7
label & goto
label要寫在for循環的開始而不是結束的地方。直接break退出到指定的位置。
func lab() { LABLE: for i := 0; i < 10; i++ { for true { i++ if i == 6 { break LABLE } fmt.Println(i) } } }
goto語句可以跳轉到本函數內的某個標簽
func got() { i := 0 HERE: fmt.Println(i) i++ if i == 5 { return } goto HERE }
select
select與switch類似,不過select有較多限制。
每個case語句里必須是一個channel操作;
select { case ch <- 0: //如果0寫入,則進行該case case <- ch: //如果讀到數據,則進行該case default: //如果上面的都沒有成功,則進入default處理 }

package main import ( "fmt" "time" ) /* 隨機向ch中寫入一個0或者1的過程,當然這是個死循環。 */ func main() { ch := make(chan int, 1) for { select { case ch <- 0: case ch <- 1: } i := <-ch fmt.Println(i) time.Sleep(time.Second) } }

package main import ( "fmt" "time" ) /* channel超時處理 一直沒有從ch中讀取到數據,但從timeout中讀取到了數據 */ func main() { ch := make(chan bool) timeout := make(chan bool, 1) go func() { time.Sleep(time.Second*2) timeout <- true }() select { case <- ch: fmt.Println("This is ch.") case <- timeout: fmt.Println("This is timeout.") } }
函數
Go 語言最少有個 main() 函數。
函數聲明告訴了編譯器函數的名稱,返回類型,和參數。
不支持重載,一個包不能有兩個名字一樣的函數。
func function_name( [parameter list] ) [return_types] {
函數體
}
命名返回值的名字(return可以不指定變量):

func add(a, b int) (c int) { c = a + b return }

func calc(a, b int) (sum int, avg int) { sum = a + b avg = (a +b)/2 return }
_標識符,用來忽略返回值:

func calc(a, b int) (sum int, avg int) { sum = a + b avg = (a +b)/2 return } func main() { sum, _ := calc(100, 200) }
函數也是一種類型,一個函數可以賦值給變量
package main import "fmt" //申明一個函數類型 type add_func func(int, int) int func add(a, b int) int { return a + b } func operator(op add_func, a int, b int) int { return op(a, b) } func main() { c := add fmt.Println(c) //0x1087050 sum := operator(c, 1, 2) fmt.Println(sum) //300 }
可變參數
其中arg是一個slice,我們可以通過arg[index]依次訪問所有參數;通過len(arg)來判斷傳遞參數的個數。
0個或多個參數 func add(arg…int) int { }
1個或多個參數 func add(a int, arg…int) int { }
2個或多個參數 func add(a int, b int, arg…int) int { }

package main import ( "fmt" ) //返回值指定為sum變量,默認會return這個變量 func add(a int, b int, arg ...int) (sum int) { sum = a + b for i := 0; i < len(arg); i++ { sum += arg[i] } return } func concat(s string, arg ...string) string { str := s for i := 0; i < len(arg); i++ { str += arg[i] } return str } func main() { sum := add(1, 2, 3, 4, 5, 6, 7) fmt.Println(sum) //28 str := concat("nick", " ", "and", " ", "dawn", ".") fmt.Println(str) //nick and dawn. }
main & init & defer
main & init
Go程序會自動調用init()和main(),所以你不需要在任何地方調用這兩個函數。
defer
- 當函數返回時,執行defer語句;
- 多個defer語句,按先進后出的方式執行;
- defer語句中的變量,在defer聲明時確定變量。
- 觸發異常也會走defer語句。

package main import "fmt" //聲明defer時,變量i就為0 func test1() { i := 0 defer fmt.Println(i) i++ return } //棧,先進先出 func test2() { for i := 0; i < 5; i++ { defer fmt.Printf("->%d", i) } } func main() { test1() test2() } 輸出: 0 ->4->3->2->1->0

package main import ( "fmt" ) func main() { defer_call() } func defer_call() { defer func() { fmt.Println("打印前") }() defer func() { fmt.Println("打印中") }() defer func() { fmt.Println("打印后") }() panic("觸發異常") } /* 打印后 打印中 打印前 panic: 觸發異常 */
作用域
- 在函數內部聲明的變量叫做局部變量,生命周期僅限於函數內部。
- 在函數外部聲明的變量叫做全局變量,生命周期作用於整個包,如果是大寫的,則作用於整個程序。

package main import "fmt" var name string func main() { name = "Nick" fmt.Println(name) f1() } func f1() { name := "Dawn" fmt.Println(name) f2() } func f2() { fmt.Println(name) } 輸出: Nick Dawn Nick
匿名函數 & 閉包
匿名函數
匿名函數是由一個不帶函數名的函數聲明和函數體組成。
package main func main() { f := func(x, y int) int { return x + y } f(1,1) ch := make(chan int) func (ch chan int) { ch <- 9 }(ch) }
閉包
閉包是一個函數和與其相關的引用環境組合而成的實體。
函數可以存儲到變量中作為參數傳遞給其它函數,能夠被函數動態的創建和返回。
func Adder() func(int) int { var x int return func(d int) int { x += d return x } } f := Adder() fmt.Println(f(1)) //1 fmt.Println(f(10)) //11 fmt.Println(f(100)) //111

package main import ( "fmt" "strings" ) func makeSuffix(suffix string) func(string) string { return func(name string) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main() { f1 := makeSuffix(".png") fmt.Println(f1("name1")) //name1.png fmt.Println(f1("name2")) //name2.png f2 := makeSuffix(".jpg") fmt.Println(f2("name1")) //name1.jpg fmt.Println(f2("name2")) //name2.jpg }
值傳遞 & 引用傳遞
無論是值傳遞,還是引用傳遞,傳遞給函數的都是變量的副本;
值傳遞是值的拷貝,引用傳遞是地址的拷貝;
一般來說,地址拷貝更為高效。而值拷貝取決於拷貝的對象大小,對象越大,則性能越低。
map、slice、chan、指針、interface默認以引用的方式傳遞。
new 內置函數 用來分配內存,主要用來分配值類型,比如int、struct,返回的是指針;
make 內置函數 用來分配內存,主要用來分配引用類型,比如chan、map、slice。
程序初始化與執行過程

指針類型(&*)
普通類型,變量存的就是值,也叫值類型;
指針類型,變量存的是一個地址,這個地址存的才是值。
變量是一種占位符,用於引用計算機內存地址;
Go 語言的取地址符是 &,放到一個變量前使用就會返回相應變量的內存地址。
獲取指針類型所指向的值,使用:*。
一個指針變量可以指向任何一個值的內存地址它指向那個值的內存地址。
申明如下:
var age *int //指向整型 var height *float32 //指向浮點型
當一個指針被定義后沒有分配到任何變量時,它的值為 nil。
nil 指針也稱為空指針。
栗子
package main import "fmt" func main() { var ptr *int num := 100 ptr = &num fmt.Println(ptr) //0xc42000e1f8 fmt.Println(*ptr) //100 *ptr = 200 fmt.Println(num) //200 }
package main import "fmt" func change(num *int) { fmt.Println(num) //0xc42000e1f8 fmt.Println(*num) //100 *num = 1000 fmt.Println(num) //0xc42000e1f8 fmt.Println(*num) //1000 } func main() { num := 100 fmt.Println(&num) //0xc42000e1f8 change(&num) fmt.Println(&num) //0xc42000e1f8 fmt.Println(num) //1000 }