欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go打印結構體提升代碼調試效率實例詳解

 更新時間:2024年02月01日 14:41:43   作者:波羅學?碼途漫漫  
這篇文章主要介紹了Go打印結構體提升代碼調試效率實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

不知道大家是否遇到打印復雜結構的需求?

結構體的特點是有點像是一個盒子,但不同于 slice 與 map,它里面可以放很多不同類型的東西,如數(shù)字、字符串、slice、map 或者其他結構體。

但,如果我們想看看盒子中內(nèi)容,該怎么辦呢?這時我們就要能打印結構體了。

打印結構體的能力其實挺重要的,它能幫我們檢查理解代碼,提高調試效率,確保代碼運行正確。

本文讓我們以此為話題,聊聊 GO 語言中如何打印結構體。這些方法,基本上同樣適用于其他復雜數(shù)據(jù)結構

讓我們直接開始吧!

定義結構體

首先,我們來定義一個結構體,它會被接下來所有的方法用到。

代碼如下所示:

type Author struct {
    Name string
    Age  int8
    Sex  string
}

type Article struct {
    ID      int64
    Title   string
    Author  *Author
    Content string
}

我們定義了一個 Article 結構體用于表示一篇文章,內(nèi)部包含內(nèi)部實現(xiàn)了 ID、Title 和 Content 基礎屬性,還有一個字段是 Author 結構體指針,用于保存作者信息。

我將先介紹四種基本的方式。

使用 fmt.Printf

最簡單的方法是使用 fmt.Printf 函數(shù),如果希望顯示一些詳情,可和 %+v 格式化符號配合。這樣可以直接打印出結構體的字段名和值。

我們可以這樣打印它,代碼如下:

func main() {
    article := Article{
        ID:      1,
        Title:   "How to Print a Structure in Golang",
        Author:  &Author{"poloxue", 18, "male"},
        Content: "This is a blog post",
    }
    fmt.Printf("%+v\n", article)
}

輸出:

{ID:1 Title:How to Print a Structure in Golang Author:0xc0000900c0 Content:This is a blog post}

如上所示,這段代碼會打印出 article 結構體的所有字段值。不過,如果仔細觀察,會發(fā)現(xiàn)它的 Author 字段只打印了指針地址 - Author:0xc0000900c0 ,沒有輸出它的內(nèi)容。

這其實是符合預期的。*Author 是指針類型,它的值自然就是地址。

如果我就想打印 Author 字段的內(nèi)容,可通過用 fmt.Printf 打印指針實際指向內(nèi)容。

fmt.Print("%+v\n", article.Author)

輸出:

&{Name:poloxue Age:18 Sex:male}

我在測試的時候,發(fā)現(xiàn)個有趣的現(xiàn)象:

如果打印的是結構體指針,它會自動解引用,即能把內(nèi)容打印出來。如上的代碼所示,無論是

Printf("%+v\n", article.Author)

還是

Printf("%\n", *article.Author)

都能打印出結構體的內(nèi)容。

但如果打印的結構體,包含結構體指針字段,則不會將內(nèi)容打印出來,而只會打印地址,即指針值。

我猜測,如此設計的原因是為了防止深層遞歸,或者循環(huán)引用。

想明白的話,似乎是個顯而易見的事情。

實現(xiàn) String 方法

除了以上將 Author 字段單獨拿出打印,我們還有其他方法實現(xiàn)嗎?當然有,這就是本節(jié)要說的 - 實現(xiàn) String 方法。

這其實是 Go 提供的一種機制,一個類型如果滿足 Stringer 接口,即實現(xiàn)了 String 方法,打印時返回的就是 String 方法的返回內(nèi)容。

Stringer 定義如下:

type Stringer interface {
    String() string
}

當我們使用 fmt.Printf 打印結構體時,就會調用定義的 String 方法,控制結構體的輸出格式。例如:

func (a Article) String() string {
    return fmt.Sprintf("Title: %s, Author: %s, Content: %s", a.Title, a.Author.Name, a.Content)
}

