go
什麼是 Go
是一門併發支持、垃圾加收的編譯型系統編程語言,具有靜態編譯語言的高性能和動態語言的,主要特點如下
- 類型安全和內存安全
- 以非常直觀和極低代價的方案實現高併發
- 高效的垃圾回收機制
- 快速編譯(同時解決了 C 語言中頭文件太多的問題)
- UTF-8 支持
安裝
- 源碼安裝
- 標準包安裝
- 第三方安裝
標準包安裝,一路下一步。安裝完後,會自動添加如下環境變量,使用go env查看
GOARCH="amd64" # cup架構
GOBIN="/usr/local/go/bin"
GOCACHE="/Users/willzhao/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/willzhao/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/3q/z9zfyn256718rwvw071831tr0000gn/T/go-build200798760=/tmp/go-build -gno-record-gcc-switches -fno-common"
根據約定,GOPATH 下需要建立 3 個目錄
- bin (存放編譯後生成的可執行文件,這個文件裏生成的文件一般拿到上一級去執行,要不可能會提示找不到依賴包)
- pkg (存放編譯後生成的包文件)
- src (存放項目源碼)
常用命令
- go get 獲取遠程包(如果在 git 上獲取要先安裝 git,如果是在 google code 上下載遠程包的話需要安裝 hg
- go run 直接運行程序
- go build 測試編譯,檢查是否有編譯錯誤(什麼樣的源碼可以編譯成可執行文件呢? package 名稱爲 main 的,如果不是就不會生成可執行文件)
- go fmt 格式化源碼(部分 ide 在保存時自動調用)
- go install 編譯包文件並編譯整個程序(會找 main 的包,在 bin 文件夾中生成可執行文件)
go install是建立在 GOPATH 上的,無法在獨立的目錄裏使用go installGOPATH下的 bin 目錄放置的是使用go install生成的可執行文件,可執行文件的名稱來自於編譯時的包名go install輸出目錄始終爲 GOPATH 下的 bin 目錄,無法使用-o 附加參數進行自定義GOPATH下的 pkg 目錄放置的是編譯期間的中間文件- go test 運行測試文件,名字形如
xxx_test.go,正常的文件不在命名成這樣 - godoc 查看文檔 godoc fmt,可以建立本地的網站來查看 godoc -http:8080
hello world
package main
import (
"fmt"
)
func main(){
fmt.Print("Hello World! 你好世界!") // 注意Print
}
// 好多的教程沒提,這裏最好是使用idear的開發環境來做 它可以設置一個project path
// 目錄結構 如上面所提src pkg bin
// 通過查看idear的run configuration確認,編譯命令執行如下
/*
GOROOT=/usr/local/go #gosetup
GOPATH=/Users/willzhao/Documents/Develop/go/hello_world:/usr/local/go #gosetup
/usr/local/go/bin/go build -i -o /Users/willzhao/Documents/Develop/go/hello_world/bin/HelloWorldRun main #gosetup 注意main是包名
/Users/willzhao/Documents/Develop/go/hello_world/bin/HelloWorldRun #gosetup
*/
Go 的內置關鍵字(25 個)
25 個,並且都是小寫
| break | default | func | interface | select |
| case | defer | go | map | struct |
| chan | else | goto | package | switch |
| const | fallthrough | if | range | type |
| continue | for | import | return | var |
註釋
- // 單行
/* */多行
程序一般結構
- Go 程序是通過 package 來組織(與 Python 類似) 必須放在非註釋的第一行。
- 只有 package 名稱爲 main 的包可以包含 main 函數,一般建議 package 的名稱和目錄名一致 package 名稱不要使用駝峯標記法,使用下劃線
- 別名調用 如
import std "fmt" - 省略調用 如
import . "fmt"使用時可以直接使用Print來調用(不建議這樣使用,導致功能不知道是哪個包導入的) - 省略調用和別名調用不能同時使用
- 一個可執行程序有且僅有一個 main 包
- 通過 import 關鍵字來導入其它非 main 包,導入多個包時可以用一個 import 加括號,導入包如果沒有用,編譯時會報錯
- 通過
const關鍵字來進行常量定義 - 通過函數體外部使用 var 關鍵字來進行全局變量的聲明與賦值
- 通過 type 關鍵字來進行結構 struct 或接口 interface 聲明
- 通過 func 關鍵字來進行函數聲明
可見性規則
Go 語言中,使用大小寫來決定常量、變量、類型、接口、結構或函數是否可以被外部包所調用,可以快速確定是否調用錯誤
- 首字母小寫即爲 private
- 首字母大寫即爲 public
聲明多個常量變量
// 常量定義
const (
PI = 3.14
const1 = "1"
const2 = 2
const3 = 3
)
// 全局變量聲明與賦值,不能在函數體內聲明
var (
name = "gopher"
name1 = "1"
name2 = 2
name3 = 3
)
// 一般類型聲明
type (
newType int
type1 float32
type2 string
type3 byte
)
數據類型
- 布爾型 bool
- 長度 1 個字節
- 取值範圍 true, false
- 不能用其它值代表 true 或 false
- 整型 int/uint
- 根據平臺可能爲 32 或 64 位
- 8 位整型 int8/uint8
- 長度 1 字節
- -128~127/0~255
- 字節型 byte(uint8 別名)
- 16 位整型 int16/uint16
- 長度 2 字節
- -32768~32767/0~65536
- 32 位整型 int32(rune)/uint32
- 長度 4 字節
- 64 位整型 int64/uint64
- 浮點型 float32/float64
- 長度 4/8 字節
- 小數位精確到 7/15 小數位
- 複數 complex64/complex128
- 長度 8/16 字節
- 足夠保存指針的 32 位或 64 位整數型 uintptr
- 其它值類型
- array/struct/string
- 引用類型
- slice/map/chan
- 接口類型 inteface
- 函數類型 func
以上類型的默認類型
// 數值爲0 bool是false string爲"" 數組則根據它所指定的類型
// math.MinInt8 math有一些常量來定義值範圍邊界
package main
import (
"fmt"
"math"
)
type (
byte int8
rune int32
)
單個變量的聲明與賦值
- 變量聲明格式:var 變量名 變量類型
- 變量賦值格式:變量名稱=表達式
- 聲明的同時賦值:var 變量名稱 變量類型 = 表達式 這裏的變量類型可以省略,編譯器可以推斷出來類型
package main
import (
"fmt
)
func main(){
var b int;
// 或 變量聲明與賦值的最簡寫法
a:=1
}
多個變量的聲明與賦
- 全局變量的聲明可以使用 var()的方式進和簡寫
- 全局變量的聲明不可以省略 var,但可以並行方式 即 var ()
- 所有變量都可以使用類型推斷
- 局部變量不可以使用 var()的方式簡寫,只能使用並行方式
package main
import (
"fmt"
)
var (
aaa = "hello"
sss,bbb= 1,2
// ccc := 3 這方式是錯的
)
func Test(){
// 多變量聲明
var a,b,c,d int
// 多個變量的賦值
a,b,c,d = 1,2,3,4
// 多個變量聲明的同時賦值
var e,f,g,h int = 5,6,7,8
// 省略變量類型,由系統推斷
var i,j,k,l = 9,10,11,12
// 多個變量聲明與賦值的最簡寫法
i,m,n,o :=13,14,15,16
aa,_,cc,dd = 1,2,3,4 //_是忽略符號
}
類型轉換:不存在隱式轉換,所有類型轉換必須顯式聲明,轉換必須在兩咱兼容的之間
類型與變量
package main
import (
"fmt"
"strconv"
)
func main(){
var a int = 65
b:=string(a)
fmt.Print(b) // A
b = strconv.Itoa(a)
a = strconv.Atoi(b)
fmt.Print(b)
fmt.Print(a)
}
常量定義
- 常量的值在編譯時就已經確定
- 常量定義格式與變量基本相同
- 等號右側必須是常量或者常量表達式
- 常量表達式中的函數必須是內置函數
const a int = 1
const b = 'A'
const (
c = a
d = b
)
const (
e = 1
f
g
)
// f g 等一上行定義的值
const (
a,b = 1,"2"
c,d
)
// c = 1 d="2" 格式要一致(與上一行)
// iota 從第一個計數
const (
a = 'A'
b = iota
c = 'B'
d = iota
)
// 65
// 1
// 66
// 3
// 在每個組中都是從0開始加1的
// 常量不想被外包使用,則定義時不要以大寫母開頭,比如使用劃線或小寫字母如c
// 可以做一個常量枚舉
const (
Monday = iota
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
運算符
- ^ ! (一元)
- * / % \<\< >> & &^
- + - | ^ (二元)
- == != \< \<= >= >
- \<- (專門用於 channel)
- 位運算符 & | ^異或 &^(位清空 and not)
- &&
- ||
這裏說明一下^&
// 計算x&^y 首先我們先換算成2進制 0000 0010 &^ 0000 0100 = 0000 0010 如果ybit位上的數是0則取x上對應位置的值, 如果ybit位上爲1則取結果位上取0
package main
import (
"fmt"
)
func main(){
var x,y int = 2,4
fmt.Print(x^&y) // 2
}
實現一個計算機存儲單位枚舉
const (
_ = iota // B = iota
KB float64 = 1 << (iota*10)
MB float64 = 1 << (iota*10)
GB float64 = 1 << (iota*10)
TB float64 = 1 << (iota*10)
PB float64 = 1 << (iota*10)
EB float64 = 1 << (iota*10)
ZB float64 = 1 << (iota*10)
YB float64 = 1 << (iota*10)
)
指針
Go 雖然保留了指針,但與其它編程語言不同的是,在 Go 當中不支持指針運算”->“運算符,而直接採用"."選擇符來操作指針目標對象的成員
- 操作符”&“取變量地址,使”*“通過指針間接訪問目標對象
- 默認值爲 nill 而 NULL
package main
import (
"fmt"
)
func main(){
a:=1;
var b *int = &a
fmt.Print(*b) // 1
}
遞增遞減
++ —— 在 Go 語言中是不能作爲表達式而是作爲語句,所以只能寫在單獨一行
條件表達式
- if 沒有括號,可以使用初始化語句,並且是塊級作用域
- 左括號必須放在 if 語句同一行
package main
import (
"fmt"
)
func main(){
a := 10
if a :=4; a > 0 {
fmt.Println(a)
}
fmt.Println(a)
}
循環語句 for
只有 for 一個循環語句,但它支持 3 種形式,
- 初始化和步進表達式可以是多個值
- 條件語句每次都會被重新檢查,因此不建議在條件語句中使用函數,儘量提前計算好並以變量或常量代替
- 左大括號必須和條件語句一行
func main(){
a:=1
for {
a++
if a>3 {
break
}
}
fmt.Printf(a)
}
// 2
func main() {
a := 1
for a <= 3 {
a++
}
fmt.Println(a)
}
// 3
func main() {
a := 1
for i := 0;i<3;i++ {
a++
}
fmt.Println(a)
}
switch
- 可以使用任何類型或表達式作爲條件語句
- 不需要寫 break,一旦條件符合自動終止
- 如希望繼續執行下一個 case,需要使用 fallthrough 語句
- 支持一個初始化表達式(可以是並行方式),右側需要跟分號
- 左大括號必須和條件語句在同一行
func main() {
a := 1
switch a {
case 0:
fmt.Println("a=0")
case 1:
fmt.Println("a=1")
}
fmt.Println(a)
}
func main() {
a := 1
switch {
case a>=0:
fmt.Println("a=0")
fallthrough
case a>=1:
fmt.Println("a=1")
default:
fmt.Println("None")
}
fmt.Println(a)
}
func main() {
switch a := 1; { //注意右側的分號,a的作用域範圍只在這個switch塊中
case a>=0:
fmt.Println("a=0")
fallthrough
case a>=1:
fmt.Println("a=1")
}
}
跳轉語句
goto break continue
- 三個語法都可以配合標籤使用
- 標籤名區分大小寫,若不使用會造成編譯錯誤
- break 和 continue 配合標籤可以用於多層循環的跳出,跳出 LABEL 標識的
- goto 是調整執行位置,與其它 2 語句配合標籤的結果並不相同
func main() {
LABEL:
for i:=0;i<10;i++ {
if i>2 {
break LABEL
} else {
fmt.Println(i)
}
}
}
func main() {
LABEL:
for i:=0;i<10;i++ {
for {
fmt.Println(i)
continue LABEL
}
}
}
func gotest() {
i := 0
LABEL:
for i < 10 {
for {
fmt.Println(i)
i++
//goto LABEL
continue LABEL
}
}
}
數組
- 定義
var <varName>[n]<type> = 0 - 長度也是類型的一部分,因此具有不同長度的數組爲不同類型
- 注意區分指向數組的指針和指針數組
- 數組在 Go 中爲值類型
- 數組之間可以使用==或!=進行比較,但不能使用
或 - 可以使用 new 來創建數組,此方法返回一個指向數組的指針
- 支持多維數組
var a [2]int
var b [2]int
var c [2]string
a := [2]{1,2}
b := [20]{19:1} //第20個位置設置成1,其它設置成0值
c := [...]int{1,2,3,4,5} //長度爲5的數組
d := [...]int{0:1,1:2,2:3}
e := [...]int(19:1) //長度20
// 指向數組的指針
var p *[20]int = &e
// 指針數組
x,y := 1,2
a := [...]*int{&x,&y} //保存的是指針
// 類型的不同不能進行直接比較
// 如果用new 創建的數組它返回的就是一個指向數組的指針
p := new([10]int) //p可以直接用索引來操作數組
p[1] = 2
fmt.Println(p)
// 多維數組
a :=[2][3]{
{1,1,1},
{2,2,2},
}
fmt.Println(a)
// 冒泡排序
func main() {
a := [...]int(5,6,3,9)
fmt.Println(a)
num := len(a)
for i:=0;i<num;i++ {
for j:=i+1;j<num;j++ {
if a[i]<a[j] {
tmp:=a[i]
a[i] = a[j]
a[j] = tmp
}
}
}
fmt.Println(a)
}
切片
- 本身並不是數組,它指向底層的數組
- 作爲爲變長數組的替代方案,可以關聯底層數組的局部或全部
- 爲引用類型
- 可以直接創建或從底層數組獲取生成
- 使用 len()獲取元素個數,cap()獲取容量
- 一般使用 make()創建
- 如果多個 slice 指向相同的底層數組,其中一個的值改變會影響全部
make([]T,len,cap),其中 cap 可以省略,則和 len 值相同- len 表示存數的元素個數,cap 表示容量
func main() {
//這樣就是一個slice
var s1 []int
fmt.Println(s1)
a:=[10]int{}
fmt.Println(a)
s2:=a[5:10] //索引開始(包含)結束(不包含)
s3:=a[5:len(a)] //索引開始(包含)結束(不包含)
s4:=a[5:] //索引開始(包含)結束(不包含)
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(s4)
// make
s5 := make([]int,22,1) //參數2是多少個,參數3 初始容量,當容量超出初始容量,它每次都會增加一倍
}
func t1() {
s1:=make([]int,3,10)
fmt.Println(len(s1),cap(s1))
a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
sa:=a[2:5]
fmt.Println(string(sa))
sb:=a[3:5]
fmt.Println(string(sb))
}
- reslice
- reslice 時索引被 slice 的切片爲準
- 索引不可以超過被 slice 切片的容量 cap()值
- 索引越界不會導致底層數組的重新分配而是引發錯誤
- append
- 可以在 slice 尾部追回元素
- 可以將一個 slice 追回在另一個 slice 尾部
- 如果最終長度未超過追回到 slice 容量則返回原始 slice
- 如果超過追回到的 slice 容量則將重新分配數組並拷貝原始數據
- copy
// reslice
a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
sa:=a[2:5] //cde
sb:=sa[1:3] //de
fmt.Println(string(sb))
// append
s1:=make([]int,3,6)
fmt.Printf("%p\n",s1)
s1 = append(s1,1,2,3)
fmt.Printf("%v %p\n",s1,s1)
s1 = append(s1,1,2,3)
fmt.Printf("%v %p\n",s1,s1)
/*
0xc000018120
[0 0 0 1 2 3] 0xc000018120
[0 0 0 1 2 3 1 2 3] 0xc00006e060
*/
// copy
map
- 類似其它語言中的哈希或者字典,以 key-value 形式存儲數據
- key 必須是支持==或!==比較運算類型,不可以是函數,map 或 slice
- map 查找比線性搜索快很多,但比使用索引訪問數據的類型慢 100 倍
- map 使用 make()創建,支持
:=這種簡寫方式 make([keyType]valueType,cap)cap 表示容量,可省略- 超出容量時會自動擴容,但儘量提供一個合理的初始值
- 使用 len()獲取元素個數
- 鍵值對不存在時自動添加,使用 delete()刪除某鍵值對
- 使用用
for range對 map 和 slice 進行迭代操作
func main() {
var m map[int]string
m = make(map[int]string)
// var m map[int]string = make(map[int]string)
// m:= make(map[int]string)
m[1] = "OK"
a = m[1]
delete(m,1)
// 更復雜的map
var m = map[int]map[int]string //m 的value又是一個map
m = make(map[int]map[int]string)
a,ok := m[2][1]
fmt.Println(ok)
if !ok {
m[2] = make(map[int]string)
}
m[2][1] = "GOOD"
a, ok = m[2][1]
fmt.Println(ok)
// for range
for k,v := range map {
}
}
func t1() {
sm:=make([]map[int]string,5) //聲明一個slice 值是map[int]string
for _,v:=range sm {
v=make(map[int]string,1) //v每次都是一個copy,所以不能這樣寫
fmt.Println(v)
}
fmt.Println(sm)
}
func t2() {
sm:=make([]map[int]string,5) //聲明一個slice 值是map[int]string
for i :=range sm {
sm[i]=make(map[int]string,1) //v每次都是一個copy,所以不能這樣寫
sm[i][1] = "OK"
fmt.Println(sm[i])
}
fmt.Println(sm)
}
// 對map的間接排序
對 map 的間接排序
package main
import (
"fmt"
"sort"
)
func main() {
m:=map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"}
s:=make([]int,len(m))
i:=0
for k,_ := range m {
s[i] = k
i++
}
// sort.Ints(s) // 沒有這句,可以看一下打印結構
fmt.Println(s)
}
// map 鍵值對調
func main() {
m1:=map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"}
fmt.Println(m1)
m2:=make(map[string]int)
for k,v := range m1 {
m2[v] = k
}
fmt.Println(m2)
}
函數 function
- Go 函數不支持嵌套,重載,默認參數
- 定義函數使用關鍵字 func,且左大括號不能另起一行
- 函數也可以作爲一種類型使用
- 支持以以下特性
- 無需聲明原型
- 不定長變參
- 多返回值
- 命名返回值參數
- 匿名函數
- 閉包
func A(a ...int)(int,int,int){
// ...不定長可變參
a,b,c = 1,2,4
return //默認返回a b c // 返回參數可以寫成a,b,c int
// 注意傳入的時候都是拷貝 一種情況是拷貝值 另一種情況是拷貝地址(還是指向同一個位置)
}
// 匿名函數
func main(){
a:=func(){
fmt.Println("func A")
}
a()
// 調用閉包
f:=closure(10)
fmt.Println(f(1))
fmt.Println(f(2))
}
// 閉包
func closure(x int) func(int)int{
return func (y int)int {
return x*y
}
}
defer
類似析構,執行方式類似其它語言中的析構函數,在函數體執行結束後按照調用順序的相反順序逐個執行。
- 即使函數發生嚴重錯誤也會執行
- 支持匿名函數的調用
- 常用的資源清理、文件關閉、解鎖以及記錄時間等操作
- 通過與匿名函數配合可在 return 之後修改函數計算結果
- 如果函數體內某變量作爲 defer 時匿名函數的參數,則在定義 defer 即已經獲得了拷貝,否則則是引用某個變量的地址
- Go 沒有異常機制,但有 panic/recover 模式來處理錯誤
- panic 可以在任何地方引發,但 recover 只有在 defer 調用的函數中有效
func main() {
for i:=0;i<3;i++ {
// 當main函數執行完閉後纔開始執行,而且i是(因爲是閉包,是地址的拷貝)每次輸出3
defer func() {
fmt.Println(i)
}()
}
// 如果在傳入i會怎麼樣?
for i:=0;i<3;i++ {
defer func(i int) {
fmt.Println(i)
}(i)
}
}
// panic之前註冊defer來恢復到正常執行
func main() {
A()
B()
C()
}
func A() {
fmt.Println("Func A")
}
func B() {
defer func(){
if err:=reocver(); err!=nil {
fmt.Println("Recover in B")
}
}()
panic("Panic in B")
}
func C() {
fmt.Println("func C")
}
閉包,最外層不能是一個匿名函數
// 分析結果
package main
import (
"fmt"
)
func main() {
var fs = [4]func(){}
for i:=0;i<4;i++ {
defer fmt.Println("defer i=",i)
defer func(){
fmt.Println("defer_closure i=", i) // 閉包 引用值拷貝
}()
fs[i] = func(){
fmt.Println("closure i = ",i) // 閉包 引用值拷貝
}
}
for _,f:=range fs {
f()
}
}
結構 struct
- Go 中的 struct 與 c 中的 struct 非常相似,並且 Go 沒有 class
- 使用
type <name> struct{}定義結構,名稱遵循可見規則 - 支持指向自身的指針類型成員
- 支持匿名結構,可用作成員或定義成員變量
- 匿名結構也可以用於 map 的值
- 可以使用字面值對結構進行初始化
- 允許直接通過指針來讀寫結構成員
- 相同類型的成員可進行直接拷貝賦值
- 支持
==與!=比較運算符,但不支持>或< - 支持匿名字段,本質上是定義了某個類型名爲名稱字段
- 嵌入結構作爲匿名字段看起來像繼承,但不是繼承
- 可以使用匿名指針
// 定義結構
type test struct{}
// 定義人
type person struct{
Name string,
Age int
}
func main() {
// a:=test{}
a:=person{}
a.Name = "joe"
a.Age = 19
fmt.Println(a)
// 可以使用字面值初始化
b:=person{
Name:"Will",
Age:18
}
fmt.Println(b)
A(b) // 值的拷貝
fmt.Perintln(b) //age 18
B(&b) //第次都要對b取地址是不是很麻煩,我可以在定義b時就寫成指針 如b:=&person{Name:"Will",Age:18}, b就是指向結構的指針
// 使用屬性時也不用像c下樣*b.Name 而是直接寫b.Name="OK"
fmt.Perintln(b) //age 13
}
func A(per person) {
per.Age=13
fmt.Println("A",per)
}
// 如果想修改傳入的結構類型,則需要傳入地址拷貝
func B(per *person) {
per.Age=13
fmt.Println("A",per)
}
// 匿名結構
func main() {
a: = struct {
Name string
Age int
}{
Name:"Joe",
Age:19
}
fmt.Println(a)
}
// 如下
type person struct {
Name string
Age int
// 匿名結構
Contact struct {
Phone,City string
}
}
func main() {
a:=person{Name:"joe",Age:18}
a.Contact.Phone = "12xxx"
a.Contact.City = "Bei Jing"
fmt.Println(a)
}
// 字段匿名,必須嚴格按照聲明的順序
type person struct {
string,
int
}
func main(){
a:=person{"joe",19} //調換順序會報錯
fmt.Println(a)
// 相同結構可以賦值 相同類型之間
b:=person{}
b=a
fmt.Println(b)
}
// 怎樣實現類似繼承的功能 嵌入
type human struct{
Sex int
}
type teacher struct {
human
Name string
Age int
}
type student struct{
human
Name string
Age int
}
func main() {
a:=teacher{Name:"Joe",Age:18,human:human{Sex:0}}
b:=student{Name:"Jay",Age:19,human:human{Sex:1}}
a.Name = "joe2"
a.Age=13
a.human.Sex = 2
// a.Sex=3 可以直接使用
fmt.Println(a,b)
// 嵌入結構怎麼賦值
}
如果匿名字段和外層結構有同名字段,應該如何進行操作?
type A struct {
B
// C 會報錯 編譯器無法定你要使用哪個Name
Name string
}
type B struct {
Name string
}
type C struct {
Name string
}
func main(){
a:=A{Name:"A",B:B{Name:"B"}}
fmt.Println(a.Name,a.B.Name)
}
方法 method
- Go 中雖然沒有 class,但依舊有 method
- 通過顯示說明 receiver 來實現與某個類型的組合
- 只能爲同一個包中的類型定義方法
- receiver 可以是類型的值或指針
- 不存在方法重載
- 可以使用值或指針來調用方法,編譯器會自動完成轉換
- 從某種意義上說,方法是函數的語法糖,因爲 receiver 其實就是方法所接收的第一個參數
- 如果外部結構和嵌入結構存在同名方法,則優先調用外部結構方法
- 類型別名不會擁有底層類型所附帶的方法
- 方法可以調用結構中的非公開字段
type A struct {
Name string
}
type B struct {
Name string
}
func main() {
a:=A{}
a.Print()
fmt.Println(a.Name)
b:=B{}
b.Print()
fmt.Println(b.Name)
}
// 定義方法,先寫括號定義了A這個類型有一個方法Pring
func (a *A) Print(){ //引入拷貝
a.Name="AA"
fmt.Println("A")
}
func (b B) Print(){
fmt.Println("A")
}
// 類型別名和方法的組合
type TZ int
func main(){
var a TZ
a.Print()
}
// 第一個括號就是receiver 就是誰來接收它
func (a *TZ) Print(){
fmt.Println("TZ")
}
方法和函數
type TZ int
func main(){
var a TZ
a.Print() //method value
(*TZ).Print(&a) // method expression
}
func (a *TZ) Print(){
fmt.Println("TZ")
}
// 方法是可以訪問結構的私有字段,大寫小寫只對package的限制的。在本包內都是可見的
// 根據爲結構增加方法的知識,嘗試聲明一個底層類型爲int的類型,並實現調用某個方法就遞增100
// 如 a:=0 調用a.Increase()之後,a從0變成100
type TZ int
func (tz *TZ) Increase(num int){
*tz+=TZ(num) //要做強制類型轉換
}
func main() {
// 因爲不是結構類型所以要使用var
var a TZ
a.Increase(100)
fmt.Println(a)
}
接口 interface
- 不需要顯式說明你實現了哪個接口,只要實現了相同簽名的功能,編譯器就會認是實現了相應的接口
- 接口是一個或多個方法簽名的集合
- 只要某個類型擁有該接口的所有方法簽名,即算實現該接口,無需顯式聲明實現了哪個接口,這稱爲 Structural Typing
- 接口可以匿名嵌入其它接口,或嵌入到結構中
- 將對象賦值給接口時,會發生拷貝,而接口內部存儲的是指向這個複製品的指針,既無法修改複製器的狀態,也無法獲取指針
- 只有當接口存儲的類型和對象都爲 nil 時,接口才等於 nil
- 接口調用不會做 receiver 的自動轉換
- 接口同樣支持匿名字段方法
- 接口也可以實現類似 OOP 中的多態
- 空接口可以作爲任何類型數據的容器
type USB interface {
Name() string
Connect()
}
type PhoneConnector struct{
name string
}
func (pc PhoneConnector) Name() string {
return pc.name
}
func (pc PhoneConnector) Connect() {
fmt.Println("Connect:", pc.name)
}
func main() {
var a USB
a = PhoneConnect{"PhoneConnector"}
// 可以簡寫成a:= PhoneConnect{"PhoneConnector"} , 但這樣寫不能直觀的看出來是否是實現了接口USB
a.Connect()
Disconnect(a)
}
func Disconnect(usb USB){
// 判斷類型
if pc,ok:=usb.(PhoneConnect);ok {
fmt.Println("Disconnected")
return
}
fmt.Println("Unknown device")
}
// 可以允許傳遞空接口
func Disconnect(usb interface{}){
// 判斷類型
if pc,ok:=usb.(PhoneConnect);ok {
fmt.Println("Disconnected")
return
}
fmt.Println("Unknown device")
// type switch
switch v:=usb.(type){
case PhoneConnector:
fmt.Println("Disconnected",v.name)
default:
fmt.Println("Unknown device.")
}
}
// 嵌入接口
type USB interface {
Name() string
Connecter
}
type Connecter interface {
Connect()
}
// 類型轉換 USB可以轉換成Connecter 但Connecter不能轉換成USB
反射 reflection
- 反射可大大提高程序的靈活性,使得 interface{}有更大的發揮餘地
- 反射使用 TypeOf 和 ValueOf 函數從接口中獲取目標對象信息
- 反射會將匿名字段作爲獨立字段(匿名字段本質)
- 想要利用反射修改對象狀態,前提是 interface.data 是 settable,即 point-interface
- 通過反射可以“動態”調用方法
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) Hello() {
fmt.Println("Hello world.")
}
type Manger struct{
User
title string
}
func main() {
u:=User(1,"OK",12)
Info(u)
// 取manager的匿名字段
m:=Manager{User:User{1,"ok",32},title:"boss"1}
t:=reflect.TypeOf(m)
fmt.Printf("%#v\n",t.Field(0))
fmt.Printf("%#v\n",t.Field(1))
//也可以使用下面這種方法
fmt.Printf("%#v\n",t.FieldByIndex([]int{0,1})) //取匿名字段的第二個字段
}
func Info(o interface{}) {
t:=reflect.TypeOf(o)
fmt.Println("Type:",t.Name())
// 判斷類型
if k:=t.Kind();k!=reflect.Struct{
fmt.Println("XX")
return
}
v:=reflect.ValueOf(o)
fmt.Println("Fields:")
for i:=0;i<t.NumField();i++ {
f:=t.Field(i)
val:=v.Field(i).Interface()
fmt.Printf("%6s:%v=%v\n",f.Name,f.Type,val)
}
// 打印方法
for i:=0;i<t.NumMethod();i++{
m:=t.Method(i)
fmt.Printf("%6s:%v\n",m.Name,m.Type)
}
}
type User struct {
Id int
Name string
Age int
}
func (u User) Hello(name string){
fmt.Println("Hello",name," my name is ",u.Name)
}
func main() {
x:=123
v:=reflect.ValueOf(x)
v.Elem().SetInt(999)
fmt.Println(x)
// 處理複雜結構
u:=User{1,"OK",12}
Set(&u)
fmt.Println(u)
// 反射調用方法
v:=reflect.ValueOf(u)
mv:=v.MethodByName("Hello")
args:=[]reflect.Value{reflect.ValueOf("joe")}
mv.Call(args)
}
func Set(o interface{}){
v:=reflect.ValueOf(o)
if v.Kind()==reflect.Ptr&&!v.Elem().CanSet() {
fmt.Println("不能修改")
return
}else{
v=v.Elem()
}
f:=v.FieldByName("Name")
if !f.IsValid() {
fmt.Println("BAD")
return
}
if f.Kind()==reflect.String {
f.SetString("HaHa")
}
}
併發 concurrency
併發不是並行,併發主要由切換時間片來實現“同時”運行,在並行則是直接利用多核實現多線程的運行,但 go 可以設置使用核數,以發揮多枋計算機的能力,Goroutine 奉行通過通信來共享內存,而不是共享內存來通信;
定義一個函數,使用 go 命令
// 怎麼運行一個go routine
package main
import (
"fmt"
"time"
)
func main() {
go Go()
time.Sleep(2*time.Second)
}
func Go() {
fmt.Println("Go! go! go!")
}
// 以上的是最簡單的go routine
// 以上這種方式無法做一Go()執行完畢後,通知main函數,“我執行完畢了,可以結束了”
// 要做到這種通信的能力,我們使用channel
channel
- channel 是 goroutine 的溝通橋樑,大都是阻塞同步的
- 通過 make 創建,close 關閉
- 可以使用 for range 來迭代不斷操作 channel
- 可以設備單向或雙向通道
- 可以設置緩存大小,在未被填滿前不會發生阻塞
func main() {
c:=make(chan bool)
go func(){
fmt.Println("GO GO GO!!!")
c <- true //執行go go go 打印,把channel的一個返回值通知main可以結束
}()
<-c //main函數執行到這個等待channel的返回值,當有返回值結束
}
// 使用for range來迭代
func main() {
c:=make(chan bool)
go func(){
fmt.Println("go go go")
c <- true
close(c)
}()
for v :=range c {
fmt.Println(v)
}
}
// 使用channel迭代時,必須有一個地方明確關閉它,並且是關閉成功的。否則程序會變成死鎖的
// 有緩存與無緩存的主要區別
// 無緩存時 是阻塞的
// 有緩存時
func main() {
c:=make(chan bool)
go func() {
fmt.Println("Go Go Go")
<-c
}()
c <- true //無緩存時它會等到這個值被讀掉,再結束
}
// 而有緩存時,
func main() {
c:make(chan bool,1)
go func(){
fmt.Println("Go Go Go")
<-c
}()
c <- true //有緩存時它會把值放到緩存中,愛讀不讀:);不會執行匿名函數,
}
// 即有緩存是異步的,無緩存是同步阻塞的
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) //使用多核,提升效率
c:=make(chan bool)
for i:=0;i<10;i++ {
go Go(c,i)
}
<-c
}
func Go(c chan bool,index int) {
a:=1
for i:=0;i<100000000;i++ {
a+=i
}
fmt.Prinln(i,a)
if index == 9 {
c <- true
}
}
// 上例中,在使用多核提交效率時,用最後一個channel的值來判斷結束是不對的
// 導致有些channel沒有執行就退出了
// 要解決這個問題可以使用緩存 ,如下
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) //使用多核,提升效率
c:=make(chan bool,10) //有10個channel就創建10個緩存
for i:=0;i<10;i++ {
go Go(c,i)
}
for i:=0;i<10;i++{
<-c //取10次channel的值就結束了
}
}
func Go() {
a:=1
for i:=0;i<100000000;i++ {
a+=i
}
fmt.Prinln(i,a)
c <- true
}
// 第二種解決方案,通過sync包來解決的,通過wait group
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) //使用多核,提升效率
// 創建waitGroup來實現,main函數主要是來判斷waitgroup的完成數來結束
wg:=sync.WaitGroup{}
wg.Add(10)
for i:=0;i<10;i++ {
go Go(wg,i)
}
wg.Wait()
}
func Go(wg *sync.WaitGroup,index int) {
a:=1
for i:=0;i<100000000;i++ {
a+=i
}
fmt.Prinln(i,a)
wg.Done() //標記完成一個
}
select
- 可以處理一個或多個 channel 的發送與接收
- 同時有多個可用的 channel 時按隨機順序處理
- 可用空的 select 來阻塞 main 函數
- 可設置超時
package main
import(
"fmt"
)
func main() {
c1,c2:=make(chan int),make(chan string)
o:=make(chan bool)
go func(){
for{
select{
case v,ok:=<-c1:
if !ok {
o<-true
break
}
fmt.Println("c1",v)
case v,ok:=<-c2
if !ok{
o<-true
break
}
fmt.Println("c2",v)
}
}
}()
c1<-1
c2<-"Hi"
c1<-3
c2<-"hello"
close(c1)
close(c2)
<-o
}
將 select 作爲發送者來實現一個應用
package main
import (
"fmt"
)
func main() {
c:=make(chan int)
go func(){
for v:=range c{
fmt.Println(v)
}
}()
for{
select {
case c<-0:
case c<-1:
}
}
}
// 超時 c一直沒有輸入值
func main() {
c:=make(chan bool)
select{
case v:<-c:
fmt.Println(v)
case <-time.After(3*time.Second):
fmt.Println("")
}
}
項目
主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/6714