GO语言圣经入门篇|青训营

60 阅读6分钟

1.1. Hello, World

package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}

Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换成计算机的机器指令(译注:静态编译)。

前面是先要import package来调用基础包,然后在main function中可以调用library中的函数。

GO语言的强大之处在于它的基础包非常多,而且兼容性非常好,要好好的利用。

1.2. 命令行参数

os.Args变量是一个字符串(string)的切片(slice)‘

os包以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可从os包的Args变量获取;os包外部使用os.Args访问该变量。

package mainimport (    "fmt"    "os"    "strings")//!+func main() {    fmt.Println(strings.Join(os.Args[1:], " "))}

这是书中的源代码,乍一看,挺简单的,s用来存放所有的命令行字符,sep用来加上空格,便于区分,os.Args会返回字符串切片类型。第一次进入循环时,sep还没有被赋值,此时sep为空,所以在第一次“s+=”操作时,前面没有空格,s加完后sep被赋值为空格,以后每次"s+="操作时都相当于加上一个“空格+命令行”,如此运行下来,s中便不会出现头尾有空格的情况,因此这里的sep赋值的位置和"s+="的位置是很考究的。

1.3. 查找重复的行

os.Open函数返回两个值。第一个值是被打开的文件(*os.File),其后被Scanner读取。

os.Open返回的第二个值是内置error类型的值。如果err等于内置值nil(译注:相当于其它语言里的NULL),那么文件被成功打开。读取文件,直到文件结束,然后调用Close关闭该文件,并释放占用的所有资源。相反的话,如果err的值不是nil,说明打开文件时出错了。这种情况下,错误值描述了所遇到的问题。我们的错误处理非常简单,只是使用Fprintf与表示任意类型默认格式值的动词%v,向标准错误流打印一条信息,然后dup继续处理下一个文件;continue语句直接跳到for循环的下个迭代开始执行。

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

  • 格式化

    %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          变量的类型
    %%          字面上的百分号标志(无操作数)
    

    ioutil.ReadFile(filename)函数返回一个字节切片(byte slice)

1.4. GIF动画

练习 1.6: 修改Lissajous程序,修改其调色板来生成更丰富的颜色,然后修改SetColorIndex的第三个参数,看看显示结果吧。

package mainimport (    "image"    "image/color"    "image/gif"    "io"    "log"    "math"    "math/rand"    "net/http"    "os"    "time")//!-main// Packages not needed by version in book.//!+mainvar palette []color.Color// const (//  whiteIndex = 0 // first color in palette//  blackIndex = 1 // next color in palette// )func main() {    //!-main    // The sequence of images is deterministic unless we seed    // the pseudo-random number generator using the current time.    // Thanks to Randall McPherson for pointing out the omission.    rand.Seed(time.Now().UTC().UnixNano())    for i := 0; i < 10; i++ {        r := uint8(rand.Uint32() % 256)        g := uint8(rand.Uint32() % 256)        b := uint8(rand.Uint32() % 256)        palette = append(palette, color.RGBA{r, g, b, 0xff})    }    if len(os.Args) > 1 && os.Args[1] == "web" {        //!+http        handler := func(w http.ResponseWriter, r *http.Request) {            lissajous(w)        }        http.HandleFunc("/", handler)        //!-http        log.Fatal(http.ListenAndServe("localhost:8000", nil))        return    }    //!+main    lissajous(os.Stdout)}func lissajous(out io.Writer) {    const (        cycles  = 5     // number of complete x oscillator revolutions        res     = 0.001 // angular resolution        size    = 100   // image canvas covers [-size..+size]        nframes = 64    // number of animation frames        delay   = 8     // delay between frames in 10ms units    )    freq := rand.Float64() * 3.0 // relative frequency of y oscillator    anim := gif.GIF{LoopCount: nframes}    phase := 0.0 // phase difference    for i := 0; i < nframes; i++ {        rect := image.Rect(0, 0, 2*size+1, 2*size+1)        img := image.NewPaletted(rect, palette)        for t := 0.0; t < cycles*2*math.Pi; t += res {            x := math.Sin(t)            y := math.Sin(t*freq + phase)            img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),                (uint8)(i%10))        }        phase += 0.1        anim.Delay = append(anim.Delay, delay)        anim.Image = append(anim.Image, img)    }    gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors}//!-main

这里是修改了palette variable,把它赋成了一个切片,里面存了10种颜色,这样可以让每一个相邻的frame 得到跟之前的frame不同的颜色,让最后的输出变的颜色不一样了,并不是之前的单调的黑白颜色。

1.5. 获取URL

http.Get函数是创建HTTP请求的函数,如果获取过程没有出错,那么会在resp这个结构体中得到访问的请求结果。resp的Body字段包括一个可读的服务器响应流。ioutil.ReadAll函数从response中读取到全部内容;将其结果保存在变量b中。resp.Body.Close关闭resp的Body流,防止资源泄露,Printf函数会将结果b写出到标准输出流中。

1.6. 并发获取多个URL

我觉得这里的goroutine设计的非常的神奇,可以让程序去开始很多的轻量级的并发小程序。这样的开发使得并行的程序可以高质量的运行起来。

1.7. Web服务

Go语言的内置库使得写一个类似fetch的web服务器变得异常地简单。在本节中,我们会展示一个微型服务器,这个服务器的功能是返回当前用户正在访问的URL。比如用户访问的是 http://localhost:8000/hello ,那么响应是URL.Path = "hello"。

// Server1 is a minimal "echo" server.
package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", handler) // each request calls handler
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

// handler echoes the Path component of the request URL r.
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}

main函数将所有发送到/路径下的请求和handler函数关联起来,/开头的请求其实就是所有发送到当前站点上的请求,服务监听8000端口。发送到这个服务的“请求”是一个http.Request类型的对象,这个对象中包含了请求中的一系列相关字段,其中就包括我们需要的URL。当请求到达服务器时,这个请求会被传给handler函数来处理,这个函数会将/hello这个路径从请求的URL中解析出来,然后把其发送到响应中,这里我们用的是标准输出流的fmt.Fprintf。

handler := func(w http.ResponseWriter, r *http.Request) {
    lissajous(w)
}
http.HandleFunc("/", handler)

在这些程序中,我们看到了很多不同的类型被输出到标准输出流中。比如前面的fetch程序,把HTTP的响应数据拷贝到了os.Stdout,lissajous程序里我们输出的是一个文件。fetchall程序则完全忽略到了HTTP的响应Body,只是计算了一下响应Body的大小,这个程序中把响应Body拷贝到了ioutil.Discard。在本节的web服务器程序中则是用fmt.Fprintf直接写到了http.ResponseWriter中。