func main() {
    article := Article{
        ID:      1,
        Title:   "How to Print a Structure in Golang",
        Author:  &Author{"poloxue", 18, "male"},
        Content: "This is a blog post",
    }
    fmt.Println(article)
}

輸出:

Title: How to Print a Structure in Golang, Author: poloxue, Content: This is a blog post

檢查結果,的確是 String 方法中定義的形式?,F(xiàn)在,我們可以隨心所欲定義打印格式了。

這種方式還有一個特點,就是性能高。畢竟,它沒有任何啰嗦,直接到拿到結果。

到這里,我們已經(jīng)有能力打印結構體了。但這里也有些缺點。

首先,不夠美觀,輸出結構易讀性差。這不利于快速定位。

其次,每次都要自定義輸出。如果只是為了 debug 調試代碼,而不是功能代碼,希望有更方便方式直接打印出所有內(nèi)容。

json.MarshalIndent

首先,如何實現(xiàn)美化輸出呢?

如果你想要一個更美觀的輸出格式,最便捷的方式,可使用標準庫 encoding/json 的 json.MarshalIndent 函數(shù),它會將結構體轉換為 JSON 格式,且能控制縮進,使輸出更易于閱讀。

示例代碼,如下所示:

import (
    "encoding/json"
    "fmt"
)

func main() {
    article := Article{
        ID:      1,
        Title:   "How to Print a Structure in Golang",
        Author:  &Author{"poloxue", 18, "male"},
        Content: "This is a blog post",
    }
    articleJSON, _ := json.MarshalIndent(article, "", "    ")
    fmt.Println(string(articleJSON))
}

如上的代碼中,json.MarshalIndent 的第三個參數(shù)表示縮進的大小。我們看下代碼的輸出吧。

輸出:

{
    "ID": 1,
    "Title": "How to Print a Structure in Golang",
    "Author": {
        "Name": "poloxue",
        "Age": 18,
        "Sex": "male"
    },
    "Content": "This is a blog post"
}

以這樣美觀的 JSON 格式打印的 Article 結構體,明顯易讀了許多。

reflect 包打印復雜結構

如果想完全控制結構體打印,還可使用 reflect 包。它不僅僅是可以拿到 Go 變量的值,其他信息,如結構體的字段名和類型,都可輕而易舉拿到。

這也是為什么可通過 reflect 包能最大粒度控制輸出格式。

示例代碼,如下所示:

import (
    "fmt"
    "reflect"
)

func main() {
    article := Article{
        ID:      1,
        Title:   "How to Print a Structure in Golang",
        Author:  &Author{"poloxue", 18, "male"},
        Content: "This is a blog post",
    }

    val := reflect.ValueOf(article)
    for i := 0; i < val.NumField(); i++ {
        field := val.Type().Field(i)
        fmt.Printf(
            "Type: %v, Field: %s, Value: %v\n",
            field.Type,
            field.Name,
            val.Field(i),
        )
    }
}

輸出:

Type: int64, Field: ID, Value: 1
Type: string, Field: Title, Value: How to Print a Structure in Golang
Type: *main.Author, Field: Author, Value: &{poloxue 18 male}
Type: string, Field: Content, Value: This is a blog post

我們輸出了字段類型、名稱和值。

當然,reflect 提供了靈活性,但具體的打印格式,我們就要自己按需求自行定義。前面介紹的 Printf 函數(shù),內(nèi)部實現(xiàn)本質上也依賴了 reflect 。

如果想要深度打印信息,即使是指針類型字段,也可通過 reflect 繼續(xù)深度打印。

代碼類似于:

if field.Type.Kind() == reflect.Pointer {
    fmt.Println(val.Field(i).Elem())
}

即如果是指針類型,通過 Elem() 繼續(xù)深入它的內(nèi)部。

當然,如果你希望得到結構體類型相關信息。reflect 甚至可以在結構體沒有實例化打印其類型的詳情。

func printStructType(t reflect.Type) {
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Printf("%s: %s\n", field.Name, field.Type)
    }
}

func main() {
    t := reflect.TypeOf((*Article)(nil)).Elem()
    printStructType(t)
}

核心就是那句 (*Article)(nil) 得到一個類型為 *Article 的 nil。也算是類型內(nèi)存空間的占用。

