Golang 各種打印方法詳解

在 Golang 程序開發過程中,打印輸出是我們進行調試和日誌記錄的重要手段。Go 語言提供了豐富的打印函數,主要集中在 fmtlog 包中。本文將詳細介紹這些打印方法的使用場景、特點和示例代碼。

目錄

  1. fmt 包的打印函數
  2. log 包的打印函數
  3. 格式化動詞
  4. 自定義打印格式
  5. 實用技巧與最佳實踐

fmt 包的打印函數

fmt 包是 Go 語言中最常用的打印包,提供了多種打印函數,可以滿足不同場景的需求。

這三個函數是最基礎的打印函數,直接將內容輸出到標準輸出(通常是控制台)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
// Print:簡單打印,不換行,元素之間沒有空格
fmt.Print("Hello", "World") // 輸出:HelloWorld

// Println:打印後換行,元素之間自動添加空格
fmt.Println("Hello", "World") // 輸出:Hello World

// Printf:格式化打印,使用格式化動詞
name := "Gopher"
age := 10
fmt.Printf("Name: %s, Age: %d\n", name, age) // 輸出:Name: Gopher, Age: 10
}

Sprint/Sprintln/Sprintf

這三個函數與上面的類似,但不是直接打印到控制台,而是返回格式化後的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
// Sprint:返回字符串,不換行,元素之間沒有空格
s1 := fmt.Sprint("Hello", "World")
fmt.Println(s1) // 輸出:HelloWorld

// Sprintln:返回字符串,包含換行符,元素之間自動添加空格
s2 := fmt.Sprintln("Hello", "World")
fmt.Print(s2) // 輸出:Hello World\n

// Sprintf:返回格式化後的字符串
name := "Gopher"
age := 10
s3 := fmt.Sprintf("Name: %s, Age: %d", name, age)
fmt.Println(s3) // 輸出:Name: Gopher, Age: 10
}

Fprint/Fprintln/Fprintf

這三個函數允許將內容打印到指定的 io.Writer 接口,例如文件、網絡連接等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"os"
)

func main() {
// 創建一個文件
file, err := os.Create("output.txt")
if err != nil {
fmt.Println("無法創建文件:", err)
return
}
defer file.Close()

// Fprint:打印到指定的 io.Writer
fmt.Fprint(file, "Hello", "World")

// Fprintln:打印到指定的 io.Writer,並添加換行
fmt.Fprintln(file, "Hello", "World")

// Fprintf:格式化打印到指定的 io.Writer
name := "Gopher"
age := 10
fmt.Fprintf(file, "Name: %s, Age: %d\n", name, age)

fmt.Println("內容已寫入到 output.txt 文件")
}

log 包的打印函數

log 包提供了簡單的日誌功能,適合用於記錄程序運行過程中的信息、警告和錯誤。

log Print/Println/Printf

這些函數類似於 fmt 包中的對應函數,但會自動添加時間戳和前綴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "log"

func main() {
// 設置日誌前綴
log.SetPrefix("[INFO] ")

// Print:打印日誌
log.Print("系統啟動") // 輸出:[INFO] 2025/03/23 22:39:38 系統啟動

// Println:打印日誌並換行
log.Println("用戶登錄") // 輸出:[INFO] 2025/03/23 22:39:38 用戶登錄

// Printf:格式化打印日誌
user := "admin"
log.Printf("用戶 %s 執行了操作", user) // 輸出:[INFO] 2025/03/23 22:39:38 用戶 admin 執行了操作
}

Fatal/Fatalln/Fatalf

這些函數在打印日誌後會調用 os.Exit(1) 終止程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "log"

func main() {
// Fatal:打印錯誤信息並終止程序
// log.Fatal("致命錯誤")

// Fatalln:打印錯誤信息並換行,然後終止程序
// log.Fatalln("致命錯誤")

// Fatalf:格式化打印錯誤信息,然後終止程序
err := "數據庫連接失敗"
// log.Fatalf("錯誤: %s", err)

// 注意:以上代碼被注釋掉,因為它們會終止程序
log.Println("這些函數會終止程序,所以在示例中被注釋掉")
}

Panic/Panicln/Panicf

這些函數在打印日誌後會調用 panic(),引發 panic。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "log"

func main() {
// 使用 defer 和 recover 捕獲 panic
defer func() {
if r := recover(); r != nil {
log.Println("捕獲到 panic:", r)
}
}()

// Panic:打印信息並引發 panic
// log.Panic("發生嚴重錯誤")

// Panicln:打印信息並換行,然後引發 panic
// log.Panicln("發生嚴重錯誤")

// Panicf:格式化打印信息,然後引發 panic
err := "非法操作"
// log.Panicf("錯誤: %s", err)

// 注意:以上代碼被注釋掉,因為它們會引發 panic
log.Println("這些函數會引發 panic,所以在示例中被注釋掉")
}

格式化動詞

在使用 PrintfSprintfFprintf 等格式化打印函數時,需要使用格式化動詞來指定如何格式化參數。以下是常用的格式化動詞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import "fmt"

