標準庫 - fmt/format.go 解讀

來源:互聯網
上載者:User

標籤:

// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// go/src/fmt/format.go// version 1.7package fmtimport ("strconv""unicode/utf8")// 用於進位轉換const (ldigits = "0123456789abcdefx"udigits = "0123456789ABCDEFX")// 作為參數使用,方便閱讀者明白傳入的參數是什麼含義const (signed   = trueunsigned = false)// 用於記錄“預留位置”中是否指定了相應的標記// 將標誌放在一個單獨的結構體中便於清理type fmtFlags struct {widPresent  bool // 寬度值precPresent bool // 精度值minus       bool // - 標記plus        bool // + 標記sharp       bool // # 標記space       bool // 空格標記zero        bool // 0 標記// 對於特殊格式 %+v 和 %#v,單獨設定 plusV/sharpV 標誌。plusV  bool // +vsharpV bool // #v}// fmt 是一個原始的格式化器,用於 Printf 等函數中。// 它將格式化結果輸出到一個緩衝區中,緩衝區必須單獨指定。type fmt struct {buf *buffer // *[]bytefmtFlags    // 結構體,定義了許多標誌wid  int    // 寬度prec int    // 精度// intbuf 足夠儲存二進位格式的 int64intbuf [68]byte}// 複位所有標誌func (f *fmt) clearflags() {f.fmtFlags = fmtFlags{}}// 結構體初始化(必須提供緩衝區)func (f *fmt) init(buf *buffer) {f.buf = buff.clearflags()}// 將資料寫入緩衝區(僅寫入 n 位元組的填充字元)func (f *fmt) writePadding(n int) {if n <= 0 {return}buf := *f.buf// 先做容量判斷,如果容量不足,則進行擴充oldLen := len(buf)newLen := oldLen + nif newLen > cap(buf) {// 預設將容量翻倍,但要保證能夠容納寫入的// 內容,所以要 +nbuf = make(buffer, cap(buf)*2+n)copy(buf, *f.buf)}// 確定要寫入的字元padByte := byte(‘ ‘)if f.zero {padByte = byte(‘0‘)}// 開始寫入padding := buf[oldLen:newLen]for i := range padding {padding[i] = padByte}// buf 有可能進行了擴充(地址發生了改變),所以要寫回去*f.buf = buf[:newLen]}// 將資料寫入緩衝區(寫入位元組切片,同時處理寬度填充)func (f *fmt) pad(b []byte) {// 如果沒有寬度值,則直接寫入if !f.widPresent || f.wid == 0 {f.buf.Write(b)return}// 寬度值是以字元作為單位,而不是位元組(沒考慮漢字的感受)。width := f.wid - utf8.RuneCount(b)if !f.minus {// 指定了 ‘-‘ 標記,在左邊填充f.writePadding(width)f.buf.Write(b)} else {// 未指定 ‘-‘ 標記,在右邊填充f.buf.Write(b)f.writePadding(width)}}// 將資料寫入緩衝區(寫入字串,同時處理寬度填充)func (f *fmt) padString(s string) {// 如果沒有寬度值,則直接寫入if !f.widPresent || f.wid == 0 {f.buf.WriteString(s)return}// 寬度值是以字元作為單位,而不是位元組(沒考慮漢字的感受)。width := f.wid - utf8.RuneCountInString(s)if !f.minus {// 指定了 ‘-‘ 標記,在左邊填充f.writePadding(width)f.buf.WriteString(s)} else {// 未指定 ‘-‘ 標記,在右邊填充f.buf.WriteString(s)f.writePadding(width)}}// 將資料寫入緩衝區(布爾值)func (f *fmt) fmt_boolean(v bool) {if v {f.padString("true")} else {f.padString("false")}}// 將資料寫入緩衝區(寫入 Unicode 碼點)// Unicode 碼點格式為 "U+FFFF",如果指定了 # 標記,則格式為 "U+FFFF ‘相應字元‘"。func (f *fmt) fmt_unicode(u uint64) {// 臨時緩衝區,容量為 68 位元組,如果容量不夠用,可以進行進行擴充。buf := f.intbuf[0:]// 1、判斷容量是否夠用// 如果沒有指定精度,那麼容量肯定夠用,因為即便使用 %#U 對 -1 進行格式化,// 所需的最大儲存空間也只有 18 位元組("U+FFFFFFFFFFFFFFFF"),沒有超過 68。// 只有指定了過大的精度之後(比如 100),才有可能超出容量範圍。// 所以下面對容量的判斷,只和精度有關。// 預設精度為 4(如果碼點長度不足 4 位,則添加前置 0,比如 U+0065,如果// 碼點長度超過精度值,則忽略精度)prec := 4// 如果指定了精度,則判斷格式化結果是否會超出 buf 範圍if f.precPresent && f.prec > 4 {prec = f.prec// 估算所需的儲存空間:"U+"、精度、" ‘"、相應字元、"‘"。width := 2 + prec + 2 + utf8.UTFMax + 1if width > len(buf) {buf = make([]byte, width)}}// 開始格式化// 從右向左進行格式化更容易一些i := len(buf)// 2、處理 # 標記// 在最後添加 ‘相應字元‘。// 前提是數值必須在 Unicode 碼點範圍內,並且字元可列印if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) {i--buf[i] = ‘\‘‘i -= utf8.RuneLen(rune(u))utf8.EncodeRune(buf[i:], rune(u))i--buf[i] = ‘\‘‘i--buf[i] = ‘ ‘}// 3、將 u 格式化為十六進位數值。for u >= 16 {i--                     // 確定 buf 的寫入下標buf[i] = udigits[u&0xF] // 與 1111 相與,擷取十六進位的個位元,然後查表取字元。prec--                  // 精度被用掉一個u >>= 4                 // 丟棄十六進位的個位元}i--                         // 處理最後一個個位元buf[i] = udigits[u]prec--// 4、處理精度資訊(添加前置 0)for prec > 0 {i--buf[i] = ‘0‘prec--}// 5、處理前置 "U+"。i--buf[i] = ‘+‘i--buf[i] = ‘U‘// 6、處理寬度資訊(填充空格)oldZero := f.zerof.zero = falsef.pad(buf[i:])f.zero = oldZero}// 將資料寫入緩衝區(整數:包括有符號和無符號,處理進位轉換)// u:要格式化的整數。base:進位。isSigned:是否有符號。digits:進位轉換表格// 十六進位大小寫通過 digits 參數確定func (f *fmt) fmt_integer(u uint64, base int, isSigned bool, digits string) {// 1、修正參數// 如果確實有符號,則將 u 中儲存的負數變為正數negative := isSigned && int64(u) < 0if negative {u = -u // 相當於 -int64(u),類型轉換不會改變值內容,所以減哪個都一樣,-u 省一步轉換操作}// 臨時緩衝區,容量為 68 位元組,如果容量不夠用,可以進行進行擴充。buf := f.intbuf[0:]// 2、判斷容量是否夠用if f.widPresent || f.precPresent {// 需要額外的 3 個位元組來存放帶符號的 "0x"。// 這裡為了提高效率,直接將寬度和精度相加(實際上寬度和精度是重疊的),// 因為在大多數情況下,相加的結果都不會太大,width > len(buf) 的情況很// 少出現,很少會重新分配記憶體,所以這裡只是為了確保安全而已。相反,如果// 用判斷語句計算準確的 width 值,反而會降低效率。width := 3 + f.wid + f.precif width > len(buf) {buf = make([]byte, width)}}// 3、確定精度資訊// 註:有兩種方式為整數添加前置字元為零:%.3d 或 %08d,// 如果同時使用了這兩種寫法,那麼 0 標記將被忽略,會使用空格實現寬度填充。// 預設精度為 0,如果指定了精度,則使用指定的精度。prec := 0if f.precPresent {prec = f.prec// 如果精度指定為 0,值也指定為 0,則表示無內容,只用空格進行填充。// 例如:fmt.Printf("%#8.d", 0)if prec == 0 && u == 0 {oldZero := f.zerof.zero = falsef.writePadding(f.wid)f.zero = oldZeroreturn}// 如果沒有指定精度,但指定了 0 標記和寬度,// 則將寬度值轉換為精度值,由精度處理函數去處理前置 0。} else if f.zero && f.widPresent {prec = f.wid// 如果指定了符號位,則留下一個符號位if negative || f.plus || f.space {prec--}}// 從右至左進行格式化更容易一些i := len(buf)// 4、開始編碼// 使用常數進行除法和模數操作可以更有效率。// case 順序按照使用頻繁度排序。switch base {case 10:for u >= 10 {i--                              // 確定緩衝區的寫入下標next := u / 10                   //去掉個位元// 這裡用了 - 和 * 求餘數,而沒有用 %,是不是比 % 更快一些?buf[i] = byte(‘0‘ + u - next*10) // 擷取個位元u = next}case 16:for u >= 16 {i--                    // 確定緩衝區的寫入下標buf[i] = digits[u&0xF] // 與 1111 相與,擷取十六進位的個位元,然後查表取字元。u >>= 4                // 丟棄十六進位的個位元}case 8:for u >= 8 {i--                      // 確定緩衝區的寫入下標buf[i] = byte(‘0‘ + u&7) // 與 111 相與,擷取八進位的個位元,然後轉換為字元。u >>= 3                  // 丟棄八進位的個位元}case 2:for u >= 2 {i--                      // 確定緩衝區的寫入下標buf[i] = byte(‘0‘ + u&1) // 與 1 相與,擷取二進位的個位元,然後轉換為字元。u >>= 1                  // 丟棄二進位的個位元}default:panic("fmt: unknown base; can‘t happen") // 未知進位制}i--                // 最後的個位元還沒處理,在這裡進行處理buf[i] = digits[u] // 所有進位的個位元都可以查表取字元。// 5、處理精度資訊(添加前置 0)for i > 0 && prec > len(buf)-i {i--buf[i] = ‘0‘}// 6、處理首碼:0x、0 等if f.sharp {switch base {case 8:if buf[i] != ‘0‘ {i--buf[i] = ‘0‘}case 16:// 根據參數 digits 確定大小寫:0x、0Xi--buf[i] = digits[16]i--buf[i] = ‘0‘}}// 7、處理符號位if negative {i--buf[i] = ‘-‘} else if f.plus {i--buf[i] = ‘+‘} else if f.space {i--buf[i] = ‘ ‘}// 8、處理寬度資訊(填充空格)oldZero := f.zerof.zero = falsef.pad(buf[i:])f.zero = oldZero}// 將字串截斷到指定精度func (f *fmt) truncate(s string) string {if f.precPresent {n := f.precfor i := range s {n--if n < 0 {return s[:i]}}}return s}// 將資料寫入緩衝區(字串:處理寬度和精度資訊)func (f *fmt) fmt_s(s string) {s = f.truncate(s)f.padString(s)}// 將資料寫入緩衝區(字串/位元組切片:十六進位格式)func (f *fmt) fmt_sbx(s string, b []byte, digits string) {// 1、計算結果長度// 擷取要處理的字串或位元組切片長度length := len(b)if b == nil {length = len(s)}// 只處理精度範圍內的內容if f.precPresent && f.prec < length {length = f.prec}// 每個元素(位元組)需要 2 個位元組儲存其十六進位編碼。width := 2 * lengthif width > 0 {if f.space {// 元素之間有空格,所以需要在每個元素前面都添加 0x 或 0X。// 每個元素的十六進位編碼剛好是 2 個位元組,所以乘以 2 之後,// 剛好每個元素多出 2 個位元組的空間來存放 0x 或 0Xif f.sharp {width *= 2}// 各個元素之間將被一個空格隔開width += length - 1} else if f.sharp {// 元素之間沒有空格,只需要在開頭添加一個 0x 或 0X 即可。width += 2}} else { // 元素為空白,則僅用空格填充寬度,比如:fmt.Printf("%8x", "")if f.widPresent {f.writePadding(f.wid)}return}// 2、處理“左”寬度資訊if f.widPresent && f.wid > width && !f.minus {f.writePadding(f.wid - width)}// 3、開始編碼buf := *f.buf// 在第一個元素前面添加前置 0x 或 0X。if f.sharp {buf = append(buf, ‘0‘, digits[16])}// 遍曆各個元素(位元組)var c bytefor i := 0; i < length; i++ {// 元素之間添加空格,每個元素前面添加 0x 或 0X。if f.space && i > 0 {buf = append(buf, ‘ ‘)if f.sharp {buf = append(buf, ‘0‘, digits[16])}}// 對當前元素進行編碼if b != nil {c = b[i] } else {c = s[i]}buf = append(buf, digits[c>>4], digits[c&0xF])}// 由於 append 操作,緩衝區可能被擴充*f.buf = buf// 4、處理“右”寬度資訊if f.widPresent && f.wid > width && f.minus {f.writePadding(f.wid - width)}}// 將資料寫入緩衝區(字串:十六進位格式)func (f *fmt) fmt_sx(s, digits string) {f.fmt_sbx(s, nil, digits)}// 將資料寫入緩衝區(位元組切片:十六進位格式)func (f *fmt) fmt_bx(b []byte, digits string) {f.fmt_sbx("", b, digits)}// 將資料寫入緩衝區(字串:帶雙引號,未轉義)// 如果指定了 # 標記,而且字串不包含任何控制字元(除定位字元),// 則會返回一個原始字串(帶反引號)。func (f *fmt) fmt_q(s string) {// 1、處理精度資訊// 將字串截斷到指定精度s = f.truncate(s)// 2、開始編碼// 處理 # 號if f.sharp && strconv.CanBackquote(s) {f.padString("`" + s + "`")return}// 臨時緩衝區,存放臨時編碼結果buf := f.intbuf[:0]// 編碼並處理寬度資訊if f.plus {// 非 ASCII 字元將被轉換為 Unicode 碼點f.pad(strconv.AppendQuoteToASCII(buf, s))} else {// 非 ASCII 字元將正常輸出f.pad(strconv.AppendQuote(buf, s))}}// 將資料寫入緩衝區(字元)// 如果字元不是有效 Unicode 編碼,則寫入 ‘\ufffd‘func (f *fmt) fmt_c(c uint64) {r := rune(c)// 超出 Unicode 範圍if c > utf8.MaxRune {r = utf8.RuneError}// 臨時緩衝區buf := f.intbuf[:0]// 對 r 進行編碼w := utf8.EncodeRune(buf[:utf8.UTFMax], r)// 將編碼結果經過填充後寫入緩衝區f.pad(buf[:w])}// 將資料寫入緩衝區(字元:帶單引號,未轉義)// 如果字元不是有效 Unicode 編碼,則寫入 ‘\ufffd‘func (f *fmt) fmt_qc(c uint64) {r := rune(c)// 超出 Unicode 範圍if c > utf8.MaxRune {r = utf8.RuneError}// 臨時緩衝區buf := f.intbuf[:0]// 編碼並處理寬度資訊if f.plus {// 非 ASCII 字元將被轉換為 Unicode 碼點f.pad(strconv.AppendQuoteRuneToASCII(buf, r))} else {// 非 ASCII 字元將被正常輸出f.pad(strconv.AppendQuoteRune(buf, r))}}// 將資料寫入緩衝區(float64)// 如果參數所提供的動詞是一個有效格式區分符,則可以在 strconv.AppendFloat 中// 將其轉換為位元組類型傳入。func (f *fmt) fmt_float(v float64, size int, verb rune, prec int) {// 1、開始編碼// “預留位置”中的精度將覆蓋參數中的預設精度if f.precPresent {prec = f.prec}// 格式化數值,結果寫入臨時緩衝區,為 + 號預留一個空格num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size)// 2、處理符號// 如果轉換結果中有符號,則去掉預留的空格,否則將預留的空格轉換為 + 號if num[1] == ‘-‘ || num[1] == ‘+‘ {num = num[1:]} else {num[0] = ‘+‘}// 如果指定了空格標記,而沒有指定 + 標記,則將 + 改為空白格。if f.space && num[0] == ‘+‘ && !f.plus {num[0] = ‘ ‘}// 3、處理無窮大和非數字// 它看起來不像一個數字,所以不應該用 0 填充。if num[1] == ‘I‘ || num[1] == ‘N‘ {oldZero := f.zerof.zero = false// 如果沒有指定符號,則移除 NaN 前面的符號if num[1] == ‘N‘ && !f.space && !f.plus {num = num[1:]}f.pad(num)f.zero = oldZeroreturn}// 4、處理填充後寫入 f.buf// 指定了 + 號 || 未指定 + 號,且結果不是以 + 開頭。if f.plus || num[0] != ‘+‘ {// 如果用 0 進行了左填充,那麼我們希望符號在所有 0 的前面。if f.zero && f.widPresent && f.wid > len(num) {f.buf.WriteByte(num[0])          // 寫入符號f.writePadding(f.wid - len(num)) // 進行填充f.buf.Write(num[1:])             // 寫入符號之外的內容return}f.pad(num)return}// 未指定 + 號,但結果是以 + 開頭,則去掉 + 號。f.pad(num[1:])}

標準庫 - fmt/format.go 解讀

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.