安裝
官網下載地址:https://golang.org/dl/ ,根據系統平臺下載對應資源包,安裝或解壓到對應目錄,然后配置環境變量
GOROOT
:go安裝(非默認安裝目錄時需要配置)
PATH
: go安裝目錄/bin(必需)
GOPATH
:go項目目錄(命令安裝依賴時需要配置)
GOPATH
目錄約定有三個子目錄:bin
編譯后生成的可執行文件,pkg
編譯后生成的文件,src
存放源代碼
認知
命名
- 駝峰式命名,不要使用下劃線
- 根據首字母的大小寫來確定可以訪問的權限,無論是方法名、常量、變量名還是結構體的名稱,如果首字母大寫,則可以被其他的包訪問(公有);如果首字母小寫,則只能在本包中使用(私有)
變量
- 用
var
聲明變量,函數內可用:=
簡化聲明和初始化 -
_
忽略返回值 -
const
用于定義常量
包導入
import (
"test/pkg1" //默認導入方式,調用包內函數:pkg1.func1()
p2 "test/pkg2" //別名方式,調用包內函數:p2.func2()
. "test/pkg3" //省略前綴,調用包內函數:func3()
_ "test/pkg4" //僅執行包內init()函數,無法調用包內其他函數
)
常使用命令行 go get xxx
可以從指定源上面下載或者更新指定的代碼和依賴,并對他們進行編譯和安裝
函數
-
main()
一個包內只能有一個main函數 -
init()
一個包內可以存在多個init函數,導入時都會被調用,在main函數之前被執行 - 常見寫法
package main
import (
"errors"
"fmt"
)
func f1(num1 int, num2 int) int {
return num1 + num2
}
func f2(num1 int, num2 int) (rs int) {
rs = num1 + num2
return
}
func f3(num1 int, num2 int) (rs int, err error) {
if num2 == 0 {
err = errors.New("num2 is empty.")
return
}
rs = int(num1 / num2)
return rs, nil
}
func main() {
num1 := 5
num2 := 3
if rs, err := f3(num1, num2); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%d / %d = %d", num1, num2, rs)
}
}
-
defer
代碼塊,在函數內部使用,會在函數結束或返回時被調用(先進后出)
func main() {
fmt.Println(0)
for i := 1; i <= 3; i++ {
defer fmt.Println(i)
}
fmt.Println(4)
// 0 4 3 2 1
}
類型
-
string
字符串類型,相當于[]byte
-
interface{}
空接口類型,可以放入其他所有類型,一般用來定義未知類型的變量
類型轉換
Golang是靜態類型的編程語言,所有數據的類型在編譯期確定。 Golang中即使是底層存的是同一個類型,聲明的類型不一樣,也要強制轉換才能互用。
- Go語言類型轉換基本格式如:
type_name(expression)
如果強制轉換一些無法轉換的類型,將會報錯func main() { var a int = 1 fmt.Printf("%T", a) // int fmt.Printf("%T", float64(a)) // float64 }
控制語句
-
if - else
// demo1 if a == 1 { fmt.Println(1) } else if a == 2 { fmt.Println(2) } else { fmt.Println(3) } // demo2 if b := a; b == 1 { fmt.Println(1) } else { fmt.Println(2) }
-
switch - case
var a int = 1 // demo1 switch a { case 1: fmt.Println(1) case 2: fmt.Println(2) fallthrough //連接執行下一個case case 3, 4, 5: fmt.Println(345) default: fmt.Println(0) } // demo2 switch b := a; b { case 1: fmt.Println(1) default: fmt.Println(0) } // demo3 switch { case a == 1: fmt.Println(1) case a == 2, a == 3: fmt.Println(23) default: fmt.Println(0) } // demo4 接口類型判斷 t := func() interface{} { return 1 }() switch t.(type) { case int: fmt.Println("int") case string: fmt.Println("string") }
-
for
//基于計數器的迭代 for i := 0; i < 10; i++ { fmt.Println(i) } //基于條件判斷的迭代 i := 10 for i > 0 { fmt.Println(i) i-- } //無限循環 for { fmt.Println(1) } for true { fmt.Println(1) } //for-range迭代器 //map,slice,array,channel for key, value := range array1 { fmt.Printf("%d => %v \n", key, value) }
-
select - case
可以用來處理channel操作select { case <-ch1: fmt.Println(1) case <-ch2: fmt.Println(2) case <-time.After(time.Minute): fmt.Println(9) default: fmt.Println(0) }
數組 Array
數組是類型相同的元素的集合
var arr1 [5]int
arr2 := [5]int{1, 2, 3}
arr3 := [...]int{1, 2, 3}
arr4 := [...]int{3: 3, 5: 5}
arr5 := [4][2]int{{1, 2}, {3, 4}}
fmt.Println(arr1, "\n", arr2, "\n", arr3, "\n", arr4, "\n", arr5)
// [0 0 0 0 0]
// [1 2 3 0 0]
// [1 2 3]
// [0 0 0 3 0 5]
// [[1 2] [3 4] [0 0] [0 0]]
切片 Slice
切片是一個 長度可變的數組,是對數組一個連續片段的引用,是一個引用類型
//1.基于現有數組創建切片
var arr1 [10]int = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
//array[start:end] 不包含end,其中start和end在為第一或最后一個時可以省略
slice1 := arr1[3:7]
slice2 := arr1[:4]
//2,直接創建切片
//make([]T, length, capacity)
slice3 := make([]int, 2, 2)
slice4 := []int{1, 2, 3, 4}
//切片可以通過append,copy函數來進行追加和復制
slice4 = append(slice4, 5)
slice4 = append(slice4, 6)
slice5 := make([]int, len(slice4), cap(slice4)*2)
copy(slice5, slice4)
//在追加時,當切片的len超過cap時,切片的內存會重新分配,返回的新的切片不再指向原數組,
//新切片的cap會變為原來的2倍
集合map
Map是一種無序的鍵值對的集合
Map通過 key 來快速檢索數據,key 類似于索引, 指向數據的值
//聲明定義
var map1 map[string]int //nil
//map1 = make(map[string]int)
map1 = map[string]int{"a": 1, "b": 2}
//添加
map1["c"] = 3
//刪除
delete(map1, "b")
//是否存在
if _, ok := map1["c"]; ok {
fmt.Println("C is exist")
} else {
fmt.Println("C is not exist")
}
自定義結構體 Struct
結構體由一系列屬性組成,每個屬性都有自己的類型和值
package main
import (
"fmt"
)
type A struct {
b int
c int
d int
}
func main() {
var a1 A
a1.b = 2
fmt.Println(a1)
// {2 0 0} 值類型
a2 := new(A)
a2.b = 2
fmt.Println(a2)
// &{2 0 0} 引用類型
a3 := A{b: 3, c: 4}
fmt.Println(a3)
// {3 4 0} 值類型
a4 := &A{3, 4, 5} //此寫法需要全部參數賦值
fmt.Println(a4)
// &{3 4 5} 引用類型
}
- 初始化后, 字段默認為該類型的空值
- 直接定義A類型或使用
A{}
來完成初始化返回的是值類型,
使用new()
或&A{}
初始化返回的是引用類型, 兩者不一樣
struct 嵌套與函數擴展
package main
import (
"fmt"
)
type A struct {
a1 int
a2 int
}
type B struct {
A // B嵌套A,B將可以使用A的參數和方法
b1 int
b2 int
}
func (a *A) Af() {
fmt.Println("a func")
}
func (b *B) Bf() {
fmt.Println("b func")
}
func main() {
b := new(B)
b.A.a1 = 1
b.a2 = 2
b.b1 = 3
fmt.Println(b) // &{{1 2} 3 0}
b.Af() //a func
b.Bf() //b func
}
如果A、 B兩個struct不在同一個包中, B將無法讀寫A中的私有字段和方法
接口 Interface
接口是一些方法的集合, 只要實現了接口對應的方法, 就等于實現了此接口,
因此golang中所有的類型都實現了空接口interface{}
,在一些函數中如果傳入參數類型不固定,都可以使用interface{}
代替傳入,但在具體使用該參數時仍需要將類型轉換回對應類型。
package main
import (
"fmt"
)
type Ai interface {
a()
b()
}
type Ci interface {
c()
}
type AC interface { // 接口嵌套
Ai
Ci
}
type T1 struct{}
type T2 struct{}
func (t *T1) a() {
fmt.Println("a1")
}
func (t *T1) b() {
fmt.Println("b1")
}
func (t *T1) c() {
fmt.Println("c1")
}
func (t *T2) a() {
fmt.Println("a2")
}
func (t *T2) c() {
fmt.Println("c2")
}
func main() {
//定義接口類型變量
var test1 Ai
test1 = new(T1) // T1實現了a()和b(),所以可以初始化T1類型賦值給Ai接口類型
test1.a() // 輸出 a1
// var test2 Ai
// test2 = new(T2) // 無法編譯通過,T2沒有實現Ai接口的b(),所以無法初始化T1類型賦值給Ai接口類型
//定義接口類型變量
var test3, test4 Ci
test3 = new(T1) // T1實現了c(),所以可以初始化T1類型賦值給Ci接口類型
test3.c() // 輸出 c1
test4 = new(T2) // T2實現了c(),所以可以初始化T2類型賦值給Ci接口類型
test4.c() // 輸出 c2
//定義接口類型變量
var test5 AC
test5 = new(T1) // T1實現了a()、b()、c(),所以可以初始化T1類型賦值給AC接口類型
test5.a() // a1
test5.b() // b1
test5.c() // c1
// var test6 AC
// test6 = new(T2) // 無法編譯通過,T2沒有實現AC接口的b(),所以無法初始化T2類型賦值給AC接口類型
}
接口類型轉換
- 斷言x.(T) , x是接口類型,如
str, ok := value.(string)
- Type switch,詳情見上述
控制語句 > switch-case > demo4
協程 Goroutines
協程是在一個線程中模擬多個協程并發執行
沒有優先級、 非搶占式調度: 任務不能主動搶占時間片
每個協程都有自己的堆棧和局部變量
golang調度學習: http://www.lxweimin.com/p/9db2dcb1ccb7
給調用函數使用 go 關鍵字即可將函數放到協程中執行
package main
import (
"fmt"
)
func f1() {
fmt.Println(1)
}
func f2() {
fmt.Println(2)
}
func main() {
go f1()
f2()
}
反復執行上面代碼,我們會發現很大概率上只會輸出2,極小概率會同時輸出1、2,因為在使用go
激活 f1 后,主線程直接就往下執行 f2 ,最后主線程退出,其中 f1 還沒來的及執行打印就已經結束了。
如何固定讓主線程的 f2 在 f1 執行只會在執行呢?
-
sync.WaitGroup
package main import ( "fmt" "sync" ) func f1() { fmt.Println(1) wg.Done() // 完成一個計數器,計數 -1 } func f2() { fmt.Println(2) } var wg sync.WaitGroup func main() { wg.Add(1) // 添加一個等待計數器,計數 +1 go f1() wg.Wait() // 主線程堵塞等待,在計數器為0時才會向下執行 f2() }
-
信道Channel
可以通過它們發送類型化的數據在協程之間通信, 可以避開一些內存共享導致的坑通過通道傳遞的數據同一時間只有一個協程可以訪問。
在默認無緩沖通道情況下,如果通道中沒有數據, 那 <-ch 數據取出就阻塞,package main import ( "fmt" ) func f1() { fmt.Println(1) ch <- 1 //將數據傳入ch } func f2() { fmt.Println(2) } var ch chan int //聲明信道和信道數據的類型 func main() { ch = make(chan int) //初始化,分配內存 go f1() <-ch //取出ch中的數據,若ch中沒有數據,則ch會堵塞,直到ch中有數據傳入 f2() }
如果通道已有數據, 那 ch<-value 數據存入就阻塞。有緩沖通道是指在未填滿指定數量之前不會堵塞, 數量滿了之后就會像無緩沖一樣堵塞, 直到通道數據被取出ch := make(chan ch_type) // 無緩沖信道,ch_type可以為任意類型 ch := make(chan ch_type, buf_num) //有緩沖信道,buf_num為緩沖數量 ch <- 123 // 將數據傳入信道 // 無緩沖時,信道將會堵塞,直到ch取出數據 // 有緩沖時,信道將無限制傳入,直到傳入數量大于緩沖數量,才會堵塞,直到ch取出數據才會繼續傳入 data := <- ch // 將數據從信道中取出,并賦給data變量 // 信道將會堵塞,直到ch有數據傳入
Http web服務
使用Golang的標準庫 net/http可以搭建一個Go Web服務器
package main
import (
"fmt"
"log"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
http.HandleFunc("/", hello)
err := http.ListenAndServe("127.0.0.1:80", nil)
if err != nil {
log.Fatal("ListenAndServe:", err.Error())
}
}
Request: 用戶請求的信息,用來解析用戶的請求信息,包括post、get、file等信息
Response: 服務器需要反饋給客戶端的信息
Handler: 處理請求和生成返回信息的處理邏輯
在編寫項目代碼時,有時會覺得每個函數都要寫(w http.ResponseWriter, req *http.Request)
,就會覺得很麻煩,所以做了以下測試
package main
import (
"fmt"
"log"
"net/http"
)
type CTX struct {
ResponseWriter http.ResponseWriter
Request *http.Request
}
var Ctx *CTX
func Handle(url string, f func()) {
http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
Ctx = &CTX{
ResponseWriter: w,
Request: r,
}
f()
})
}
func main() {
Handle("/", home)
Handle("/hello", hello)
Handle("/ping", new(Ping).ping)
err := http.ListenAndServe("127.0.0.1:80", nil)
if err != nil {
log.Fatal("ListenAndServe:", err.Error())
}
}
////////////////////////////////////////////
func home() {
fmt.Fprintln(Ctx.ResponseWriter, "home")
}
func hello() {
fmt.Fprintln(Ctx.ResponseWriter, "hello world")
}
type Ping struct{}
func (p *Ping) ping() {
fmt.Fprintln(Ctx.ResponseWriter, "pong")
}
錯誤類型
- 實現error接口
type error interface{ Error() string }
package main import ( "fmt" ) type Myerror struct { msg string } func (e *Myerror) Error() string { return e.msg } func main() { var err error err = &Myerror{"test error"} fmt.Println(err) //test error }
- 使用errors包函數
package main import ( "errors" "fmt" ) func main() { var err error err = errors.New("test error") fmt.Println(err) //test error }
- 使用fmt包函數
package main import ( "fmt" ) func main() { var err error err = fmt.Errorf("%s error", "test") fmt.Println(err) //test error }
- error可以使用在函數返回
package main import ( "errors" "fmt" ) func main() { if v, err := div(2, 0); err != nil { fmt.Println("error:", err) } else { fmt.Println("2/0 = ", v) } } func div(a, b int) (rs int, err error) { if b == 0 { err = errors.New("division by zero") return } return a / b, nil }