基礎知識 - Golang 中的格式化輸入輸出

------------------------------------------------------------

【格式化輸出】

// 格式化輸出:將 arg 列表中的 arg 轉換爲字符串輸出

// 使用動詞 v 格式化 arg 列表,非字符串元素之間添加空格
Print(arg列表)
// 使用動詞 v 格式化 arg 列表,全部元素之間添加空格,結尾添加換行符
Println(arg列表)
// 使用格式字符串格式化 arg 列表
Printf(格式字符串, arg列表)

// Print 類函數會返回已處理的 arg 數量和遇到的錯誤信息。

------------------------------------------------------------

【格式字符串】

  格式字符串由普通字符和佔位符組成,例如:

"abc%+ #8.3[3]vdef"

  其中 abc 和 def 是普通字符,其它部分是佔位符,佔位符以 % 開頭(注:%% 將被轉義爲一個普通的 % 符號,這個不算開頭),以動詞結尾,格式以下:

%[旗標][寬度][.精度][arg索引]動詞

  方括號中的內容能夠省略。

------------------------------------------------------------

【旗標】

旗標有如下幾種:

+   :對於數值類型老是輸出正負號(其它用法在動詞部分說明)。

-   :在右邊進行寬度填充,而不是默認的左邊。

空格:對於數值類型的正數,保留一個空白的符號位(其它用法在動詞部分說明)。

0   :用 0 進行寬度填充而不用空格,對於數值類型,符號將被移到全部 0 的前面。

#   :相關用法在動詞部分說明。

  其中 "0" 和 "-" 不能同時使用,優先使用 "-" 而忽略 "0"。

------------------------------------------------------------

【寬度和精度】

  「寬度」和「精度」均可以寫成如下三種形式:

數值 | * | arg索引*

  其中「數值」表示使用指定的數值做爲寬度值或精度值,「*」表示使用當前正在處理的 arg 的值做爲寬度值或精度值,若是這樣的話,要格式化的 arg 將自動跳轉到下一個。「arg索引*」表示使用指定 arg 的值做爲寬度值或精度值,若是這樣的話,要格式化的 arg 將自動跳轉到指定 arg 的下一個。

  寬度值:用於設置最小寬度。

  精度值:對於浮點型,用於控制小數位數,對於字符串或字節數組,用於控制字符數量(不是字節數量)。

  對於浮點型而言,動詞 g/G 的精度值比較特殊,在適當的狀況下,g/G 會設置總有效數字,而不是小數位數。

------------------------------------------------------------

【arg 索引】

  「arg索引」由中括號和 arg 序號組成(就像上面示例中的 [3]),用於指定當前要處理的 arg 的序號,序號從 1 開始:

'[' + arg序號 + ']'

------------------------------------------------------------

【動詞】

  「動詞」不能省略,不一樣的數據類型支持的動詞不同。

------------------------------

[通用動詞]

v:默認格式,不一樣類型的默認格式以下:

  布爾型:t
  整 型:d
  浮點型:g
  複數型:g
  字符串:s
  通 道:p
  指 針:p

#v:默認格式,以符合 Go 語法的方式輸出。特殊類型的 Go 語法格式以下:

  無符號整型:x

T:輸出 arg 的類型而不是值(使用 Go 語法格式)。

------------------------------

[布爾型]

t:輸出 true 或 false 字符串。

------------------------------

[整型]

b/o/d:輸出 2/8/10 進制格式
x/X  :輸出 16 進制格式(小寫/大寫)
c    :輸出數值所表示的 Unicode 字符
q    :輸出數值所表示的 Unicode 字符(帶單引號)。對於沒法顯示的字符,將輸出其轉義字符。
U    :輸出 Unicode 碼點(例如 U+1234,等同於字符串 "U+%04X" 的顯示結果)

對於 o/x/X:
若是使用 "#" 旗標,則會添加前導 0 或 0x。

對於 U:
若是使用 "#" 旗標,則會在 Unicode 碼點後面添加相應的 '字符'(前提是該字符必須可顯示)

------------------------------

[浮點型和複數型]

b  :科學計數法(以 2  爲底)
e/E:科學計數法(以 10 爲底,小寫 e/大寫 E)
f/F:普通小數格式(二者無區別)
g/G:大指數(指數 >= 6)使用 %e/%E,其它狀況使用 %f/%F

