• 欢迎光临~

fmt包

开发技术 开发技术 2022-10-28 次浏览

fmt包前言

  • fmt包实现了类似C语言printfscanf的格式化I/O。格式化动作('verb')源自C语言但更简单。
  • fmt包主要分为①向外输出内容②获取输入内容两大部分

1. 输出(写入操作,与Writer相关)

1.1 fmt.Print系列函数

Print系列函数功能:

  • Print:采用默认格式将其参数格式化并写入标准输出。如果两个相邻的参数都不是字符串,会在它们的输出之间添加空格。返回写入的字节数和遇到的任何错误。
  • Println:采用默认格式将其参数格式化并写入标准输出。总是会在相邻参数的输出之间添加空格并在输出结束后添加换行符。返回写入的字节数和遇到的任何错误。
  • Printf:根据format参数生成格式化的字符串并写入标准输出。返回写入的字节数和遇到的任何错误。

print有三个相关的函数:

//type any = interface{},any表示任意类型
//...表示可以传入任意个参数
func Print(a ...any) (n int, err error) {
   return Fprint(os.Stdout, a...)
}

func Println(a ...any) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}

//`format string`表示格式化的字符串
func Printf(format string, a ...any) (n int, err error) {
	return Fprintf(os.Stdout, format, a...)
}

可以看出,这三个函数最终都调用了Fprint系列函数,os.Stdoutstandard out)代表标准输出,即控制台输出

1.2 格式化占位符

1.2.1 通用占位符

占位符 说明
%v 值的默认格式表示
%+v 类似%v,但输出结构体时会添加字段名
%#v 值的Go语法表示(所在包名+结构体名称)
%T 打印值的类型
%% 打印百分号

测试用例:

测试文件代码所在文件结构:

fmt包

type User struct {
	Id int64
}

func TestPrintf(t *testing.T) {
	user := &User{Id: 1}
	fmt.Printf("%vn", user)	//&{1}
	fmt.Printf("%+vn", user)	//&{Id:1}
	fmt.Printf("%#vn", user)	//&fmttest.User{Id:1}
	fmt.Printf("%Tn", user)	//*fmttest.User
	fmt.Printf("%%n")			//%
}

1.2.2 布尔型

占位符 说明
%t true或false

测试用例:

func TestPrintf2(t *testing.T) {
   fmt.Printf("%tn", true)//true
}

1.2.3 整型

占位符 说明
%b 表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于“U+%04X”
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示

测试用例:

func TestPrintf3(t *testing.T) {
	n := 180
	fmt.Printf("%bn", n) 		//10110100
	fmt.Printf("%cn", n) 		//´
	fmt.Printf("%dn", n) 		//180
	fmt.Printf("%on", n) 		//264
	fmt.Printf("%xn", n) 		//b4
	fmt.Printf("%Xn", n) 		//B4
	fmt.Printf("%Un", n) 		//U+00B4
	a := 96
	fmt.Printf("%qn", a)      	//'`'
	fmt.Printf("%qn", 0x4E2D) 	//'中'
}

1.2.4 浮点数与复数

占位符 说明
%b 无小数部分、二进制指数的科学计数法,如-123456p-78
%e 科学计数法,如-1234.456e+78
%E 科学计数法,如-1234.456E+78
%f 有小数部分但无指数部分,如123.456
%F 等价于%f
%g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)

测试用例:

func TestPrintf4(t *testing.T) {
   f := 18.54
   fmt.Printf("%bn", f)	//5218546068215562p-48
   fmt.Printf("%en", f)	//1.854000e+01
   fmt.Printf("%En", f)	//1.854000E+01
   fmt.Printf("%fn", f)	//18.540000
   fmt.Printf("%Fn", f)	//18.540000
   fmt.Printf("%gn", f)	//18.54
   fmt.Printf("%Gn", f)	//18.54
}

1.2.5 字符串和[]byte

占位符 说明
%s 直接输出字符串或者[]byte
%q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x 每个字节用两字符十六进制数表示(a-f)
%X 每个字节用两字符十六进制数表示(A-F)

测试用例:

