fmt 库简介和示例【GO 基础】

〇、关于 fmt

fmt 标准库是 Go 语言标准库的一部分,提供了格式化字符串、输入输出等基本功能。通过 fmt 库,我们可以进行简单的格式化输出、读取用户输入、错误输出等操作。

fmt 库实现了类似 C 语言 printf 和 scanf 的格式化 I/O,主要分为向外输出内容和获取输入内容两大部分,本文将通过示例来分别测试。

一、输出

GO 语言的输出是由 Print 系列函数完成的,下面列一下都有哪些输出类型。

1.1 Print-直接输出

Print 函数直接输入内容到终端,是最简单的输出方式。

package main

import (
	"fmt"
)

func main() {
	fmt.Print("第一段")
	fmt.Print("第二段")
	fmt.Print("第三段")
}

由输出可见,Print 函数直接输出引号中的内容,不进行任何修饰。

1.2 Printf-以指定格式输出

fmt.Printf 是 Go 语言 fmt 包中的一个函数,用于格式化输出字符串。

它的基本语法是 fmt.Printf(format, a, b, c, ...),其中 format 是一个字符串,用于指定输出的格式,a, b, c, ... 是需要输出的参数。fmt.Printf 会按照 format 字符串中的格式说明符,将参数转换为相应的格式输出。

对于 format 中的格式说明,有比较多格式配置,下面总结下。

1.2.1 通用占位符

占位符 说明
%v 根据值的默认格式标识仅输出值
%+v 在 v% 的基础上,带上字段名
%#v 按照值的 GO 语法表示输出,类型+值
%T 输出值的类型
%% 输出单个字符:百分号

下面通过输出三个类型(常量、字符串、自定义结构)来简单演示下:

package main

import "fmt"

func main() {
	pi := 3.1415926
	fmt.Printf("%%v-pi :%v \n", pi) // %v 值的类型输出
	fmt.Printf("%%+v   :%+v \n", pi)
	fmt.Printf("%%#v   :%#v \n", pi)
	fmt.Printf("%%T    :%T \n", pi)
	str := "chengzi"
	fmt.Printf("%%v-str:%v \n", str)
	fmt.Printf("%%+v   :%+v \n", str)
	fmt.Printf("%%#v   :%#v \n", str)
	fmt.Printf("%%T    :%T \n", str)
	stu := struct {
		name    string
		address string
	}{"橙子", "河南"}
	fmt.Printf("%%v-stu:%v \n", stu)
	fmt.Printf("%%+v   :%+v \n", stu)
	fmt.Printf("%%#v   :%#v \n", stu)
	fmt.Printf("%%T    :%T \n", stu)
}

1.2.2 布尔类型占位符

针对布尔类型,有个专用的占位符:%t,用于直接输出 true/false

如果要输出的字段不是布尔类型,不会中断程序运行,只会有警告提示。

package main

import "fmt"

func main() {
	fmt.Printf("%%t :%t \n", true)
	fmt.Printf("%%t :%t \n", false)
	fmt.Printf("%%t :%t \n", "chengzi")
}

 

1.2.3 整型数值占位符

占位符 说明
%b 输出值对应的二进制格式值
%c 输出值对应的 Unicode 码值
%d 输出值对应的十进制格式值
%o 输出值对应的八进制格式值
%x 输出值对应的十六进制格式值,小写字母 a~f
%X 输出值对应的十六进制格式值,大写字母 A~F
%U 输出值对应的 Unicode 格式:U+1234
%q 该值对应的单引号括起来的 Go 语法字符字面值,必要时会采用安全的转义表示

注:统一码(Unicode),也叫万国码、单一码,由统一码联盟开发,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。其中 3400-4DBF/4E00-9FFF/20000-3FFFF,表示中日韩越统一表意文字(CJKV Unified Ideographs)。

如下通过对整数 100 的多类型输出,示例说明一下各个占位符对应的区别:

package main

import "fmt"

func main() {
	num := 100
	fmt.Printf("%%b :%b\n", num)
	fmt.Printf("%%c :%c\n", num)
	fmt.Printf("%%d :%d\n", num)
	fmt.Printf("%%o :%o\n", num)
	fmt.Printf("%%x :%x\n", num)
	fmt.Printf("%%X :%X\n", num)
	fmt.Printf("%%U :%U\n", num)
	fmt.Printf("%%q :%q\n", num)
}

1.2.4 浮点数占位符

