安裝
官網(wǎng)下載地址:https://golang.org/dl/ ,根據(jù)系統(tǒng)平臺(tái)下載對(duì)應(yīng)資源包,安裝或解壓到對(duì)應(yīng)目錄,然后配置環(huán)境變量
GOROOT
:go安裝(非默認(rèn)安裝目錄時(shí)需要配置)
PATH
: go安裝目錄/bin(必需)
GOPATH
:go項(xiàng)目目錄(命令安裝依賴時(shí)需要配置)
GOPATH
目錄約定有三個(gè)子目錄:bin
編譯后生成的可執(zhí)行文件,pkg
編譯后生成的文件敞恋,src
存放源代碼
認(rèn)知
命名
- 駝峰式命名胃珍,不要使用下劃線
- 根據(jù)首字母的大小寫來確定可以訪問的權(quán)限,無論是方法名顽分、常量、變量名還是結(jié)構(gòu)體的名稱施蜜,如果首字母大寫卒蘸,則可以被其他的包訪問(公有);如果首字母小寫翻默,則只能在本包中使用(私有)
變量
- 用
var
聲明變量缸沃,函數(shù)內(nèi)可用:=
簡(jiǎn)化聲明和初始化 -
_
忽略返回值 -
const
用于定義常量
包導(dǎo)入
import (
"test/pkg1" //默認(rèn)導(dǎo)入方式,調(diào)用包內(nèi)函數(shù):pkg1.func1()
p2 "test/pkg2" //別名方式修械,調(diào)用包內(nèi)函數(shù):p2.func2()
. "test/pkg3" //省略前綴趾牧,調(diào)用包內(nèi)函數(shù):func3()
_ "test/pkg4" //僅執(zhí)行包內(nèi)init()函數(shù),無法調(diào)用包內(nèi)其他函數(shù)
)
常使用命令行 go get xxx
可以從指定源上面下載或者更新指定的代碼和依賴肯污,并對(duì)他們進(jìn)行編譯和安裝
函數(shù)
-
main()
一個(gè)包內(nèi)只能有一個(gè)main函數(shù) -
init()
一個(gè)包內(nèi)可以存在多個(gè)init函數(shù)翘单,導(dǎo)入時(shí)都會(huì)被調(diào)用吨枉,在main函數(shù)之前被執(zhí)行 - 常見寫法
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
代碼塊,在函數(shù)內(nèi)部使用哄芜,會(huì)在函數(shù)結(jié)束或返回時(shí)被調(diào)用(先進(jìn)后出)
func main() {
fmt.Println(0)
for i := 1; i <= 3; i++ {
defer fmt.Println(i)
}
fmt.Println(4)
// 0 4 3 2 1
}
類型
-
string
字符串類型貌亭,相當(dāng)于[]byte
-
interface{}
空接口類型,可以放入其他所有類型认臊,一般用來定義未知類型的變量
類型轉(zhuǎn)換
Golang是靜態(tài)類型的編程語言圃庭,所有數(shù)據(jù)的類型在編譯期確定。 Golang中即使是底層存的是同一個(gè)類型失晴,聲明的類型不一樣剧腻,也要強(qiáng)制轉(zhuǎn)換才能互用。
- Go語言類型轉(zhuǎn)換基本格式如:
type_name(expression)
如果強(qiáng)制轉(zhuǎn)換一些無法轉(zhuǎn)換的類型师坎,將會(huì)報(bào)錯(cuò)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 //連接執(zhí)行下一個(gè)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
//基于計(jì)數(shù)器的迭代 for i := 0; i < 10; i++ { fmt.Println(i) } //基于條件判斷的迭代 i := 10 for i > 0 { fmt.Println(i) i-- } //無限循環(huán) 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) }
數(shù)組 Array
數(shù)組是類型相同的元素的集合
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
切片是一個(gè) 長(zhǎng)度可變的數(shù)組恕酸,是對(duì)數(shù)組一個(gè)連續(xù)片段的引用,是一個(gè)引用類型
//1.基于現(xiàn)有數(shù)組創(chuàng)建切片
var arr1 [10]int = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
//array[start:end] 不包含end胯陋,其中start和end在為第一或最后一個(gè)時(shí)可以省略
slice1 := arr1[3:7]
slice2 := arr1[:4]
//2蕊温,直接創(chuàng)建切片
//make([]T, length, capacity)
slice3 := make([]int, 2, 2)
slice4 := []int{1, 2, 3, 4}
//切片可以通過append,copy函數(shù)來進(jìn)行追加和復(fù)制
slice4 = append(slice4, 5)
slice4 = append(slice4, 6)
slice5 := make([]int, len(slice4), cap(slice4)*2)
copy(slice5, slice4)
//在追加時(shí)遏乔,當(dāng)切片的len超過cap時(shí)义矛,切片的內(nèi)存會(huì)重新分配,返回的新的切片不再指向原數(shù)組盟萨,
//新切片的cap會(huì)變?yōu)樵瓉淼?倍
集合map
Map是一種無序的鍵值對(duì)的集合
Map通過 key 來快速檢索數(shù)據(jù)凉翻,key 類似于索引, 指向數(shù)據(jù)的值
//聲明定義
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")
}
自定義結(jié)構(gòu)體 Struct
結(jié)構(gòu)體由一系列屬性組成捻激,每個(gè)屬性都有自己的類型和值
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} //此寫法需要全部參數(shù)賦值
fmt.Println(a4)
// &{3 4 5} 引用類型
}
- 初始化后制轰, 字段默認(rèn)為該類型的空值
- 直接定義A類型或使用
A{}
來完成初始化返回的是值類型,
使用new()
或&A{}
初始化返回的是引用類型胞谭, 兩者不一樣
struct 嵌套與函數(shù)擴(kuò)展
package main
import (
"fmt"
)
type A struct {
a1 int
a2 int
}
type B struct {
A // B嵌套A垃杖,B將可以使用A的參數(shù)和方法
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兩個(gè)struct不在同一個(gè)包中丈屹, B將無法讀寫A中的私有字段和方法
接口 Interface
接口是一些方法的集合调俘, 只要實(shí)現(xiàn)了接口對(duì)應(yīng)的方法, 就等于實(shí)現(xiàn)了此接口,
因此golang中所有的類型都實(shí)現(xiàn)了空接口interface{}
旺垒,在一些函數(shù)中如果傳入?yún)?shù)類型不固定彩库,都可以使用interface{}
代替?zhèn)魅耄诰唧w使用該參數(shù)時(shí)仍需要將類型轉(zhuǎn)換回對(duì)應(yīng)類型先蒋。
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實(shí)現(xiàn)了a()和b(),所以可以初始化T1類型賦值給Ai接口類型
test1.a() // 輸出 a1
// var test2 Ai
// test2 = new(T2) // 無法編譯通過骇钦,T2沒有實(shí)現(xiàn)Ai接口的b(),所以無法初始化T1類型賦值給Ai接口類型
//定義接口類型變量
var test3, test4 Ci
test3 = new(T1) // T1實(shí)現(xiàn)了c(),所以可以初始化T1類型賦值給Ci接口類型
test3.c() // 輸出 c1
test4 = new(T2) // T2實(shí)現(xiàn)了c()竞漾,所以可以初始化T2類型賦值給Ci接口類型
test4.c() // 輸出 c2
//定義接口類型變量
var test5 AC
test5 = new(T1) // T1實(shí)現(xiàn)了a()司忱、b()皇忿、c(),所以可以初始化T1類型賦值給AC接口類型
test5.a() // a1
test5.b() // b1
test5.c() // c1
// var test6 AC
// test6 = new(T2) // 無法編譯通過沫换,T2沒有實(shí)現(xiàn)AC接口的b()贷屎,所以無法初始化T2類型賦值給AC接口類型
}
接口類型轉(zhuǎn)換
- 斷言x.(T) 湿颅, x是接口類型,如
str, ok := value.(string)
- Type switch繁扎,詳情見上述
控制語句 > switch-case > demo4
協(xié)程 Goroutines
協(xié)程是在一個(gè)線程中模擬多個(gè)協(xié)程并發(fā)執(zhí)行
沒有優(yōu)先級(jí)、 非搶占式調(diào)度: 任務(wù)不能主動(dòng)搶占時(shí)間片
每個(gè)協(xié)程都有自己的堆棧和局部變量
golang調(diào)度學(xué)習(xí): http://www.reibang.com/p/9db2dcb1ccb7
給調(diào)用函數(shù)使用 go 關(guān)鍵字即可將函數(shù)放到協(xié)程中執(zhí)行
package main
import (
"fmt"
)
func f1() {
fmt.Println(1)
}
func f2() {
fmt.Println(2)
}
func main() {
go f1()
f2()
}
反復(fù)執(zhí)行上面代碼糊闽,我們會(huì)發(fā)現(xiàn)很大概率上只會(huì)輸出2梳玫,極小概率會(huì)同時(shí)輸出1、2右犹,因?yàn)樵谑褂?code>go激活 f1 后提澎,主線程直接就往下執(zhí)行 f2 ,最后主線程退出念链,其中 f1 還沒來的及執(zhí)行打印就已經(jīng)結(jié)束了盼忌。
如何固定讓主線程的 f2 在 f1 執(zhí)行只會(huì)在執(zhí)行呢?
-
sync.WaitGroup
package main import ( "fmt" "sync" ) func f1() { fmt.Println(1) wg.Done() // 完成一個(gè)計(jì)數(shù)器掂墓,計(jì)數(shù) -1 } func f2() { fmt.Println(2) } var wg sync.WaitGroup func main() { wg.Add(1) // 添加一個(gè)等待計(jì)數(shù)器谦纱,計(jì)數(shù) +1 go f1() wg.Wait() // 主線程堵塞等待,在計(jì)數(shù)器為0時(shí)才會(huì)向下執(zhí)行 f2() }
-
信道Channel
可以通過它們發(fā)送類型化的數(shù)據(jù)在協(xié)程之間通信君编, 可以避開一些內(nèi)存共享導(dǎo)致的坑通過通道傳遞的數(shù)據(jù)同一時(shí)間只有一個(gè)協(xié)程可以訪問跨嘉。
在默認(rèn)無緩沖通道情況下兑燥,如果通道中沒有數(shù)據(jù)亮瓷, 那 <-ch 數(shù)據(jù)取出就阻塞,package main import ( "fmt" ) func f1() { fmt.Println(1) ch <- 1 //將數(shù)據(jù)傳入ch } func f2() { fmt.Println(2) } var ch chan int //聲明信道和信道數(shù)據(jù)的類型 func main() { ch = make(chan int) //初始化,分配內(nèi)存 go f1() <-ch //取出ch中的數(shù)據(jù)吃嘿,若ch中沒有數(shù)據(jù)祠乃,則ch會(huì)堵塞,直到ch中有數(shù)據(jù)傳入 f2() }
如果通道已有數(shù)據(jù), 那 ch<-value 數(shù)據(jù)存入就阻塞贪嫂。有緩沖通道是指在未填滿指定數(shù)量之前不會(huì)堵塞寺庄, 數(shù)量滿了之后就會(huì)像無緩沖一樣堵塞, 直到通道數(shù)據(jù)被取出ch := make(chan ch_type) // 無緩沖信道,ch_type可以為任意類型 ch := make(chan ch_type, buf_num) //有緩沖信道力崇,buf_num為緩沖數(shù)量 ch <- 123 // 將數(shù)據(jù)傳入信道 // 無緩沖時(shí)斗塘,信道將會(huì)堵塞,直到ch取出數(shù)據(jù) // 有緩沖時(shí)亮靴,信道將無限制傳入馍盟,直到傳入數(shù)量大于緩沖數(shù)量,才會(huì)堵塞茧吊,直到ch取出數(shù)據(jù)才會(huì)繼續(xù)傳入 data := <- ch // 將數(shù)據(jù)從信道中取出贞岭,并賦給data變量 // 信道將會(huì)堵塞八毯,直到ch有數(shù)據(jù)傳入
Http web服務(wù)
使用Golang的標(biāo)準(zhǔn)庫(kù) net/http可以搭建一個(gè)Go Web服務(wù)器
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: 用戶請(qǐng)求的信息,用來解析用戶的請(qǐng)求信息瞄桨,包括post话速、get、file等信息
Response: 服務(wù)器需要反饋給客戶端的信息
Handler: 處理請(qǐng)求和生成返回信息的處理邏輯
在編寫項(xiàng)目代碼時(shí)芯侥,有時(shí)會(huì)覺得每個(gè)函數(shù)都要寫(w http.ResponseWriter, req *http.Request)
泊交,就會(huì)覺得很麻煩,所以做了以下測(cè)試
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")
}
錯(cuò)誤類型
- 實(shí)現(xiàn)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包函數(shù)
package main import ( "errors" "fmt" ) func main() { var err error err = errors.New("test error") fmt.Println(err) //test error }
- 使用fmt包函數(shù)
package main import ( "fmt" ) func main() { var err error err = fmt.Errorf("%s error", "test") fmt.Println(err) //test error }
- error可以使用在函數(shù)返回
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 }