1. 程式人生 > >Golang筆記--基礎篇彙總

Golang筆記--基礎篇彙總

Go程式結構:

  • 包名
  • 匯入其他的包
  • 常量的定義
  • 全域性變數的宣告和負責
  • 一般型別宣告
  • 結構的宣告
  • 介面的宣告
  • 由main函式作為程式入口啟動點

Go別名和省略呼叫:

在匯入包時可以使用別名來防止混淆,或者是使用省略呼叫來減少程式碼

別名:

//在匯入包時定義別名
import io “fmt”
//使用時直接使用別名,而不是原來的包名
io.Println("Hello World")

省略呼叫(不建議使用,易混淆):

//在匯入包時宣告省略呼叫
import . “fmt”
//使用時直接使用要使用的函式,不用加包名
Println("Hello World")

Go中的可見性規則:

使用大小寫來決定該常量,變數,型別,介面,結構或者函式是否可以被外部呼叫。

  • 函式名首字母小寫為private函式

  • 函式名首字母大寫為public函式

基本型別:

(布林型)bool:

  • 長度:1位元組
  • 取值範圍:true,false(不可以用1,0表示)

(整形型)int/uint:

  • 長度:取決於所在平臺,32或64位

(8位整型)int8/uint8:

  • 長度:1位元組
  • 取值範圍:-128~127/0~255

(位元組型) byte(unit8):

  • 長度:1位元組
  • 取值範圍: 0~255

(16位整型)int16/uint16:

  • 長度:2位元組
  • 取值範圍:-32768~32768/0~65535

(32位整型)int32(rune)/uint32:

  • 長度:4位元組
  • 取值範圍:-(2^31)~(2^31)-1/0~(2^32)-1

(64位整型)int64/uint64:

  • 長度:8位元組
  • 取值範圍:-(2^64)~(2^64)-1/0~(2^64)-1

(浮點型)float32/64:

  • 長度:4/8位元組
  • 小數位:精確到7/15位小數位

(複數)complex64/128:

  • 長度:8/16位元組

(無符號指標)uintptr:

  • 長度:可以儲存指標的32位或者是64位整型

引用型別:

  • slice,map,chan

其他值型別:

  • array ,struct,string

介面型別:

  • interface

函式型別:

  • func

變數的宣告和賦值:

單個變數的宣告和賦值:

//變數的宣告
//(var <變數名稱> <變數型別>)
var a int

//變數的賦值
//(<變數名稱> = <表示式>)
a = 1

//變數宣告同時賦值
//(var <變數名稱> [變數型別] = <表示式>)
var a int = 65

//變數宣告同時賦值
//(var <變數名稱> = <表示式>)
var a = 2

//變數的宣告同時賦值
//<變數名> := <表示式>
a := 2

多個變數的宣告和賦值:

全域性變數:

//多個變數的宣告
//<var> [變數名1],[變數名2],[變數名3]... <型別名>
var a,b,c,d int

//多個變數的賦值
//[變數名1],[變數名2],[變數名3] = <表示式>,<表示式>,<表示式>...
a,b,c = 1,2,"string"

//多個變數的宣告和賦值
//var(<變數名> = <表示式>...)
var(
    a = 1
    b = "string"
)

區域性變數:


//多個變數的宣告
//<var> [變數名1],[變數名2],[變數名3]... <型別名>
var a,b,c,d int

//多個變數的賦值
//[變數名1],[變數名2],[變數名3] = <表示式>,<表示式>,<表示式>...
a,b,c = 1,2,"string"

//多個變數的宣告和賦值
//<變數名><變數名><變數名>... := <表示式><表示式><表示式>...
a,b,c := 1,2,3

變數的型別轉換:

  • Go中不存在隱式轉換,所有的型別轉換都需要顯式宣告

  • 型別轉換隻能發生在兩個相互相容的型別之間

  • 型別轉換格式:

    • < V alueA> [:]= < TypeOfValueA> (< ValueB >)

