package main import "fmt" func main() { for { var x int // n, _ := fmt.Scanln(&x) n, _ := fmt.Scan(&x) fmt.Println("n:", n," x:", x) if n == 0 { break } } }
输入:
123 456 789
当使用 Scan
方法时,控制台打印:
n: 1 x: 123 n: 1 x: 456 n: 1 x: 789
但,当使用 Scanln
方法时,打印如下:
n: 1 x: 123 n: 1 x: 56 n: 1 x: 89
根据官方文档,Scanln
除了在换行处停止之外,应和 Scan
有相同的表现,那么为什么会出现实例中的意外表现呢?
回答:
Scanln is similar to Scan, but stops scanning at a newline and after the final item there must be a newline or EOF.
官方文档写了两个区别,你看到一个,还有一个是最后一项之后必须有一个换行。
建议你看看 err 。
如 @fefe 所强调的,文档中强调了 Scanln
方法在最后一个元素后要有换行符或 EOF。
Scanln is similar to Scan, but stops scanning at a newline and after the final item there must be a newline or EOF.
也就是说这里不正确使用了 Scanln 方法,至于为什么 Scanln 会比 Scan 方法少读取一个字符,是因为 Scanln
方法在读取传入参数对应个数的元素后,会判断下一个字符是不是 \n
或者 EOF
,这里判断时就会多扫描一个字符(但这个字符不存入变量),因此下一次读取时缺少了这个字符。而 Scan
方法不会进行这个判断,只是进行连续的读入,因此这里使用 Scan
方法是没有问题的。关键的源码如下:
// Scan 和 Scanln 都掉用了 doScan 这个方法来读取输入 // 但不同点在于 Scanln 设置了 s.nlIsEnd 为 true // doScan does the real work for scanning without a format string. func (s *ss) doScan(a []any) (numProcessed int, err error) { defer errorHandler(&err) for _, arg := range a { s.scanOne('v', arg) numProcessed++ } // Check for newline (or EOF) if required (Scanln etc.). if s.nlIsEnd { for { r := s.getRune() // 判断下一个字符是否合法 if r == '\n' || r == eof { break } if !isSpace(r) { s.errorString("expected newline") break } } } return }
