Golang基礎語法補充

村望老弟發表於2021-11-24

1. switch

package main

import (
    "fmt"
    "os"
)

//從命令輸入引數,在switch中進行處理

func main() {
    //C: argc , **argv
    //Go: os.Args ==> 直接可以獲取命令輸入,是一個字串切片 []string
    cmds := os.Args

    //os.Args[0] ==> 程式名字
    //os.Args[1] ==> 第一個引數 ,以此類推
    for key, cmd := range cmds {
        fmt.Println("key:", key, ", cmd:", cmd, ", cmds len:", len(cmds))
    }

    if len(cmds) < 2 {
        fmt.Println("請正確輸入引數!")
        return
    }

    switch cmds[1] {
    case "hello":
        fmt.Println("hello")
        //go 的switch, 預設加上break了,不需要手動處理
        //如果想向下穿透的話,那麼需要加上關鍵字: fallthrough
        fallthrough
    case "world":
        fmt.Println("world")
    default:
        fmt.Println("default called!")
    }
}

2. 標籤

package main

import "fmt"

func main() {
    //標籤 LABEL1
    //goto LABEL  ===> 下次進入迴圈時,i不會儲存之前的狀態,重新從0開始計算,重新來過
    //continue LABEL1 ===> continue會跳到指定的位置,但是會記錄之前的狀態,i變成1
    //break LABEL1  ==> 直接跳出指定位置的迴圈

    //標籤的名字是自定義的,後面加上冒號
LABEL121:
    for i := 0; i < 5; i++ {
        for j := 0; j < 5; j++ {
            if j == 3 {
                //goto LABEL1
                //continue LABEL1
                break LABEL121
                //break
            }

            fmt.Println("i:", i, ",j:", j)
        }
    }

    fmt.Println("over!")
}

3. 列舉const+iota

package main

import "fmt"

//在go語言中沒有列舉型別,但是我們可以使用const + iota(常量累加器)來進行模擬
//模擬一個一週的列舉
const (
    MONDAY    = iota       //0
    TUESDAY   = iota       //1
    WEDNESDAY = iota       //2
    THURSDAY               //3  ==> 沒有賦值,預設與上一行相同iota ==> 3
    FRIDAY                 //4
    SATURDAY               //5
    SUNDAY                 //6
    M, N      = iota, iota //const屬於預編譯期賦值,所以不需要:=進行自動推導
)

const (
    JANU = iota + 1 //1
    FER             //2
    MAR             //3
    APRI            //4
)

//1. iota是常量組計數器
//2.iota從0開始,每換行遞增1
//3. 常量組有個特點如果不賦值,預設與上一行表示式相同
//4.如果同一行出現兩個iota,那麼兩個iota的值是相同的
//5.每個常量組的iota是獨立的,如果遇到const iota會重新清零

func main() {

    fmt.Println("列印周:")
    fmt.Println(MONDAY)
    fmt.Println(TUESDAY)
    fmt.Println(WEDNESDAY)
    fmt.Println(THURSDAY)
    fmt.Println(FRIDAY)
    fmt.Println("M:", M, ",N:", N)

    fmt.Println("列印月份:")
    fmt.Println(JANU) //1
    fmt.Println(FER)  //2
    fmt.Println(MAR)  //3

    //var number int
    //var name string
    //var flag bool
    //
    //
    ////可以使用變數組來將統一定義變數
    //var (
    //    number int
    //    name string
    //    flag bool
    //)

}

在goland中配置git shell,並且顯示中文:

www.bbsmax.com/A/n2d9YvN6dD/

我的安裝路徑D:\Program Files (x86)\Tools\Git\Git\etc)下bash.bashrc檔案

export LANG="zh_CN.UTF-8"
export LC_ALL="zh_CN.UTF-8"

git終端顯示中午設定:

4. 結構體

在go語言中,使用結構體來模擬類

package main

import "fmt"

//c語言裡面,我們可以使用typedef  int MyInt
type MyInt int //type相當於typdef