常量的初始化和列舉

  • 常量的值在編譯時就已經確定

  • 常量得到的定義格式與變數基本相同

  • 常量右側的表示式必須是常量或者是常量表達式

  • 常量表達式中的函式必須是內建函式

  • 在常量組中定義時,若是不提供初始值,則預設使用上一行的表示式

  • iota是常量的計數器,從0開始,組中每定義一個常量,就會自動增1

  • 每遇到一個const關鍵字,iota就會重置為0

const (
    FIRST  = iota //FIRST=0
    SECOND        //SECOND = 1
    THRID         //THRID = 2
)

運算子

  • 所有的運算子從左到右結合
  • 優先順序從高到低依次是
運算子 優先順序
^ ! 一元運算子
* / % << >> & &^ 二元運算子
+ - | ^ 二元運算子
== != < <= > >= 二元運算子
<- 用於channel
&&
||

指標

Go中的指標採用”.”選擇符來操作指標物件的成員

  • 操作符”&”取地址變數,操作符”*”訪問目標物件
  • 指標的預設值為nil而不是NULL
//指標的定義
var p *[]int
//只針的賦值
p = &a

自增/自減運算子

++(自增運算子)和–(自減運算子)不再是作為表示式,而是作為單獨的語句出現。也就是說他們都不可以出現在運算子的右側

//錯誤使用
a := 1++;
//正確使用
a := 1
a++

流程控制語句

if語句

在if之後沒有括號,可以直接寫判斷表示式,而且大括號必須和if在同一行,支援在if語句中進行變數的初始化。

if a, b := 1, 2; a >= 0 || b >= 0 {
        fmt.Println(a)
        fmt.Println(b)
    }

for語句

只有for一個迴圈語句關鍵字,但是支援3種形式,for中的條件語句每次都會被檢查,因此不建議在條件語句中使用函式,同if語句一樣,左大括號必須和條件語句在同一行。可以通過break來跳出迴圈。

for a, b := 1, 2; a >= 0 || b >= 0; {
        fmt.Println(a + b)
        a--
        b--
}

switch語句

  • 可以使用任何型別或者是表示式作為條件語句
  • 不需要寫break,符合條件時自動跳出
  • 若是希望執行下一個case,需要使用fallthrough語句
  • 支援一個初始化表示式(可以是並行方式),右側需要跟分號
  • 左大括號必須和條件語句在一行
//傳統使用方式
a := 1
    switch a {
    case 0:
        fmt.Println("a == 0")
    case 1:
        fmt.Println("a == 1")
    default:

    }

//在case中使用表示式
a := 1
    switch {
    case a >= 0:
        fmt.Println("a == 0")
        fallthrough //這樣可以使滿足case之後仍然進行判斷,而不是直接跳出switch語句
    case a >= 1:
        fmt.Println("a == 1")
    default:
        fmt.Println("預設處理方式") //當所有的case都不滿足使,會使用default來進行處理

    }

跳轉語句(goto,break,continue)

  • 三個語句都可以配合標籤使用
  • 標籤名區分大小寫,而且定義了的標籤不使用的話會報編譯錯誤
  • break和continue可以配合標籤來跳出多層的迴圈
  • goto是調整執行的位置,與其他兩個語句配合標籤的結果不相同

Array陣列

  • 定義陣列的格式為:var <陣列名> [陣列長度]<陣列型別> = 0
  • 陣列長度也是型別的一部分,具有不同長度的陣列為不同型別
  • 要區分指向陣列的指標和指標陣列的區別
  • 陣列之間可以使用 == 和!=進行比較,但是不可以使用<或者是>比較
  • 可以使用new來建立陣列,但是返回的只是一個指向陣列的指標
  • Go支援多維陣列
//可以使用陣列的下標來指定具體某個陣列元素的初始值,若不指定則全部使用型別零值
f := [20]int{19: 1}
    for i := 0; i < 20; i++ {
        fmt.Println(f[i])
    }

Slice切片

  • 切片本身不是陣列,他是一種引用型別,他用來指向底層的陣列
  • 他可以指向底層陣列的全部或者是部分,可以作為可變長陣列的替代方案
  • 使用len()獲取元素個數,使用cap()獲取容量大小
  • 一般使用make()來建立:make([]T,len,cap),len表示元素的個數,cap表示切片的容量,cap省略時預設和len值相同