func TestPrintf5(t *testing.T) {
   s := "我是字符串"
   b := []byte{65, 66, 67}
   fmt.Printf("%sn", s) 	//我是字符串
   fmt.Printf("%sn", b) 	//ABC
   fmt.Printf("%qn", s) 	//"我是字符串"
   fmt.Printf("%xn", s) 	//e68891e698afe5ad97e7aca6e4b8b2
   fmt.Printf("%Xn", s) 	//E68891E698AFE5AD97E7ACA6E4B8B2
}

1.2.6 指针

占位符 说明
%p 表示为十六进制,并加上前导的0x

1.2.7 宽度标识符

例:%10.2

宽度:通过一个紧跟在百分号后面的十进制数(例子中的10)指定,如果未指定宽度,则表示值时除必须之外不作填充。

精度:通过(可选的)宽度后和点号后的跟的十进制数(例子中的2)指定。若未指定精度,会使用默认精度;若点号后没有跟数字,表示精度为0。

占位符 说明
%f 默认宽度,默认精度
%10f 宽度9,默认精度
%.2f 默认宽度,精度2
%10.2f 宽度9,精度2
%10.f 宽度9,精度0

测试用例:

func TestPrintf6(t *testing.T) {
   n := 13.16
   fmt.Printf("%fn", n)                //13.160000
   fmt.Printf("%10fn", n)              // 13.160000
   fmt.Printf("%10sn", "我是字符串")  	 //     我是字符串
   fmt.Printf("%.1fn", n)              //13.2
   fmt.Printf("%10.2fn", n)            //     13.16
   fmt.Printf("%10.fn", n)             //        13
}

1.2.8 其他flag

占位符 说明
+ 总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义)
空格 对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(%x或%X)会给各打印的字节之间加空格
- 在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐)
# 八进制数前加0(%#o),十六进制数前加0x(%#x)或0x(%#X),指针去掉前面的0x(%#p)对%q(%#q),对%U(%#U)会输出空格和单引号括起来的go字面值
0 使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面

测试用例:

func TestPrintf7(t *testing.T) {
   s := "我是字符串"
   fmt.Printf("% dn", 10)       		// 10
   fmt.Printf("%sn", s)           		//我是字符串
   fmt.Printf("%10sn", s)             	//     我是字符串
   fmt.Printf("%-10sn", s)            	//我是字符串   
   fmt.Printf("%10.2fn", 10.14)  		//     10.14
   fmt.Printf("%-10.2fn", 10.14) 		//10.14     
   fmt.Printf("%010sn", s)            	//00000我是字符串
}

1.3 fmt.Fprint系列函数

Fprint系列函数功能:

  • Fprint:采用默认格式将其参数格式化并写入w。如果两个相邻的参数都不是字符串,会在它们的输出之间添加空格。返回写入的字节数和遇到的任何错误。
  • Fprintln:采用默认格式将其参数格式化并写入w。总是会在相邻参数的输出之间添加空格并在输出结束后添加换行符。返回写入的字节数和遇到的任何错误。
  • Fprintf:根据format参数生成格式化的字符串并写入w。返回写入的字节数和遇到的任何错误。

【注】

  • Fprint系列与Print系列的区别就在于写入对象不同,两者的写入对象:

    • Fprint系列:Writer类型变量w
    • Print系列:标准输出os.stdout
  • Fprint系列函数用在写文件中。


Fprint有三个相关的函数:

func Fprint(w io.Writer, a ...any) (n int, err error) {
	p := newPrinter()
	p.doPrint(a)
	n, err = w.Write(p.buf)
	p.free()
	return
}

func Fprintln(w io.Writer, a ...any) (n int, err error) {
	p := newPrinter()
	p.doPrintln(a)
	n, err = w.Write(p.buf)
	p.free()
	return
}

func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
   p := newPrinter()
   p.doPrintf(format, a)
   n, err = w.Write(p.buf)
   p.free()
   return
}

1.3.1 测试用法1:用Fprint向控制台输出内容

