學習go語言一篇就夠了(持續更新)
前言:寫部落格也寫了差不多一年了,我更多的時候是記錄自己學習的情況,有時也有自己工作上遇到的bug,自己有時候也比較迷茫,不知道怎麼去寫博文,我也很想別人跟我提提建議,但是有時候覺得寫寫部落格還是很有成就感的,我有時候覺得快速的把知識點過一遍然後進行大量的練習和補充每個知識點的不足,讓自己寫的博文能更加完善些(關於這個博文是一套視訊和兩本連起來總結的,所有有時候會感覺知識點有點混亂)
學習方向
區塊鏈研發工程師
Go伺服器端/遊戲軟體工程師
golang分散式/雲端計算軟體工程師
學習方法
先 know how
, 在know why
Golang的語言特點
注意事項
以"go" 為副檔名
執行入口是main()函式
嚴格區分大小寫
每個語句後面不帶分號
定義的變數和import的包如果沒有使用到,程式碼不能編譯通過
shift+tab整體向左移動(編輯器)
使用一個表示式形容Go語言: Go=C+Python
print() println() //print+line printf() //print+format Sprintln()//獲取字串 Sprint() Sprintf() %T , 列印變數的資料型別 %d , 列印整數,十進位制 %f , 列印浮點,小數 %t , 列印字串 %s , 列印字串 %v , 原樣輸出 %c , 列印對應的unicode字元 %p , 列印地址
變數的定義
-
var 變數 資料型別
變數名=賦值
-
縮寫成一行
var 變數名 資料型別=賦值
-
型別推斷
var 變數名=賦值
-
省略var
變數: =賦值
b:=2 //簡短宣告不支援全域性變數
在同一區域同一資料型別內不斷變化的
var num int //預設為0 跟js差不多 undefined
_
只寫,不能讀
常量
const 常量 資料型別=賦值
const 常量名=賦值
所有的字母大寫
如果私有 前加小寫字元c
一組常量中,如果某個常量沒有初始值,那麼預設和上一行一致
iota
:計數器 預設為0
const( e=iota f g ) //每當定義一個常量累加1
整型
int
uint
和作業系統有關
浮點
float32
單精度 float64
雙精度
字串
一個字元byte
Go語言字元使用UTF-8 英文字元1個位元組 漢字3個位元組
字串一旦賦值了,就不能在修改
反引號是直接輸出``
布林(bool)
只能取true false不能取0,1 佔用一個位元組
Go在不同型別的變數之間賦值是需要顯式轉換,也就是說Golang中資料型別不能自動轉換
var i int32 = 100 var n1 float32 = float32(i) var n2 int8 = int8(i) var n3 int64 = int64(i) fmt.Print("i=%v n1=%v n2=%v n3=%v", i, n1, n2, n3) %v 需要轉換的變數
基本資料型別 的預設值
基本型別轉string型別
fmt.Sprintf strconv.FormatFloat strconv.FormatInt strconv.formatbool
string型別轉基本資料型別
-
strconv ParseInt() ParseFloat() ParseBool() ParseUint() n1,_=strconv.ParseInt(str2,10,64) f1,_=strconv.ParseFloat(str3,64)
算術運算子
-
對於除號"/" 只保留整數部分而捨棄小數部分
Golang的自增自減只能當作一個獨立語言使用
例如 i++ i-- ++和--只能寫在變數的後面,不能寫在變數的前面
go 語言明確不支援三元運算子
鍵盤輸入語句
fmt.Scanln()
fmt.Scanf()
fmt.scanf
可以按照指定的格式輸入
fmt.Scanf("%s,%d,%f,%t",&name,&age,&sal,&ispass)
通過&變數儲存資料
fmt.Scanln(&name)
if x>y { fmt.Println("true") } if elseif
/*希望函式內的變數能改變函式外的數值,可以傳入變數的地址& 函式內以指標的方式操作變數*/ func test03(n1 *int) { *n1=*n1+10 fmt.Println("test03 n1=",*n1) } func main(){ num:=20 test03(#) fmt.Println("main() num",num) }
go函式不支援過載
自定義資料型別
type 自定義資料型別 資料型別
type myInt int var num1 myInt var num2 int num1=400 num2=int(num1)//這裡依然需要顯示轉換,go認為myInt和int兩個型別
args
是 slice
切片,通過 args[index]
可以訪問各個值
func sum(n1 int,args... int)int{ sum:=n1 for i:=0;i<len(args);i++{ sum+=args[i]//args[0] 表示取出args切片的第一個元素之 } return sum } func main(){ res:=sum(1,2,3,4,5,5,6,6) fmt.Println("res=",res) }
每一個原始檔都可以 包含一個init函式,init會在main函式前呼叫
執行順序

bufio包
reader:=NewReader(os.Stuio) reader.ReadLine()====>[]byte reader.ReadString('\n')-->String
內建包
math包提供數學計算的函式
math.Abs()
go語言特有
fallthrough
穿透
當某個case匹配成功後執行,如果有fallthrough,那麼後面緊鄰的case不再匹配,直接執行
//math/rand包下 //時間 t1:=time.Now().Unix() fmt.Println(t1) //設定種子數 rand.Seed(t1) //設定獲取隨機數的種子數 num1:=rand.Intn(10) //[0,10) fmt.Println(num1)
陣列
var arr [4]int var b=[4]int{1,2,3,4} f:=[...] int{1,2,3,4,5} len()長度 cap() 容量 //因為陣列是定長的容器,長度和容量是相同的
range 對陣列 取陣列的下標和value for index,value :=range arr{ fmt.Println("下標是",index,"數值是",value) } for _,v :=range arr{ fmt.Println(v) }
二維陣列 arr:=[4][3]int{{1,2,3},{1,2,3},{1,2,3},{1,2,3}}
切片
切片slice 同陣列類似,也叫做變長陣列 是一個引用型別的容器,指向了一個底層資料 因為切片是引用資料的資料,直接拷貝的是地址 淺拷貝:拷貝的資料地址 copy() 深拷貝: 拷貝的資料本身 s2:=[]int{1,2,3,4} 內建函式 make() s1:=make([] 型別 ,len,cap)第一個引數:切片的型別,第二個引數:len,第三個引數容量,如果省略,預設跟第二個一樣 ---------------- s2:=make([] int,3) s2=append(s2,1,2,3,4) a[start:end] //包左不包右 當向切片新增資料時,如果沒有超過容量,直接新增,如果超過容量,自動擴容(成倍增加) /*陣列傳遞的是資料 切片傳遞的是地址*/ s1:=[] int{1,2,3,4,5} fmt.Println(s1) s2:=s1 fmt.Println(s2) s2[0]=100 fmt.Println(s1) fmt.Println("--------") s3:=[5] int{1,2,3,4,5} fmt.Println(s3) s4:=s3 fmt.Println(s4) s4[0]=100 fmt.Println(s4) fmt.Println(s3) copy copy(s1,s2) 把s2拷貝到s1,但是不會改變長度,能裝多少裝多少 s1 := []int{1, 2, 3, 4, 5} s2 := []int{6, 7, 8, 9} copy(s1,s2) fmt.Println(s1)
字串操作
-
一個位元組的切片
-
strings包下的字串函式
strings.Contains() 是否包含指定的內容
strings.ContainsAny() 是否包含任意一個字元
Repeat 自己拼接自己count次
大小寫轉換 ToLower() ToUpper()
切割Split() SplitN()
Index IndexAny
LastIndex() LastIndexAny()
Replace(s,old,new,n)
Trim()
HasPerfix() 以xx字首開頭
HasSuffix() 以xx字尾結尾
查詢
EqualFold不區分大小寫的字串比較
index 找不到返回-1
TrimSpace() 將字串左邊兩邊的空格去掉
Trim("元字串",指定要去除的) 將指定的字串左右兩邊空格去掉(有區別哦)
...
-
字串遍歷,同時處理有中文的問題
r:[]rune(str)
str:="hello被" r:=[]rune(str) for i:=0;i<len(r);i++{ fmt.Printf("%c\n",r[i]) }
-
字串轉整數
n,err=strconv.Atoi("12")
b:="65" s2, _ :=strconv.Atoi(b) fmt.Printf("%v",s2)
-
整數轉字串
str=strconv.Itoa(1234)
-
字串 轉[]byte:
var bytes=[]byte("hello go")
-
[]byte轉字串
str=string([]byte{'a','b','c'})
-
十進位制轉2,8,16進位制
str=strconv.FormatInt(123,2)
//2->8,16
map, 對映
key ,value 也是一個容器,儲存的無序鍵值對
key不重複,重複就覆蓋
map宣告是不會分配記憶體的,初始化需要make,分配記憶體後才能賦值和使用
型別 %T
m:=map[string]int{"one":1,"two":2} m:=make(map[string]int) nil 空 將map存進切片中 s1:=make([] map[string]string,0,3)
通過key獲取value,當key如果不存在的時候,我們會得到value的預設值
map1[key]=value,根據key獲取map中對應的value
value,ok:=map[key]
如果鍵值對存在,value就是對應的資料,ok為true
如果key不存在獲取,獲取的是 值的預設值,ok為false,預設key為0
value,ok:=map[key] f ok{ fmt.Println("對應的數值是:",v1) }else{ fmt.Println("操作的key不存在",vl) }
內建函式delete
- delete(map1,key)
sort排序
sort.Ints(key) 字元按照字串的編碼值排序
str="FKJKJFJdfjakfjakKSKJW" map1 := make(map[byte]int) for i := 0; i < len(str); i++ { val, ok := map1[str[i]] if ok { val++ } else { val = 1 } map1[str[i]] = val } keys := make([]byte, 0, len(map1)) for k := range map1 { keys = append(keys, k) } for _, k := range keys { fmt.Printf("%c,%d\n", k, map1[k]) }
map1:=make(map[string]map[string]string) //需要定義長度 map1["name"]=make(map[string]string,3) map1["liming"]=make(map[string]string,3) map1["name"]["name3"]="張三" map1["name"]["name1"]="李四" map1["name"]["name2"]="王五" map1["liming"]["xiaoming1"]="小明" map1["liming"]["xiaoming2"]="小紅" map1["liming"]["xiaoming3"]="小白" for k,v :=range map1{ fmt.Println(k,v) for k2,v2:=range v{ fmt.Println(k2,v2) } }
time
time.new 獲取當前時間
格式化事件 fmt.Printf
fmt.SPrintf
now:=time.Now() fmt.Printf("now=%v,now\n",now) fmt.Printf("%d-%d-%d %d:%d:%d\n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
time.Format()
fmt.Printf(now.Format("2018-12-12 14:34:23))
Unix() 1970到現在的毫秒值
UnixNano 1970到現在的納秒值
內建函式
len
new
異常處理
//使用defer+recover 來捕獲異常 defer func() { err := recover() //recover()內建函式,可以捕獲到異常 if err != nil { //說明捕獲到錯誤 fmt.Println("err=", err) } }() errors.New("錯誤說明") 會返回一個err型別的值表示一個錯誤 //自定義錯誤 // 定義一個函式,用於求矩形的面積 func getArea(wid,len float64)(float64,error){ errorMsg:="" if wid<=0{ errorMsg="寬度為負數" } if len<0{ if errorMsg==""{ errorMsg="長度為負數" }else{ errorMsg+=",長度也為負數" } } if errorMsg !=""{ return 0,&errorRect{errorMsg,wid,len} } area:=wid*len return area,nil }
函式
go語言支援函數語言程式設計+面向物件
- 函式:獨立功能,直接呼叫
- 方法:物件的功能,物件來呼叫
可變引數: 引數名...
函式中,可變引數想當於切片
位置引數>可變引數 (順序)
寫返回值的時候,必須在函式上宣告返回值的型別
函數語言程式設計
一個函式可以作為另一個函式的引數或返回值
遞迴函式
自己呼叫自己
func getgui(n int) int{ if n==1||n==2{ return 1 } return getgui(n-1)+getgui(n-2) }
匿名函式
func(a,b int){ fmt.Println(a,b) }(1,2)
高階函式(回撥函式)
根據go語言函式的資料型別的特點,可以將函式作為另一個函式的引數
func add(a,b int) int{ return a+b } func oper(m,n int,fun func(int,int)int)int{ res:=fun(m,n) return res } func main() { fmt.Println( oper(10,20,add)) }
閉包
引用外部變數的匿名函式
支援將函式作為另一個函式的返回值
func f(i int) func() int{ return func() int{ i++ return i } } func main() { str:=f(10) fmt.Println(str()) fmt.Println(str()) } ----------------------------- func ExFunc(n int) func () { sum:=n a:=func(){ fmt.Println(sum+1) } return a } func main() { myFunc:=ExFunc(10) myFunc() }
函式的defer(延時機制)
在執行到defer是,暫時不執行,會將defer後面的壓入獨立的棧,按照先入後出的方式出棧,執行
先defer再return
獲取變數的地址,用&
獲取指標型別所指向的值*
fmt.Println(#) var ptr *int=# 值型別,都有對應的指標型別 新式為*資料型別
結構體
//定義一個類,就是定義一個結構體 //類的定義,欄位屬性,行為方法 type 結構體名稱 struct{ name type age int } //定義結構體變數 var p1 結構體名稱 p2:=Person{"mary",20} p6:=new(Person) var p1 Person p1.Age=10 p1.Name="小明" var p2 *Person=&p1 fmt.Println((*p2).Age) 注意不能*p2.Age寫,因為.的優先順序比* 高 // 實現結構體的淺拷貝 d4:=new(dog) d4.color="黃色" d4.age=2 d4.kind="中華田園犬" fmt.Println(d4) d5:=d4 d5.kind="張三" fmt.Println(d5,d4) d6:=&d1//*dog d6.kind="金毛" fmt.Println(d6,d1) //匿名結構體:沒有名字的結構體,在建立匿名結構體,同時建立物件 p1:=struct { name string ageint }{ name: "zhangsan", age:30, } //匿名欄位 type student struct{ string int//匿名欄位(不能有重複欄位) } s1:=student{"zhgsan",12} fmt.Println(s1.string,s1.int) type Book struct{ bookName string price float64 auther string } //巢狀struct的名稱衝突 有以下兩個名稱衝突的規則(報錯) * 外部struct覆蓋內部struct的同名欄位,同名方法(1) * 同級別的struct出現同名欄位,方法將報錯(2) type A struct{ a int b int } type B struct{ b int c string d string } type C struct { A//繼承 B a string c string } (1) C.a和C.c分別覆蓋A.a和B.c (2) A.b和B.b 為什麼呢? 繼承的"提升欄位" //結構體巢狀 //模擬面向物件:聚合關係 //一個類作為另一個類的屬性 //定義一個書的結構體 type Book struct{ bookName string price float64 auther string } //定義一個人的結構體 type Person struct{ name string age int book Book//聚合 } func main() { p3:=Person{ name:"Jerry", age:26, book:Book{ bookName:"Go語言是怎麼練成的", price:55.0, auther:"張三", }, } fmt.Println(p3.book.auther,p3.book.price,p3.book.auther) p4:=Person{"李曉華",20,Book{"四萬個為什麼",44.8,"張三"}} fmt.Println(p4.book.auther,p4.book.price,p4.book.auther) }
- struct 的資料型別:值型別:預設深拷貝
- 如果結構體的欄位型別是:指標,slice,和map的零值都是nil,還沒有分配空間
- 如果需要使用這樣的欄位 ,需要先make,才能使用
-
結構體是值型別
方法
type Person struct{ name string age int } type Student struct { Person school string } //方法 func (p Person) eat(){ fmt.Println("父類的方法,吃我我頭") } func (s Student) study(){ fmt.Println("子類的方法..") } func (s Student) eat(){ fmt.Println("子類重寫父類的方法") }
多型繼承
介面( interface
):功能的描述的集合
- 定義有哪些功能
介面的意義
- 解耦合:程式和程式之間的關聯程度,降低耦合性
- 繼承關係:增加耦合
注意點:
- 當需要介面型別的物件時,那麼可以使用任意實現類物件代替
- 介面物件不能訪問實現類的屬性
/* 多型*/ //定義一個介面 type Shape interface{ peri() float64//周長 area() float64 //面積 } //定義實現類:三角形 type Triangle struct { a,b,c float64//三個邊 } func (t Triangle)peri() float64{ return t.a+t.b+t.c//周長 } func (t Triangle)area() float64{ p:=t.peri() s:=math.Sqrt(p*(p-t.a)*(p-t.b)*(p-t.c))//面積 return s } type Circle struct { radius float64//半徑 } func (c Circle)peri()float64{ return c.radius*2*math.Pi //面積 } func (c Circle)area()float64{ return math.Pow(c.radius,2)*math.Pi } func teshShape(s Shape) { fmt.Println("周長",s.peri(),"面積",s.area()) } //轉型 func getType(s Shape) { /*方法一:instance,ok:=介面物件.(實際型別) 如果該介面物件是對應的實際型別,那麼instance就是轉型之後物件 */ ins,ok:=s.(Triangle) if ok{ fmt.Println("是三角形",ins.a,ins.b,ins.c) }else if ins,ok:=s.(Circle);ok{ fmt.Println("是圓形,半徑是:",ins.radius) } } //2 轉型(向下轉型) func getType2(s Shape) { /*方法二:介面物件.(type),配合switch和case語句使用*/ switch ins := s.(type) { case Triangle: fmt.Println("三角形", ins.a, ins.b, ins.c) case Circle: fmt.Println("圓形", ins.radius) //case int: // fmt.Println("整型資料...") } } func main() { /* 多型:一個事物的多種形態 go語言:通過介面模擬多型性 一個實現類的物件: 看作是一個實現類型別:能夠訪問實現類中的方法和屬性 還可以看作是對應介面型別:只能夠訪問介面中定義的方法 用法一:一個函式如果接受介面型別作為引數,那麼實際上可以傳入介面的任意實現類物件作為引數 */ t1:=Triangle{3,4,5} fmt.Println(t1.peri()) fmt.Println(t1.area()) fmt.Println(t1.a,t1.b,t1.c) var s1 Shape s1=t1 fmt.Println(s1.peri()) fmt.Println(s1.area()) var c1 Circle c1=Circle{4} fmt.Println(c1.peri()) fmt.Println(c1.area()) fmt.Println(c1.radius) var s2 Shape=Circle{5} fmt.Println(s2.area(),s2.peri()) //定義一個介面型別的陣列 arr:=[4]Shape{t1,s1,c1,s2} fmt.Println(arr) //介面型別的物件-->對應實現類型別 getType(c1) }
空介面
也是一個介面,但是該介面中沒有任何方法
所有可以將任意型別作為該介面的實現
//定義一個map:string作為key,任意型別作為value map1:=make(map[string]interface{}) map1["name"]="王二狗" map1["age"]=12 //空介面 type A interface {} var a1 A=Cata{"花貓",1} var a2 A=Persona{"王二狗","男性"}
練習1
/* 三維座標,求兩點的距離 */ type Point struct{ x,y,z float64 } func (p Point) PrintInfo(){ fmt.Println(p.x,p.y,p.z) } func (p Point) getDisance3(p2 Point) float64{ dis:=math.Sqrt(math.Pow(p.x-p2.x,2)+math.Pow(p.y-p2.y,2)+math.Pow(p.z-p2.z,2)) return dis } func main() { p1:=Point{1,2,4} p2:=Point{0,0,0} p1.PrintInfo() p2.PrintInfo() fmt.Println(p1.getDisance3(p2)) }