性能壓測

我嘗試了不同的打印方法后,也進行了一個簡單的性能測試。

測試結果如下所示:

BenchmarkFmtPrintf-16              2631248     447.3 ns/op
BenchmarkJSONMarshalIndent-16       997448      1016 ns/op
BenchmarkCustomStringMethod-16     5135541     225.5 ns/op
BenchmarkReflection-16             2030233     594.9 ns/op

測試結果顯示,使用自定義的 String 方法是最快的,而 json.MarshalIndent 則是最慢的。

這意味著如果你關心程序的運行速度,最好使用自定義的String方法來打印結構體。

這里單獨提醒一點,因為 fmt.Printf 的內(nèi)部是使用反射,所以要能測試出 String() 自定義的效果,內(nèi)部實現(xiàn)就不要用 fmt.Sprintf 等方法格式化字符,而是推薦使用 strconv 中的一些函數(shù)。

示例代碼:

func (a Article) String() string {
    return "{ID:" + strconv.Itoa(int(a.ID)) + ", Title:" + a.Title + ",AuthorName:" + a.Author.Name + "}"
}

這樣才能真正意義上測試出 String 自定義的優(yōu)勢。不靠套娃,最終得到用了 String 等于沒用的效果。

如果想知道為什么 strconv 更快,可閱讀我之前的一篇文章:GO 中高效 int 轉換 string 的方法與源碼剖析

三方庫

前面介紹了 4 種打印結構內(nèi)容的方案,。特別是第四種,提供了最大化的自由度。但缺點是要自定義,非常麻煩。

接下來,我嘗試推薦一些好用的三方庫,它們將我們常用的一些模式實踐成庫,便于我們使用。

go-spew

我們首先來看看一個叫做 go-spew 的第三方庫。

這個庫提供了深度打印 Go 數(shù)據(jù)結構的功能,對于調試非常有用。它可以遞歸地打印出結構體的字段,即使是嵌套的結構體也能打印出來。

例如:

import "github.com/davecgh/go-spew/spew"

func main() {
    article := Article{
        ID:      1,
        Title:   "How to Print a Structure in Golang",
        Author:  &amp;Author{"poloxue", 18, "male"},
        Content: "This is a blog post",
    }
    spew.Dump(article)
}

這樣會詳細地打印出 article 結構體的所有內(nèi)容。

輸出如下:

