《Go语言圣经》学习笔记一、几个简单的例子入门Go语言

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SakuraMashiro/article/details/80643927

一、Go语言的优势

Go是一种编译型语言,被很多人称为21世纪的C语言,因其简洁、高效的特性,以及并发协程(goroutine)和消息通信(channels)等良好的机制,在服务端开发尤其是现在分布式、大数据的敏捷开发环境中得到了许多人青睐。

二、Go语言的由来

Go语言最初是由Google的工程师在开发过程中有感于传统编程语言的臃肿,并开始思考有关程序设计语言的一些关键性问题,发起了一个叫Golang的项目,决定创造一种新语言来取代C++,Go语言的团队还包括大名鼎鼎的C语言和Unix之父,可谓是“正统出身”。虽然它的性能可能仍比不上C,但这种速度的差距不会超过一个量级(已经是接近完美的编译速度),另外其接近python等解释型语言的开发效率,也是它到欢迎的原因之一。现在许多的云项目,都是用golang来开发,包括著名的Docker。

三、入门小例子

以下例子是《Go语言圣经》前几章的一些示例程序,如果大家有其他编程语言基础的话,只需要10几分钟就可以快速地了解Go语言的一些特性以及和其他语言的一些区别。

1.打印命令行参数的程序

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)
}

短短的几行,我们依次来看一下,首先开头是包package的声明,和别的编程语言类似,下面是import关键字导入其他的包,包名用双引号包裹,多个包放在一个括号里,“fmt”这个包里面是关于一些标准输入输出的函数和变量,“os”这个package提供了操作系统无关(跨平台)的,与系统交互的一些函数和相关的变量。接着就是我们的main函数了。首先是通过var声明了两个string类型的变量,用逗号隔开,变量类型放在后面;然后注意下面的for循环,与传统的一些语言不同,go语言的for循环条件不需要用括号括起来,并且左花括号需要和for语句在同一行,不过结构仍是典型的开始条件、循环条件、递增操作,其中任何一部分在go语言里都可以省略。
比如下面这种for循环,和传统的while循环效果完全一致:

// a traditional "while" loop
for condition {
// ...
}

当然了,如果你连唯一的条件都省了,那么for循环就会变成一个无限循环,像下面这样:

// a traditional infinite loop
for {
// ...
}

你可以靠break或者return语句来终止掉循环。
循环的开始条件 i := 1是一个简短变量声明,相当于

var i int
i = 1

下面的 len(os.Args) 则是取os.Args这个数组的长度,os.Args是一个字符串的slice(和Python语言中的切片类似,是一个简版的动态数组),里面存的是命令行的参数。我们可以通过索引来访问,比如os.Args[1]就是第一个命令行参数。后面的逻辑就很简答了,循环遍历参数数组的值,累加到字符串s,并用空格分隔,通过fmt.Println()打印到控制台。

2.获取url的程序
程序结构

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
)
func main() {
    for _, url := range os.Args[1:] {
        resp, err := http.Get(url)
        if err != nil {
            fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
            os.Exit(1)
        }  
        b, err := ioutil.ReadAll(resp.Body)
        defer resp.Body.Close()
        if err != nil {
            fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
            os.Exit(1)
        }
        fmt.Printf("%s", b)
   }
}

对于一个slice数组,我们可以用形如s[m:n]的形式来获取到一个slice的子集,比如a = [1, 2, 3, 4, 5], a[0:3] = [1, 2, 3],不包含最后一个元素。s[m:n]会返回第m到第n-1个元素,上面这个例子里用到的省略形式os.Args[1:]等价于os.Args[1:len(os.Args)],即获取所有的命令行参数。
函数多返回值
每一次for循环,range函数都会返回一对结果;当前迭代的下标以及在该下标处的元素的值。这个例子不需要这个下标,但是因为range函数要求我们必须同时处理下标和元素两个返回值。这种时候可以在声明一个接收下标的临时变量来解决这个问题,但Go语言又不允许只声明变量而在后续代码里不使用,如果
你这样做了编译器会返回一个编译错误。Go语言中这种情况的解决方法是用空白标识符,对,就是代码里那个下划线_。空白标识符可以在任何你需要接收自己不想处理的值时使用。这里使用它来忽略掉range返回的那个没什么用的下标值。
所以 for _, url := range os.Args[1:] 这句话相当于声明并初始化了url这个变量,让其值等于遍历时Args数组的值,而数组的索引被我们抛弃。