//C:
//struct Person {
//
//}

//go語言結構體使用type + struct來處理
type Student struct {
    name   string
    age    int
    gender string
    score  float64
}

func main() {
    t1 := struct {
        a int
    }{
        a : 100
    }

    fmt.Println(t1)

    var i, j MyInt
    i, j = 10, 20

    fmt.Println("i+j:", i+j)

    //建立變數,並賦值
    lily := Student{
        name:   "Lily",
        age:    20,
        gender: "女生",
        //score:  80, //最後一個元素後面必須加上逗號,如果不加逗號則必須與}同一行
        //}
        score: 80} //最後一個元素後面必須加上逗號,如果不加逗號則必須與}同一行

    //使用結構體各個欄位
    fmt.Println("lily:", lily.name, lily.age, lily.gender, lily.score)

    //結構體沒有-> 操作
    s1 := &lily
    fmt.Println("lily 使用指標s1.name列印:", s1.name, s1.age, s1.gender, s1.score)
    fmt.Println("lily 使用指標(*s1).name列印:", (*s1).name, s1.age, s1.gender, s1.score)

    //在定義期間對結構體賦值時,如果每個欄位都賦值了,那麼欄位的名字可以省略不寫
    //如果只對區域性變數賦值,那麼必須明確指定變數名字
    Duke := Student{
        name: "Duke",
        age:  28,
        //"男生",
        // 99,
    }
    Duke.gender = "男生"
    Duke.score = 100

    fmt.Println("Duke:", Duke)
}

5.init函式

C語言沒有init函式,C語言一般需要自己去寫init,然後在建構函式中呼叫

Go語言自帶init函式,每一個包都可以包含一個或多個init函式

package sub

import "fmt"

//0.這個init會在包被引用的時候(import)進行自動呼叫
//1.init函式沒有引數,沒有返回值,原型固定如下
//2.一個包中包含多個init時,呼叫順序是不確定的(同一個包的多個檔案中都可以有init)
//3. init函式時不允許使用者顯示呼叫的
//4. 有的時候引用一個包,可能只想使用這個包裡面的init函式(mysql的init對驅動進行初始化)
//但是不想使用這個包裡面的其他函式,為了防止編譯器報錯,可以使用_形式來處理
//import _ "xxx/xx/sub"
func init() {
    fmt.Println("this is first init() in package sub ==> sub.go")
}

func init() {
    fmt.Println("this is second init() in package sub ==> sub.go ")
}

//在go語言中,同一層級目錄,不允許出現多個包名
func Sub(a, b int) int {
    //init() ==> 不允許顯示呼叫
    test4() //由於test4與sub.go在同一個包下面,所以可以使用,並且不需要sub.形式
    return a - b
}

utils.go

package sub

//package utils //不允許出現多個包名

import "fmt"

func init() {
    fmt.Println("this is init in sub utils.go")
}

func test4() {
    fmt.Println("this is test4() in sub/utils!")
}

main.go

package main

import (
    _ "day02/05-init函式/sub" //此時,只會呼叫sub裡面的init函式,編譯還不會出錯
    //"fmt"
)

func main() {
    //res := sub.Sub(10, 5)
    //fmt.Println("sub.Sub(20,10) =", res)
}

效果:

使用init場景,在配置檔案管理器中寫init,用於載入配置檔案並解析:

configManager {
    //解析載入配置檔案
    //IP, PORT
}

6.defer(延遲)

延遲,關鍵字,可以用於修飾語句,函式,確保這條語句可以在當前棧退出的時候執行

lock.Lock()
a = "hello"
lock.Unlock()   <=== 經常容易忘掉解鎖

go語言可以使用defer來解決這個問題

{
    lock.Lock()
    defer lock.Unlock()   <=== 在當前棧退出的時候(例如:函式結束時)
    a = "hello"
}

{
    f1,_ := file.Open()
    defer f1.Close()
}

例項:

package main

import (
    "fmt"
    "os"
)