func TestFprint1(t *testing.T) {
	fmt.Fprint(os.Stdout, "向控制台输出写入的字符串n")
	fmt.Fprintln(os.Stdout, "向控制台输出写入的字符串")
	fmt.Fprintf(os.Stdout, "%s向控制台输出写入的字符串", "张三")
}

控制台输出:

向控制台输出写入的字符串
向控制台输出写入的字符串
张三向控制台输出写入的字符串

【预备知识】

  • os.OpenFile(name string, flag int, perm FileMode)的参数: name:文件名,flag:操作方式, perm:文件权限;

  • 关于flag参数,操作方式列表如下:

const (
    O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
    O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
    O_RDWR   int = syscall.O_RDWR   // 读写模式打开文件
    O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
    O_CREATE int = syscall.O_CREAT  // 如果不存在将创建一个新文件
    O_EXCL   int = syscall.O_EXCL   // 和O_CREATE配合使用,文件必须不存在
    O_SYNC   int = syscall.O_SYNC   // 打开文件用于同步I/O
    O_TRUNC  int = syscall.O_TRUNC  // 如果可能,打开时清空文件
)

【注意】可以选择多个操作方式,用“|”隔开。

  • 关于perm参数:

    1. 参数共4位,第1位总为0,表示八进制;第2到4位表示权限,第2位表示是否可读、第3位表示是否可写、第4位表示是否可执行;
    2. 例如:0000 不可读写、不可执行; 0001 不可读写 可执行; 0010 可写不可读,不可执行; 0100 可读不可写,不可执行;由于是二进制表示,所以可以将其转化为10进制的数值,比如0100可以转化为4, 0111为7,0110为6;也可以记作:4读2写1执行; 最多不大于7,在表示权限的时候可以使用8进制,来区分用户/用户组/其他用户的分别权限;比如0764表示文件所属用户可读可写可执行,用户组其他用户仅可读可写,其他非用户组用户仅可读;
    3. 查看权限可以用os.FileMode(perm).String来在控制台进行打印。如:
    func TestFprint3(t *testing.T) {
    	s := os.FileMode(0777).String()
    	fmt.Println(s)
    }
    

    控制台输出:

    -rwxrwxrwx
    
    1. -rwxrwxrwx表示普通文件,r表示可读,w表示可写,x表示可执行。
      • 第1位:文件属性,一般常用的是“ - ”,表示是普通文件;“ d ”表示是一个目录。
      • 第2~4位:文件所有者的权限rwx(可读/可写/可执行)。
      • 第5~7位:文件所属用户组的权限rwx(可读/可写/可执行)。
      • 第8~10位:其他人的权限rwx(可读/可写/可执行)。
    2. 0777表示:创建了一个普通文件,所有人拥有所有的读、写、执行权限;
      0666表示:创建了一个普通文件,所有人拥有对该文件的读、写权限,但是都不可执行;
      0644表示:创建了一个普通文件,文件所有者对该文件有读写权限,用户组和其他人只有读权限,都没有执行权限。

1.3.2 测试用法2:用Fprint向文件写入内容

func TestFprint2(t *testing.T) {
   file, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
   if err != nil {
      panic(err)
   }
   fmt.Fprintf(file, "%s向文件写入了若干内容", "张三")
}

程序运行会生成一个test.txt文件,文件内容如下:

fmt包

os.O_APPEND表示若再次运行内容则会继续在文件尾部添加,多次运行程序后的test.txt文件内容如下:

fmt包

1.3.3 测试用法3:通过http库的一些函数和fmt.Fprint在web服务上的应用

实现一个需求:向浏览器页面输出文本内容。

package main

import (
   "fmt"
   "net/http"
)

func main() {
   //设置通过/greetToTheWorld来访问一个传入的匿名函数
   http.HandleFunc("/greet", func(w http.ResponseWriter, r *http.Request) {
      fmt.Fprintln(w, "hello,world!")
   })
    
   //设置访问端口,可以用localhost:8080/greet或127.0.0.1:8080/greet访问远程调用的函数
   err := http.ListenAndServe(":8080", nil)
   if err != nil {
      fmt.Println("error is ", err)
   }
}

浏览器端:

fmt包

1.4 fmt.Sprint系列函数

