1. Printing
Sample
type user struct {
name string
}
func main() {
u := user{"tang"}
//Printf 格式化输出
fmt.Printf("% + v\n", u) //格式化输出结构
fmt.Printf("%#v\n", u) //输出值的 Go 语言表示方法
fmt.Printf("%T\n", u) //输出值的类型的 Go 语言表示
fmt.Printf("%t\n", true) //输出值的 true 或 false
fmt.Printf("%b\n", 1024) //二进制表示
fmt.Printf("%c\n", 11111111) //数值对应的 Unicode 编码字符
fmt.Printf("%d\n", 10) //十进制表示
fmt.Printf("%o\n", 8) //八进制表示
fmt.Printf("%q\n", 22) //转化为十六进制并附上单引号
fmt.Printf("%x\n", 1223) //十六进制表示,用a-f表示
fmt.Printf("%X\n", 1223) //十六进制表示,用A-F表示
fmt.Printf("%U\n", 1233) //Unicode表示
fmt.Printf("%b\n", 12.34) //无小数部分,两位指数的科学计数法6946802425218990p-49
fmt.Printf("%e\n", 12.345) //科学计数法,e表示
fmt.Printf("%E\n", 12.34455) //科学计数法,E表示
fmt.Printf("%f\n", 12.3456) //有小数部分,无指数部分
fmt.Printf("%g\n", 12.3456) //根据实际情况采用%e或%f输出
fmt.Printf("%G\n", 12.3456) //根据实际情况采用%E或%f输出
fmt.Printf("%s\n", "wqdew") //直接输出字符串或者[]byte
fmt.Printf("%q\n", "dedede") //双引号括起来的字符串
fmt.Printf("%x\n", "abczxc") //每个字节用两字节十六进制表示,a-f表示
fmt.Printf("%X\n", "asdzxc") //每个字节用两字节十六进制表示,A-F表示
fmt.Printf("%p\n", 0x123) //0x开头的十六进制数表示
}
2. Scanning
一组类似的函数通过扫描已格式化的文本来产生值。 Scan、Scanf 和 Scanln 从 os.Stdin 中读取; Fscan、Fscanf 和 Fscanln 从指定的 io.Reader 中读取; Sscan、Sscanf 和 Sscanln 从实参字符串中读取。 Scanln、Fscanln 和 Sscanln 在换行符处停止扫描,且需要条目紧随换行符之后; Scanf、Fscanf 和 Sscanf 需要输入换行符来匹配格式中的换行符;其它函数则将换行符视为空格。
Scanf、Fscanf 和 Sscanf 根据格式字符串解析实参,类似于 Printf。例如,%x 会将一个整数扫描为十六进制数,而 %v 则会扫描该值的默认表现格式。
格式化行为类似于 Printf,但也有如下例外:
%p 没有实现
%T 没有实现
%e %E %f %F %g %G 都完全等价,且可扫描任何浮点数或复数数值
%s 和 %v 在扫描字符串时会将其中的空格作为分隔符
标记 # 和 + 没有实现
在使用 %v 占位符扫描整数时,可接受友好的进制前缀0(八进制)和0x(十六进制)。
宽度被解释为输入的文本(%5s 意为最多从输入中读取5个 rune 来扫描成字符串),而扫描函数则没有精度的语法(没有 %5.2f,只有 %5f)。
当以某种格式进行扫描时,无论在格式中还是在输入中,所有非空的连续空白字符 (除换行符外)都等价于单个空格。由于这种限制,格式字符串文本必须匹配输入的文本,如果不匹配,扫描过程就会停止,并返回已扫描的实参数。
在所有的扫描参数中,若一个操作数实现了 Scan 方法(即它实现了 Scanner 接口), 该操作数将使用该方法扫描其文本。此外,若已扫描的实参数少于所提供的实参数,就会返回一个错误。
所有需要被扫描的实参都必须是基本类型或 Scanner 接口的实现。
注意:Fscan 等函数会从输入中多读取一个字符(rune),因此,如果循环调用扫描函数,可能会跳过输入中的某些数据。一般只有在输入的数据中没有空白符时该问题才会出现。若提供给 Fscan 的读取器实现了 ReadRune,就会用该方法读取字符。若此读取器还实现了 UnreadRune 方法,就会用该方法保存字符,而连续的调用将不会丢失数据。若要为没有 ReadRune 和 UnreadRune 方法的读取器加上这些功能,需使用 bufio.NewReader。
3. Print 序列函数
这里说的 Print 序列函数包括:Fprint/Fprintf/Fprintln/Sprint/Sprintf/Sprintln/Print/Printf/Println。之所以将放在一起介绍,是因为它们的使用方式类似、参数意思也类似。
一般的,我们将 Fprint/Fprintf/Fprintln 归为一类;Sprint/Sprintf/Sprintln 归为一类;Print/Printf/Println 归为另一类。其中,Print/Printf/Println 会调用相应的F开头一类函数。如:
func Print(a ...interface{}) (n int, err error) {
return Fprint(os.Stdout, a...)
}
Fprint/Fprintf/Fprintln 函数的第一个参数接收一个io.Writer类型,会将内容输出到 io.Writer 中去。而 Print/Printf/Println 函数是将内容输出到标准输出中,因此,直接调用 F类函数 做这件事,并将 os.Stdout 作为第一个参数传入。
Sprint/Sprintf/Sprintln 是格式化内容为 string 类型,而并不输出到某处,需要格式化字符串并返回时,可以用这组函数。
在这三组函数中,S/F/Printf函数通过指定的格式输出或格式化内容;S/F/Print函数只是使用默认的格式输出或格式化内容;S/F/Println函数使用默认的格式输出或格式化内容,同时会在最后加上"换行符"。
Print 序列函数的最后一个参数都是 a ...interface{} 这种不定参数。对于S/F/Printf序列,这个不定参数的实参个数应该和formt参数的占位符个数一致,否则会出现格式化错误;而对于其他函数,当不定参数的实参个数为多个时,它们之间会直接(对于S/F/Print)或通过" "(空格)(对于S/F/Println)连接起来(注:对于S/F/Print,当两个参数都不是字符串时,会自动添加一个空格,否则不会加。感谢guoshanhe1983 反馈。官方 effective_go 也有说明)。利用这一点,我们可以做如下事情:
result1 := fmt.Sprintln("studygolang.com", 2013)
result2 := fmt.Sprint("studygolang.com", 2013)
result1的值是:studygolang.com 2013,result2的值是:studygolang.com2013。这起到了连接字符串的作用,而不需要通过strconv.Itoa()转换。
Print 序列函数用的较多,而且也易于使用(可能需要掌握一些常用的占位符用法),接下来我们结合 fmt 包中几个相关的接口来掌握更多关于 Print 的内容。
4. Stringer 接口
Stringer接口的定义如下:
type Stringer interface {
String() string
}
根据 Go 语言中实现接口的定义,一个类型只要有 String() string 方法,我们就说它实现了 Stringer 接口。而在本节开始已经说到,如果格式化输出某种类型的值,只要它实现了 String() 方法,那么会调用 String() 方法进行处理。
我们定义如下struct:
type Person struct {
Name string
Age int
Sex int
}
我们给Person实现String方法,这个时候,我们输出Person的实例:
p := &Person{"polaris", 28, 0}
fmt.Println(p)
输出:
&{polaris 28 0}
接下来,为Person增加String方法。
func (this *Person) String() string {
buffer := bytes.NewBufferString("This is ")
buffer.WriteString(this.Name + ", ")
if this.Sex == 0 {
buffer.WriteString("He ")
} else {
buffer.WriteString("She ")
}
buffer.WriteString("is ")
buffer.WriteString(strconv.Itoa(this.Age))
buffer.WriteString(" years old.")
return buffer.String()
}
这个时候运行:
p := &Person{"polaris", 28, 0}
fmt.Println(p)
输出变为:
This is polaris, He is 28 years old
可见,Stringer接口和Java中的ToString方法类似。
5. Formatter 接口
Formatter 接口的定义如下:
type Formatter interface {
Format(f State, c rune)
}
官方文档中关于该接口方法的说明:
Formatter 接口由带有定制的格式化器的值所实现。 Format 的实现可调用 Sprintf 或 Fprintf(f) 等函数来生成其输出。
也就是说,通过实现 Formatter 接口可以做到自定义输出格式(自定义占位符)。
接着上面的例子,我们为 Person 增加一个方法:
func (this *Person) Format(f fmt.State, c rune) {
if c == 'L' {
f.Write([]byte(this.String()))
f.Write([]byte(" Person has three fields."))
} else {
// 没有此句,会导致 fmt.Printf("%s", p) 啥也不输出
f.Write([]byte(fmt.Sprintln(this.String())))
}
}
这样,Person便实现了Formatter接口。这时再运行:
p := &Person{"polaris", 28, 0}
fmt.Printf("%L", p)
输出为:
This is polaris, He is 28 years old. Person has three fields.
这里需要解释以下几点:
1)fmt.State 是一个接口。由于 Format 方法是被 fmt 包调用的,它内部会实例化好一个 fmt.State 接口的实例,我们不需要关心该接口;
2)可以实现自定义占位符,同时 fmt 包中和类型相对应的预定义占位符会无效。因此例子中 Format 的实现加上了 else 子句;
3)实现了 Formatter 接口,相应的 Stringer 接口不起作用。但实现了 Formatter 接口的类型应该实现 Stringer 接口,这样方便在 Format 方法中调用 String() 方法。就像本例的做法;
4)Format 方法的第二个参数是占位符中%后的字母(有精度和宽度会被忽略,只保留字母);
一般地,我们不需要实现 Formatter 接口。如果对 Formatter 接口的实现感兴趣,可以看看标准库 math/big 包中 Int 类型的 Formatter 接口实现。
小贴士
State接口相关说明:
type State interface {
// Write is the function to call to emit formatted output to be printed.
// Write 函数用于打印出已格式化的输出。
Write(b []byte) (ret int, err error)
// Width returns the value of the width option and whether it has been set.
// Width 返回宽度选项的值以及它是否已被设置。
Width() (wid int, ok bool)
// Precision returns the value of the precision option and whether it has been set.
// Precision 返回精度选项的值以及它是否已被设置。
Precision() (prec int, ok bool)
// Flag returns whether the flag c, a character, has been set.
// Flag 返回标记 c(一个字符)是否已被设置。
Flag(c int) bool
}
fmt 包中的 print.go 文件中的type pp struct实现了 State 接口。由于 State 接口有 Write 方法,因此,实现了 State 接口的类型必然实现了 io.Writer 接口。
6. Scan 序列函数
该序列函数和 Print 序列函数相对应,包括:Fscan/Fscanf/Fscanln/Sscan/Sscanf/Sscanln/Scan/Scanf/Scanln。
一般的,我们将Fscan/Fscanf/Fscanln归为一类;Sscan/Sscanf/Sscanln归为一类;Scan/Scanf/Scanln归为另一类。其中,Scan/Scanf/Scanln会调用相应的F开头一类函数。如:
func Scan(a ...interface{}) (n int, err error) {
return Fscan(os.Stdin, a...)
}
Fscan/Fscanf/Fscanln 函数的第一个参数接收一个 io.Reader 类型,从其读取内容并赋值给相应的实参。而 Scan/Scanf/Scanln 正是从标准输入获取内容,因此,直接调用 F类函数 做这件事,并将 os.Stdin 作为第一个参数传入。
Sscan/Sscanf/Sscanln 则直接从字符串中获取内容。
对于Scan/Scanf/Scanln三个函数的区别,我们通过例子来说明,为了方便讲解,我们使用Sscan/Sscanf/Sscanln这组函数。
- Scan/FScan/Sscan
var (
name string
age int
)
n, _ := fmt.Sscan("polaris 28", &name, &age)
// 可以将"polaris 28"中的空格换成"\n"试试
// n, _ := fmt.Sscan("polaris\n28", &name, &age)
fmt.Println(n, name, age)
输出为:
2 polaris 28
不管"polaris 28"是用空格分隔还是"\n"分隔,输出一样。也就是说,Scan/FScan/Sscan 这组函数将连续由空格分隔的值存储为连续的实参(换行符也记为空格)。
- Scanf/FScanf/Sscanf
var (
name string
age int
)
n, _ := fmt.Sscanf("polaris 28", "%s%d", &name, &age)
// 可以将"polaris 28"中的空格换成"\n"试试
// n, _ := fmt.Sscanf("polaris\n28", "%s%d", &name, &age)
fmt.Println(n, name, age)
输出:
2 polaris 28
如果将"空格"分隔改为"\n"分隔,则输出为:1 polaris 0。可见,Scanf/FScanf/Sscanf 这组函数将连续由空格分隔的值存储为连续的实参, 其格式由 format 决定,换行符处停止扫描(Scan)。
- Scanln/FScanln/Sscanln
var (
name string
age int
)
n, _ := fmt.Sscanln("polaris 28", &name, &age)
// 可以将"polaris 28"中的空格换成"\n"试试
// n, _ := fmt.Sscanln("polaris\n28", &name, &age)
fmt.Println(n, name, age)
输出:
2 polaris 28
Scanln/FScanln/Sscanln表现和上一组一样,遇到"\n"停止(对于Scanln,表示从标准输入获取内容,最后需要回车)。
一般地,我们使用 Scan/Scanf/Scanln 这组函数。
提示
如果你是Windows系统,在使用 Scanf 时,有一个地方需要注意。看下面的代码:
for i := 0; i < 2; i++ {
var name string
fmt.Print("Input Name:")
n, err := fmt.Scanf("%s", &name)
fmt.Println(n, err, name)
}
编译、运行(或直接 go run ),输入:polaris 回车。控制台内如下:
Input Name:polaris
1 <nil> polaris
Input Name:0 unexpected newline
为什么不是让输入两次?第二次好像有默认值一样。
同样的代码在Linux下正常。
目前的解决方法是:换用Scanln或者改为Scanf("%s\n", &name)。
9. Scanner 和 ScanState 接口
基本上,我们不会去自己实现这两个接口,只需要使用上文中相应的 Scan 函数就可以了。这里只是简单的介绍一下这两个接口的作用。
任何实现了 Scan 方法的对象都实现了 Scanner 接口,Scan 方法会从输入读取数据并将处理结果存入接收端,接收端必须是有效的指针。Scan 方法会被任何 Scan、Scanf、Scanln 等函数调用,只要对应的参数实现了该方法。Scan 方法接收的第一个参数为ScanState接口类型。
ScanState 是一个交给用户定制的 Scanner 接口的参数的接口。Scanner 接口可能会进行一次一个字符的扫描或者要求 ScanState 去探测下一个空白分隔的 token。
在fmt包中,scan.go 文件中的 ss 结构实现了 ScanState 接口。