func main() {
    //1.延遲,關鍵字,可以用於修飾語句,函式,確保這條語句可以在當前棧退出的時候執行
    //2. 一般用於做資源清理工作
    //3. 解鎖、關閉檔案
    //4. 在同一個函式中多次呼叫defer,執行時類似於棧的機制:先後入後出

    filename := "01-switch.go"
    readFile(filename)
}

func readFile(filename string) {
    //func Open(name string) (*File, error) {
    //1. go語言一般會將錯誤碼作為最後一個引數返回
    //2. err一般nil代表沒有錯誤,執行成功,非nil表示執行失敗
    f1, err := os.Open(filename)

    //匿名函式,沒有名字,屬於一次性的邏輯 ==> lamada表示式
    defer func(a int) {
        fmt.Println("準備關閉檔案, code:", a)
        _ = f1.Close()
    }(100) //建立一個匿名函式,同時呼叫

    if err != nil {
        fmt.Println("os.Open(\"01-switch.go\") ==> 開啟檔案失敗, err:", err)
        return
    }

    defer fmt.Println("0000000")
    defer fmt.Println("1111111")
    defer fmt.Println("2222222")

    buf := make([]byte, 1024)  //byte ==> char ==> uint8
    //func (f *File) Read(b []byte) (n int, err error) {
    n, _ := f1.Read(buf)
    fmt.Println("讀取檔案的實際長度:", n)
    fmt.Println("讀取的檔案內容:", string(buf))  ==> 型別轉換

}

go語言支援類的操作,但是沒有class關鍵字,使用struct來模擬類

1.封裝-繫結方法

package main

import "fmt"

//Person類,繫結方法:Eat,Run,Laugh, 成員
//public,private
/*
class Person {
public :
    string name
    int age

public :
    Eat() {
        xxx
    }
}

*/

//任何type的型別,都可以繫結方法
type MyInt1 int

func (mi *MyInt1) printMyInt() {
    fmt.Println("MyInt value is:", *mi)
}

type Person struct {
    //成員屬性:
    name   string
    age    int
    gender string
    score  float64
}
/*
Person:::Eat() {

}
*/

//在類外面繫結方法
func (this *Person) Eat() {
    //fmt.Println("Person is eating")
    //類的方法,可以使用自己的成員
    //fmt.Println(this.name + " is eating!")
    this.name = "Duke"
}

func (this Person) Eat2() {
    fmt.Println("Person is eating")
    //類的方法,可以使用自己的成員
    this.name = "Duke"
}

func main() {
    lily := Person{
        name:   "Lily",
        age:    30,
        gender: "女生",
        score:  10,
    }

    lily1 := lily

    fmt.Println("Eat,使用p *Person,修改name的值 ...")
    fmt.Println("修改前lily:", lily) //lily
    lily.Eat()
    fmt.Println("修改後lily:", lily) //Duke

    fmt.Println("Eat2,使用p Person,但是不是指標 ...")
    fmt.Println("修改前lily:", lily1) //lily
    lily1.Eat2()
    fmt.Println("修改後lily:", lily1) //lily

    var myint1 MyInt1 = 100
    myint1.printMyInt()
}

2. 類繼承

package main

import "fmt"

type Human struct {
    //成員屬性:
    name   string
    age    int
    gender string
}

//在類外面繫結方法
func (this *Human) Eat() {
    fmt.Println("this is :", this.name)
}

//定義一個學生類,去巢狀一個Hum
type Student1 struct {
    hum    Human //包含Human型別的變數, 此時是類的巢狀
    score  float64
    school string
}

//定義一個老師,去繼承Human
type Teacher struct {
    Human          //直接寫Huam型別,沒有欄位名字
    subject string //學科
}

