go语言圣经初读| 青训营笔记

121 阅读7分钟

这是我参与「第五届青训营 」笔记创作活动的第1天。 今天学习了go语言基础和三个入门项目,我因为有些语法基础还算能跟得上,初读了go语言圣经,以下是我对今日学习内容重点部分的整理,引用了go语言圣经中的内容。

Go语言的简洁性:贯彻简洁编程哲学。

与c等更底层的编程语言相比,golang更注重程序员使用和维护的安全和方便,安全如编译时的检查,数组越界会报错,不允许声明未使用的变量等;从使用上说,提供简单固定的语法供程序员使用,本身只有少量的特性,新手在写程序时不需要考虑底层的事情,这也是新手容易上手的原因。但相比与python等更上层的语言又更为灵活,如切片。有人说GO语言是C语言+python,从使用的角度看是说兼顾了上层和下层的 语言风格却独树一帜的简洁吧。

以下来源go语言圣经原文

拥有自动垃圾回收、一个包系统、函数作为一等公民、词法作用域、系统调用接口、只读的UTF8字符串等。但是Go语言本身只有很少的特性,也不太可能添加太多的特性。例如,它没有隐式的数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储。但是,语言本身是成熟和稳定的,而且承诺保证向后兼容:用之前的Go语言编写程序可以用新版本的Go语言编译器和标准库直接构建而不需要修改代码。

Go语言有足够的类型系统以避免动态语言中那些粗心的类型错误,但是,Go语言的类型系统相比传统的强类型语言又要简洁很多。

Go语言鼓励当代计算机系统设计的原则,特别是局部的重要性。它的内置数据类型和大多数的准库数据结构都经过精心设计而避免显式的初始化或隐式的构造函数。

库和工具使用了大量的约定来减少额外的配置和解释,从而最终简化程序的逻辑。

Go语言提供了基于CSP的并发特性支持。

Golang编译源文件的命令

Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换

成计算机的机器指令(译注:静态编译)。Go语言提供的工具都通过一个单独的命令go调用,go命令有一系列子命令。最简单的一个子命令就是run。这个命令编译一个或多个以.go结尾的源文件,链接库文件,并运行最终生成的可执行文件。

$ go run helloworld.go

Go语言原生支持Unicode,它可以处理全世界任何语言的文本。 如果不只是一次性实验,你肯定希望能够编译这个程序,保存编译结果以备将来之用。可以用build子命令:

$ go build helloworld.go

这个命令生成一个名为helloworld的可执行的二进制文件。

os包

(c语言里面的main()里的参数,shell里11-10)

以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可从os包的Args变量获取;os包外部使用os.Args访问该变量。 os.Args变量是一个字符串(string)的切片(slice)。 os.Args的第一个元素:os.Args[0],是命令本身的名字;其它的元素则是程序启动时传给它的参数。 下面是Unix里echo命令的一份实现,echo把它的命令行参数打印成一行。

// Echo1 prints its command-line arguments.
package main

import (
    "fmt"
    "os"
)

func main() {
    var s, sep string
    for i := 1; i < len(os.Args); i++ {
        s += sep + os.Args[i]
        sep = " "
    }
    fmt.Println(s)
}

使用range

func main() {
    s, sep := "", ""
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = " "
    }
    fmt.Println(s)
}

如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使用strings包的Join函数:

func main() {
    fmt.Println(strings.Join(os.Args[1:], " "))
}

最后,如果不关心输出格式,只想看看输出值,或许只是为了调试,可以用Println为我们格式化输出。

fmt.Println(os.Args[1:])

bufio包

例1

打印标准输入中多次出现的行,以重复次数开头。

// Dup1 prints the text of each line that appears more than
// once in the standard input, preceded by its count.
package main

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

func main() {
    counts := make(map[string]int)
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() {
        counts[input.Text()]++
    }
    // NOTE: ignoring potential errors from input.Err()
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

解释

map的迭代顺序并不确定,从实践来看,该顺序随机,每次运行都会变化。(防止恶意哈希冲突)

继续来看bufio包,它使处理输入和输出方便又高效。Scanner类型是该包最有用的特性之一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。

程序使用短变量声明创建bufio.Scanner类型的变量input

input := bufio.NewScanner(os.Stdin)

该变量从程序的标准输入中读取内容。每次调用input.Scan(),即读入下一行,并移除行末的换行符;读取的内容可以调用input.Text()得到。Scan函数在读到一行时返回true,不再有输入时返回false

实践

自己写的一个例子,读取输入流中的数字存储为数组

package main

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

func main() {
   input := bufio.NewScanner(os.Stdin)
   nums := make([]int, 0, 50)
   for input.Scan() {
      t, err := strconv.Atoi(input.Text())//将读入的内容转化为数字
      if err == nil {//只有是数字才存入数组
         nums = append(nums, t)
      }
   }
   fmt.Println(nums)
}

例2

dup程序的下个版本读取标准输入或是使用os.Open打开各个具名文件,并操作它们。

// Dup2 prints the count and text of lines that appear more than once
// in the input.  It reads from stdin or from a list of named files.
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),其后被Scanner读取。

os.Open返回的第二个值是内置error类型的值。如果err等于内置值nil(译注:相当于其它语言里的NULL),那么文件被成功打开。读取文件,直到文件结束,然后调用Close关闭该文件,并释放占用的所有资源。 这里只使用了简单的错误处理。

map是一个由make函数创建的数据结构的引用。map作为参数传递给某函数时,该函数接收这个引用的一份拷贝(copy,或译为副本),被调用函数对map底层数据结构的任何修改,调用者函数都可以通过持有的map引用看到。

列3

dup的前两个版本以"流”模式读取输入,并根据需要拆分成多个行。理论上,这些程序可以处理任意数量的输入数据。还有另一个方法,就是一口气把全部输入数据读到内存中,一次分割为多行,然后处理它们。下面这个版本,dup3,就是这么操作的。

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

func main() {
    counts := make(map[string]int)
    for _, filename := range os.Args[1:] {
        data, err := ioutil.ReadFile(filename)
        if err != nil {
            fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
            continue
        }
        for _, line := range strings.Split(string(data), "\n") {
            counts[line]++
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

解释

这个例子引入了ReadFile函数(来自于io/ioutil包),其读取指定文件的全部内容,strings.Split函数把字符串分割成子串的切片。(Split的作用与前文提到的strings.Join相反。)

ReadFile函数返回一个字节切片(byte slice),必须把它转换为string,才能用strings.Split分割。

小结

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

总结

今日跟着视频又复习了一遍基础语法,主要是读了go语言圣经入门篇,对go语言特性有了更深的了解,又学习了视频中用到最多的两个包os包和bufio包,实现的了命令行输入和文件输入等,对输入流的处理。我对学习编程的态度是面向需求学习,必要的会细读,之后会一遍通读go语言圣经,一遍跟上视频课。