占位符 说明
%e 科学计数法,小写字母:-1.234567e+89
%E 科学计数法,大写字母:-1.234567E+89
%f 有小数(六位)、无指数:123.456789
%F 同上
%g 以使用更简洁、准确的格式输出为目的,灵活采用 %e 或 %f 格式,小数保留前十四位
%G 以使用更简洁、准确的格式输出为目的,灵活采用 %E 或 %F 格式
package main

import "fmt"

func main() {
	f := 123.45600
	fmt.Printf("f-%%e  :%e\n", f)
	fmt.Printf("f-%%E  :%E\n", f)
	fmt.Printf("f-%%f  :%f\n", f)
	fmt.Printf("f-%%F  :%F\n", f)
	fmt.Printf("f-%%g  :%g\n", f) // 最多保留十四位小数
	fmt.Printf("f-%%G  :%G\n", f) // 最多保留十四位小数
	ff := 123.4560001001000111001000011001
	fmt.Printf("ff-%%e :%e\n", ff)
	fmt.Printf("ff-%%E :%E\n", ff)
	fmt.Printf("ff-%%f :%f\n", ff)
	fmt.Printf("ff-%%F :%F\n", ff)
	fmt.Printf("ff-%%g :%g\n", ff) // 最多保留十四位小数,末尾若是 0 默认省去
	fmt.Printf("ff-%%G :%G\n", ff) // 最多保留十四位小数
}

1.2.5 字符串和 []byte 占位符

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

import "fmt"

func main() {
	str := "橙子"
	fmt.Printf("%%s: %s\n", str)
	fmt.Printf("%%q: %q\n", str)
	fmt.Printf("%%x: %x\n", str)
	fmt.Printf("%%X: %X\n", str)
	byte := []byte("橙子")
	fmt.Printf("%%v: %v\n", byte)
	fmt.Printf("%%s: %s\n", byte)
}

1.2.6 指针占位符

指针也是一个专门的占位符 %p,表示‘十六进制字符串’+‘0x’

package main

import "fmt"

func main() {
	a := 18
	fmt.Printf("%p\n", &a)
	fmt.Printf("%#p\n", &a)
}

1.2.7 宽度标识符

宽度标识符就是以浮点数占位符中的 %f 为基础,增加宽度配置。%[n].[m]f 其中 n 表示整个数值的宽度,m 表示小数位宽度。如下示例与代表的意义:

占位符 说明
%f 默认宽度、默认精度
%9f 宽度 9、默认精度
%.2f 默认宽度、精度 2
%9.2f 宽度 9、精度 2
%9.f 宽度 9、精度 0

注意:小数点也占用一个位数。

package main

import "fmt"

func main() {
	n := 88.88
	fmt.Printf("%%f    :%f\n", n) // 默认小数位为六位
	fmt.Printf("%%9f   :%9f\n", n)
	fmt.Printf("%%10f  :%10f\n", n)
	fmt.Printf("%%.2f  :%.2f\n", n)
	fmt.Printf("%%9.2f :%9.2f\n", n)
	fmt.Printf("%%9.f  :%9.f\n", n)
}

 

1.2.8 其他占位符

占位符 说明
+ 总是输出数值的正负号;对 %q(%+q)会生成全部是 ASCII 字符的输出(通过转义)
 (一个空格)

对数值:正数前加空格而负数前加负号

对字符串:采用%x或%X时(% x或% X)会给各打印的字节之间加空格

-(一个横杠) 在输出右边填充空白,而不是默认的左边(即从默认的右对齐切换为左对齐)
#

