1. 程式人生 > >學習go語言一篇就夠了(持續更新)

學習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 else  if 
     /*希望函式內的變數能改變函式外的數值,可以傳入變數的地址&
       函式內以指標的方式操作變數*/
    func test03(n1 *int) {
       *n1=*n1+10
       fmt.Println("test03 n1=",*n1)
    }
    func main(){
       num:=20
       test03(&num)
       fmt.Println("main() num",num)
    }

go函式不支援過載

自定義資料型別

type 自定義資料型別 資料型別

    type myInt int
    var num1 myInt
    var num2 int
    num1=400
    num2=int(num1)  //這裡依然需要顯示轉換,go認為myInt和int兩個型別

argsslice切片,通過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(&num)
var ptr *int=&num
值型別,都有對應的指標型別 新式為*資料型別

結構體

    //定義一個類,就是定義一個結構體
    //類的定義,欄位屬性,行為方法
    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
            age  int
        }{
            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))
}