Chapter 1 入门

1.1 Hello, World

//helloworld.go
package main // 声明程序属于哪个包
import "fmt" // 引用哪个包

func main(){  // “{” 必须与 func 在同一行
    fmt.Println("Hello, 世界") //支持Unicode可以处理所有国家的语言
}

Go是一个变编译型语言

go run helloworld.go # 直接运行
go build helloworld.go # 编译成二进制文件helloworld

Go代码是使用包来管理的package + 属于哪个包, main包很特殊定义了一个可以单独执行的程序,函数main为程序开始的地方。

Go不需要分号,但在特定符号后的换行符会被转换成分号因此换行可能影响到Go语言的解析,go对代码的格式化要求非常严格,养成使用*gofmt*的习惯。

1.2 命令行参数

Go中的slice与python中的类似前开后闭。

package main

import (  // 这种写法,或者使用两种
    "fmt"
    "os" // 与平台无关,直接和操作系统交互
)

func main(){
    var s, sep string  // 声明两个类型为string的对象
    fmt.Println(sep)
    for i := 1; i < len(os.Args); i++ {  // := 短变量声明,给与适当的类型
        s += sep + os.Args[i] // Args[0] 就是命令本身的名字
        sep = " "
    }
    fmt.Println(s)
}   

变量可以在声明的时候初始化,如果没有初始化就默认为空值,字符串的空值为"", 数字类型的空值为0。

//for是唯一的循环语句
for initalization; condition; post {  // 大括号是必须的且左括号要与post在同一行
}

//传统的while循环
for condition {
}

//无限循环

for {

}

每一次迭代range产生一对索引和元素

//for循环直接在字符串或者slice上进行迭代
package main

import (
    "fmt"
    "os"
)

func main() {
    s, sep := "", ""
    for _, arg := range os.Args[1:] {  // Go不允许有无用的temp出现
        s += sep + arg  // 旧的字符串会当成垃圾回收,大规模数据代价较大
        sep = " "
    }
    fmt.Println(s)
}

以下的声明方式是等价的

s := ""  // 函数内部使用,不适合包级别的变量
var s string // 直接默认""
var s = "" // 很少用
var s string = "" // 冗余

// 主要使用前两个

垃圾回收机制,大量的数据迭代起来代价较大

fmt.Println(strings.Join(os.Args[1:]), " ")  // 一行代码解决问题,与python类似
fmt.Println(os.Args[1:]) // 不care格式, slice可以直接输出
// 练习1.1 修改echo程序输出os.Agrs[0]
func main(){
    fmt.Println(os.Args[0])
}
// 练习1.2 输出参数的索引和值,每行一个
func main(){
    for index, value := range os.Args{
        fmt.Println(index, value)
    }
}
// 1.3 咕咕咕,等着看完后面的章节再写 TODO

1.3 找出重复行

//dup1.go
package main

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

func main(){
    counts := make(map[string]int)  // 生成键值对,键可以进行比较
    input := bufio.NewScanner(os.Stdin)
    for input.Scan(){
        counts[input.Text()]++ // 不存在没关系直接转换成零值
    }
    for line, n := range counts { // range搞出来的东西是随机的,程序依赖于某种特定的序列,产生索引和索引对应的值
        if n < 1 {
            fmt.Println("%d\t%s", n, line)
        }
    }
}
//dup2.go
package main

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

func countLines(f *os.File, counts map[string]int) {  // counts是一个引用类型,值会跟着改变
    input := bufio.NewScanner(f)
    for input.Scan() {   // CTRL + D 终止输入
        counts[input.Text()]++
    }
}

func main() {
    counts := make(map[string]int)
    files := os.Args[1:]
    if len(files) == 0 {
        countLines(os.Stdin, counts)
    } else {
        for _, arg := range os.Args[1:] {
            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)
        }
    }
}

Fprintf formats according to a format specifier and writes to w.根据文档的解释来解释fmt.Fprintf(os.Stderr, "dup2: %v\n", err),就是将数据写入到os.Stderr中,Printf函数也是基于Fprintf的改版,只不过w换成了os.Stdout, 之所以使用Stderr是因为Stderr无缓冲实时性好。

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)  // data ascii
        fmt.Println(string(data))
        if err != nil {
            fmt.Fprint(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)
        }

    }
}

不同于之前的流式输出,ioutil.ReadFile直接将文件读入内存中并返回数据的ascii码形式(包括换行符等特殊符号), string()将ascii码转换成字符串,类似于python,split函数负责分割字符串。

// 练习1.4 修改dup2程序,输出出现重复行的文件名称
package main

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

func countLines(f *os.File, counts map[string]int, dup_files  map[string]int) {
    input := bufio.NewScanner(f)
    if f == os.Stdin {
        for input.Scan() {   // CTRL + D
            counts[input.Text()]++
        }
    } else {
        for input.Scan() {
            counts[input.Text()]++
            if counts[input.Text()] > 1 {  // 如果出现重复行就++
                dup_files[f.Name()]++  // Name()文件名称
            }
        }
    }

}

func main() {
    counts := make(map[string]int)
    dup_files := make(map[string]int) // 创建一个(文件名:是否有重复行)键值对
    files := os.Args[1:]
    if len(files) == 0 {
        countLines(os.Stdin, counts, dup_files)
    } else {
        for _, arg := range os.Args[1:] {
            f, err := os.Open(arg)
            if err != nil {
                fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
                continue
            }
            countLines(f, counts, dup_files)
            f.Close()
        }
    }
    for file, n := range dup_files {
        if n >= 1 {  // 如果文件名对应的值大于等于1判断文件存在重复行
            fmt.Printf("%d\t%s\n", n, file)
        }
    }
}

1.4 GIF 动画