func main() {
    s1 := Student1{
        hum: Human{
            name:   "Lily",
            age:    18,
            gender: "女生",
        },
        school: "昌平一中",
    }

    fmt.Println("s1.name:", s1.hum.name)
    fmt.Println("s1.school:", s1.school)

    t1 := Teacher{}
    t1.subject = "語文"
    t1.name = "榮老師" //下面這幾個欄位都是繼承自Human
    t1.age = 35
    t1.gender = "女生"

    fmt.Println("t1 :", t1)
    t1.Eat()

    //繼承的時候,雖然我們沒有定義欄位名字,但是會自動建立一個預設的同名欄位
    //這是為了在子類中依然可以操作父類,因為:子類父類可能出現同名的欄位
    fmt.Println("t1.Human.name:", t1.Human.name)
}

訪問許可權

//在go語言中,許可權都是通過首字母大小來控制
//1. import ==》 如果包名不同,那麼只有大寫字母開頭的才是public的
//2. 對於類裡面的成員、方法===》只有大寫開頭的才能在其他包中使用

3. interface(介面)

package main

import "fmt"

//在C++中,實現介面的時候,使用純虛擬函式代替介面
//在go語言中,有專門的關鍵字 interface來代表介面
//interface不僅僅是用於處理多型的,它可以接受任意的資料型別,有點類似void

func main() {
    //func Println(a ...interface{}) (n int, err error) {
    fmt.Println("")

    //var i,j,k int
    //定義三個介面型別
    var i, j, k interface{}
    names := []string{"duke", "lily"}
    i = names
    fmt.Println("i代表切片陣列:", i)

    age := 20
    j = age
    fmt.Println("j代表數字:", j)

    str := "hello"
    k = str
    fmt.Println("k代表字串:", k)

}

package main

import "fmt"

//在C++中,實現介面的時候,使用純虛擬函式代替介面
//在go語言中,有專門的關鍵字 interface來代表介面
//interface不僅僅是用於處理多型的,它可以接受任意的資料型別,有點類似void

func main() {
    //func Println(a ...interface{}) (n int, err error) {
    fmt.Println("")

    //var i,j,k int
    //定義三個介面型別
    var i, j, k interface{}
    names := []string{"duke", "lily"}
    i = names
    fmt.Println("i代表切片陣列:", i)

    age := 20
    j = age
    fmt.Println("j代表數字:", j)

    str := "hello"
    k = str
    fmt.Println("k代表字串:", k)

    //我們現在只知道k是interface,但是不能夠明確知道它代表的資料的型別
    kvalue, ok := k.(int)  //<<<==== 做型別的二次確認
    if !ok {
        fmt.Println("k不是int")
    } else {
        fmt.Println("k是int, 值為:", kvalue)
    }

    //最常用的場景: 把interface當成一個函式的引數,(類似於print),使用switch來判斷使用者輸入的不同型別
    //根據不同型別,做相應邏輯處理

    //建立一個具有三個介面型別的切片
    array := make([]interface{}, 3)
    array[0] = 1
    array[1] = "Hello world"
    array[2] = 3.14

    for _, value := range array {
        //可以獲取當前介面的真正資料型別
        switch v := value.(type) {
        case int:
            fmt.Printf("當前型別為int, 內容為:%d\n", v)
        case string:
            fmt.Printf("當前型別為string, 內容為: %s\n", v)
        case bool:
            fmt.Printf("當前型別為bool, 內容為: %v\n", v) //%v可以自動推到輸出型別
        default:
            fmt.Println("不是合理的資料型別")
        }
    }
}

4. 多型

C語言的多型需要父子繼承關係

go語言的多型不需要繼承,只要實現相同的介面即可

package main

import "fmt"

//實現go多型,需要實現定義介面
//人類的武器發起攻擊,不同等級子彈效果不同

//定義一個介面, 注意型別是interface
type IAttack interface {
    //介面函式可以有多個,但是隻能有函式原型,不可以有實現
    Attack()
    //Attack1()
}

//低等級
type HumanLowLevel struct {
    name  string
    level int
}

func (a *HumanLowLevel) Attack() {
    fmt.Println("我是:", a.name, ",等級為:", a.level, ", 造成1000點傷害")
}

//高等級
type HumanHighLevel struct {
    name  string
    level int
}