L := [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := L//指向底層陣列L的全部
fmt.Println(s1)

//----------

L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
s1 := L[3:6] //指向底層陣列L的部分,指向的是陣列中下標為3,4,5的數
fmt.Println(s1)

//-----------
sl := make([]int, 3, 9)//[]int----所指向的型別,3----切片的長度,9----切片容量

Reslice

  • Reslice時索引以被slice的切片為準
  • 索引不可以超過被slice的切片的容量值
  • 索引越界不會導致底層陣列的重新分配,而是引發錯誤
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}

    slice := L[2:7] //輸出為2,3,4,5,6
    reslice := slice[1:4]//在slice的基礎上再進行slice,輸出為3,4,5
    fmt.Println(reslice)

Append

  • 可以在slice尾部追加元素
  • 可以將一個slice追加在另一個slice的尾部
  • 如果最終長度未超過追加到slice的容量,那麼就返回原始slice
  • 如果最終長度超過最忌到的slice的容量,那麼就重新分配陣列並拷貝元素資料
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7]
fmt.Printf("%v %p", slice, slice)
slice = append(slice, 2, 3, 4, 5)
fmt.Printf("%v %p", slice, slice)

Copy

L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7]
slice2 := []int{1, 2, 3}
copy(slice, slice2)//將slice2中的元素拷貝到slice中
fmt.Println(slice, slice2)
//slice輸出為[1,2,3,3,4,5,6,7,8]

Map

  • 和其他語言中的雜湊表類似,以Key-Value的形式來儲存值
  • Key必須是支援==或者是!=比較運算的型別,不可以是函式,map,或者是slice
  • Map查詢比線性搜尋快很多,但是比使用索引訪問慢很多
  • Map使用make建立,支援:=的宣告賦值方式
  • 使用len()函式獲取元素個數,在超出容量之後會自動擴容
  • 鍵值對不存在時自動新增,使用delete刪除鍵值對
  • 使用for range對map和slice進行遍歷操作
//宣告map變數:var關鍵字 變數名 map關鍵字 [Key型別]Value型別
var m map[int]string 


//初始化map變數
m = map[int]string{1:"ok",2:"hello"}
//或者是
m = make(map[int]string)

//宣告並賦值
var m map[int]string := make(map[int]string)

//其實map[int]string整體是作為一個型別名稱的

//增加
m[1] = "ok"
//刪除
delete(m, 1)

//多層map的巢狀
var m map[int]map[int]string     //map的value型別為map
    m = make(map[int]map[int]string) //對最外層的map進行初始化
    m[1] = make(map[int]string)      //對內層中Key為1的map進行初始化
    m[1][1] = "hello"

    fmt.Println(m[1][1])

//利用多返回值確定map是否初始化
data, ok := m[1][1]
    if !ok {
        m[1] = make(map[int]string)
    }
    m[1][1] = "hello"
    data, ok = m[1][1]
    fmt.Println(data, ok)

//通過for range來遍歷map和slice,但是所有對value的操作都是對map和slice拷貝的操作
for k,v := range map{
        //k是key,v是value
}

Function函式

  • 定義函式使用關鍵字func,左起大括號不能另起一行
  • 不支援函式巢狀,過載和預設引數
  • 支援的特性:無需宣告函式原型,不定長形參,多返回值,命名返回值引數,匿名函式,閉包
  • 函式也可以作為一種型別使用,賦值
//func關鍵字 函式名 形參列表,返回值列表
func A(data1 int,data2 int,data3 int) (a int, b int, c string) {
    a, b, c = 1, 2, "hello"
    fmt.Println(d)
    return a, b, c
}

//go中支援不定長引數
func A(d ...int) (a int, b int, c string) {
    //通過在型別名前加...來使用不定長引數,但是不定長引數只能作為引數列表的最後一個引數進行宣告
    //不定長引數傳入的其實是一個slice切片,但是其實際上是傳入的一個值拷貝,在函式內修改不會影響原值

}

匿名函式

不給函式指定名稱,而是直接將其作為變數型別使用

//匿名函式,直接將函式作為變數進行使用
    a := func() {
        fmt.Println("匿名函式")
    }
    a()

