写Go代码的语言语句时候,从终端读数据这件事,输入比想象中要绕一点。语言语句很多语言里一个 scanf 就搞定的输入事,Go 偏偏给你留了好几个入口,语言语句选错了要么读不干净,输入要么卡在那儿不动。语言语句
最常用的输入是 fmt.Scan 系列。它按空白字符切分,语言语句空格、输入换行都算分隔符。语言语句看个例子:
var name string var age int fmt.Scan(&name,输入 &age) fmt.Println(name, age) 输入 Tom 25,回车,语言语句它读到 Tom 和 25。输入看起来挺顺。语言语句问题出在混合输入上。假如你先用 Scan 读了一个整数,再用 bufio 去读一整行,那个换行符还留在缓冲区里,下一次读取直接拿到空字符串。这个坑我踩过不止一次。
所以实际项目里,我更倾向用 bufio.NewReader(os.Stdin)。它能按行读,控制权在你手里:
reader := bufio.NewReader(os.Stdin) line, _ := reader.ReadString('\n') line = strings.TrimSpace(line) TrimSpace 这一步别省。Windows 上换行是 \r\n,不处理的话字符串末尾会多一个 \r,拿去比较或者解析数字都会出错。有人用 strings.TrimRight(line, "\r\n"),效果一样,看个人习惯。
读一整行之后往往还要拆成多个字段。这时候 strings.Fields 很好用,它按任意空白切分,不管中间有几个空格:
parts := strings.Fields(line) a, _ := strconv.Atoi(parts[0]) b, _ := strconv.Atoi(parts[1]) 这种写法在算法题里特别常见,因为输入格式经常是“一行若干个数”,用 Scan 逐个读反而容易和后面的行读取打架。
另一个容易被忽略的是 fmt.Scanln。它和 Scan 的区别在于遇到换行就停,不会继续往下读。但它的行为有点怪:如果一行里参数没填满,它会把剩余参数置零并返回错误。很多人以为 Scanln 就是“读一行”,其实不是。真正读一行还是得靠 bufio。
说到错误处理,fmt.Scan 的返回值经常被忽略。它返回成功扫描的项数和错误。输入 “abc” 给一个 int 变量,扫描项数是 0,err 非 nil。如果不检查,变量保持零值,程序继续跑,后面逻辑全乱。我见过有人调试半天,最后发现是输入类型不匹配但没报错。
如果输入量很大,比如几十万行,bufio.Scanner 比 ReadString 更合适。它内部有缓冲区,默认 64KB,按行切分效率高。用法也简单:
scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { line := scanner.Text() // 处理 } 不过 Scanner 有个默认行长度限制,单行超过 64KB 会报 bufio.Scanner: token too long。遇到超长行得手动调 scanner.Buffer 扩大缓冲。这个限制平时碰不到,一旦碰到就很懵。
选哪个?我的习惯是:算法题、小工具,bufio.NewReader 加 ReadString 足够;需要逐行流式处理大文件或大量输入,用 Scanner;只有确定输入格式非常规整、全是空白分隔的简单场景,才用 fmt.Scan。混用多种读取方式时,尤其要小心缓冲区里残留的换行符——这是 Go 输入处理里最常翻车的地方。