func main() {
// 通用格式化動詞
fmt.Printf("%v\n", 123) // 以默認格式打印值:123
fmt.Printf("%+v\n", struct{Name string}{"Gopher"}) // 打印結構體時包含字段名:{Name:Gopher}
fmt.Printf("%#v\n", []int{1, 2, 3}) // Go 語法表示:[]int{1, 2, 3}
fmt.Printf("%T\n", 3.14) // 類型:float64

// 布爾值
fmt.Printf("%t\n", true) // true

// 整數
fmt.Printf("%d\n", 123) // 十進制:123
fmt.Printf("%b\n", 123) // 二進制:1111011
fmt.Printf("%o\n", 123) // 八進制:173
fmt.Printf("%x\n", 123) // 十六進制(小寫):7b
fmt.Printf("%X\n", 123) // 十六進制(大寫):7B

// 浮點數
fmt.Printf("%f\n", 123.456) // 默認精度:123.456000
fmt.Printf("%.2f\n", 123.456) // 指定精度:123.46
fmt.Printf("%e\n", 123.456) // 科學計數法(小寫):1.234560e+02
fmt.Printf("%E\n", 123.456) // 科學計數法(大寫):1.234560E+02

// 字符串和字符
fmt.Printf("%s\n", "Hello") // 字符串:Hello
fmt.Printf("%q\n", "Hello") // 帶引號的字符串:"Hello"
fmt.Printf("%c\n", 65) // 字符:A

// 指針
x := 123
fmt.Printf("%p\n", &x) // 指針地址:0xc0000160b8

// 寬度和精度
fmt.Printf("|%6d|%6.2f|\n", 123, 123.456) // | 123|123.46|
fmt.Printf("|%-6d|%-6.2f|\n", 123, 123.456) // |123 |123.46|
}

自定義打印格式

Go 語言允許通過實現 fmt.Stringerfmt.GoStringerfmt.Formatter 等接口來自定義類型的打印格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
"fmt"
"strconv"
)

// 定義一個 Person 結構體
type Person struct {
Name string
Age int
}

// 實現 fmt.Stringer 接口
func (p Person) String() string {
return p.Name + ", " + strconv.Itoa(p.Age) + "歲"
}

// 實現 fmt.GoStringer 接口
func (p Person) GoString() string {
return "Person{Name: \"" + p.Name + "\", Age: " + strconv.Itoa(p.Age) + "}"
}

// 實現 fmt.Formatter 接口
func (p Person) Format(f fmt.State, c rune) {
switch c {
case 's':
fmt.Fprint(f, p.String())
case 'v':
if f.Flag('#') {
fmt.Fprint(f, p.GoString())
} else {
fmt.Fprint(f, p.String())
}
case 'q':
fmt.Fprintf(f, "\"%s\"", p.String())
}
}

func main() {
person := Person{Name: "張三", Age: 30}

// 使用 %s 格式化(調用 String 方法)
fmt.Printf("%s\n", person) // 輸出:張三, 30歲

// 使用 %#v 格式化(調用 GoString 方法)
fmt.Printf("%#v\n", person) // 輸出:Person{Name: "張三", Age: 30}

// 使用 %q 格式化(調用 Format 方法)
fmt.Printf("%q\n", person) // 輸出:"張三, 30歲"
}

實用技巧與最佳實踐

1. 使用 log 包進行日誌記錄

在實際應用中,推薦使用 log 包或第三方日誌庫(如 logruszap 等)進行日誌記錄,而不是直接使用 fmt.Print 系列函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"log"
"os"
)

func main() {
// 設置日誌輸出到文件
logFile, err := os.Create("app.log")
if err != nil {
log.Fatal("無法創建日誌文件:", err)
}
defer logFile.Close()

// 配置日誌格式:日期、時間、文件名、行號
log.SetOutput(logFile)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.SetPrefix("[APP] ")

// 記錄日誌
log.Println("應用程序啟動")
log.Printf("配置加載完成,版本: %s", "1.0.0")

// ... 應用程序邏輯 ...

log.Println("應用程序關閉")
}

2. 使用 %+v 打印結構體

當需要調試複雜的結構體時,使用 %+v 格式化動詞可以同時顯示字段名和值,非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type Config struct {
Host string
Port int
Debug bool
Database struct {
Name string
User string
Password string
}
}

func main() {
config := Config{
Host: "localhost",
Port: 8080,
Debug: true,
}
config.Database.Name = "mydb"
config.Database.User = "admin"
config.Database.Password = "password123"

// 使用 %+v 打印結構體,包含字段名
fmt.Printf("%+v\n", config)
// 輸出:{Host:localhost Port:8080 Debug:true Database:{Name:mydb User:admin Password:password123}}
}

3. 使用 %#v 生成可重用的代碼

在調試過程中,有時需要創建一個對象的副本。使用 %#v 可以生成 Go 語法表示,直接複製到代碼中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
data := map[string]interface{}{
"name": "張三",
"age": 30,
"roles": []string{"admin", "user"},
"active": true,
}

// 使用 %#v 生成 Go 語法表示
fmt.Printf("%#v\n", data)
// 輸出:map[string]interface {}{"active":true, "age":30, "name":"張三", "roles":[]string{"admin", "user"}}
}

4. 使用 bytes.Buffer 高效拼接字符串

當需要拼接大量字符串時,使用 bytes.Buffer 比直接使用 + 運算符或 fmt.Sprintf 更高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"bytes"
"fmt"
)

func main() {
// 使用 bytes.Buffer 拼接字符串
var buffer bytes.Buffer

buffer.WriteString("Hello")
buffer.WriteString(", ")
buffer.WriteString("World")
buffer.WriteString("!")

// 獲取最終結果
result := buffer.String()
fmt.Println(result) // 輸出:Hello, World!

// 格式化寫入
buffer.Reset()
fmt.Fprintf(&buffer, "Name: %s, Age: %d", "張三", 30)
fmt.Println(buffer.String()) // 輸出:Name: 張三, Age: 30
}

總結

Go 語言提供了豐富的打印函數,可以滿足不同場景的需求。在實際開發中,應根據具體情況選擇合適的打印方法:

  • 調試時使用 fmt.Print 系列函數
  • 日誌記錄使用 log 包或第三方日誌庫
  • 字符串處理使用 fmt.Sprint 系列函數
  • 文件輸出使用 fmt.Fprint 系列函數

掌握這些打印方法和格式化技巧,可以大大提高開發效率和代碼質量。