Sprint系列函数功能:

  • Sprint:采用默认格式将其参数格式化,串联所有输出生成并返回一个字符串。如果两个相邻的参数都不是字符串,会在它们的输出之间添加空格。
  • Sprintln:采用默认格式将其参数格式化,串联所有输出生成并返回一个字符串。总是会在相邻参数的输出之间添加空格并在输出结束后添加换行符。
  • Sprintf:根据format参数生成格式化的字符串并返回该字符串。

Sprint有三个相关的函数:

func Sprint(a ...any) string {
   p := newPrinter()
   p.doPrint(a)
   s := string(p.buf)
   p.free()
   return s
}

func Sprintln(a ...any) string {
	p := newPrinter()
	p.doPrintln(a)
	s := string(p.buf)
	p.free()
	return s
}

func Sprintf(format string, a ...any) string {
	p := newPrinter()
	p.doPrintf(format, a)
	s := string(p.buf)
	p.free()
	return s
}

当需要将不同类型的数据类型拼接成字符串时,需要将所有数据都先转换成为字符串再进行拼接,这样会比较麻烦;

Sprint系列函数被用来将若干不同数据类型的数据进行拼接,用起来会非常方便。

例如将字符串类型和整型拼接到一起:

func TestSprint(t *testing.T) {
   host := "localhost"
   port1 := 8080
   port2 := 8090
   port3 := 6379

   addr1 := fmt.Sprintf("%s:%d", host, port1)
   addr2 := fmt.Sprintln(host, ":", port2)
   addr3 := fmt.Sprint(host, ":", port3)
   fmt.Println(
      addr1,
      addr2,
      addr3,
   )
}

控制台输出:

localhost:8080 localhost : 8090
 localhost:6379

1.5 fmt.Errorf

功能:根据format参数生成格式化字符串并返回一个包含该字符串的错误。

func Errorf(format string, a ...any) error {
   p := newPrinter()
   p.wrapErrs = true
   p.doPrintf(format, a)
   s := string(p.buf)
   var err error
   if p.wrappedErr == nil {
      err = errors.New(s)
   } else {
      err = &wrapError{s, p.wrappedErr}
   }
   p.free()
   return err
}

测试用例:

func TestErrorf(t *testing.T) {
   err := fmt.Errorf("用户名格式错误:%s", "%$#!@")
   if err != nil {
      fmt.Println(err)
   }
}

控制台输出:

用户名格式错误:%$#!@

【扩展】errors.NewErrorf功能类似,以下为原码:

func New(text string) error {
   return &errorString{text}
}

可以看出errors.New仅支持输入字符串,而Errorf可以通过格式化字符来输入不同的数据类型,所以后者用法更为灵活。

2 输入(读取操作,与Reader相关)

2.1 fmt.Scan系列函数

fmt.Scan系列函数功能:

  • Scan:从标准输入扫描文本,将成功读取的空白分隔的值保存进成功传递给本函数的参数。换行视为空白。返回成功扫描的条目个数和遇到的任何错误。如果读取的条目比提供的参数少,会返回一个错误报告原因。
  • Scanln:类似Scan,但会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置。
  • Scanf:从标准输入扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数。返回成功扫描的条目个数和遇到的任何错误。(【注】Scanf参数中若出现多个值则要用空格隔开)

Scan有三个相关的函数:

func Scan(a ...any) (n int, err error) {
   return Fscan(os.Stdin, a...)
}

func Scanln(a ...any) (n int, err error) {
	return Fscanln(os.Stdin, a...)
}

func Scanf(format string, a ...any) (n int, err error) {
	return Fscanf(os.Stdin, format, a...)
}

可以看出,这三个函数最终都调用了Fscan系列函数,os.Stdinstandard in)代表标准输入,即控制台输入


[tips.]

  1. Scan()Scanln()的区别

Scan()Scanln()用法与功能效果完全一样,但只有一个不同点:

  • Scan()要求,若要输入两个值,那就必须输入两个,若要输入三个值,那就必须是三个。否则程序会一直等待,不会执行下面的程序;
  • Scanln()没有这个强制的要求,如果要求我们输入两个值,但只输入一个值,那么回车后程序依然会执行。

