标准输入
类似于C语言等当中的printf,GO语言当中的fmt.Printf()函数对一些表达式进行格式化输出工作。该函数的首个参数是一个字符串,随后的参数则是一些需要被格式化的变量。如何被格式化取决于相关对应的转换字符,相关形式为百分号加(数字)字母,它有相当多的转换,以下是一个简单的说明:
%d 十进制整数
%x, %o, %b 十六进制,八进制,二进制整数。
%f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00
%t 布尔:true或false
%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函数可以这么干。
当然它返回的是字节切片,必须把它转换为string类型,string(data)这种形式就可以了。然后在使用strings.Split函数来分割,传递两个参数,第二个参数传递按照分割的字符,这里我们可以选择\n回车符。
实现上,
bufio.Scanner、ioutil.ReadFile和ioutil.WriteFile都使用*os.File的Read和Write方法,但是,大多数程序员很少需要直接调用那些低级(lower-level)函数。高级(higher-level)函数,像bufio和io/ioutil包中所提供的那些,用起来要容易点。
值得一说的是,这个ioutil包在1.16之后已经被弃用了。可以直接导入io包来使用相关函数。