代码:
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()]++
}
执行结果;
输入文件时:
输入回车时:
代码分析:
可以将代码分成三部分来看:导入包,main函数,countlines函数
导入包部分没有什么要特别注意的,main函数中前两行是分别对counts和files进行短变量声明。需要注意一点,短变量声明的左边变量的类型是根据右侧表达式的类型来推断的。所以整段代码中counts是map型,files是切片,f,err是指针...其中counts用来记录每一行的重复情况,与dup1一致,files记录文件,如果回车(即满足len(files) == 0)则转入输入文件名为空的循环,如果不是空格,则进入判断文件名是否有效的循环。
在接下来的文件名为空的循环中使用到了countLines函数,这与dup1中的main函数相同
if len(files) == 0 {
countLines(os.Stdin, counts)
}
进入文件名不为空的循环时,代码如下:
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()
}
其中os.Open(arg)
是一个函数调用,用于打开指定路径的文件,并返回一个*os.File
类型的文件对象。这个函数接受一个参数arg
,表示要打开的文件路径。f, err := os.Open(arg)
将打开的文件对象赋值给变量f
,同时将可能发生的错误赋值给变量err
。接下来,if err != nil
是一个条件语句,用于检查是否发生了错误。如果err
不为nil
,表示发生了错误,代码会执行if
语句块中的内容。在这个例子中,如果文件打开失败,会输出一个错误信息到标准错误流(os.Stderr
),并继续处理下一个文件。fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
用于格式化输出错误信息,其中%v
表示将err
的值插入到字符串中。关于nil的定义: 在Go语言中,nil
是一个预定义的标识符,表示一个零值或空值。它可以用于表示指针、接口、映射、切片、通道、函数和接口类型的零值或空值。在这句代码中,nil
表示一个指针类型的零值。具体来说,err
是一个指向error
类型的指针,而nil
表示这个指针没有指向任何有效的error
对象。error
类型是一个接口类型,用于表示可能发生的错误。当没有错误发生时,通常将错误变量设置为nil
,表示没有错误发生。因此,err != nil
这个条件判断语句用于检查err
是否为nil
,如果err
不等于nil
,表示发生了错误。
再看os.Open(arg)函数:os.Open(arg)
是一个函数调用,它是调用os
包中的Open
函数。函数是一段独立的代码块,可以通过函数名直接调用,不需要通过接收者。
在这个例子中,os.Open(arg)
是调用os
包中的Open
函数,用于打开指定路径的文件,并返回一个*os.File
类型的指针。变量f就是这个指针。
剩下的部分与dup1几乎完全一致。