从标准输入、输出认识 io


若要输出消息至控制台,可以透过fmtPrintPrintlnPrintf等函数,如果要从控制台读取使用者输入,可以透过fmtScanfScanln等函数。例如:

package main

import "fmt"

func main() {
    fmt.Print("输入名称 年龄:")
    var name string
    var age int
    fmt.Scanf("%s %d", &name, &age)
    fmt.Printf("嗨!%s!今年 %d 岁了啊?", name, age)
}

%s%d是格式符号,在 Go 中称为 verb,Go 可用的 verb 可以在fmt包的文件中找到。

Scanf就类似 C 语言中的scanf,可以格式化地获取输入,底下是个范例:

输入名称 年龄:Justin 45
嗨!Justin!今年 45 岁了啊?

在按下 Enter 键后,实际上还有个 CR(carriage return)字符还未扫描,如果只是要获取空白分隔的输入,并以换行作为结束,可以使用Scanln

package main

import "fmt"

func main() {
    fmt.Print("输入空白分隔的文本")
    var text1, text2 string
    fmt.Scanln(&text1, &text2)
    fmt.Println(text1)
    fmt.Println(text2)
}

如果是Scan的话,也是扫描以空白区隔的输入,按下 Enter 键的 CR 字符,也会被视为空白。

PrintlnPrintf会使用标准输出(Standout),如果想使用标准错误(Standard err)呢?可以透过FprintFprintlnFprintf等函数,第一个实参指定os.Stderr。例如:

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Fprintln(os.Stderr, "输出至标准错误")
}

os包的Stderr代表标准错误,而StdinStdout代表标准输入与输出,它们的类型是*os.File,若愿意的话,也可以直接操作它们,例如File定义了ReadWrite方法,可以指定一个类型为byte[]的 slice,Read会读入同样长度的数据至 slice,后者可以将同等长度的数据输出。例如:

package main

import "os"

func main() {
    buf := make([]byte, 5);
    os.Stdout.Write([]byte("输入五个数字:"))
    os.Stdin.Read(buf)
    os.Stdout.Write(buf)
}

实际上,os.File可用的方法不只有ReadWrite,先留意这两个方法的目的在于,这两个方法分别符合io.Readerio.Writer定义的行为:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

如果察看fmtFprintFprintlnFprintf等函数,可以发现它们第一个参数定义的类型并不是*os.File,而是io.Writer

func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

类似地,Fscan 字样开头的几个函数,第一个参数接受的是io.Reader

func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)

这表示,fmt包中这些函数,并不只能用于标准输入、输出或错误,例如,strings.NewReader函数,可以指定字符串,返回*Reader,这表示fmtFscanf等函数,可以从字符串读取输入。例如:

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    data := `Justin 45
             Monica 42
             Irene 12`
    r := strings.NewReader(data)
    var name string
    var age int
    for {
        if _, err := fmt.Fscanln(r, &name, &age); err == io.EOF {
            break
        }
        fmt.Printf("%s: %d\n", name, age)
    }
}

Fscanln会返回扫描的笔数,如果笔数少于指定的扫描数量,err会指出原因,在文件读取结束(End of file)时,err会是io.EOF,在上例中,数据来源是个格式确定的字符串,因此仅简单地判断err是否为io.EOF来结束扫描。

os.File不过是具有io.Readerio.Writer的行为罢了,os.File代表文件,也就是说FprintFprintlnFprintfFscanFscanlnFscanf等函数,也可以用在文件读写,其实标准输入、输出、错误等,也是被视为文件的,这在osfile.go可以看到:

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

因此 IO 之类的操作,在 Go 中非常灵活,一切都看 API 上可接受行为而定,不受类型之限制,这之后再从实际的例子中来谈。


展开阅读全文