输入输出

寒江蓑笠翁大约 11 分钟

输入输出

package main

import "fmt"

func main() {
   fmt.Println("Hello 世界!")
}

本站的第一个入门的案例就是输出一个字符串,这一节就来讲一下在Go中如何进行输入输出。

文件描述符

var (
   Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
   Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
   Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

os包下有三个外暴露的文件描述符,其类型都是*os.File,分别是:

  • os.Stdin - 标准输入
  • os.Stdout - 标准输出
  • os.Stderr - 标准错误

Go中的输入输出都离不开它们。

输出

在Go中输出有很多中方法,下面几个比较常见的

stdout

因为标准输出本身就是一个文件,所以你可以直接将字符串写入到标准输出中

package main

import "os"

func main() {
	os.Stdout.WriteString("hello world!")
}

print

Go有两个内置的函数printprintln,他们会将参数输出到标准错误中,仅做调试用,一般不推荐使用。

package main

func main() {
	print("hello world!\n")
	println("hello world")
}

fmt

最常见的用法是使用fmt包,它提供了fmt.Println函数,该函数默认会将参数输出到标准输出中。

package main

import "fmt"

func main() {
	fmt.Println("hello world!")
}

它的参数支持任意类型,如果类型实现了String接口也会调用String方法来获取其字符串表现形式,所以它输出的内容可读性比较高,适用于大部分情况,不过由于内部用到了反射,在性能敏感的场景不建议大量使用。

bufio

bufio提供了可缓冲的输出方法,它会先将数据写入到内存中,积累到了一定阈值再输出到指定的Writer中,默认缓冲区大小是4KB。在文件IO,网络IO的时候建议使用这个包。

func main() {
	writer := bufio.NewWriter(os.Stdout)
	defer writer.Flush()
	writer.WriteString("hello world!")
}

你也可以把它和fmt包结合起来用

func main() {
	writer := bufio.NewWriter(os.Stdout)
	defer writer.Flush()
	fmt.Fprintln(writer, "hello world!")
}

格式化

Go中的格式化输出功能基本上由fmt.Printf函数提供,如果你学过C系语言,一定会觉得很熟悉,下面是一个简单的例子。

func main() {
	fmt.Printf("hello world, %s!", "jack")
}

下面是Go目前所有的格式化动词。

0格式化描述接收类型
1%%输出百分号%任意
2%s输出string/[] bytestring,[] byte
3%q格式化字符串,输出的字符串两端有双引号""string,[] byte
4%d输出十进制整型值整型
5%f输出浮点数浮点
6%e输出科学计数法形式 ,也可以用于复数浮点
7%E%e相同浮点
8%g根据实际情况判断输出%f或者%e,会去掉多余的0浮点
9%b输出整型的二进制表现形式数字
10%#b输出二进制完整的表现形式数字
11%o输出整型的八进制表示整型
12%#o输出整型的完整八进制表示整型
13%x输出整型的小写十六进制表示数字
14%#x输出整型的完整小写十六进制表示数字
15%X输出整型的大写十六进制表示数字
16%#X输出整型的完整大写十六进制表示数字
17%v输出值原本的形式,多用于数据结构的输出任意
18%+v输出结构体时将加上字段名任意
19%#v输出完整Go语法格式的值任意
20%t输出布尔值布尔
21%T输出值对应的Go语言类型值任意
22%c输出Unicode码对应的字符int32
23%U输出字符对应的Unicode码rune,byte
24%p输出指针所指向的地址指针

使用fmt.Sprintf或者fmt.Printf来格式化字符串或者输出格式化字符串,看几个例子

fmt.Printf("%%%s\n", "hello world")
 
fmt.Printf("%s\n", "hello world") 
fmt.Printf("%q\n", "hello world") 
fmt.Printf("%d\n", 2<<7-1)			

fmt.Printf("%f\n", 1e2)			
fmt.Printf("%e\n", 1e2)				
fmt.Printf("%E\n", 1e2)				
fmt.Printf("%g\n", 1e2)				

fmt.Printf("%b\n", 2<<7-1)			
fmt.Printf("%#b\n", 2<<7-1)			
fmt.Printf("%o\n", 2<<7-1)			
fmt.Printf("%#o\n", 2<<7-1)			
fmt.Printf("%x\n", 2<<7-1)			
fmt.Printf("%#x\n", 2<<7-1)			
fmt.Printf("%X\n", 2<<7-1)			
fmt.Printf("%#X\n", 2<<7-1)			

type person struct {
    name    string
    age     int
    address string
}
fmt.Printf("%v\n", person{"lihua", 22, "beijing"})	
fmt.Printf("%+v\n", person{"lihua", 22, "beijing"})	
fmt.Printf("%#v\n", person{"lihua", 22, "beijing"})	
fmt.Printf("%t\n", true)							
fmt.Printf("%T\n", person{})						
fmt.Printf("%c%c\n", 20050, 20051)					
fmt.Printf("%U\n", '码')							   
fmt.Printf("%p\n", &person{})						

使用其它进制时,在%与格式化动词之间加上一个空格便可以达到分隔符的效果,例如

func main() {
	str := "abcdefg"
	fmt.Printf("%x\n", str)
	fmt.Printf("% x\n", str)
}

该例输出的结果为

61626364656667
61 62 63 64 65 66 67

在使用数字时,还可以自动补零。比如

fmt.Printf("%09d", 1)
// 000000001

二进制同理

fmt.Printf("%09b", 1<<3)
// 000001000

错误情况

格式化字符数量 < 参数列表数量

fmt.Printf("", "") //%!(EXTRA string=)

格式化字符数量 > 参数列表数量

fmt.Printf("%s%s", "") //%!s(MISSING)

类型不匹配

fmt.Printf("%s", 1) //%!s(int=1)

缺少格式化动词

fmt.Printf("%", 1) // %!(NOVERB)%!(EXTRA int=1)

输入

下面介绍常见的输入方法

read

你可以像直接读文件一样,读取输入内容,如下

func main() {
	var buf [1024]byte
	n, _ := os.Stdin.Read(buf[:])
	os.Stdout.Write(buf[:n])
}

这样用起来太麻烦了,一般不推荐使用。

fmt

我们可以使用fmt包提供的几个函数,用起来跟C差不多。

// 扫描从os.Stdin读入的文本,根据空格分隔,换行也被当作空格
func Scan(a ...any) (n int, err error) 

// 与Scan类似,但是遇到换行停止扫描
func Scanln(a ...any) (n int, err error)

// 根据格式化的字符串扫描
func Scanf(format string, a ...any) (n int, err error)

读取两个数字

func main() {
	var a, b int
	fmt.Scanln(&a, &b)
	fmt.Printf("%d + %d = %d\n", a, b, a+b)
}

读取固定长度的数组

func main() {
	n := 10
	s := make([]int, n)
	for i := range n {
		fmt.Scan(&s[i])
	}
	fmt.Println(s)
}
1 2 3 4 5 6 7 8 9 10
[1 2 3 4 5 6 7 8 9 10]

bufio

在有大量输入需要读取的时候,就建议使用bufio.Reader来进行内容读取

func main() {
    reader := bufio.NewReader(os.Stdin)
    var a, b int
    fmt.Fscanln(reader, &a, &b)
    fmt.Printf("%d + %d = %d\n", a, b, a+b)
}

scanner

bufio.Scannerbufio.Reader类似,不过它是按行读取的。

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := scanner.Text()
		if line == "exit" {
			break
		}
		fmt.Println("scan", line)
	}
}

结果如下

first line
scan first line
second line
scan second line
third line
scan third line
exit

提示

在输入输出这方面,想要练手的话,去洛谷open in new window做几道简单的ACM模式算法题就能上手熟悉了。