------------------------------

[字符串或字節切片]

s  :普通字符串
q  :雙引號引發來的 Go 語法字符串
x/X:十六進制編碼(小寫/大寫,以字節爲元素進行編碼,而不是字符)

對於 q:
若是使用了 "+" 旗標,則將全部非 ASCII 字符都進行轉義處理。
若是使用了 "#" 旗標,則輸出反引號引發來的字符串(前提是
字符串中不包含任何製表符之外的控制字符,不然忽略 # 旗標)

對於 x/X:
若是使用了 " " 旗標,則在每一個元素之間添加空格。
若是使用了 "#" 旗標,則在十六進制格式以前添加 0x 前綴。

------------------------------

[指針類型]

p :帶 0x 前綴的十六進制地址值。
#p:不帶 0x 前綴的十六進制地址值。

------------------------------

[符合類型]

複合類型將使用不一樣的格式輸出,格式以下:

結 構 體:{字段1 字段2 ...}
數組或切片:[元素0 元素1 ...]
映   射:map[鍵1:值1 鍵2:值2 ...]

指向符合元素的指針:&{}, &[], &map[]

複合類型自己沒有動詞,動詞將應用到複合類型的元素上。

結構體可使用 "+v" 同時輸出字段名。

------------------------------------------------------------

【完整示例】

package main

import "fmt"

func main() {
	// 旗標、寬度、精度、索引
	fmt.Printf("|%0+- #[1]*.[2]*[3]d|%0+- #[1]*.[2]*[4]d|\n", 8, 4, 32, 64)

	// 浮點型精度
	fmt.Printf("|%f|%8.4f|%8.f|%.4f|%.f|\n", 3.2, 3.2, 3.2, 3.2, 3.2)
	fmt.Printf("|%.3f|%.3g|\n", 12.345678, 12.345678)
	fmt.Printf("|%.2f|\n", 12.345678+12.345678i)

	// 字符串精度
	s := "你好世界!"
	fmt.Printf("|%s|%8.2s|%8.s|%.2s|%.s|\n", s, s, s, s, s)
	fmt.Printf("|%x|%8.2x|%8.x|%.2x|%.x|\n", s, s, s, s, s)

	// 帶引號字符串
	s1 := "Hello 世界!"       // CanBackquote
	s2 := "Hello\n世界!"      // !CanBackquote
	fmt.Printf("%q\n", s1)  // 雙引號
	fmt.Printf("%#q\n", s1) // 反引號成功
	fmt.Printf("%#q\n", s2) // 反引號失敗
	fmt.Printf("%+q\n", s2) // 僅包含 ASCII 字符

	// Unicode 碼點
	fmt.Printf("%U, %#U\n", '好', '好')
	fmt.Printf("%U, %#U\n", '\n', '\n')

	// 接口類型將輸出其內部包含的值
	var i interface{} = struct {
		name string
		age  int
	}{"AAA", 20}
	fmt.Printf("%v\n", i)  // 只輸出字段值
	fmt.Printf("%+v\n", i) // 同時輸出字段名
	fmt.Printf("%#v\n", i) // Go 語法格式

	// 輸出類型
	fmt.Printf("%T\n", i)
}

------------------------------------------------------------

【注意】

一、若是 arg 是一個反射值,則該 arg 將被它所持有的具體值所取代。

二、若是 arg 實現了 Formatter 接口,將調用它的 Format 方法完成格式化。

三、若是 v 動詞使用了 # 旗標(%#v),而且 arg 實現了 GoStringer 接口,將調用它的 GoString 方法完成格式化。

若是格式化操做指定了字符串相關的動詞(好比 %s、%q、%v、%x、%X),接下來的兩條規則將適用:

4。若是 arg 實現了 error 接口,將調用它的 Error 方法完成格式化。

5。若是 arg 實現了 string 接口,將調用它的 String 方法完成格式化。

  在實現格式化相關接口的時候,要避免無限遞歸的狀況,好比:

type X string

func (x X) String() string {
	return Sprintf("<%s>", x)
}

  在格式化以前,要先轉換數據類型,這樣就能夠避免無限遞歸:

func (x X) String() string {
	return Sprintf("<%s>", string(x))
}

  無限遞歸也可能發生在自引用數據類型上面,好比一個切片的元素引用了切片自身。這種狀況比較罕見,好比:

a := make([]interface{}, 1)
a[0] = a
fmt.Println(a)

------------------------------------------------------------

【格式化輸入】

// 格式化輸入:從輸入端讀取字符串(以空白分隔的值的序列),
// 並解析爲具體的值存入相應的 arg 中,arg 必須是變量地址。
// 字符串中的連續空白視爲單個空白,換行符根據不一樣狀況處理。
// \r\n 被當作 \n 處理。

// 以動詞 v 解析字符串,換行視爲空白
Scan(arg列表)
// 以動詞 v 解析字符串,換行結束解析
Scanln(arg列表)
// 根據格式字符串中指定的格式解析字符串
// 格式字符串中的換行符必須和輸入端的換行符相匹配。
Scanf(格式字符串, arg列表)

// Scan 類函數會返回已處理的 arg 數量和遇到的錯誤信息。

------------------------------------------------------------

【格式字符串】

格式字符串相似於 Printf 中的格式字符串,但下面的動詞和旗標例外:

p          :無效
T          :無效
e/E/f/F/g/G:功能相同,都是掃描浮點數或複數
s/v        :對字符串而言,掃描一個被空白分隔的子串
#/+        :無效

對於整型 arg 而言,v 動詞能夠掃描帶有前導 0 或 0x 的八進制或十六進制數值。

寬度被用來指定最大掃描寬度(不會跨越空格),精度不被支持。

若是 arg 實現了 Scanner 接口,將調用它的 Scan 方法掃描相應數據。只有基礎類型和實現了 Scanner 接口的類型可使用 Scan 類方法進行掃描。

------------------------------------------------------------

【完整示例】

// 對於 Scan 而言,回車視爲空白
func main() {
	a, b, c := "", 0, false
	fmt.Scan(&a, &b, &c)
	fmt.Println(a, b, c)
	// 在終端執行後,輸入 abc 1 回車 t 回車
	// 結果 abc 1 true
}

// 整型變量也能夠解析八進制和十六進制
func main() {
	a, b, c := "", 0, false
	fmt.Scanln(&a, &b, &c)
	fmt.Println(a, b, c)
	// 在終端執行後,輸入 def 0x20 T 回車
	// 結果 def 32 true
}

// 格式字符串能夠指定寬度
func main() {
	a, b, c := "", 0, false
	fmt.Scanf("%4s%d%t", &a, &b, &c)
	fmt.Println(a, b, c)
	// 在終端執行後,輸入 1234567true 回車
	// 結果 1234 567 true
}

// 指定寬度不能跨越空白
func main() {
	a, b, c := "", 0, false
	fmt.Scanf("%4s%d%t", &a, &b, &c)
	fmt.Println(a, b, c)
	// 在終端執行後,輸入 12 34567True 回車
	// 結果 12 34567 true
}

// %c 老是匹配下一個字符,包括空格
func main() {
	a, b, c := "", 0, 0
	fmt.Scanf("%s%c%d", &a, &b, &c)
	fmt.Println(a, b, c)
	// 在終端執行後,輸入 abc 1 回車
	// 結果 abc 32 1
}

------------------------------------------------------------

【注意】

  連續調用 FScan 可能會丟失數據,由於 FScan 中使用了 UnreadRune 對讀取的數據進行撤銷,而參數 io.Reader 只有 Read 方法,不支持撤銷。好比:

package main

import (
	"fmt"
	"io"
	"strings"
)

// 實現 Reader 接口的字符串類型
type stringReader string

func (r *stringReader) Read(b []byte) (n int, err error) {
	n = copy(b, *r)
	*r = (*r)[n:]
	if n == 0 {
		err = io.EOF
	}
	return
}

func main() {
	// 下面的操做將丟失字符 a
	s := stringReader("123abcd")
	a, b := 0, ""
	fmt.Fscan(&s, &a)
	fmt.Fscan(&s, &b)
	fmt.Println(a, b) // 123 bcd

	// 若是使用 strings.NewReader 則不會丟失,由於它實現了 RuneScanner 接口
	r := strings.NewReader("123abcd")
	c, d := 0, ""
	fmt.Fscan(r, &c)
	fmt.Fscan(r, &d)
	fmt.Println(c, d) // 123 abcd
}

------------------------------------------------------------



相關文章
相關標籤/搜索