Go 输入输出详解

Go 编程实战系列(十五)

上一篇《Go fmt 包详解》中,我们学习了 fmt.Printfmt.Printlnfmt.Printffmt.Sprintf 等常用格式化方法。本篇继续学习 Go 中非常基础但非常重要的内容:输入与输出。

所谓输入,就是程序从外部读取数据;输出,就是程序将处理结果发送到外部。最简单的输入来自键盘,最简单的输出就是终端打印。实际开发中,输入输出还包括文件、网络、管道等多种形式。

本文主要介绍:

  • 标准输入和标准输出;

  • fmt.Scan

  • fmt.Scanln

  • fmt.Scanf

  • bufio.Reader

  • bufio.Writer

  • os.Stdin

  • os.Stdout

  • 输入错误处理;

  • 实际命令行程序案例。

一、什么是输入输出?

程序运行时通常需要和外部环境交互。

例如,一个登录程序需要用户输入:

复制代码
用户名
密码

程序处理后输出:

复制代码
登录成功

这里:

复制代码
用户名、密码 = 输入
登录成功 = 输出

再比如计算器:

复制代码
输入:
10
20

输出:
30

Go 中最基础的输入输出通常通过:

bash 复制代码
fmt
bufio
os

这些标准库完成。

二、标准输入与标准输出

操作系统通常为程序提供三个标准流:

c 复制代码
标准输入 stdin
标准输出 stdout
标准错误 stderr

Go 中分别对应:

lua 复制代码
os.Stdin
os.Stdout
os.Stderr

其中 os.Stdin 通常连接键盘,os.Stdout 通常连接终端。

例如:

go 复制代码
package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Fprintln(os.Stdout, "Hello Go")
}

输出:

复制代码
Hello Go

实际上,我们平时写的:

arduino 复制代码
fmt.Println("Hello Go")

最终也是把内容输出到标准输出。

三、使用 fmt.Scan 输入数据

最简单的输入方法是:

scss 复制代码
fmt.Scan()

例如:

go 复制代码
package main

import "fmt"

func main() {
	var name string

	fmt.Print("请输入姓名:")
	fmt.Scan(&name)

	fmt.Println("你好:", name)
}

运行:

复制代码
请输入姓名:Tom
你好: Tom

这里需要重点注意:

yaml 复制代码
&name

为什么要加 &

因为 fmt.Scan 需要修改变量中的值,因此需要获得变量地址。

如果写成:

scss 复制代码
fmt.Scan(name)

就不能正确接收输入。

四、同时输入多个值

fmt.Scan 可以一次读取多个数据。

例如:

go 复制代码
package main

import "fmt"

func main() {
	var name string
	var age int

	fmt.Print("请输入姓名和年龄:")

	fmt.Scan(&name, &age)

	fmt.Println("姓名:", name)
	fmt.Println("年龄:", age)
}

输入:

复制代码
Tom 20

输出:

复制代码
姓名: Tom
年龄: 20

多个输入值之间可以使用空格、换行等空白字符分隔。

五、Scan 会自动进行类型解析

例如:

csharp 复制代码
var age int

fmt.Scan(&age)

如果输入:

复制代码
20

Go 会尝试把输入内容解析成 int

再比如:

go 复制代码
var price float64
fmt.Scan(&price)

输入:

复制代码
99.9

最终变量中就是对应的浮点数。

如果输入内容和目标类型不匹配,则会返回错误。

例如:

go 复制代码
var age int

_, err := fmt.Scan(&age)

if err != nil {
	fmt.Println("输入错误:", err)
}

如果用户输入:

复制代码
hello

就无法转换成整数。

六、fmt.Scan 的返回值

fmt.Scan 会返回两个值:

css 复制代码
n, err := fmt.Scan(...)

其中:

复制代码
n

表示成功扫描的数据数量。

复制代码
err

表示是否出现错误。

例如:

go 复制代码
package main

import "fmt"

func main() {
	var name string
	var age int

	n, err := fmt.Scan(&name, &age)

	fmt.Println("成功读取:", n)

	if err != nil {
		fmt.Println("读取失败:", err)
	}
}

实际开发中,不建议完全忽略 err

七、fmt.Scanln

ScanlnScan 类似。

例如:

csharp 复制代码
var name string

fmt.Scanln(&name)

它会读取一行中的数据,在遇到换行时结束。

示例:

go 复制代码
package main

import "fmt"

func main() {
	var name string
	var age int

	fmt.Print("请输入姓名和年龄:")

	fmt.Scanln(&name, &age)

	fmt.Println(name, age)
}

输入:

复制代码
Tom 20

输出:

复制代码
Tom 20

需要注意,Scanln 并不适合直接读取包含空格的完整句子。

例如用户输入:

复制代码
Hello Go World

如果只有一个字符串变量:

vbnet 复制代码
var text string
fmt.Scanln(&text)

通常只能得到第一个单词:

复制代码
Hello

如果需要读取整行文本,更适合使用 bufio.Reader

八、fmt.Scanf

Scanf 可以按照指定格式读取数据。

例如:

