Go中的标准输入处理及测试
前言
fmt.Scan, fmt.Scanln, 和 fmt.Scanf 是 Go 语言标准库中用于读取用户输入的函数。在使用过程中,我发现了他们不符合预期的表现,于是进行了多种测试,并通过源码分析了其背后的原理。
测试
fmt.Scanln
读取输入到指定变量,直到遇到换行符,如果不符合要求会返回错误
test1 读入整数
func main() {
scanln_test()
}
func scanln_test() {
var a int
n, err := fmt.Scanln(&a)
fmt.Println("n=", n, "err=", err, "a=", a)
}
测试结果
PS D:\code\GO\test> go run .\main.go
123 56
n= 1 err= expected newline a= 123
PS D:\code\GO\test> go run .\main.go
s999
n= 0 err= expected integer a= 0
PS D:\code\GO\test> go run .\main.go # 第一个字符
1s1
n= 1 err= expected newline a= 1
PS D:\code\GO\test> go run .\main.go
222
n= 1 err= <nil> a= 222
PS D:\code\GO\test> go run .\main.go
n= 0 err= unexpected newline a= 0
test2 读入字符串
func scanln_test() {
var a string
n, err := fmt.Scanln(&a)
fmt.Println("n=", n, "err=", err, "a=", a)
}
测试结果
PS D:\code\GO\test> go run .\main.go # 空格及其后面的内容同样没有被读入
111111 00000
n= 1 err= <nil> a= 111111
fmt.Scan
忽略输入前面的空格和换行,读取到输入后,遇到换行或空格返回
test1 读入字符串
func scan_test() {
var a string
n, err := fmt.Scan(&a)
fmt.Println("n=", n, "err=", err, "a=", a)
}
测试结果
PS D:\code\GO\test> go run .\main.go
1d1d1 asd
n= 1 err= <nil> a= 1d1d1
PS D:\code\GO\test> go run .\main.go
n= 1 err= <nil> a= 1s1s1ddd
PS D:\code\GO\test> go run .\main.go // 后面输入了一些回车和换行
333
n= 1 err= <nil> a= 333
test2 读入Int
func scan_test() {
var a int
n, err := fmt.Scan(&a)
fmt.Println("n=", n, "err=", err, "a=", a)
}
测试结果
PS D:\code\GO\test> go run .\main.go
11 11
n= 1 err= expected newline a= 11
1111
n= 1 err= <nil> a= 1111
PS D:\code\GO\test> go run .\main.go
11 11
n= 1 err= <nil> a= 11
PS D:\code\GO\test> go run .\main.go
1s1
n= 1 err= <nil> a= 1
PS D:\code\GO\test> go run .\main.go
asdas111
n= 0 err= expected integer a= 0
PS D:\code\GO\test> go run .\main.go
2
n= 1 err= <nil> a= 2
PS D:\code\GO\test> go run .\main.go
s
n= 0 err= expected integer a= 0
fmt.Scanf
格式化读入,暗含强制类型转换,如果格式中没有前导空格,会自动开头忽略空格但不会忽略换行
test1 读入int
func scanf_test() {
var a int
n, err := fmt.Scanf("%d", &a)
fmt.Println("n=", n, "err=", err, "a=", a)
}
测试结果
PS D:\code\GO\test> go run .\main.go
111
n= 1 err= <nil> a= 111
PS D:\code\GO\test> go run .\main.go
44 44
n= 1 err= <nil> a= 44
PS D:\code\GO\test> go run .\main.go
44
n= 1 err= <nil> a= 44
PS D:\code\GO\test> go run .\main.go
asdas11
n= 0 err= expected integer a= 0
PS D:\code\GO\test> go run .\main.go
1222ss
n= 1 err= <nil> a= 1222
PS D:\code\GO\test> go run .\main.go
n= 0 err= unexpected newline a= 0
PS D:\code\GO\test> go run .\main.go
sss
n= 0 err= expected space in input to match format a= 0
test2 格式中前面有空格
但如果在格式参数中写入了前导空格,则输入变量值前面的前必须也有空格,如果空格数目多余格式中的,会自动忽略多余的,即时a是string类型也是一样的,其他字符前面的空格永远不会写入变量。
func scanf_test() {
var a int
n, err := fmt.Scanf(" %d", &a)
fmt.Println("n=", n, "err=", err, "a=", a)
}
测试结果
PS D:\code\GO\test> go run .\main.go
11
n= 0 err= expected space in input to match format a= 0
PS D:\code\GO\test> go run .\main.go
1
n= 1 err= <nil> a= 1
PS D:\code\GO\test> go run .\main.go
111
n= 1 err= <nil> a= 111
test3 格式中用\n
如果格式中有\n同样会要求输入对应数目的换行
func scanf_test() {
var a int
n, err := fmt.Scanf("\n%d", &a)
fmt.Println("n=", n, "err=", err, "a=", a)
}
测试结果
PS D:\code\GO\test> go run .\main.go
1
n= 1 err= <nil> a= 1
PS D:\code\GO\test> go run .\main.go
10
n= 0 err= newline in format does not match input a= 0
PS D:\code\GO\test> go run .\main.go
n= 0 err= unexpected newline a= 0
test4 格式后面有空格
格式后面的空格不被严格要求
func scanf_test() {
var a int
n, err := fmt.Scanf("%d ", &a)
fmt.Println("n=", n, "err=", err, "a=", a)
}
测试结果
PS D:\code\GO\test> go run .\main.go // 下面值输入了一个44,并没有其他空格
44
n= 1 err= <nil> a= 44
test5 格式的变量之间有空格
多个变量之间的空格也不会被严格要求
func scanf_test() {
var a, b int
n, err := fmt.Scanf("%d %d", &a, &b)
fmt.Println("n=", n, "err=", err, "a=", "b=", b)
}
测试结果
PS D:\code\GO\test> go run .\main.go
11 44
n= 2 err= <nil> a= 11
PS D:\code\GO\test> go run .\main.go
11 11
n= 2 err= <nil> a= b= 11
PS D:\code\GO\test> go run .\main.go
11 11
n= 2 err= <nil> a= b= 11
test6 存在其他字符的情况
空格之间有其他字符时,也不会严格要求空格数目, 但必须有空格
func scanf_test() {
var a, b int
n, err := fmt.Scanf("%d * %d", &a, &b)
fmt.Println("n=", n, "err=", err, "a=", "b=", b)
}
测试结果
PS D:\code\GO\test> go run .\main.go
11*11
n= 1 err= expected space in input to match format a= b= 0
PS D:\code\GO\test> go run .\main.go
11 * 11
n= 2 err= <nil> a= b= 11
PS D:\code\GO\test> go run .\main.go
11 * 11
n= 2 err= <nil> a= b= 11
PS D:\code\GO\test> go run .\main.go
222 222
n= 1 err= input does not match format a= b= 0
PS D:\code\GO\test> go run .\main.go
11 * 11
n= 2 err= <nil> a= b= 11
PS D:\code\GO\test> go run .\main.go
11 * 11
n= 2 err= <nil> a= b= 11
test7 格式的变量之间没有空格
哪怕格式中没有空格也要至少有一个空格
func scanf_test() {
var a, b int
n, err := fmt.Scanf("%d%d", &a, &b)
fmt.Println("n=", n, "err=", err, "a=", "b=", b)
}
测试结果
PS D:\code\GO\test> go run .\main.go
11111
n= 1 err= unexpected newline a= b= 0
PS D:\code\GO\test> go run .\main.go
11 11
n= 2 err= <nil> a= b= 11
源码剖析
调用流程
fmt中Scan、Scanln、Scanf,分别指向了Fscan、Fscanln、Fscanf三个函数。传入的第一个参数是用户标准输入作为io的输入。
// C:\Program Files\Go\src\fmt\scan.go
// 扫描扫描从标准输入读取的文本,将连续的空格分隔值存储到连续的参数中。换行算作空格。
// 它返回成功扫描的项目数。如果这小于参数的数量,err将报告原因。
func Scan(a ...any) (n int, err error) {
return Fscan(os.Stdin, a...)
}
// Scanln类似于Scan,但在换行处停止扫描,并且在最后一项之后必须有换行符或EOF。
func Scanln(a ...any) (n int, err error) {
return Fscanln(os.Stdin, a...)
}
// Scanf扫描从标准输入读取的文本,将空格分隔的连续值存储到由格式决定的连续参数中。
// 它返回成功扫描的项目数。如果这小于参数的数量,err将报告原因。
// 输入中的换行符必须与格式中的换行符相匹配。
// 唯一的例外是: %c总是扫描输入中的下一个符文,即使它是空格(或制表符等)或换行符。
func Scanf(format string, a ...any) (n int, err error) {
return Fscanf(os.Stdin, format, a...)
}
而这三个函数最终都是使用了newScanState函数,其中Scan和Scanln都使用了s的doScan方法,而Scanf使用的是doScanf方法。
// Fscan扫描从r读取的文本,将连续的空格分隔值存储为连续的参数。换行符算作空格。
// 它返回成功扫描的项目数。如果这小于参数的数量,err将报告原因。
func Fscan(r io.Reader, a

最低0.47元/天 解锁文章
178

被折叠的 条评论
为什么被折叠?