func (a *HumanHighLevel) Attack() {
    fmt.Println("我是:", a.name, ",等級為:", a.level, ", 造成5000點傷害")
}

//定義一個多型的通用介面,傳入不同的物件,呼叫同樣的方法,實現不同的效果 ==》 多型
func DoAttack(a IAttack) {
    a.Attack()
}

func main() {
    //var player interface{}
    var player IAttack //定義一個包含Attack的介面變數

    lowLevel := HumanLowLevel{
        name:  "David",
        level: 1,
    }

    highLevel := HumanHighLevel{
        name:  "David",
        level: 10,
    }

    lowLevel.Attack()
    highLevel.Attack()

    //對player賦值為lowLevel,介面需要使用指標型別來賦值
    player = &lowLevel
    player.Attack()

    player = &highLevel
    player.Attack()

    fmt.Println("多型......")
    DoAttack(&lowLevel)
    DoAttack(&highLevel)
}

  1. 定義一個介面,裡面設計好需要的介面,可以有多個(Attack (), Attack()1 ..)

  2. 任何實現了這個介面的類,都可以賦值給這個介面,從而實現多型

  3. 多個類之間不需要有繼承關係

  4. 如果interface中定義了多個介面,那麼實際的類必須全部實現介面函式,才可以賦值

1. 基礎

併發:電腦同時聽歌,看小說,看電影。cpu根據時間片進行劃分,交替執行這個三個程式。我們人可以感覺是同時產生的。

並行:多個CPU(多核)同時執行

c語言裡面實現併發過程使用的是多執行緒(C++的最小資源單元),程式

go語言裡面不是執行緒,而是go程 ==> goroutine,go程是go語言原生支援的

每一個go程佔用的系統資源遠遠小於執行緒,一個go程大約需要4K~5K的記憶體資源

一個程式可以啟動大量的go程:

  • 執行緒 ==》幾十個
  • go程可以啟動成百上千個, ===》 對於實現高併發,效能非常好
  • 只需要在目標函式前加上go關鍵字即可
package main

import (
    "fmt"
    "time"
)

//這個將用於子go程使用
func display() {
    count := 1
    for {
        fmt.Println("=============> 這是子go程:", count)
        count++
        time.Sleep(1 * time.Second)
    }
}

func main() {
    //啟動子go程
    //go display()
    go func() {
        count := 1
        for {
            fmt.Println("=============> 這是子go程:", count)
            count++
            time.Sleep(1 * time.Second)
        }
    }()

    //主go程
    count := 1
    for {
        fmt.Println("這是主go程:", count)
        count++
        time.Sleep(1 * time.Second)
    }
}

啟動多個字go程,他們會競爭cpu資源

package main

import (
    "fmt"
    "time"
)

//這個將用於子go程使用
func display(num int) {
    count := 1
    for {
        fmt.Println("=============> 這是子go程:", num, "當前count值:", count)
        count++
        //time.Sleep(1 * time.Second)
    }
}

func main() {
    //啟動子go程
    for i := 0; i < 3; i++ {
        go display(i)
    }

    //go func() {
    //    count := 1
    //    for {
    //        fmt.Println("=============> 這是子go程:", count)
    //        count++
    //        time.Sleep(1 * time.Second)
    //    }
    //}()

    //主go程
    count := 1
    for {
        fmt.Println("這是主go程:", count)
        count++
        time.Sleep(1 * time.Second)
    }
}

2. 提前退出go程

package main

import (
    "fmt"
    "runtime"
    "time"
)

//return  ===> 返回當前函式
//exit ===> 退出當前程式
//GOEXIT ===> 提前退出當前go程

func main() {
    go func() {
        go func() {
            func() {
                fmt.Println("這是子go程內部的函式!")
                //return //這是返回當前函式
                //os.Exit(-1) //退出程式
                runtime.Goexit() //退出當前go程
            }()

            fmt.Println("子go程結束!") //這句會列印嗎? 會1:  不列印2
            fmt.Println("go 2222222222 ")

        }()
        time.Sleep(2 * time.Second)
        fmt.Println("go 111111111111111")
    }()

    fmt.Println("這是主go程!")
    time.Sleep(3 * time.Second)
    fmt.Println("OVER!")
}