go 复制代码
package main

import "fmt"

func main() {
	var name string
	var age int

	fmt.Print("请输入:")

	fmt.Scanf("%s %d", &name, &age)

	fmt.Println(name, age)
}

输入:

复制代码
Tom 20

输出:

复制代码
Tom 20

这里:

perl 复制代码
%s

表示字符串。

perl 复制代码
%d

表示整数。

它和之前学习的 Printf 格式占位符非常类似。

九、为什么需要 bufio?

对于简单输入:

scss 复制代码
fmt.Scan()

已经够用。

但是如果需要读取:

  • 一整行文本;

  • 包含空格的字符串;

  • 大量数据;

  • 文件内容;

  • 高频输入输出;

通常会使用:

复制代码
bufio

bufio 表示 buffered I/O,也就是带缓冲的输入输出。

它可以减少频繁的底层读写操作,提高效率。

十、使用 bufio.Reader 读取一整行

例如:

go 复制代码
package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)

	fmt.Print("请输入一句话:")

	text, err := reader.ReadString('\n')

	if err != nil {
		fmt.Println("读取失败:", err)
		return
	}

	text = strings.TrimSpace(text)

	fmt.Println("你输入的是:", text)
}

输入:

复制代码
Hello Go World

输出:

复制代码
你输入的是: Hello Go World

这里:

lua 复制代码
bufio.NewReader(os.Stdin)

创建一个缓冲读取器。

scss 复制代码
ReadString('\n')

表示一直读取到换行符。

这种方式非常适合交互式命令行程序。

十一、为什么需要 TrimSpace?

使用:

scss 复制代码
ReadString('\n')

读取的数据通常包含结尾换行。

例如用户输入:

复制代码
Tom

实际字符串可能类似:

复制代码
Tom\n

因此通常需要:

arduino 复制代码
strings.TrimSpace(text)

去掉:

  • 空格;

  • \n

  • \r\n

  • 制表符等首尾空白。

例如:

arduino 复制代码
text = strings.TrimSpace(text)

这样后续比较字符串更加方便。

十二、使用 bufio.Scanner

除了 Reader,还可以使用:

复制代码
bufio.Scanner

例如:

erlang 复制代码
package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)

	fmt.Print("请输入内容:")

	if scanner.Scan() {
		fmt.Println("你输入:", scanner.Text())
	}
}

Scanner 默认按行读取。

输入:

复制代码
学习 Go 编程

输出:

复制代码
你输入: 学习 Go 编程

如果要连续读取多行:

scss 复制代码
for scanner.Scan() {
	fmt.Println(scanner.Text())
}

当输入结束后,循环结束。

十三、标准输出 os.Stdout

前面我们一直使用:

scss 复制代码
fmt.Println()

其实 fmt 还提供:

scss 复制代码
fmt.Fprint()
fmt.Fprintln()
fmt.Fprintf()

它们可以把内容写入指定目标。

例如写到标准输出:

lua 复制代码
fmt.Fprintln(os.Stdout, "Hello Go")

也可以格式化:

perl 复制代码
fmt.Fprintf(
	os.Stdout,
	"姓名:%s 年龄:%d\n",
	"Tom",
	20,
)

这种设计很重要,因为同一套输出逻辑不仅可以输出到终端,也可以输出到文件。

十四、使用 bufio.Writer 输出

大量输出时,可以使用:

复制代码
bufio.Writer

例如:

erlang 复制代码
package main

import (
	"bufio"
	"os"
)

func main() {
	writer := bufio.NewWriter(os.Stdout)

	writer.WriteString("Hello Go\n")
	writer.WriteString("学习输入输出\n")

	writer.Flush()
}

必须注意:

scss 复制代码
writer.Flush()

缓冲数据可能暂时存储在内存中,如果不调用 Flush(),部分内容可能还没有真正写出去。

实际项目中常见写法:

css 复制代码
writer := bufio.NewWriter(os.Stdout)
defer writer.Flush()

十五、标准错误输出

错误信息可以写到:

lua 复制代码
os.Stderr

例如:

lua 复制代码
fmt.Fprintln(
	os.Stderr,
	"程序运行失败",
)

标准输出和标准错误分离后,在 Linux、Docker、Shell 脚本和服务器环境中更加方便处理。

例如可以分别重定向:

复制代码
正常输出
错误日志

十六、读取数字并处理

我们写一个简单的加法程序:

go 复制代码
package main

import "fmt"

func main() {
	var a int
	var b int

	fmt.Print("请输入两个数字:")

	_, err := fmt.Scan(&a, &b)

	if err != nil {
		fmt.Println("输入格式错误")
		return
	}

	result := a + b

	fmt.Println("计算结果:", result)
}

输入:

复制代码
10 20

输出:

复制代码
计算结果: 30

这已经是一个最基础的交互式命令行程序。

十七、一个完整的用户信息输入案例