八进制数前加 0(%#o)

十六进制数前加 0x,对于 %x(%#x);0X,对于 %X(%#X)

指针去掉前面的 0x,对于 %q(%#q),

unicode 值,对 %U(%#U)会输出空格和单引号括起来的 Go 字面值

0 使用 0 而不是空格填充,对于数值类型会把填充的 0 放在正负号后面
package main

import "fmt"

func main() {
	fmt.Println("-----------------------------")
	num := -12.34
	num1 := 12.34
	fmt.Printf("%f %f\n", num, num1)
	fmt.Printf("%+f %+f\n", num, num1)
	fmt.Println("-----------------------------")
	num2 := -12.34
	num3 := 12.34
	fmt.Printf("%f %f\n", num2, num3)
	fmt.Printf("% f % f\n", num2, num3)
	str := "chengzi"
	fmt.Printf("%x\n% x\n", str, str)
	fmt.Println("-----------------------------")
	num4 := 77.77
	fmt.Printf("%10f\n", num4)
	fmt.Printf("%0-10f\n", num4)
	fmt.Println("-----------------------------")
	str1 := "chengzi"
	fmt.Printf("%s\n", str1)
	fmt.Printf("%8s\n", str1)   // 默认左侧填充一个空格,右对齐
	fmt.Printf("%-8s\n", str1)  // 左对齐
	fmt.Printf("%08s\n", str1)  // 填充 0
	fmt.Printf("%-08s\n", str1) // 左对齐并填充 0,可以左对齐,但此时填充无法实现
}

1.3 Println-输出后自动换行

其实 Println 就是在打印输出内容后添加一个换行符‘\n’。

package main

import "fmt"

func main() {
	fmt.Print("Println测试")
	fmt.Println("-----------------------------")
	fmt.Print("Println测试")
}

横线直接跟在了第一行,第二个输出自动进行了换行。

1.4 Fprint-将内容写入文件

Fprint 系列函数会将内容输出到一个 io.Writer 接口类型的变量 w 中,我们通常用这个函数往文件中写入内容

func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println("开始向标准输出写入内容...")
	fileObj, err := os.OpenFile("./log.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    // O_CREATE(不存在则创建)O_WRONLY(只写)O_APPEND(追加内容)
	if err != nil {
		fmt.Println("打开文件出错,err:", err)
		return
	}
	content := "chengzi子"
	// 向打开的文件句柄中写入内容
	fmt.Fprintf(fileObj, "往文件中写入信息:%s\n", content)
	fmt.Fprintln(fileObj, "又一行信息。")
	fmt.Fprint(fileObj, "再一行信息。")
	fmt.Fprint(fileObj, "最后的信息。")
	fmt.Println("写入完成!")
}

终端输出:

1.5 Sprint-将传入的数据以字符串形式输出

func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string

package main

import (
	"fmt"
)

func main() {
	s1 := fmt.Sprint("chengzi家")
	fmt.Println(s1)
	s2 := fmt.Sprintln("chengzi家")
	fmt.Println(s2)
	name := "chengzi家"
	age := 18
	s3 := fmt.Sprintf("name:%s,age:%d", name, age)
	fmt.Println(s3)
}

1.6 Errorf-返回一个包装后的错误(errors.Wrap 简介

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

func Errorf(format string, a ...interface{}) error

fmt.Errorf 的优点在于其支持格式化字符串,这使得我们可以方便地在原始错误信息中包含一些动态的数据,但是它并不会保留原始错误的堆栈跟踪信息。

Go 1.13 中引入的新特性使 fmt.Errorf 通过 %w 谓词包装错误,这样就可以保留原始错误的信息。如下:

package main

import (
	"errors"
	"fmt"
)

func main() {
	original_err := errors.New("original error")
	new_err := fmt.Errorf("an error occurred: %w", original_err)
	fmt.Println(new_err)
}

另外,errors.Wrap 和 errors.Wrapf 是包含原始错误的堆栈跟踪信息。它们是 github.com/pkg/errors 库中的函数,用于创建新的错误。它们接受一个原始错误和一个描述信息,返回一个新的错误,结构如下:

err := errors.Wrap(err, "an error occurred")
err = errors.Wrapf(err, "an error occurred: %s", "additional context")

errors.Wrap 和 errors.Wrapf 的优点在于它们会保留原始错误的堆栈跟踪信息。你可以使用 errors.Cause 函数获取到原始错误,使用 fmt.Printf("%+v", err) 打印完整的错误信息和堆栈跟踪。 

此外,errors.Wrapf 还支持格式化字符串,这意味着我们可以在错误信息中直接包含动态的数据。如下一个关于 errors.Wrap 和 errors.Wrapf 的示例:

package main

import (
	"fmt"

	"github.com/pkg/errors"
)

func main() {
	original_err := errors.New("original error")
	new_err := fmt.Errorf("fmt.Errorf(): %w", original_err)
	fmt.Println(new_err)
	fmt.Println("------------------------------------------")
	fmt.Println("------------------------------------------")
	err := errors.Wrap(original_err, "errors.Wrap()")
	fmt.Printf("fmt.Wrap():%+v\n", err)
	fmt.Println("------------------------------------------")
	fmt.Println("------------------------------------------")
	err = errors.Wrapf(original_err, "errors.Wrapf(): %s", "additional context")
	fmt.Printf("fmt.Wrapf():%+v\n", err)
}

1.6 参考:https://cloud.tencent.com/developer/article/2311646

二、输入

Go 语言 fmt 包下有 fmt.Scan、fmt.Scanf、fmt.Scanln 三个函数,可以在程序运行过程中从标准输入获取用户的输入。下面分别来介绍下。

2.1 fmt.Scan()-扫描客户端输入的文本参数值

Scan 从标准输入扫描文本,读取由空白符分割的值保存到传递给本函数的参数中,换行符或空格视为空白符

本函数返回成功扫描的数据个数和遇到的任何错误。如果读取的数据个数比提供的参数少,会返回一个错误描述原因。

func Scan(a ...interface{}) (n int, err error)

package main

import (
	"fmt"
)

func main() {
	var (
		name    string
		age     int
		married bool
	)
	fmt.Println("请输入字符参数,回车确认输入完成:")
	fmt.Scan(&name, &age, &married)
	fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}

客户端手动输入多个字符参数时,用空格或换行分隔且可以混用,最后通过回车确认输入完成。如下是几个输入示例:

2.2 fmt.Scanf-扫描可指定格式的输入参数值

相较于 fmt.Scan(),fmt.Scanf() 可以定义输入参数的格式,只有按照指定格式输入才会识别,更加标准化。

Scanf 从标准输入扫描文本,根据 format 参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中。

本函数返回成功扫描的数据个数和遇到的任何错误。

func Scanf(format string, a ...interface{}) (n int, err error)

package main

import "fmt"

func main() {
	var (
		name    string
		age     int
		married bool
	)
	fmt.Println("请输入字符参数,回车确认输入完成:")
	fmt.Scanf("1:%v 2:%v 3:%v", &name, &age, &married)
	fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}

2.3 fmt.Scanln-最后通过回车确认输入参数值

相较于 fmt.Scan(),fmt.Scanln 不允许输入多个参数间回车,只能通过空格来分隔多个参数,最后回车确认输入。

Scanln 类似 Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置。

本函数返回成功扫描的数据个数和遇到的任何错误。

func Scanln(a ...interface{}) (n int, err error)

package main

import "fmt"

func main() {
	var (
		name    string
		age     int
		married bool
	)
	fmt.Println("请输入字符参数,回车确认输入完成:")
	fmt.Scanln(&name, &age, &married)
	fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}

2.4 bufio.NewReader-读取包括空格在内的全部输入

当想获取完整获取输入的内容,但是输入的内容可能包含空格,这种情况下可以使用 bufio 包来实现。

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象
	fmt.Print("请输入内容:")
	text, _ := reader.ReadString('\n') // 读到换行
	text = strings.TrimSpace(text)
	fmt.Printf("%#v\n", text)
}

2.5 Fscan 系列-根据指定字符串扫描目标值

这几个函数功能分别类似于 fmt.Scan、fmt.Scanf、fmt.Scanln 三个函数,只不过它们不是从标准输入中读取数据而是从 io.Reader 中读取数据。注意:Fscan 系列都是和 io 阻塞的有关系。

func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)

package main

import (
	"fmt"
	"strings"
)

func main() {
	var (
		name    string
		age     int
		married bool
	)
	reader := strings.NewReader("chengzijia 18 false")
	intt, _ := fmt.Fscan(reader, &name, &age, &married)
	fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
	fmt.Printf("intt:%v\n", intt)
}

 

2.6 Sscan 系列-根据指定字符串扫描目标值

这几个函数功能分别类似于 fmt.Scan、fmt.Scanf、fmt.Scanln 三个函数,只不过它们不是从标准输入中读取数据而是从指定字符串中读取数据。

func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)

package main

import "fmt"

func main() {
	{ // Sscan
		var name string
		var alphabet_count int
		n, err := fmt.Sscan("GFG 3", &name, &alphabet_count) // 根据空格取值
		if err != nil {
			panic(err)
		}
		fmt.Printf("%d:%s, %d\n", n, name, alphabet_count)
	}
	{ // Sscanln
		var name string
		var alphabet_count int
		n, err := fmt.Sscanln("GFG 3", &name, &alphabet_count) // 根据空格取值
		if err != nil {
			panic(err)
		}
		fmt.Printf("n:%d, name:%s, alphabet_count:%d\n",
			n, name, alphabet_count)
	}
	{ // Sscanf
		var name string
		var alphabet_count int
		n, err := fmt.Sscanf("GFG is having 3 alphabets.", // 根据模板取值
			"%s is having %d alphabets.", &name, &alphabet_count)
		if err != nil {
			panic(err)
		}
		fmt.Printf("%d:%s, %d\n", n, name, alphabet_count)
	}
}

参考:http://www.topgoer.com/%E5%B8%B8%E7%94%A8%E6%A0%87%E5%87%86%E5%BA%93/fmt.html

posted @ 2023-09-27 17:13  橙子家  阅读(656)  评论(0编辑  收藏  举报