(main.Article) {
 ID: (int64) 1,
 Title: (string) (len=34) "How to Print a Structure in Golang",
 Author: (*main.Author)(0xc000100330)({
  Name: (string) (len=7) "poloxue",
  Age: (int8) 18,
  Sex: (string) (len=4) "male"
 }),
 Content: (string) (len=19) "This is a blog post"

可以看出,上面的輸出內(nèi)容包含的信息非常豐富。

如果希望自定義打印格式,可通過 spew 提供的 ConfigState 配置,如縮進,打印深度。

示例代碼:

// 設置 spew 的配置
spewConfig := spew.ConfigState{
    Indent:                  "\t", // 索引為 Tab
    DisableMethods:          true,
    DisablePointerMethods:   true,
    DisablePointerAddresses: true,
    MaxDepth:                1, // 設置打印深度為 1
}

spewConfig.Dump(article)

輸出:

(main.Article) {
    ID: (int64) 1,
    Title: (string) (len=34) "How to Print a Structure in Golang",
    Author: (*main.Author)({
        <max depth reached>
    }),
    Content: (string) (len=19) "This is a blog post"
}

因為,我將打印深度配置為 1,可以看到 Author 的字段的內(nèi)容是沒有打印的。

更多能力可自行探索。

pretty

除了 go-spew,還有一些沒那么強大,但也還不錯的庫,方便我們調試復雜數(shù)據(jù)結構,如 pretty[1]。

import (
    "fmt"
    "github.com/kr/pretty"
)

func main() {
  // 省略 ...
  fmt.Printf("%# v\n", pretty.Formatter(article))
}

輸出:main.Article{
    ID:      1,
    Title:   "How to Print a Structure in Golang",
    Author:  &main.Author{Name:"poloxue", Age:18, Sex:"male"},
    Content: "This is a blog post",
}

輸出結果為格式化的結構體輸出。

這兩個庫都已經(jīng)處于很久不更新的狀態(tài),但是功能滿足我們的需求。

結語

本文主要介紹了 Go 語言中打印結構體的不同方法。我們從簡單的 fmt.Printf 到使用反射,甚至是第三方庫,我們是有很多選擇。

簡單主題深入起來,擴展內(nèi)容也可很豐富。

關于打印結構體這個主題,還有一個部分沒有談到,就是日志如何記錄類似結構體等復雜結構類型的變量,畢竟日志對于問題調試至關重要。后面有機會,可單獨談下這個主題。

最后,希望這篇文章能幫助你在打印調試 GO 結構體等復雜結構時,不再迷茫。

以上就是Go打印結構體提升代碼調試效率實例詳解的詳細內(nèi)容,更多關于Go打印結構體的資料請關注腳本之家其它相關文章!

相關文章

  • GO項目實戰(zhàn)之Gorm格式化時間字段實現(xiàn)

    GO項目實戰(zhàn)之Gorm格式化時間字段實現(xiàn)

    GORM自帶的time.Time類型JSON默認輸出RFC3339Nano格式的,下面這篇文章主要給大家介紹了關于GO項目實戰(zhàn)之Gorm格式化時間字段實現(xiàn)的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-01-01
  • 在go中進行單元測試的案例分享

    在go中進行單元測試的案例分享

    這篇文章主要介紹了使用Go進行單元測試的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-01-01
  • go語言base64加密解密的方法

    go語言base64加密解密的方法

    這篇文章主要介紹了go語言base64加密解密的方法,實例分析了Go語言base64加密解密的技巧,需要的朋友可以參考下
    2015-03-03
  • Go語言中的流程控制結構和函數(shù)詳解

    Go語言中的流程控制結構和函數(shù)詳解

    這篇文章主要介紹了Go語言中的流程控制結構和函數(shù)詳解,本文詳細講解了if、goto、for、switch等控制語句,同時對函數(shù)相關知識做了講解,需要的朋友可以參考下
    2014-10-10
  • golang?beego框架環(huán)境搭建過程

    golang?beego框架環(huán)境搭建過程

    這篇文章主要為大家介紹了golang?beego框架環(huán)境搭建的過程腳本,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2022-04-04
  • Go?Ginrest實現(xiàn)一個RESTful接口

    Go?Ginrest實現(xiàn)一個RESTful接口

    這篇文章主要為大家介紹了Go?Ginrest實現(xiàn)一個RESTful接口示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • Golang map實現(xiàn)原理淺析

    Golang map實現(xiàn)原理淺析

    Go中Map是一個KV對集合,下面這篇文章主要給大家介紹了關于Golang中map探究的相關資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2022-12-12
  • go-zero創(chuàng)建RESTful API 服務的方法

    go-zero創(chuàng)建RESTful API 服務的方法

    文章介紹了如何使用go-zero框架和goctl工具快速創(chuàng)建RESTfulAPI服務,通過定義.api文件并使用goctl命令,可以自動生成項目結構、路由、請求和響應模型以及處理邏輯,感興趣的朋友一起看看吧
    2024-11-11
  • go中的參數(shù)傳遞是值傳遞還是引用傳遞的實現(xiàn)

    go中的參數(shù)傳遞是值傳遞還是引用傳遞的實現(xiàn)

    參數(shù)傳遞機制是一個重要的概念,它決定了函數(shù)內(nèi)部對參數(shù)的修改是否會影響到原始數(shù)據(jù),本文主要介紹了go中的參數(shù)傳遞是值傳遞還是引用傳遞的實現(xiàn),感興趣的可以了解一下
    2024-12-12
  • 淺析GO并發(fā)處理選擇sync還是channel

    淺析GO并發(fā)處理選擇sync還是channel

    這篇文章主要想來和大家討論一下,GO?語言處理并發(fā)的時候我們是選擇sync還是channel,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下
    2023-08-08

最新評論