// 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.7// 格式化输入输出的用法请参考:http://www.cnblogs.com/golove/p/3284304.htmlpackage 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 是一个基础的格式化器,用于将各种类型的数据进行宽度和精度处理后,
// 写入缓冲区,缓冲区必须单独指定。
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]
}// 下面是写入各种类型的数据。所有写入的内容都会处理宽度和精度信息。// 写入 []byte
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// 如果指定了精度,则要确保临时缓冲区够用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、处理宽度信息(用空格填充,不允许用 0 填充)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 保存。negative := isSigned && int64(u) < 0if negative {u = -u // 相当于 -int64(u),类型转换不会改变值内容,所以减哪个都一样,-u 省一步转换操作}// 临时缓冲区,容量为 68 字节,如果容量不够用,可以进行进行扩充。buf := f.intbuf[0:]// 2、确保容量够用if f.widPresent || f.precPresent {width := 3 + f.wid + f.prec// 需要额外的 3 个字节来存放带符号的 "-0x"。// 这里为了提高效率,直接将宽度和精度相加(实际上宽度和精度是重叠的),// 因为在大多数情况下,相加的结果都不会太大,结果大于 len(buf) 的情况很// 少出现,很少需要重新分配内存,所以这里只是为了确保安全而已。相反,如// 果用判断语句计算准确的 width 值,反而会降低效率。if width > len(buf) {buf = make([]byte, width)}}// 3、确定精度信息// 注:有两种方式为整数添加前导零:%.3d 或 %08d,// 如果同时使用了这两种写法,那么 0 旗标将被忽略,会使用空格实现宽度填充。// 也就是说,%08.3d 相当于 %8.3d。// 默认精度为 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、处理宽度信息(由于指定了精度,所以不能用 0 填充宽度,只能用空格填充)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 { // 如果两者都提供了,则优先处理 []bytelength = len(s)}// 只处理精度范围内的字节if f.precPresent && f.prec < length {length = f.prec}// 每个元素(字节)需要 2 个字节存储其十六进制编码。width := 2 * lengthif width > 0 {if f.space {if f.sharp {width *= 2// 元素之间有空格,所以需要在每个元素前面都添加 0x 或 0X。// 每个元素的十六进制编码刚好是 2 个字节,所以乘以 2 之后,// 刚好每个元素多出 2 个字节的空间来存放 0x 或 0X}// 各个元素之间将被一个空格隔开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++ {if f.space && i > 0 {// 元素之间添加空格buf = append(buf, ' ')if f.sharp {// 每个元素前面添加 0x 或 0Xbuf = 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)
}// 写入双引号字符串。
// 如果指定了 # 旗标,而且字符串不包含任何控制字符(除制表符),
// 则会写入反引号原始字符串。
// 如果指定了 + 旗标,则字符串中的所有非 ASCII 字符都会被转义处理。
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]// 转换结果不一定在这个 buf 中,因为转换过程中可能会扩容。// 转换结果在转换函数的返回值中,所以这个 buf 仅仅是一个参数。// 转换并写入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'
// 如果指定了 + 旗标,则非 ASCII 字符会被转义处理。
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
func (f *fmt) fmt_float(v float64, size int, verb rune, prec int) {// 1、开始编码// “占位符”中的精度将覆盖参数中的默认精度if f.precPresent {prec = f.prec}// 格式化数值,结果写入临时缓冲区,为 + 号预留一个空格// 如果 verb 是一个有效的动词,则可以在这里将其转换为字节类型传入。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、开始写入// 指定了 "+" 旗标 或 结果不是以 + 开头(以 - 或空格开头)。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:])
}

转载于:https://www.cnblogs.com/golove/p/5861971.html