3. 無緩衝管道channel

package main

import (
    "fmt"
    "time"
)

func main() {
    //sync.RWMutex{}
    //當涉及到多go程時,c語言使用互斥量,上鎖來保持資源同步,避免資源競爭問題
    //go語言也支援這種方式,但是go語言更好的解決方案是使用管道、通道 channel
    //使用通道不需要我們去進行加解鎖
    //A 往通道里面寫資料  B從管道里面讀資料,go自動幫我們做好了資料同步

    //建立管道:  建立一個裝數字的管道 ==> channel
    //strChan := make(chan string) //裝字串的管道

    //make(map[int]string, 10)
    //裝數字的管道,使用管道的時候一定要make, 同map一樣,否則是nil
    //此時是無緩衝的管道
    //numChan := make(chan int)

    //有緩衝的管道
    numChan := make(chan int, 10)

    //建立兩個go程,父親寫資料,兒子讀資料
    go func() {
        for i := 0; i < 50; i++ {
            data := <-numChan
            fmt.Println("子go程1 讀取資料  ===》 data:", data)
        }
    }()

    go func() {
        for i := 0; i < 20; i++ {
            //向管道中寫入資料
            numChan <- i
            fmt.Println("子go程2 寫入資料:", i)
            //time.Sleep(1 * time.Second)
        }
    }()

    for i := 20; i < 50; i++ {
        //向管道中寫入資料
        numChan <- i
        fmt.Println("======> 這是主go程, 寫入資料:", i)
        //time.Sleep(1 * time.Second)
    }

    time.Sleep(5 * time.Second)
}

4. 有緩衝區管道

package main

import (
    "fmt"
    "time"
)

func main() {
    //numsChan := make(chan int, 10)
    //1. 當緩衝寫滿的時候,寫阻塞,當被讀取後,再恢復寫入
    //2. 當緩衝區讀取完畢,讀阻塞
    //3. 如果管道沒有使用make分配空間,那麼管道預設是nil的,讀取、寫入都會阻塞
    //4. 對於一個管道,讀與寫的次數,必須對等

    var names chan string //預設是nil的
    names = make(chan string, 10)

    go func() {
        fmt.Println("names:", <-names)
    }()

    names <- "hello" //由於names是nil的,寫操作會阻塞在這裡
    time.Sleep(1 * time.Second)

    numsChan1 := make(chan int, 10)

    //寫
    go func() {
        for i := 0; i < 50; i++ {
            numsChan1 <- i
            fmt.Println("寫入資料:", i)
        }
    }()

    //讀,當主程式被管道阻塞時,那麼程式將鎖死崩潰
    //要求我們一定要讀寫次數保持一致
    func() {
        for i := 0; i < 60; i++ {
            fmt.Println("主程式準備讀取資料.....")
            data := <-numsChan1
            fmt.Println("讀取資料:", data)
        }
    }()

    for {
        ;
    }
}
  1. 當管道的讀寫次數不一致的時候
    1. 如果阻塞在主go程,那麼程式會崩潰
    2. 如果阻塞在子go程,那麼會出現記憶體洩露

5. for range遍歷

package main

import "fmt"

func main() {

    numsChan2 := make(chan int, 10)

    //寫
    go func() {
        for i := 0; i < 50; i++ {
            numsChan2 <- i
            fmt.Println("寫入資料:", i)
        }
        fmt.Println("資料全部寫完畢,準備關閉管道!")
        close(numsChan2)
    }()

    //遍歷管道時,只返回一個值
    //for range是不知道管道是否已經寫完了,所以會一直在這裡等待
    //在寫入端,將管道關閉,for range遍歷關閉的管道時,會退出
    for v := range numsChan2 {
        fmt.Println("讀取資料 :", v)
    }

    fmt.Println("OVER!")
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結
CunWang@Ch

相關文章