go 复制代码
package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)

	fmt.Print("请输入姓名:")
	name, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("姓名读取失败:", err)
		return
	}
	name = strings.TrimSpace(name)

	fmt.Print("请输入年龄:")
	ageText, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("年龄读取失败:", err)
		return
	}

	ageText = strings.TrimSpace(ageText)

	age, err := strconv.Atoi(ageText)
	if err != nil {
		fmt.Println("年龄必须是数字")
		return
	}

	fmt.Printf(
		"用户信息:姓名=%s,年龄=%d\n",
		name,
		age,
	)
}

输入:

复制代码
请输入姓名:Tom
请输入年龄:20

输出:

复制代码
用户信息:姓名=Tom,年龄=20

这个例子综合使用了:

bash 复制代码
bufio
os.Stdin
strings
strconv
fmt

也是实际命令行程序常见的处理方式。

十八、Scan 和 bufio 如何选择?

对于简单程序:

scss 复制代码
fmt.Scan()

使用最方便。

例如:

复制代码
输入整数
输入几个单词
简单练习程序

如果需要读取完整一行:

复制代码
bufio.Reader

更加合适。

如果需要逐行处理:

复制代码
bufio.Scanner

更加方便。

如果需要大量输出:

复制代码
bufio.Writer

通常性能更好。

可以简单记住:

复制代码
简单输入:fmt.Scan
整行输入:bufio.Reader
逐行扫描:bufio.Scanner
大量输出:bufio.Writer

十九、常见错误

1. 忘记使用地址符号

错误:

csharp 复制代码
var age int
fmt.Scan(age)

正确:

scss 复制代码
fmt.Scan(&age)

因为 Scan 需要修改变量。

2. 忽略输入错误

不推荐:

scss 复制代码
fmt.Scan(&age)

更稳妥:

go 复制代码
_, err := fmt.Scan(&age)

if err != nil {
	fmt.Println(err)
	return
}

3. 使用 Scan 读取带空格文本

例如:

复制代码
Hello Go World

Scan 会按照空白符分割,不能直接得到整句话。

推荐:

lua 复制代码
bufio.NewReader(os.Stdin)

4. bufio.Writer 忘记 Flush

错误:

css 复制代码
writer := bufio.NewWriter(os.Stdout)

writer.WriteString("Hello")

推荐:

css 复制代码
writer := bufio.NewWriter(os.Stdout)
defer writer.Flush()

二十、本章小结

本篇介绍了 Go 中最基础的输入输出操作,主要学习了:

  • 使用 fmt.Scan 获取输入;

  • 使用 fmt.Scanln 按行扫描;

  • 使用 fmt.Scanf 按格式读取;

  • 使用 bufio.Reader 读取完整文本;

  • 使用 bufio.Scanner 逐行扫描;

  • 使用 bufio.Writer 提高输出效率;

  • 使用 os.Stdin 表示标准输入;

  • 使用 os.Stdout 表示标准输出;

  • 使用 os.Stderr 输出错误信息。

输入输出是很多程序的基础。以后开发文件处理工具、CLI 工具、网络程序和 Web 服务时,都会进一步接触 Go 的 I/O 体系。

本章练习

  1. 使用 fmt.Scan 输入姓名和年龄。
  2. 编写一个程序输入两个整数并输出它们的和。
  3. 使用 bufio.Reader 读取包含空格的一句话。
  4. 使用 strconv.Atoi 将用户输入的年龄转换成整数。
  5. 使用 bufio.Scanner 连续读取多行文本。
  6. 使用 bufio.Writer 输出 100 行内容。
  7. 尝试将错误信息输出到 os.Stderr

下一篇预告

《Go 运算符详解》

下一篇将介绍 Go 中常用的运算符,包括:

  • 算术运算符;

  • 比较运算符;

  • 逻辑运算符;

  • 赋值运算符;

  • 自增与自减;

  • 位运算符;

  • 运算符优先级;

  • 实际业务判断案例。

掌握运算符之后,我们就可以开始编写更加复杂的计算和逻辑判断程序。

相关推荐
程序员爱钓鱼1 小时前
Rust 生命周期详解:生命周期标注、约束与真实项目应用
后端·面试·rust
MoonBit月兔1 小时前
MoonBit 受邀亮相 SPLASH/ISSTA 2026,与 Java、Rust、Eiffel、D 语言核心贡献者同场交流
开发语言·后端·rust
狂师2 小时前
用AI做自动化测试,哪些是真不行,哪些是你不会用?
人工智能·面试·测试
维克兜率天11 小时前
【维克】ARMA模型:AR与MA的完美结合
后端·restful
是未才12 小时前
从输入 URL 到页面返回:DNS、路由、TLS 与 HTTP 完整链路
java·后端·计算机网络
上海安当技术13 小时前
半天接入:USBKey RESTful API + C 动态库,Web 和 C/S 两套集成路径实战
前端·后端·restful·集成·usbkey
DevUI团队13 小时前
从“即兴创作”到“规格先行”,华为云码道(CodeArts)代码智能体持续深耕企业级规范驱动开发能力
前端·人工智能·后端
朦胧之14 小时前
Go 项目设计
go
weixin_4316004414 小时前
NestJS 入门(3):Guard 如何挡住未登录请求?
前端·后端·学习·nest.js