<dfn id="hx5t3"><strike id="hx5t3"><em id="hx5t3"></em></strike></dfn>

    <thead id="hx5t3"></thead><nobr id="hx5t3"><font id="hx5t3"><rp id="hx5t3"></rp></font></nobr>

    <listing id="hx5t3"></listing>

    <var id="hx5t3"></var>
    <big id="hx5t3"></big>

      
      

      <output id="hx5t3"><ruby id="hx5t3"></ruby></output>
      <menuitem id="hx5t3"><dfn id="hx5t3"></dfn></menuitem>

      <big id="hx5t3"></big>

        xialeistudio

        xialeistudio 查看完整檔案

        廣州編輯湖南工業大學  |  網絡工程 編輯  |  填寫所在公司/組織 www.ddhigh.com 編輯
        編輯

        公眾號:Nodejs之路

        個人動態

        xialeistudio 發布了文章 · 3月17日

        Golang程序設計——函數

        本文學習Go語言函數知識。函數是基本的代碼塊,用于執行一個任務。在Go語言中,函數可以接收數量不固定的參數,也可以返回多個結果。

        函數結構

        在編程領域,函數向編譯器和開發者提供了有關的信息,這些信息指明了函數該接收什么樣的輸入以及會產生什么樣的輸出。這些信息是通過函數第一行提供的,第一行稱為函數簽名。

        Go語言聲明函數語法如下:

        func 函數名稱(參數名 參數類型) (返回值名稱 返回值類型) {
          // 函數體
          return語句
        }
        1. 參數名在參數類型前面,如a int,這一點和其他語言是不同的
        2. 函數參數數量可以不固定,但是只允許最后一個參數數量不固定,而且必須是同種類型
        3. 返回值名稱不是必須的,但是參數名是必須寫的
        4. 有返回值的函數,函數體內必須包含return語句

        示例:函數定義與調用

        package main
        
        import "fmt"
        
        func sum(a, b int) int {
            return a + b
        }
        func main() {
            fmt.Printf("1+2=%d\n", sum(1, 2))
        }

        在Go語言中,如果多個參數或多個返回值類型相同,只需要在最后一個參數或返回值聲明類型。

        例如下面的函數簽名在Go語言中是合法的。

        func sum2(a, b int) (c, d int) 

        不定參數函數

        不定參數也就是數量不固定的參數。例如C語言中的printf函數就是一個典型的不定參數函數。Go語言支持不定參數函數,但是不定參數的類型必須相同。要聲明不定參數,需要使用3個點(...)。

        示例:不定參數的加法函數

        package main
        
        import "fmt"
        
        func sum(nums ...int) int {
            total := 0
            for _, n := range nums {
                total += n
            }
            return total
        }
        func main() {
            fmt.Printf("1+2+3+4=%d\n", sum(1, 2, 3, 4))
        }

        在sum函數中,nums是一個包含所有參數的切片。

        函數返回值

        多返回值

        在Go語言中,函數能聲明多個返回值,在這種情況下,return可以返回多個結果。函數調用者可通過多變量聲明接收多個返回值。

        示例:多返回值函數

        package main
        
        import (
            "errors"
            "fmt"
        )
        
        func div(a, b int) (int, error) {
            if b == 0 {
                return 0, errors.New("b is zero")
            }
            return a / b, nil
        }
        
        func main() {
            ret, err := div(2, 1)
            if err != nil {
                fmt.Println(err)
                return
            }
            fmt.Printf("2/1=%d\n", ret)
        }

        命名返回值

        命名返回值讓函數能夠在返回前將返回值賦給命名變量,這種設計有利于提高程序可讀性。要指定命名返回值,可在函數簽名的返回值類型前面添加變量名。

        示例:命名返回值函數

        package main
        
        import (
            "fmt"
        )
        
        func sum(a, b int) (total int) {
            total = a + b
            return
        }
        
        func main() {
            fmt.Printf("1+2=%d\n", sum(1, 2))
        }

        使用命名返回值后,return關鍵字可以單獨出現,當然,return關鍵字繼續返回結果值也是合法的。

        func sum(a, b int) (total int) {
            total = a + b
            return total
        }

        函數類型

        在Go語言中,函數是一種數據類型,可以將函數賦值給變量、或者作為參數傳遞,也可以作為返回值返回。

        示例:將函數作為變量、參數、返回值。

        package main
        
        import "fmt"
        
        func main() {
            // 函數作為變量
            sum := func(a, b int) int {
                return a + b
            }
            fmt.Printf("1+1=%d\n", sum(1, 1))
            // 函數作為參數
            sum2(1, 1, func(total int) {
                fmt.Printf("1+1=%d\n", total)
            })
            // 函數作為返回值
            totalFn := sum3(1, 1)
            fmt.Printf("1+1=%d\n", totalFn())
        }
        
        func sum2(a, b int, callback func(int)) {
            total := a + b
            callback(total)
        }
        
        func sum3(a, b int) func() int {
            return func() int {
                return a + b
            }
        }

        匿名函數、閉包、延遲執行函數

        匿名函數

        匿名函數指沒有名稱的函數,只有函數簽名(參數和返回值聲明)和函數體,匿名函數經常用于回調、閉包、臨時函數等。

        示例:利用匿名函數實現事件總線。

        package main
        
        import "fmt"
        
        func main() {
            emitter := make(map[string]func())
            addEventListener(emitter, "event1", func() {
                fmt.Println("event1 called")
            })
            emit(emitter, "event2")
        }
        // 添加事件監聽器
        // emitter 事件總線
        // event 事件名
        // callback 回調函數
        func addEventListener(emitter map[string]func(), event string, callback func()) {
            emitter[event] = callback
        }
        
        // 觸發事件
        // emitter 事件總線
        // event 事件名
        func emit(emitter map[string]func(), event string) {
            callback, ok := emitter[event]
            if ok {
                callback()
            }
        }

        main函數調用addEventListener時傳入的第三個函數即為匿名函數。

        閉包

        閉包可以理解為定義在一個函數內部的函數。在本質上,閉包是函數和其引用環境的組合體。引用環境即使在外部函數執行結束也不會被回收,因此可以利用閉包保存保存執行環境。

        示例:利用閉包提供唯一ID生成器。

        package main
        
        import "fmt"
        
        func main() {
            s1 := sequenceId()
            s2 := sequenceId()
            fmt.Printf("s1 -> %v\n", s1())
            fmt.Printf("s1 -> %v\n", s1())
            fmt.Printf("s2 -> %v\n", s2())
            fmt.Printf("s2 -> %v\n", s2())
        }
        
        func sequenceId() func() int {
            var id int
            return func() int {
                id++
                return id
            }
        }

        輸出如下

        s1 -> 1
        s1 -> 2
        s2 -> 1
        s2 -> 2

        函數sequenceId定義了一個局部變量id,并返回了一個子函數,子函數內部訪問了外部的id,因此這構成一個典型的閉包。在前面的內容中我們學習過變量作用域,內部總是可以訪問外部的變量或常量,而外部無法訪問內部的變量或常量。此外,由于變量id被子函數使用,因此在sequenceId函數返回后,id也不會被銷毀。

        每調用一次sequenceId函數都會返回一個新的子函數以及對應的id,因此s1和s2之間的輸出互不影響。

        注意:由于閉包會導致被引用的變量無法銷毀,因此需要注意使用,避免產生內存泄漏。

        延遲執行函數

        在實際編程中往往會打開一些資源,例如文件、網絡連接等等,這些資源在使用完畢時(無論是正常關閉或者函數異常)需要主動關閉,當函數的結束分支太多或者邏輯比較復雜時容易發生忘記關閉的情況導致資源泄漏。

        Go語言提供了defer關鍵字用來延遲執行一個函數,一般使用該函數延遲關閉資源。多個defer語句會按照先進后出的方式執行,也就是最后聲明的最先執行,典型的棧結構。

        示例:defer執行順序。

        package main
        
        import "fmt"
        
        func main() {
            defer f1()
            defer f2()
            fmt.Println("call main")
        }
        
        func f1() {
            fmt.Println("call f1")
        }
        
        func f2() {
            defer fmt.Println("defer call f2")
            fmt.Println("call f2")
        }

        輸出如下

        call main
        call f2
        defer call f2
        call f1
        1. 第一行輸出call main是因為main函數中只有一個非defer語句,因此call main最先執行
        2. 第二行輸出call f2是因為f2函數內部有一個非defer語句
        3. 第三行輸出defer call f2是因為f2函數的fmt.Println("call f2")執行完畢后才能執行defer
        4. 第四行輸出call f1是因為defer f1()最先聲明因此最后執行

        示例:基于defer和閉包構造一個函數執行耗時記錄器。

        package main
        
        import (
            "fmt"
            "time"
        )
        
        type Person struct {
            Name string
            Age  int
            Sex  string
        }
        
        func main() {
            defer spendTime()()
            time.Sleep(time.Second)
            fmt.Println("call main")
        }
        
        func spendTime() func() {
            startAt := time.Now()
            return func() {
                fmt.Println(time.Since(startAt))
            }
        }

        輸出如下

        call main
        1.002345498s

        spendTime()會返回一個閉包,因此定義defer時會初始化startAt為當前時間,defer執行時會執行閉包函數得到函數耗時。main函數為了測試方便休眠了一秒鐘,因此可以看到輸出是超過1秒的。

        小結

        本文介紹了如何在Go語言中使用函數。包括不定參數函數、多返回值和命名返回值函數以及將函數作為類型使用的方法,最后介紹了匿名函數、閉包和延遲執行函數。接下來的內容中將介紹Go語言中的結構體。

        img

        查看原文

        贊 2 收藏 3 評論 0

        xialeistudio 回答了問題 · 3月11日

        前端向后端傳參(json),接口中的參數會被序列化了,怎么處理

        你這是GET請求,參數被URLEncode了,這是正常操作,否則一些特殊字符會導致請求異常。
        后端會自動解碼一次。PHP,Java,Go等等都支持的

        關注 7 回答 6

        xialeistudio 發布了文章 · 3月9日

        Golang程序設計——數據容器

        本文學習Go語言數據容器、包括數組、切片和映射。

        數組

        數組是一個數據集合,常用于存儲用數字索引的同類型數據。Go語言的數組調用函數時使用的是值傳遞,因此形參會拷貝一份實參的值。

        在Go語言中,聲明數組需要同時指定長度和數據類型,數組長度是其類型的一部分,因此[5]int[1]int是兩種類型。

        Go語言可以對數組進行寫入、讀取、刪除、遍歷等操作。

        package main
        
        import "fmt"
        
        func main() {
            // 聲明數組并指明長度,不初始化,因此a的5個元素為int類型的零值(0)
            var a [5]int
            // 聲明數組并指明長度,并初始化4個元素,因此b的最后1個元素為int類型零值(0)
            var b = [5]int{1, 2, 3, 4}
            // 聲明數組,不指明長度,編譯器會根據值數量推導長度為4
            var c = [...]int{1, 2, 3, 4}
            // 數組寫入
            a[0] = 0
            a[1] = 1
            // 數組讀取
            fmt.Printf("a[0]=%d\n", a[0])
            // 數組刪除(賦零值)
            a[0] = 0
            // 數組的遍歷
            for index, value := range c {
                fmt.Printf("c[%d]=%d\n", index, value)
            }
            // 輸出b
            fmt.Printf("b=%v\n", b)
        }

        切片

        使用切片

        在Go語言中,數組是一個重要的類型,但是使用切片的情況更多。切片是底層數組中的一個連續片段,因此數組支持的特性切片也全部支持,必須順序遍歷、通過索引訪問元素等等。

        為何使用切片的情況更多呢?主要是因為Go語言的數組不支持自動擴容,而且不支持刪除元素,更重要的是Go語言數組是值類型,切片是引用類型,在向函數傳參時切片擁有更好的性能。

        package main
        
        import "fmt"
        
        func main() {
            // 聲明一個大小為0的int類型切片
            var a = make([]int, 0)
            // 添加三個元素
            a = append(a, 1, 2, 3)
            fmt.Println(a)
            // 遍歷元素
            for index, value := range a {
                fmt.Printf("a[%d]=%d\n", index, value)
            }
            // 聲明一個大小為4的切片
            var b = make([]int, 4)
            // 將a的元素復制到b
            copy(b, a)
            // 刪除指定下標的元素
            a = append(a[:1], a[2:]...)
            fmt.Printf("a=%v\n", a)
            fmt.Printf("b=%v\n", b)
            // 使用值初始化切片
            var c = []int{1, 2, 3, 4}
            fmt.Printf("c=%v\n", c)
            // 只定義,不初始化切片
            var d []int
            d = append(d, 1, 2, 3)
            fmt.Printf("d=%v\n", d)
        }

        聲明切片可以不使用make初始化,append也不會報錯。

        運行時結構

        切片運行時結構如下:

        type slice struct {
            array unsafe.Pointer
            len   int
            cap   int
        }
        1. array是底層數組
        2. len是數組大小,可以通過len函數獲取
        3. cap是數組容量,可以通過cap函數獲取

        make函數創建切片有兩種寫法:

        make([]int, 0) // 1
        make([]int, 0, 8) // 2
        1. 聲明了一個長度為0的切片,此時len為0,cap也為0
        2. 聲明一個長度為0,容量為8的切片,此時len為0,cap為8

        追加元素

        Go語言提供append函數追加元素到切片中,append會在必要時擴容底層數組。擴容規則如下:

        1. 新容量小于1024時,每次擴容2倍。例如現有容量為2,擴容后為4
        2. 新容量大于1024時,每次擴容1.25倍。例如現有容量為1024,擴容后為1280
        package main
        
        import "fmt"
        
        func main() {
            // 直接使用值初始化切片
            var a = []int{1, 2, 3, 4, 5}
            a = append(a, 6, 7)
            var b = []int{9, 10}
            // 追加b的全部元素到a
            a = append(a, b...)
            fmt.Printf("a=%v\n", a)
            fmt.Printf("b=%v\n", b)
        }

        范圍操作符

        切片支持取范圍操作,新切片和原切片共享底層數組,因此對切片的修改會同時影響兩個切片。

        范圍操作符語法如下:a[begin:end],左閉右開區間。因此a[1:10]包含a切片索引為1~9的元素。

        package main
        
        import "fmt"
        
        func main() {
            // 直接使用值初始化切片
            var a = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
            var b = a[1:10]
            fmt.Println(b)
            // 修改新切片元素
            b[0] = 11
            fmt.Println(a)
            fmt.Println(b)
        }

        可以看到修改b索引為0的元素為11之后,a切片也同時受到影響。

        范圍操作符的切片這一點在編程中要特別注意!

        刪除元素

        利用范圍操作符和append函數可以刪除指定的切片元素。

        package main
        
        import "fmt"
        
        func main() {
            // 直接使用值初始化切片
            var a = []int{1, 2, 3, 4, 5}
            // 刪除第2個元素
            a = append(a[:1], a[2:]...)
            fmt.Println(a)
            // 刪除第2、3個元素
            a = []int{1, 2, 3, 4, 5}
            a = append(a[:1], a[3:]...)
            fmt.Println(a)
        }

        復制元素

        通過copy函數可以復制切片的全部或部分元素。在復制切片之前,需要聲明好目標切片并設置len。

        len必須大于0,否則將不會復制任何元素。

        package main
        
        import "fmt"
        
        func main() {
            // 直接使用值初始化切片
            var a = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
            var b = make([]int, 0, 8)
            copy(b, a)
            fmt.Println(b)
            var c = make([]int, 8)
            copy(c, a[9:10])
            fmt.Println(c)
        }

        程序輸出如下:

        []
        [10 0 0 0 0 0 0 0]

        可以看到切片b沒有任何值,切片c成功復制了a的最后一個元素。

        映射

        映射也叫字典、哈希表,數組和切片是通過數字索引訪問的順序集合,而映射是通過鍵來訪問的無序集合。映射在查找方面非常高效,有著O(1)的時間復雜度,是非常常用的數據結構。

        使用映射

        映射必須初始化之后才能使用,這一點和切片不同。

        package main
        
        import "fmt"
        
        func main() {
            // 使用make初始化映射
            var a = make(map[string]int)
            a["zhangsan"] = 18
            a["lisi"] = 28
            fmt.Printf("a=%v\n", a)
            // 使用值初始化映射
            var b = map[string]int{
                "zhangsan": 18,
                "lisi":     28,
            }
            fmt.Printf("b=%v\n", b)
            // 遍歷映射
            for key, value := range b {
                fmt.Printf("%s=%d\n", key, value)
            }
        }

        下面是未初始化映射的使用

        package main
        
        import "fmt"
        
        func main() {
            var a map[string]int
            a["zhangsan"] = 1
            for k, v := range a {
                fmt.Printf("%s=%d\n", k, v)
            }
        }
        

        該程序會產生運行時錯誤:

        panic: assignment to entry in nil map
        goroutine 1 [running]:
        main.main()
        /Users/example/go/src/go-microservice-inaction/src/2.1/main.go:7 +0x5d

        運行時結構

        映射的運行時結構如下:

        type hmap struct {
            count      int
            flags      uint8
            B          uint8
            noverflow  uint16
            hash0      uint32
            buckets    unsafe.Pointer
            oldbuckets unsafe.Pointer
            nevacuate  uintptr
            extra      *mapextra
        }

        部分字段說明如下:

        1. count是目前映射的鍵值對數量
        2. B是映射的容量,對數。例如B為8,則映射容量為28=256
        3. buckets中存儲具體的鍵值對
        4. oldbuckets在擴容中會使用到
        5. nevacuate 擴容進度指示器

        當裝載因子超過6.5時,映射將發生擴容操作。裝載因子計算公式:count/2B。例如當前為為166,此時裝載因子為166/28=0.6484375,繼續插入元素時,裝載因子變為167/28= 0.65234375,此時會觸發自動擴容。

        每次擴容會增加1倍的空間,同時會對已存在的鍵值對進行漸進式遷移(一次遷移一小部分)。

        添加元素

        Go語言映射添加元素和其他語言類似,使用[]語法即可。

        var m = make(map[string]int)
        m["name"] = 18

        添加元素時運行時會自動處理擴容和鍵值對遷移,無需用戶程序關心。

        刪除元素

        要從映射中刪除元素,需要使用delete函數。

        var m = map[string]int{
          "zhangsan":18,
        }
        delete(m, "zhangsan") 

        小結

        本章介紹了Go語言常用的數據容器,其中對切片和映射的底層原理進行了簡單介紹。Go語言通過內置切片和映射解決了C語言需要手動實現這兩種常用數據結構的問題,提高了開發效率。在下一章將介紹Go語言的函數。

        img

        查看原文

        贊 2 收藏 2 評論 0

        xialeistudio 回答了問題 · 3月6日

        NIO 多路復用問題

        一個線程對應一個服務端口,對應多個客戶端fd。

        多路復用跟端口無關。
        多路復用指單線程管理多個連接的通信。
        多路復用編程流程:

        1. 初始化多路復用器,(比如epoll_create,kqueue_init
        2. 服務端接收新連接,獲得fd
        3. 將fd和事件(read/write)注冊到多路復用器(比如epoll_ctl,kqueue EV_SET)
        4. 調用poll(比如epoll_wait,kqueue)

        當注冊的fd有指定事件發生時,poll函數將獲得這些fd以及對應的事件,此時可以調用read或者write函數處理數據。

        多路復用通過事件形式通知用戶線程進行read/write/accept操作,避免了阻塞(沒數據的時候read/write/accept會阻塞線程)。

        關注 2 回答 1

        xialeistudio 贊了回答 · 3月6日

        解決有沒有輕量級的 DOM 操作 js 庫,像 jquery 的 DOM 操作一樣?

        2021年了,可以考慮直接用原生API了

        關注 9 回答 7

        xialeistudio 發布了文章 · 2月26日

        Golang程序設計——基本語法

        本文學習Go語言基本語法,例如變量和常量、數據類型、運算符、條件語句、循環語句。

        變量和常量

        變量和常量是計算機程序不可或缺的部分。本節將介紹如何在Go程序中聲明、使用變量和常量、還將介紹聲明方式和作用域。

        變量聲明

        在Go語言中,聲明變量的方式有多種。在前面的文章介紹過,Go語言是一種靜態類型語言,因此聲明變量時必須指明其類型。

        例:聲明string類型的變量。

        package main
        
        import "fmt"
        
        func main() {
            var s1 string = "Hello World"
            var s2 = "Hello World"
            var s3 string
            s3 = "Hello World"
            fmt.Println(s1, s2, s3)
        }
        • 使用關鍵字var聲明變量。
        • 如果變量類型可以通過值推導則不用聲明類型。s2通過值可以推導類型為string類型。
        • 變量可以在聲明后賦值,未賦值的變量值為該類型的零值。
        變量的類型很重要,因為這決定了可將什么值賦給該變量。例如,對于類型為string的變量,不能將整數賦值給它。將不匹配的值賦值給變量時,將導致編譯錯誤。

        例:將string類型的值賦值給int類型的變量。

        package main
        
        import "fmt"
        
        func main() {
            var i int
            i = "Hello World"
            fmt.Println(i)
        }

        編譯該文件將導致編譯錯誤。

        go build main.go 
        # command-line-arguments
        ./main.go:7:4: cannot use "Hello World" (type untyped string) as type int in assignment

        多變量聲明

        例:聲明多個類型相同的變量并進行賦值(顯式指定類型)。

        package main
        
        import "fmt"
        
        func main() {
            var s1, s2 string = "S1", "S2"
            fmt.Println(s1, s2)
        }

        例:聲明多個類型不同的變量并進行賦值(不能顯式指定類型)。

        package main
        
        import "fmt"
        
        func main() {
            var s1, i1= "S1", 1
            fmt.Println(s1, i1)
        }

        例:聲明多個類型不同的變量(顯式指定類型)。

        package main
        
        import "fmt"
        
        func main() {
            var (
                s1 string
                i1 int
            )
            s1 = "Hello"
            i1 = 10
            fmt.Println(s1, i1)
        }
        聲明變量后可以再次賦值,但是同一個變量只允許聲明一次,否則將導致編譯錯誤。

        簡短變量聲明

        函數中聲明變量時,可以用更簡潔的方式。

        package main
        
        import "fmt"
        
        func main() {
            s1 := "Hello World"
            fmt.Println(s1)
        }
        • :=表示簡短變量聲明,可以不使用var,不指定類型,但是必須進行賦值。
        • 只能在函數中使用簡短變量聲明。

        變量聲明最佳實踐

        Go語言提供了多種變量聲明方式,下面的聲明方式都是合法的。

        var s string = "Hello"
        var s1 = "Hello"
        var s2 string
        s2 = "Hello"
        s3 := "Hello"

        該使用哪種方式呢?

        Go語言對此有一個限制——只能在函數內部使用簡短變量聲明,在函數外部必須使用var進行聲明。

        在標準庫中遵循的約定如下:有初始值的情況下,在函數內使用簡短變量聲明,在函數外使用var并省略類型;無初始值的情況下使用var并指定類型。
        package main
        
        import "fmt"
        
        var s = "Hello World"
        
        func main() {
            s1 := "Hello World"
            fmt.Println(s, s1)
        }

        變量和零值

        在Go語言中,聲明變量時如果未初始化,則變量為默認值,該默認值也稱為零值。在其他語言中未初始化的值為null或undefined。

        package main
        
        import "fmt"
        
        func main() {
            var s string
            var i int
            var b bool
            var f float32
            fmt.Printf("%v %v %v %v\n", s, i, b, f)
        }

        在Go語言中,檢查變量是否為空,必須與該類型的零值比較。例如檢測string類型的變量是否為空,可以與""判定。

        package main
        
        import "fmt"
        
        func main() {
            var s string
            if s == "" {
                fmt.Println("s為空")
            }
        }

        變量作用域

        作用域指變量可以在什么地方使用,而不是說變量在哪里聲明的。Go語言使用基于塊的詞法作用域,簡單來說就是{}會產生一個作用域。

        Go語言作用域規則如下:

        1. 一對大括號({})表示一個塊,塊是可以嵌套的
        2. 對于在塊內聲明的變量,可以在本塊以及子塊中訪問
        3. 子塊可以訪問父塊的變量,父塊不能訪問子塊的變量

        例:Go語言的作用域。

        package main
        
        import "fmt"
        
        func main() {
            var s1 = "s1"
            {
                var s2 = "s2"
                // 可以訪問s1,s2
                fmt.Println(s1, s2)
                {
                    var s3 = "s3"
                    // 可以訪問s1,s2,s3
                    fmt.Println(s1, s2, s3)
                }
            }
            // 只能訪問s1
            fmt.Println(s1)
        }
        簡單來說,就是塊內可以訪問塊外的變量,塊外不能訪問塊內變量。

        聲明常量

        常量只在整個程序運行過程中都不變的值,常量必須在聲明時賦值,聲明后不可以更改。

        Go語言使用const關鍵字聲明常量。

        package main
        
        import "fmt"
        
        const s = "Hello"
        
        func main() {
            const s2 = "World"
            const s3,s4 = "Hello","World"
            fmt.Println(s, s2)
        }
        常量也支持一次聲明多個,此外常量的作用域和變量作用域一致。

        數據類型

        Go語言提供了豐富的數據類型,按類別分為布爾型、數值型(整數、浮點數、復數)、字符串型 、派生型。其中派聲型包括指針類型、數組類型、結構體類型、接口類型、Channel類型、函數類型、切片類型和Map類型。

        派生類型我們將在后面的內容中進行介紹。

        布爾類型

        布爾類型值只能為true或false。某些語言允許使用1和0來表示true和false,但Go語言不允許。

        布爾類型的零值為false。

        package main
        
        import "fmt"
        
        func main() {
            var b bool
            if b {
                fmt.Println("b是true")
            } else {
                fmt.Println("b是false")
            }
        } 

        數值型

        Go語言中數值型包含整數、浮點數以及復數。

        整數型

        類型字節數范圍
        byte10 ~ 28
        uint810 ~ 28
        int81-27 ~ 27-1
        uint1620 ~ 216
        int162-215 ~ 215-1
        uint3240 ~ 232
        int324-231 ~ 231-1
        uint6480 ~ 264
        int648263 ~ 263-1
        int平臺相關(32位或64位)
        uint平臺相關(32位或64位)

        浮點數

        類型字節數范圍
        float324-3.403E38 ~ 3.403E38
        float648-1.798E308 ~ 1.798E308

        復數

        字符串類型

        字符串可以是任何字符序列,包括數字、字母和符號。Go語言使用Unicode來存儲字符串,因此可以支持世界上所有的語言。

        下面是一些字符串示例:

        var s = "$%^&*"
        var s2 = "1234"
        var s3 = "你好"

        運算符

        運算符用于在程序運行時執行數據運算和邏輯運算。Go語言支持的運算符有:

        • 算術運算符
        • 邏輯運算符
        • 關系運算符
        • 位運算符

        算術運算符

        算術運算符是用來對數值類型進行算術運算的。下表列出了Go語言支持的算術運算符。

        運算符說明
        +相加
        -相減
        *相乘
        /相除
        %取余
        ++自增
        --自減
        package main
        
        import "fmt"
        
        func main() {
            var (
                a = 10
                b = 20
            )
            fmt.Printf("a+b=%d\n", a+b)
            fmt.Printf("a-b=%d\n", a-b)
            fmt.Printf("a*b=%d\n", a*b)
            fmt.Printf("a/b=%d\n", a/b)
            fmt.Printf("a%%b=%d\n", a%b)
            a++
            fmt.Printf("a++=%d\n", a)
            a--
            fmt.Printf("a--=%d\n", a)
        }
        和其他語言不同的是,Go語言不提供++a,--a運算符,只提供a++,a--。

        關系運算符

        關系運算符用來判斷兩個值的關系。下表列出了Go語言支持的關系運算符。

        運算符說明
        ==判斷兩個值是否相等
        !=判斷兩個值是否不相等
        >判斷運算符左邊的值是否大于右邊的值
        <判斷運算符左邊的值是否小于右邊的值
        >=判斷運算符左邊的值是否大于等于右邊的值
        <=判斷運算符左邊的值是否小于等于右邊的值
        package main
        
        import "fmt"
        
        func main() {
            var (
                a = 10
                b = 20
            )
            if a == b {
                fmt.Println("a==b")
            } else {
                fmt.Println("a!=b")
            }
        
            if a < b {
                fmt.Println("a<b")
            } else {
                fmt.Println("a>=b")
            }
        
            if a <= b {
                fmt.Println("a<=b")
            } else {
                fmt.Println("a>b")
            }
        }

        邏輯運算符

        邏輯運算符用來對操作數進行邏輯判斷。下表列出了Go語言支持的邏輯運算符。

        運算符說明
        &&邏輯與。兩邊操作數都為true則結果為true,否則為false
        \\邏輯或。兩邊操作數只要有一個為true則結果為true,否則為false
        !邏輯非。如果操作數為true則結果為false,否則為true
        package main
        
        import "fmt"
        
        func main() {
            var a, b = true, false
            if a && b {
                fmt.Println("a和b同時為true")
            } else {
                fmt.Println("a和b至少一個為false")
            }
        
            if a || b {
                fmt.Println("a和b至少一個為true")
            } else {
                fmt.Println("a和b都為false")
            }
            if !a {
                fmt.Println("a是false")
            } else {
                fmt.Println("a是true")
            }
        }

        位運算符

        位運算符用來對整數進行二進制位操作。下表列出了Go語言支持的位運算符。

        運算符說明
        &按位與
        \按位或
        ^按位異或
        >>右移
        <<左移
        package main
        
        import "fmt"
        
        func main() {
            var (
                a = 1
                b = 2
            )
            fmt.Printf("a&b=%d\n", a&b)
            fmt.Printf("a|b=%d\n", a|b)
            fmt.Printf("a^b=%d\n", a^b)
            fmt.Printf("a>>1=%d\n", a>>1)
            fmt.Printf("a<<1=%d\n", a<<1)
        }

        條件語句

        條件語句是計算機程序的重要組成部分,幾乎所有編程語言都支持。簡單地說,條件語句檢查指定的條件是否滿足,并在滿足時執行指定的操作。

        下表列出了Go語言支持的條件語句。

        if由一個布爾表達式后緊跟一個或多個語句組成。
        if...else if...else由多個布爾表達式分支組成,并提供例外分支
        switch基于不同條件執行不同操作,并提供默認操作

        例:if的使用。

        package main
        
        import "fmt"
        
        func main() {
            var a = 10
            if a > 10 {
                fmt.Println("a大于10")
            } else if a == 10 {
                fmt.Println("a等于10")
            } else {
                fmt.Println("a小于10")
            }
        }

        例:switch的使用。

        package main
        
        import "fmt"
        
        func main() {
            var a = 10
        
            switch a {
            case 1:
                fmt.Println("a等于1")
            case 2:
                fmt.Println("a等于2")
            case 10:
                fmt.Println("a等于3")
            default:
                fmt.Println("默認分支")
            }
        }
        和其他語言不同,Go語言的case分支不需要添加break。

        循環語句

        在其他語言中一般會提供for、while、foreach等關鍵字實現循環,而在Go語言中只提供for關鍵字,但是也實現了類似的效果。

        for

        for循環有著經典的三段式結構:

        1. 循環初始化
        2. 循環終止條件
        3. 循環步進條件
        package main
        
        import "fmt"
        
        func main() {
          for i := 0; i < 10; i++ {
                fmt.Println(i)
          }
        }

        while

        while循環指定循環終止條件,不滿足條件時循環一直執行并向終止條件靠攏,滿足條件后終止循環。(無終止條件的循環稱為死循環)

        package main
        
        import "fmt"
        
        func main() {
          i := 0
          for i < 10 {
                fmt.Println(i)
            i++
          }
        }

        死循環不需要終止條件。

        package main
        
        import (
          "fmt"
          "time"
        )
        
        func main() {
          i := 0
          for {
            fmt.Println(i)
            i++
            time.Sleep(time.Second)
          }
        }

        foreach

        foreach循環多用來遍歷列表、字典等數據結構。

        package main
        
        import "fmt"
        
        func main() {
          list := []int{1, 2, 3, 4, 5}
          for index, value := range list {
                fmt.Println(index, value)
          }
        }

        continue

        continue用來跳過本次循環繼續執行下次循環。

        package main
        
        import "fmt"
        
        func main() {
          for i := 0; i < 5; i++ {
            if i == 1 {
              continue
            }
            fmt.Println(i)
          }
        }

        該程序判斷i為1時跳過并執行下次循環,該程序輸出如下。

        0
        2
        3
        4

        3.1.5 break

        break用來跳出循環,后續循環將不執行。

        package main
        
        import "fmt"
        
        func main() {
          for i := 0; i < 5; i++ {
            if i == 1 {
              break
            }
            fmt.Println(i)
          }
        }

        該程序判斷i為1時跳出循環,該程序輸出如下。

        0

        小結

        本文介紹了Go語言的基本語法,包括變量和常量的使用、基礎數據類型、流程控制等知識。下一章將介紹Go語言的數據容器類型,包括數組、切片和映射。

        img

        查看原文

        贊 5 收藏 3 評論 1

        xialeistudio 發布了文章 · 2月25日

        修復GitTalk出現Forbidden問題

        GitTalk失效原因

        對于所有自建博客的博主來書,GitTalk應該不陌生。GitTalk通過Github的OpenAPI以及issues功能實現社區評論,確實是一大亮點。

        今天在查看文章的時候發現評論區出現了Forbidden錯誤,通過檢查網絡請求發現獲取Github Token時請求了以下鏈接

        https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/access_token

        通過查詢GitTalk官方文檔發現github.com的oauth是不允許跨域請求的,cors-anywhere.herokuapp.com是一個第三方提供的CORS代理服務,會默認放行所有CORS請求。目前由于該CORS代理服務遭到濫用,因此做了限制,導致GitTalk失效。

        解決方案

        通過自己的nginx進行反向代理轉發即可。

        修改gitalk初始化參數

        筆者使用的是hexo+icarus主題,其他主題或者博客系統也是類似做法。

        編輯themes/icarus/layout/comment/gitalk.ejs

        <script>
            var gitalk = new Gitalk({
                clientID: '<%= get_config('comment.client_id') %>',
                clientSecret: '<%= get_config('comment.client_secret') %>',
                id: '<%= md5(page.path) %>',
                repo: '<%= get_config('comment.repo') %>',
                owner: '<%= get_config('comment.owner') %>',
                admin: <%- JSON.stringify(get_config('comment.admin'))%>,
                createIssueManually: <%= get_config('comment.create_issue_manually', false) %>,
                distractionFreeMode: <%= get_config('comment.distraction_free_mode', false) %>,
                proxy: '/github/login/oauth/access_token' // 新添加的
            })
            gitalk.render('comment-container')
        </script>

        nginx配置

        編輯nginx配置,筆者的博客域名為www.ddhigh.com,因此需要限制CORS來源域名,否則將有盜用風險!

        location /github {
            add_header Access-Control-Allow-Origin www.ddhigh.com;
          add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
          add_header Access-Control-Allow-Headers 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
          if ($request_method = 'OPTIONS') {
              return 204;
          }
          proxy_pass https://github.com/; # 尾部斜杠不能少
        }

        執行nginx -s reload配置。

        訪問測試

        訪問新寫的文章https://www.ddhigh.com/2021/0...,可以看到界面上已經正常了。

        image-20210225121050348

        查看Chrome網絡狀況,可以看到已經走了自己配置的CORS跨域了。

        Request URL: https://www.ddhigh.com/github/login/oauth/access_token
        Request Method: POST
        Status Code: 200 
        Remote Address: 106.52.24.199:443
        Referrer Policy: strict-origin-when-cross-origin
        查看原文

        贊 0 收藏 0 評論 0

        xialeistudio 發布了文章 · 2月25日

        Go語言程序設計

        Go語言概述

        語言歷史

        Go語言也稱為Golang,是由Google公司開發的一種靜態強類型、編譯型、語言原生支持并發、具有垃圾回收功能的編程語言。起源于2007年,并在2009年正式對外發布。Go語言是非常年輕的一門語言,它的主要目標是“兼具 Python 等動態語言的開發速度和 C/C++等編譯型語言的性能與安全性”。

        Go語言是編程語言設計的又一次嘗試,是對類C語言的重大改進,它不但能讓你訪問底層操作系統,還提供了強大的網絡編程和并發編程支持。Go語言的用途眾多,可以進行網絡編程、系統編程、并發編程等等。

        Go語言的推出,旨在不損失應用程序性能的情況下降低代碼的復雜性,具有“部署簡單、并發性好、語言設計良好、執行性能好”等優勢。

        Go語言有時候被描述為“21世紀的C語言”。Go 從C語言繼承了相似的表達式語法、控制流結構、基礎數據類型、調用參數傳值、指針等很多思想,還有C語言編譯后的運行效率。

        Go語言沒有類和繼承的概念,通過組合來實現代碼復用,同時它通過接口(interface)的概念來實現多態性。所以Go語言的面向對象編程和傳統面向對象語言(如C++和Java)并不相同。

        Go語言有一個吉祥物,在會議、文檔頁面和博文中,大多會包含下圖所示的 Go Gopher,這是才華橫溢的插畫家 Renee French 設計的,她也是 Go 設計者之一 Rob Pike 的妻子。

        img

        語言特性

        語法簡單

        Go語言的設計思想類似Unix的“少即是多”。Go語言的語法規則嚴謹,沒有歧義,這使得Go語言簡單易學。Go語言保留了指針,但通常情況下禁止指針運算(保留unsafe包操作指針的能力)。此外,Go語言還內置切片和字典,在保留運行性能的同時也提高了開發效率。

        語言級別支持并發

        主流的并發模型有多進程模型、多線程模型。和主流多并發模型不同,Go語言采用了基于CSP的協程實現,并且在運行時做了更深度的優化處理。這使得語言級別上并發編程變得極為容易,無須處理回調、也無需關注線程切換,只需要添加一個go關鍵字即可。

        “通過通信去共享內存,而不是通過共享內存去通信”,go語言內置的channel數據結構配合go關鍵字實現并發通信及控制,這對于需要考慮內存可見性等問題的多線程模型來說,是一個良好的解決方案。

        高效的垃圾回收

        Go語言的每次升級,垃圾回收器必然是核心組件里修改最多的部分。從并發清理,到降低STW時間,直到Go的1.5版本實現并發標記,逐步引入三色標記和寫屏障等等,都是為了能讓垃圾回收在不影響用戶邏輯的情況下更好地工作。從最開始的秒級別STW到目前的微秒級STW,Go語言開發團隊一直在垃圾回收方面進行努力。

        靜態鏈接

        靜態編譯的好處顯而易見。將運行時、依賴庫直接打包到可執行文件內部,簡化了部署和發布操作,無須事先安裝運行環境和下載諸多第三方庫。雖然相比動態編譯增加了可執行文件的大小,但是省去了依賴庫的管理。隨著微服務和容器化的發展,這也成為了Go語言的殺手锏之一,一個二進制文件即可運行服務。

        標準庫

        功能完善、質量可靠的標準庫為編程語言提供了有力的支持。在不借助第三方擴展的情況下,就可完成大部分基礎功能開發,這大大降低了學習和使用成本。

        Go語言標準庫可以說極為豐富。其中值得稱道的是net/http,僅須簡單幾條語句就能實現一個高性能 Web Server。

        工具鏈

        完整的工具鏈對于項目開發極為重要。Go語言在此做得相當不錯,無論是編譯、格式化、錯誤檢查、幫助文檔,還是第三方包下載、更新都有對應的工具。

        值得一提的gofmt工具,為了解決開發者經常遇到的“代碼風格不統一”的難題,官方直接通過gofmt指定一套標準,可以看出go語言在工程方面確實解決了許多實際問題。

        此外Go語言內置完整測試框架,其中包括單元測試、性能測試、代碼覆蓋率、數據競爭,以及用來調優的pprof,這些都是保障代碼能正確而穩定運行的必備利器。

        Go語言應用場景

        Go 語言從發布1.0版本以來備受眾多開發者關注并得到廣泛使用,Go 語言的簡單、高效、并發特性吸引了眾多傳統語言開發者的加入,而且人數越來越多。

        鑒于Go語言的特點和設計的初衷,Go語言作為服務器編程語言,很適合處理日志、數據打包、虛擬機處理、文件系統、分布式系統、數據庫代理等;網絡編程方面,Go語言廣泛應用于Web應用、API應用、下載應用等;除此之外,Go語言還適用于內存數據庫和云平臺領域,目前國外很多云平臺都是采用Go開發。

        • 服務器編程。例如處理日志、數據打包、虛擬機處理、文件系統等。
        • 分布式系統、數據庫代理器、中間件等。例如Etcd。
        • 網絡編程。這一塊目前應用最廣,包括Web應用、API應用、下載應用等等。
        • 開發云平臺。目前國內外很多云平臺在采用Go開發。

        Go語言知名項目

        Go發布之后,很多公司特別是云計算公司開始用Go重構他們的基礎架構,很多基礎設施都是直接采用Go進行了開發,誕生了許多熱門項目。

        基礎設施

        代表項目:docker、kubernetes、etcd、consul等。

        數據庫

        代表項目:influxdb、cockroachdb等。

        微服務

        代表項目:go-kit、micro、kratos等。

        安裝Go語言

        Go語言可用于FreeBSD、Linux、Windows和macOS等操作系統。有關對這些平臺的要求,請參與Go語言網站列出的系統需求。

        Go語言的官方網站為https://golang.org/,國內的用戶可以訪問https://golang.google.cn/dl/。通常情況下,按照本文的步驟進行安裝不會出現問題,遇到安裝問題的讀者,請通過公眾號與我聯系。

        Windows系統

        下載鏈接

        默認安裝到C:go目錄下,建議不要更改安裝目錄。

        GOPATH配置

        安裝完畢后需要配置GOPATH,GOPATH是Go語言用來存放第三方源碼、二進制文件、類庫等文件的路徑。

        1. 例如系統用戶名為demo,則需要新建以下三個目錄:
        • C:Usersdemogosrc 存放源碼
        • C:Usersdemogopkg 存放類庫
        • C:Usersdemogobin 存在二進制文件
        1. 環境變量設置:
        • 新增GOPATH,值為C:Usersdemogo
        • 新增PATH(已存在則編輯),值為C:Usersdemogobin

        Linux系統

        Linux具有眾多發行版,如Ubuntu、CentOS、RedHat、Debian等等,所有發行版的安裝步驟是一致的,區別是根據CPU架構選擇不同的發布包。

        常見的個人計算機CPU架構為amd64,下載amd64架構的發布包即可。

        Linux配置命令

        下載壓縮包

        wget https://golang.google.cn/dl/go1.15.8.linux-amd64.tar.gz

        移動到opt目錄

        mv go1.15.8.linux-amd64.tar.gz /opt

        解壓

        tar xf go1.15.8.linux-amd64.tar.gz

        新建GOPATH目錄

        cd ~

        mkdir go

        cd go

        mkdir pkg src bin

        編輯 ~/.bashrc文件, 添加bin路徑到PATH環境變量中

        echo 'GOPATH=用戶主目錄/go' >> ~/.bashrc

        echo 'PATH=/opt/go/bin:$GOPATH/bin:$PATH' >> ~/.bashrc

        更新環境變量

        source ~/.bashrc

        測試安裝結果

        go version

        macOS系統

        Apple公司于2020年發布了采用M1芯片(arm64架構)的硬件產品,支持M1芯片的Go語言版本為1.16,根據CPU架構選擇對應的pkg包安裝即可。

        macOS配置命令

        新建GOPATH目錄

        cd ~

        mkdir go

        cd go

        mkdir pkg src bin

        編輯 ~/.bashrc文件, 添加bin路徑到PATH環境變量中

        echo 'GOPATH=用戶主目錄/go' >> ~/.bashrc

        echo 'PATH=$GOPATH/bin:$PATH' >> ~/.bashrc

        更新環境變量

        source ~/.bashrc

        測試安裝結果

        go version

        配置集成開發環境

        本節將介紹如何在本地計算機上配置集成開發環境,以下步驟使用macOS版本作為示例,其他操作系統類似。

        Visual Studio Code(簡稱VSCode)是由微軟開發的、同時支持Windows、Linux和macOS操作系統的開源編輯器,它支持測試,并且內置了git功能,提供了豐富的語言支持與常用編程工具。

        1. 打開官方網站 https://code.visualstudio.com/,點擊藍色按鈕下載即可。
        2. 新版本的VSCode不再內置中文語言包,需要安裝語言包擴展。安裝VSCode后打開VSCode編輯器,在擴展窗口中搜索“Chinese”,安裝第一個即可。

        image-20191022102923920

        1. 用VSCode新建一個空項目,打開項目之后新建main.go,此時VSCode右下角會彈出Go工具鏈安裝的提示,選擇”Install All“即可。

        編寫HTTP服務器

        代碼

        package main
        ?
        import (
         "io"
         "net/http"
        )
        ?
        func main() {
         http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
         io.WriteString(w, "hello world")
         })
         http.ListenAndServe(":8080", nil)
        }

        程序結構說明

        • package 關鍵字聲明文件所在的包,每個go文件都必須聲明。每個可執行程序都必須包含main包,程序的入口點為main包的func main函數
        • import 關鍵字聲明需要導入的包,代碼中需要使用http服務器相關方法,因此導入了http包
        • func main程序的入口點

        編譯并運行程序

        編譯并運行文件是開發過程中的一個常見步驟,Go提供了完成這個步驟的快捷途徑。

        Go語言提供了build和run兩個命令來編譯運行Go程序:

        • go build 會編譯可執行文件,并不執行
        • go run 不會創建可執行文件,直接執行

        使用go run運行HTTP服務器,之后通過瀏覽器打開即可。

        小結

        本文介紹了Go語言的安裝以及集成開發環境的配置。通過HTTP服務器演示了Go程序的開發過程。

        下一章將學習Go語言的基本語法:

        • 變量和常量
        • 數據類型
        • 運算符
        • 條件語句
        • 循環語句

        0.jpeg

        查看原文

        贊 1 收藏 1 評論 0

        xialeistudio 發布了文章 · 2月6日

        golang依賴注入工具wire指南

        wire與依賴注入

        Wire 是一個的Golang依賴注入工具,通過自動生成代碼的方式在編譯期完成依賴注入,Java體系中最出名的Spring框架采用運行時注入,個人認為這是wire和其他依賴注入最大的不同之處。

        依賴注入(Dependency Injection)也稱作控制反轉(Inversion of Control),個人給控制反轉下的定義如下:

        當前對象需要的依賴對象由外部提供(通常是IoC容器),外部負責依賴對象的構造等操作,當前對象只負責調用,而不關心依賴對象的構造。即依賴對象的控制權交給了IoC容器。

        下面給出一個控制反轉的示例,比如我們通過配置去創建一個數據庫連接:

        // 連接配置
        type DatabaseConfig struct {
            Dsn string 
        }
        
        func NewDB(config *DatabaseConfig)(*sql.DB, error) {
            db,err := sql.Open("mysql", config.Dsn)
            if err != nil {
                return nil, err
            }
            // ...
        }
        
        fun NewConfig()(*DatabaseConfig,error) {
            // 讀取配置文件
            fp, err := os.Open("config.json")
            if err != nil {
                return nil,err
            }
            defer fp.Close()
            // 解析為Json
            var config DatabaseConfig
            if err:=json.NewDecoder(fp).Decode(&config);err!=nil {
                return nil,err
            }
            return &config, nil
        }
        
        func InitDatabase() {
            cfg, err:=NewConfig()
            if err!=nil {
                log.Fatal(err)
            }
            db,err:=NewDB(cfg)
            if err!=nil {
                log.Fatail(err)
            }
            // db對象構造完畢
        }

        數據庫配置怎么來的,NewDB方法并不關心(示例代碼采用的是NewConfig提供的JSON配置對象),NewDB只負責創建DB對象并返回,和配置方式并沒有耦合,所以即使換成配置中心或者其他方式來提供配置,NewDB代碼也無需更改,這就是控制反轉的魔力!

        來看一個反面例子,也就是控制正轉:

        當前對象需要的依賴由自己創建,即依賴對象的控制權在當前對象自己手里。
        type DatabaseConfig struct {
            Dsn string 
        }
        
        func NewDB()(*sql.DB, error) {
            // 讀取配置文件
            fp, err := os.Open("config.json")
            if err != nil {
                return nil,err
            }
            defer fp.Close()
            // 解析為Json
            var config DatabaseConfig
            if err:=json.NewDecoder(fp).Decode(&config);err!=nil {
                return nil,err
            }
            // 初始化數據庫連接
            db,err = sql.Open("mysql", config.Dsn)
            if err != nil {
                return
            }
            // ...
        }

        在控制正轉模式下,NewDB方法需要自己實現配置對象的創建工作,在示例中需要讀取Json配置文件,這是強耦合的代碼,一旦配置文件的格式不是Json,NewDB方法將返回錯誤。

        依賴注入固然好用,但是像剛才的例子中去手動管理依賴關系是相當復雜也是相當痛苦的一件事,因此在接下來的內容中會重點介紹golang的依賴注入工具——wire。

        上手使用

        通過go get github.com/google/wire/cmd/wire安裝好wire命令行工具即可。

        在正式開始之前需要介紹一下wire中的兩個概念:ProviderInjector

        • Provider:負責創建對象的方法,比如上文中控制反轉示例NewDB(提供DB對象)和NewConfig(提供DatabaseConfig對象)方法。
        • Injector:負責根據對象的依賴,依次構造依賴對象,最終構造目的對象的方法,比如上文中控制反轉示例InitDatabase方法。

        現在我們通過wire來實現一個簡單的項目。項目結構如下:

        |--cmd
            |--main.go
            |--wire.go
        |--config
            |--app.json
        |--internal
            |--config
                |--config.go
            |--db
                |--db.go

        config/app.json

        {
          "database": {
            "dsn": "root:root@tcp(localhost:3306)/test"
          }
        }

        internal/config/config.go

        package config
        
        import (
            "encoding/json"
            "github.com/google/wire"
            "os"
        )
        
        var Provider = wire.NewSet(New) // 將New方法聲明為Provider,表示New方法可以創建一個被別人依賴的對象,也就是Config對象
        
        type Config struct {
            Database database `json:"database"`
        }
        
        type database struct {
            Dsn string `json:"dsn"`
        }
        
        func New() (*Config, error) {
            fp, err := os.Open("config/app.json")
            if err != nil {
                return nil, err
            }
            defer fp.Close()
            var cfg Config
            if err := json.NewDecoder(fp).Decode(&cfg); err != nil {
                return nil, err
            }
            return &cfg, nil
        }
        

        internal/db/db.go

        package db
        
        import (
            "database/sql"
            _ "github.com/go-sql-driver/mysql"
            "github.com/google/wire"
            "wire-example2/internal/config"
        )
        
        var Provider = wire.NewSet(New) // 同理
        
        func New(cfg *config.Config) (db *sql.DB, err error) {
            db, err = sql.Open("mysql", cfg.Database.Dsn)
            if err != nil {
                return
            }
            if err = db.Ping(); err != nil {
                return
            }
            return db, nil
        }

        cmd/main.go

        package main
        
        import (
            "database/sql"
            "log"
        )
        
        type App struct { // 最終需要的對象
            db *sql.DB
        }
        
        func NewApp(db *sql.DB) *App {
            return &App{db: db}
        }
        
        func main() {
            app, err := InitApp() // 使用wire生成的injector方法獲取app對象
            if err != nil {
                log.Fatal(err)
            }
            var version string
            row := app.db.QueryRow("SELECT VERSION()")
            if err := row.Scan(&version); err != nil {
                log.Fatal(err)
            }
            log.Println(version)
        }

        cmd/wire.go

        重點文件,也就是實現Injector的核心所在:

        // +build wireinject
        
        package main
        
        import (
            "github.com/google/wire"
            "wire-example2/internal/config"
            "wire-example2/internal/db"
        )
        
        func InitApp() (*App, error) {
            panic(wire.Build(config.Provider, db.Provider, NewApp)) // 調用wire.Build方法傳入所有的依賴對象以及構建最終對象的函數得到目標對象
        }

        文件編寫完畢,進入cmd目錄執行wire命令會得到以下輸出:

        C:\Users\Administrator\GolandProjects\wire-example2\cmd>wire
        wire: wire-example2/cmd: wrote C:\Users\Administrator\GolandProjects\wire-example2\cmd\wire_gen.go

        表明成功生成wire_gen.go文件,文件內容如下:

        // Code generated by Wire. DO NOT EDIT.
        
        //go:generate go run github.com/google/wire/cmd/wire
        //+build !wireinject
        
        package main
        
        import (
            "wire-example2/internal/config"
            "wire-example2/internal/db"
        )
        
        // Injectors from wire.go:
        
        func InitApp() (*App, error) {
            configConfig, err := config.New()
            if err != nil {
                return nil, err
            }
            sqlDB, err := db.New(configConfig)
            if err != nil {
                return nil, err
            }
            app := NewApp(sqlDB)
            return app, nil
        }

        可以看到生成App對象的代碼已經自動生成了。

        Provider說明

        通過NewSet方法將本包內創建對象的方法聲明為Provider以供其他對象使用。NewSet可以接收多個參數,比如我們db包內可以創建Mysql和Redis連接對象,則可以如下聲明:

        var Provider = wire.NewSet(NewDB, NewRedis)
        
        func NewDB(config *Config)(*sql.DB,error) { // 創建數據庫對象
            
        }
        
        func NewRedis(config *Config)(*redis.Client,error) { // 創建Redis對象
        }

        wire.go文件說明

        wire.go文件需要放在創建目標對象的地方,比如我們ConfigDB對象最終是為App服務的,因此wire.go文件需要放在App所在的包內。

        wire.go文件名不是固定的,不過大家習慣叫這個文件名。

        wire.go的第一行// +build wireinject是必須的,含義如下:

        只有添加了名稱為"wireinject"的build tag,本文件才會編譯,而我們go build main.go的時候通常不會加。因此,該文件不會參與最終編譯。

        wire.Build(config.Provider, db.Provider, NewApp)通過傳入config以及db對象來創建最終需要的App對象

        wire_gen.go文件說明

        該文件由wire自動生成,無需手工編輯?。?!

        //+build !wireinject標簽和wire.go文件的標簽相對應,含義如下:

        編譯時只有未添加"wireinject"的build tag,本文件才參與編譯。

        因此,任意時刻下,wire.gowire_gen.go只會有一個參與編譯。

        高級玩法

        cleanup函數

        在創建依賴資源時,如果由某個資源創建失敗,那么其他資源需要關閉的情況下,可以使用cleanup函數來關閉資源。比如咱們給db.New方法返回一個cleanup函數來關閉數據庫連接,相關代碼修改如下(未列出的代碼不修改):

        internal/db/db.go

        func New(cfg *config.Config) (db *sql.DB, cleanup func(), err error) { // 聲明第二個返回值
            db, err = sql.Open("mysql", cfg.Database.Dsn)
            if err != nil {
                return
            }
            if err = db.Ping(); err != nil {
                return
            }
            cleanup = func() { // cleanup函數中關閉數據庫連接
                db.Close()
            }
            return db, cleanup, nil
        }

        cmd/wire.go

        func InitApp() (*App, func(), error) { // 聲明第二個返回值
            panic(wire.Build(config.Provider, db.Provider, NewApp))
        }

        cmd/main.go

        func main() {
            app, cleanup, err := InitApp() // 添加第二個參數
            if err != nil {
                log.Fatal(err)
            }
            defer cleanup() // 延遲調用cleanup關閉資源
            var version string
            row := app.db.QueryRow("SELECT VERSION()")
            if err := row.Scan(&version); err != nil {
                log.Fatal(err)
            }
            log.Println(version)
        }

        重新在cmd目錄執行wire命令,生成的wire_gen.go如下:

        func InitApp() (*App, func(), error) {
            configConfig, err := config.New()
            if err != nil {
                return nil, nil, err
            }
            sqlDB, cleanup, err := db.New(configConfig)
            if err != nil {
                return nil, nil, err
            }
            app := NewApp(sqlDB)
            return app, func() { // 返回了清理函數
                cleanup()
            }, nil
        }

        接口綁定

        在面向接口編程中,代碼依賴的往往是接口,而不是具體的struct,此時依賴注入相關代碼需要做一點小小的修改,繼續剛才的例子,示例修改如下:

        新增internal/db/dao.go

        package db
        
        import "database/sql"
        
        type Dao interface { // 接口聲明
            Version() (string, error)
        }
        
        type dao struct { // 默認實現
            db *sql.DB
        }
        
        func (d dao) Version() (string, error) {
            var version string
            row := d.db.QueryRow("SELECT VERSION()")
            if err := row.Scan(&version); err != nil {
                return "", err
            }
            return version, nil
        }
        
        func NewDao(db *sql.DB) *dao { // 生成dao對象的方法
            return &dao{db: db}
        }

        internal/db/db.go也需要修改Provider,增加NewDao聲明:

        var Provider = wire.NewSet(New, NewDao)

        cmd/main.go文件修改:

        package main
        
        import (
            "log"
            "wire-example2/internal/db"
        )
        
        type App struct {
            dao db.Dao // 依賴Dao接口
        }
        
        func NewApp(dao db.Dao) *App { // 依賴Dao接口
            return &App{dao: dao}
        }
        
        func main() {
            app, cleanup, err := InitApp()
            if err != nil {
                log.Fatal(err)
            }
            defer cleanup()
            version, err := app.dao.Version() // 調用Dao接口方法
            if err != nil {
                log.Fatal(err)
            }
            log.Println(version)
        }

        進入cmd目錄執行wire命令,此時會出現報錯:

        C:\Users\Administrator\GolandProjects\wire-example2\cmd>wire
        wire: C:\Users\Administrator\GolandProjects\wire-example2\cmd\wire.go:11:1: inject InitApp: no provider found for wire-example2/internal/db.Dao
                needed by *wire-example2/cmd.App in provider "NewApp" (C:\Users\Administrator\GolandProjects\wire-example2\cmd\main.go:12:6)
        wire: wire-example2/cmd: generate failed
        wire: at least one generate failure

        wire提示inject InitApp: no provider found for wire-example2/internal/db.Dao,也就是沒找到能提供db.Dao對象的Provider,咱們不是提供了默認的db.dao實現也注冊了Provider嗎?這也是go的OOP設計奇特之處。

        咱們修改一下internal/db/db.goProvider聲明,增加db.*daodb.Dao的接口綁定關系:

        var Provider = wire.NewSet(New, NewDao, wire.Bind(new(Dao), new(*dao)))

        wire.Bind()方法第一個參數為interface{},第二個參數為實現。

        此時再執行wire命令就可以成功了!

        結尾

        wire工具還有很多玩法,但是就筆者個人工作經驗而言,掌握本文介紹到的知識已經能夠勝任絕大部分場景了!

        查看原文

        贊 3 收藏 2 評論 0

        xialeistudio 回答了問題 · 1月14日

        觸發硬件加速的css動畫,還會不會引起回流?

        不會,硬件加速的動畫使用的層是單獨的層,回流使用的層是layout層。兩個層互不干擾,通過合成展示最終界面。

        關注 3 回答 2

        認證與成就

        • 認證信息 《ThinkPHP實戰》、《ThinkPHP5實戰》作者,后端工程師
        • 獲得 1845 次點贊
        • 獲得 39 枚徽章 獲得 1 枚金徽章, 獲得 14 枚銀徽章, 獲得 24 枚銅徽章

        擅長技能
        編輯

        開源項目 & 著作
        編輯

        注冊于 2015-01-08
        個人主頁被 27.2k 人瀏覽

        一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