总结:Scan()必须等待所要求的值输入完成才能执行程序,但是Scanln()不管输入的数据是否完成,只要回车就直接执行。

  1. Scanf的特点
  • 如果在Scanf()中写了两个输入值但是只输入一个,那样未输入的值就是该值类型的默认值;
  • Scanf支持用户定制化输入,即输入时必须按照设置好的参数格式来对数据进行输入。

测试用例1:Scan()

    fmt.Print("please enter the userName and age:")
    var userName string
    var age int
    count, _ := fmt.Scan(&userName, &age)
    fmt.Println("the userName is:", userName, "nthe age is:", age)
    fmt.Println("user has entered", count, "values")

控制台输入liam 23后:

please enter the userName and age:liam 23
the userName is: liam 
the age is: 23
user has entered 2 values

测试用例2.1:Scanf()常规用法

	fmt.Print("please enter the userName:")
	var userName string
	fmt.Scanf("%s", &userName)
	fmt.Println("the userName is:", userName)

控制台输入liam后:

please enter the userName:liam
the userName is: liam

测试用例2.2:Scanf()定制化输入

var (
   name    string
   age     int
   marriage bool
)

fmt.Print("please enter the information:")
fmt.Scanf("1:%s 2:%d 3:%t", &name, &age, &marriage)//Scanf参数中若出现多个值则要用空格隔开
fmt.Printf("name:%s, age:%d, married:%t", name, age, marriage)

所谓定制化输入,就是在控制台输入数据时必须按照设置好的参数格式来对数据进行输入,不然变量就无法接收输入的数据。

控制台输入1:liam 2:23 3:false后控制台输出:

name:liam, age:23, married:false

2.2 fmt.Fscan系列函数

fmt.Fscan系列函数功能:

  • Fscan:从r扫描文本,将成功读取的空白分隔的值保存进成功传递给本函数的参数。换行视为空白。返回成功扫描的条目个数和遇到的任何错误。如果读取的条目比提供的参数少,会返回一个错误报告原因。
  • Fscanln:类似Fscan,但会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置。
  • Fscanf:从r扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数。返回成功扫描的条目个数和遇到的任何错误。(【注】Fscanf参数中若出现多个值则要用空格隔开)

【注】

  • Fscan系列与Scan系列的区别就在于读取对象不同,两者的读取对象:

    • Fscan系列:Reader类型变量r
    • Scan系列:标准输入os.stdin
  • Fprint系列函数用在读文件中。


Fscan有三个相关的函数:

func Fscan(r io.Reader, a ...any) (n int, err error) {
   s, old := newScanState(r, true, false)
   n, err = s.doScan(a)
   s.free(old)
   return
}

func Fscanln(r io.Reader, a ...any) (n int, err error) {
	s, old := newScanState(r, false, true)
	n, err = s.doScan(a)
	s.free(old)
	return
}

func Fscanf(r io.Reader, format string, a ...any) (n int, err error) {
	s, old := newScanState(r, false, false)
	n, err = s.doScanf(format, a)
	s.free(old)
	return
}

测试用例1:

【预备知识】

strings包中有一个函数,可以用来创建一个Reader类型。

函数名 功能
func NewReader(s string) *Reader 创建一个从s读取数据的Reader。本函数类似bytes.NewBufferString,但是更有效率,且为只读的。
	var (
		name     string
		age      int
		marriage bool
	)
	//Fscan
	r1 := strings.NewReader("Liam 23 false")
	fmt.Fscan(r1, &name, &age, &marriage)
	fmt.Printf("name:%s,age:%d,marriaged:%tn", name, age, marriage)
	
	//Fscanf
	r2 := strings.NewReader("sprite : 28 : true")
	fmt.Fscanf(r2, "%s : %d : %t", &name, &age, &marriage)
	fmt.Printf("name:%s,age:%d,marriaged:%t", name, age, marriage)

控制台输出:

name:Liam,age:23,marriaged:false
name:sprite,age:28,marriaged:true
程序员灯塔
转载请注明原文链接:fmt包
喜欢 (0)