函式閉包

//定義返回值為一個函式
func closure(x int) func(int) int {
    return func(y int) int {
        return x + y
    }
}
//使用函式
f := closure(10)
fmt.Println(f(1))

defer

  • 在函式體執行結束之後按照呼叫順序的相反順序逐個執行
  • 即使函式發生嚴重錯誤也會執行
  • 支援匿名函式的呼叫
  • 常用於資源釋放,清理等工作
  • 在函式return之後還可以對函式的計算結果進行修改
//列印結果會是a,c,b
func main{
    fmt.Println("a")
    defer fmt.Println("b")
    defer fmt.Println("c")
}

//列印結果是3,3,3,因為傳入的是一個引用,所以當迴圈體執行完之後i指向的是3,然後開始反向的順序執行defer,所以會是3,3,3
func main(){
    for i := 0; i < 3; i++ {
        defer func() {
            fmt.Println(i)
        }()
    }
}

Panic/Recover:

go中沒有try/catch語句來進行異常處理,都是用panic和recover來就進行

  • panic可以在需要的地方將程式中斷,不再執行
    ,可以在任何地方引發

  • recover可以配合defer函式來進行程式的恢復執行,但是隻有在defer呼叫的函式中有效

func main(){
    B()
    C()
    D()
}
func B() {
    fmt.Println("B")
}

func C() {

    //defer函式必須在panic之前,不然無法註冊的defer函式
    //recover只能在defer呼叫的函式內使用
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Recover in C")
        }
    }()
    panic("Panic in c")
}

func D() {
    fmt.Println("D")
}

Struct

  • 定義方法:type< Name > struct{},和C語言很相似
  • 結構之間可以相互的組合,但是沒有繼承
  • 同樣遵循命名的可見性規則
type Person struct {
    Name string
    Age  int
}

//匿名結構,不需要為定義的結構命名,直接定義好結構之後使用
a := struct {
        Name  string
        Color string
    }{
        Name:  "cai",
        Color: "Red",
    }
//在初始化時,內部的匿名結構不可以通過指定欄位名來命名,只能通過操作符'.'來初始化。
type Person struct {
    Name   string
    Age    int
    Family struct {
        Mom string
    }
}
//對結構體進行初始化
per := Person{
        Name: "wqc",
        Age:  10,
}
//只能這樣初始化內部的結構體
per.Family.Mom = "媽媽"

struct中的面向物件

在Go中不存在繼承關係,主要是通過組合的方式來實現一些公有屬性的抽象。

type People struct {
    Name string
    Age  int
    Sex  int
}

type Student struct {
    People
    Sid int
}
//對使用結構組合的結構進行初始化
xiaoMing := Student{
        Sid: 123,
        People: People{
            Name: "xiaoMing",
            Age:  12,
            Sex:  1,
        },
    }

    xiaoMing.Age = 100
    xiaoMing.Name = "whh"
    xiaoMing.Sex = 11

    fmt.Println(xiaoMing)

Method方法

  • Go中通過顯式說明receiver來實現與某個型別的組合

  • 只能為同一個包中的型別定義方法

  • Receiver可以是型別的值或者是指標

  • 不存在方法的過載

  • 可以使用值或者是指標來呼叫方法,編譯器會自動完成轉換

  • 如果外部結構和嵌入結構存在同名方法,則優先呼叫外部結構的方法

  • 類型別名不會擁有底層型別所附帶的方法

  • 方法可以使用結構中非公開的欄位

//方法的定義方式
type A struct {
    Name string
}

//func(要繫結的結構名) 函式名(引數列表)(返回值列表)

//值傳遞方式傳入的只是一個拷貝,無法對外部的值產生影響
func (a A) Print(x int) (flag bool) {
    fmt.Println("1")
}
//指標傳遞,傳入的是一個地址的拷貝,可以修改外部的值
func (a *A) Print(x int) (flag bool) {
    fmt.Println("1")
}

//在Go中不允許方法過載,以上邊為例:也就是說結構體A只能有一個叫Print的方法。

