GO语言笔记(六) | 青训营

80 阅读5分钟

标准输入

类似于C语言等当中的printf,GO语言当中的fmt.Printf()函数对一些表达式进行格式化输出工作。该函数的首个参数是一个字符串,随后的参数则是一些需要被格式化的变量。如何被格式化取决于相关对应的转换字符,相关形式为百分号加(数字)字母,它有相当多的转换,以下是一个简单的说明:

%d          十进制整数
%x, %o, %b  十六进制,八进制,二进制整数。
%f, %g, %e  浮点数: 3.141593 3.141592653589793 3.141593e+00
%t          布尔:truefalse
%c          字符(rune) (Unicode码点)
%s          字符串
%q          带双引号的字符串"abc"或带单引号的字符'c'
%v          变量的自然形式(natural format)
%T          变量的类型
%%          字面上的百分号标志(无操作数)

其中,%v还有一些需要说的,我们可以让其输入的更加详细:%+v在打印结构体的时候会包括相关字段名。%#v则更加详细的打印相关复杂数据结构信息,甚至可以直接复制到GO代码当中进行初始化相关变量。

但总的来说,经常用到的格式化就那几种,更多的只需要临时查看即可,熟能生巧也就记住了。

还有就是除了上面的格式化打印还有以ln结尾的格式化函数Println等函数,他们会像使用%v一样自动选择合适的格式来打印参数,但是会在两个参数之间用空格分隔,并且回来末尾打印换行符。

另外一个代码块:

package main

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

func main() {
    counts := make(map[string]int)
    files := os.Args[1:]
    if len(files) == 0 {
        countLines(os.Stdin, counts)
    } else {
        for _, arg := range files {
            f, err := os.Open(arg)
            if err != nil {
                fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
                continue
            }
            countLines(f, counts)
            f.Close()
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

func countLines(f *os.File, counts map[string]int) {
    input := bufio.NewScanner(f)
    for input.Scan() {
        counts[input.Text()]++
    }
    // NOTE: ignoring potential errors from input.Err()
}
//代码来源于书籍

这次我们是从文件当中读取内容,使用os.Open函数来打开相应文件,它会返回两个值:

  • 一个是被打开的文件(*os.File),这是一个指针,代表了一个打开的文件的引用。

*os.File 是指向 os.File 结构体的指针。这个结构体表示操作系统中的文件描述符,并提供了一组方法(如 Read, Write, Close 等)来操作和与这个文件进行交互。

当你使用 os.Open 打开一个文件并获得 *os.File 之后,你可以对该文件进行各种操作,例如读取它的内容、写入数据等。

  • 另外一个值是一个内置的error类型的值,它表示在尝试打开文件时发生的任何错误。

如果打开文件失败,那么我们就可以通过判断err来决定下一步操作,避免错误执行之后的语句。在这里我们进行的continue来进行下一个循环,忽略当前文件。当然更详细的我们还可以判断err具体的东西,不过目前我不会,所以不讲。


我们可以在命令行运行这段代码的后面添加命令行参数,这个参数是文件名或者其绝对/相对路径,这样才算是正确的输入,如果没有参数则会通过标准输入读取数据。顺便说一下标准输入、标准输出、标准错误流都是*os.File 类型的指针。

fmt.Fprintf 函数用于将格式化的输出写入指定的 io.Writer。在这个例子中,它将错误信息写入 os.Stderr,即标准错误输出流。

这个流是相对标准输出流独立的,可以更加明显的发现错误,通过配置还可以写入另外的文件当中方便查看问题。而且它是无缓冲的输出流,所以当有大量数据时候,错误数据也会立刻显示。

在代码当中我们使用了自己编写的函数,并且在声明之前就调用了它,这是没有问题的。因为函数和包级别的变量可以任意顺序声明,并不影响调用。但是建议还是要有一定的顺序,这样方便同意查看等。

而在代码块下面的那个函数当中我们看到,传入的参数一个是os.File类型指针,还有一个键值变量。map是一个由make函数创建的数据结构引用,当其作为参数传入的时候是传递的引用的一份拷贝,当函数对于该底层数据结构进行操作的时候,主函数等也可以看到。相当于其他语言当中的的引用传递,指针是另外一个指针,但是指向的是同一块儿内存。

还有另外一种读取,就是上面的那些不都是按照行来读取吗?我们的GO还有其他的包提供了一次性读取的能力:io/ioutil包,它里面有一个ReadFile函数可以这么干。

image-20230825231357029

当然它返回的是字节切片,必须把它转换为string类型,string(data)这种形式就可以了。然后在使用strings.Split函数来分割,传递两个参数,第二个参数传递按照分割的字符,这里我们可以选择\n回车符。

实现上,bufio.Scannerioutil.ReadFileioutil.WriteFile都使用*os.FileReadWrite方法,但是,大多数程序员很少需要直接调用那些低级(lower-level)函数。高级(higher-level)函数,像bufioio/ioutil包中所提供的那些,用起来要容易点。

值得一说的是,这个ioutil包在1.16之后已经被弃用了。可以直接导入io包来使用相关函数。