标准库 - fmt/format.go 解读相关推荐

  1. 标准库 - fmt/scan.go 解读

    // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a B ...

  2. 标准库 - fmt/print.go 解读

    // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a B ...

  3. print python 带回车_python标准库threading源码解读【二】

    紧接着上一篇文章继续解析源码 甘蔗:python标准库threading源码解读[一]​zhuanlan.zhihu.com 目录 Event的介绍和用法 Event源码解析 以后的内容尽量少一点并且 ...

  4. c语言fmt,Go 标准库-fmt

    简介 fmt包实现了类似C语言printf和scanf的格式化I/O.格式化动作('verb')源自C语言但更简单. 占位符:// 通用verbs %v 值的默认格式 %+v 类似%v,但输出结构体时 ...

  5. Golang标准库中的fmt

    Golang标准库中的fmt fmt包实现了类似C语言printf和scanf的格式化I/O.主要分为向外输出内容和获取输入内容两大部分. 1. 向外输出 标准库fmt提供了以下几种输出相关函数. P ...

  6. 11-标准库fmt以及文件操作

    标准库fmt包 输出 Print print系列函数会将内容输出到系统的标准输出,区别在于print函数直接输出内容,Printf函数支持格式化输出字符串,Println函数会在输出内容结尾天际一个换 ...

  7. 一文了解 Go fmt 标准库输入函数的使用

    耐心和持久胜过激烈和狂热. 哈喽大家好,我是陈明勇,今天分享的内容是 Go fmt 标准库输入函数的使用.如果本文对你有帮助,不妨点个赞,如果你是 Go 语言初学者,不妨点个关注,一起成长一起进步,如 ...

  8. 一文了解 Go fmt 标准库输出函数的使用

    耐心和持久胜过激烈和狂热. 哈喽大家好,我是陈明勇,今天分享的内容是 Go fmt 标准库输出函数的使用.如果本文对你有帮助,不妨点个赞,如果你是 Go 语言初学者,不妨点个关注,一起成长一起进步,如 ...

  9. golang中文文档_Golang 标准库 限流器 time/rate 设计与实现

    限流器是后台服务中十分重要的组件,在实际的业务场景中使用居多,其设计在微服务.网关.和一些后台服务中会经常遇到.限流器的作用是用来限制其请求的速率,保护后台响应服务,以免服务过载导致服务不可用现象出现 ...

最新文章

  1. ​防火墙(一)主机型防火墙
  2. 如何检查变量的类型是否为字符串?
  3. 一步步用zTree(2)
  4. Android 用MediaRecorder录制视频太短崩的问题
  5. 作者:司光亚(1967-),男,国防大学信息作战与指挥训练教研部教授,主要研究方向为战争复杂系统建模仿真。...
  6. js一键批量打印_为什么我推荐你用3D打印技术制造模具?
  7. 剪映电脑版_插上手机秒变2K屏笔记本!TNT go扩展本评测:欢迎使用下一代电脑...
  8. css px转rem工具,支持生产整个css文件统一修改
  9. Linux四剑客详解——grep
  10. delphi 热成像摄像机源代码_红外热成像技术广泛应用于夜间及恶劣气候下目标的监控...
  11. yum安装bind常用工具
  12. 【strtok()】——分割字符串
  13. putty详细使用说明
  14. Synchronized快
  15. WIN10锁屏久了宕机(死机)解决方案
  16. 微信小程序标题栏加logo–基于IView-weapp
  17. 峰会实录 | 镜舟科技CEO孙文现:基于StarRocks打造企业级极速统一数据分析产品
  18. 简单计算 ( 山东科技大学第二届ACM校赛)
  19. 爬虫基础-requests库的使用
  20. OkHttp的Okio在CacheInterceptor中的应用

热门文章

  1. 使用restTemplate的post请求传输文件与文件数组
  2. 西红柿的自我修养,是时候回来了。
  3. Java集成Redis key过期通知
  4. Matroska数据封装
  5. LCD段码显示屏常见故障问题总结
  6. 三七互娱陷入买量瓶颈
  7. 用TB5128FTG来替换THB6128(LV8728)的驱动方案
  8. spring boot中jackson时间格式和东八区的设置
  9. 在oracle中实现DateDiff函数的功能
  10. 慕课软件工程(习题集)