Interface介面

  • 介面是一個或者是多個方法簽名的集合

  • 只要某個型別擁有該介面的所有方法簽名,計算實現該介面,無需顯示宣告實現了那個介面,稱為structural Typing

  • 介面只有方法宣告,沒有實現,沒有欄位

  • 介面可以嵌入其他介面或者是結構中去

  • 只有當介面儲存的型別和物件都為nil時接口才等於nil

  • 將物件賦值給介面時會發生拷貝,而介面內部儲存的是指向這個複製品的指標,既然無法修改複製品的狀態,也無法回去指標

  • 空介面可以最為任何型別資料的容器

//定義介面
type USB interface {
    Name() string
    Connect()
}

//定義結構
type PhoneConnector struct {
    name string
}

//為結構宣告實現了介面的方法
func (pc PhoneConnector) Name() string {
    fmt.Println("Name ")
    return pc.name
}
//為結構宣告實現了介面的方法
func (pc PhoneConnector) Connect() {
    fmt.Println("Connect...", pc.name)
}

型別斷言

  • 通過型別斷言的ok patteern可以判斷介面中的資料型別
  • 使用type switch則可以針對空介面進行比較全面的型別判斷

介面轉換

  • 可以將擁有超集的介面轉換為子集的介面

Reflecttion反射

  • 反射可以提高程式的靈活性,使得interface有更大的發揮餘地
  • 方式使用TypeOf和ValueOf函式重介面中獲取目標物件資訊
  • 反射會將匿名欄位作為獨立欄位
  • 想要利用反射修改物件狀態,前提是interface.data是settable
  • 可以通過反射動態呼叫方法

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

func (user User) Hello() {
    fmt.Println("Hello World")
}

func Info(o interface{}) {
    t := reflect.TypeOf(o)
    fmt.Println(t.Name())

    //獲取所包含的欄位的資訊
    v := reflect.ValueOf(o)
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Println(f.Name, f.Type, val)
    }
    //獲取所包含的方法資訊
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Println(m.Name, m.Type)

    }
}

func main() {
    u := User{1, "ok", 12}
    Info(u)
}

可以通過使用反射來獲取變數的地址資訊,然後直接對地址中所存的資訊進行修改


x := 9
v := reflect.ValueOf(&x)
v.Elem().SetInt(999)
fmt.Println(x)

通過反射來修改介面中的欄位值
- 首先判斷是否獲取到欄位,而且值可以被修改
- 然後檢驗是否獲取到所需要的欄位
- 修改欄位值

func Set(o interface{}) {
    //獲取介面中的欄位值
    v := reflect.ValueOf(o)
    //檢驗是否獲取正確
    if v.Kind() == reflect.Ptr && !v.Elem().CanSet() {
        fmt.Println("XXX")
        return
    } else {
        v = v.Elem()
    }

    //獲取具體某一欄位
    f := v.FieldByName("Name")

    //檢驗是否成功獲取到
    if !f.IsValid() {
        fmt.Println("錯誤")
    }

    //通過set方法修改欄位值
    if f.Kind() == reflect.String {
        f.SetString("正確")
    }

}

Concurrency併發

高併發是Go的核心亮點,其實goroutine是官方實現的“超級執行緒池”,每個例項4-5KB的佔記憶體佔用和由於實現機制而大幅減少的建立和銷燬開銷。通過通訊來進行訊息互動,而不是通過共享記憶體來通訊。

通訊方式Channel

  • Channel是goroutine溝通的橋樑,大都是阻塞同步的

  • 通過make建立,通過close關閉

  • 可以使用for range來迭代不斷操作Channel

  • 可以設定通道為單項或者是雙向

  • 可以設定快取大小,在未被填滿前不會發生阻塞

func main() {
    //宣告一個通道
    channel := make(chan bool)

    go func() {
        fmt.Println("Go Go Go")
        //向通道中寫入資料
        channel <- true
        //關閉通道
        close(channel)
    }()

    //通過for range從通道中讀取資料
    for v := range channel {
        fmt.Println(v)
    }

}

Select

  • 可以處理一個或多個channel的傳送和接收

  • 同時有多個可用的channel是按隨機順序處理

  • 可用空的select來阻塞main函式

  • 可設定超時機制