下面 resp, err := http.Get(url) 又是一个函数多返回值的例子,http包的Get函数可以获取url的值,并返回一个http.Response结构体的引用和错误信息err,这里我们声明两个变量来接收,并通过判断err是否为空(go语言里的null值就是nil)来进行错误处理。可以看到,if语句的判断条件也是没有括号的,但是处理内容必须要用花括号包裹。fmt.Fprintf()用来格式化打印输出信息,和c语言很类似。
下面通过ioutil工具包里的函数来读取resp结构体里的流信息,同样用两个变量来接收返回结果,由于err前面已经声明过,所以这里变成了简单的赋值。

下面的defer关键字也是Go语言一个比较优越的特性。它可以延迟执行一个函数,直到调用defer的函数本身返回(return语句/运行到函数结尾自动返回/对应的goroutine panic)之前执行。可以用defer来做一些资源的释放工作,防止内存泄露等问题。类似于java的try catch语句块,在finally里面进行资源释放,可是go语言明显看上去更简洁。

3.并发获取多个url的程序

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "time"
)

func main() {

    start := time.Now()
    ch := make(chan string)
    for _, url := range os.Args[1:] {
        go fetch(url, ch) // start a goroutine
    }
    for range os.Args[1:] {
        fmt.Println(<-ch) // receive from channel ch
    }
    fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}

func fetch(url string, ch chan<- string) {
    start := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprint(err) // send to channel ch
        return
    }
    nbytes, err := io.Copy(ioutil.Discard, resp.Body)
    resp.Body.Close() // don't leak resources
    if err != nil {
        ch <- fmt.Sprintf("while reading %s: %v", url, err)
        return
    }
    secs := time.Since(start).Seconds()
    ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}

使用时直接在命令行参数中输入多个url地址即可,用空格分隔。
该程序会用过goroutine并发获取这些url的内容,并打印出请求的时间。
这里面用到了Go比较重要的两个特性:goroutine和channel

goroutine(协程,微线程)是一种函数的并发执行方式,而channel是用来在goroutine之间进行参数传递。main函数也是运行在一个goroutine中,而go function则表示创建一个新的goroutine,并在这个这个新的goroutine里执行这个函数。

main函数中我们用make这个内建函数创建了一个传递string类型参数的channel,对每一个命令行参数,我们都用go这个关键字来创建一个goroutine,并且让函数在这个goroutine异步执行http.Get方法。这个程序里的io.Copy会把响应的Body内容拷贝到ioutil.Discard输出流中(这是一个垃圾桶,可以向里面写一些不需要的数据),因为我们需要这个方法返回的字节数,但是又不想要其内容。每当请求返回内容时,fetch函数都会往ch这个channel里写入一个字符串,由main函数里的第二个for循环来处理并打印channel里的这个字符串。

4.Web服务
Go语言可以让我们轻松地实现一个web服务,下面一个简短的程序实现了一个微型的服务,这个服务的功能是返回当前用户正在访问的URL。也就是说比如用户访问的是http://localhost:8000/hello , 那么响应是URL.Path = “hello”。

package main
import (
    "fmt"
    "log"
    "net/http"
)
func main() {
    http.HandleFunc("/", handler) // each request calls handler
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func handler(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}

我们只用了八九行代码就实现了一个个Web服务程序,这都是多亏了标准库里的方法已经帮我们处理了大量的工作。main函数会将所有发送到/路径下的请求和handler函数关联起来,/开头的请求其实就是所有发送到当前站点上的请求,我们的服务跑在了8000端口上。发送到这个服务的“请求”是一个http.Request类型的对象,这个对象中包含了请求中的一系列相关字段,其中就包括我们需要的URL。当请求到达服务器时,这个请求会被传给handler函数来处理,这个函数会将/hello这个路径从请求的URL中解析出来,然后把其发送到响应中,

猜你喜欢

转载自blog.csdn.net/SakuraMashiro/article/details/80643927