前言

笔者是做前端开发工作的,对Js、Nodejs比较熟悉。对其他技术不熟悉但是非常愿意接触,前端构建的工具链中也逐步有其他语言的接入,比如esbuild(GO语言), swc(RUST)。率性就学习下GO语言,拓展自己的技术宽度。

GO 语言

相信大家网上随便搜一下都能找到介绍GO语言的,这里我就不重复说了。

Go 语言具有很强的表达能力,它简洁、清晰而高效。得益于其并发机制, 用它编写的程序能够非常有效地利用多核与联网的计算机,其新颖的类型系统则使程序结构变得灵活而模块化。 Go 代码编译成机器码不仅非常迅速,还具有方便的垃圾收集机制和强大的运行时反射机制。 它是一个快速的、静态类型的编译型语言,感觉却像动态类型的解释型语言。

简单总结一下就是:易学习高性能跨平台

GO 语言之旅

笔者是通过 《GO语言之旅》这个教程初步学习的,大家也可以通过这个简单的线上课程去了解GO语言的一些基本特性和使用方式。

go 语言和javascript相似也是有包管理的机制,不过与npm包不一样,不需要发布,直接通过线上的地址导入
举个例子,下面的例子就是使用第三方包log包去打印日志。 github.com/sirupsen/logrus 则是导入的地址

package main

import log "github.com/sirupsen/logrus"

func main() {
	log.Infof("Hello world!")
}

现在笔者接触的go包管理一般用的是go module管理,go.mod配置文件是下面这样的结构。 这两行和 npm package.json 的 name 字段的功能很类似。定义了模块名称

// go.mod
module github.com/sirupsen/logrus

go 1.12

go 模块管理在使用的时候因为不熟悉还踩了不少的坑,比如不能互相导入会导致编译器报错。如果想了解更多的go module的学习可以阅读深入Go Module之go.mod文件解析

练习题

直接通过练习题的讲解,可以更快速的让大家了解语言特性,其他想要学习了解GO语言的同学也可以自己先学习下GO语言课程里做一遍练习题,没有思路再通过笔者的注释学习。

练习:循环与函数

题目(一)练习:循环与函数 利用循环计算出近似值

package main

import (
    "fmt"
    "math"
)

func Sqrt(x float64) float64 {
    z := 1.0// 定义一个初始值并对它初始化
    temp := 0.0//  临时变量,作为记录z 上次的值
    // for 循环
    for {  
        z = z - (z*z-x)/(2*z)// 计算出最新的z值
        fmt.Println(z)
        if math.Abs(z-temp) < 0.000000000000001 {
            break//  当值停止改变(或改变非常小)的时候退出循环
        } else {
            temp = z// 赋值最终的结果
        }
    }
    return z
}

func main() {
    fmt.Println("猜测:", Sqrt(2))
    fmt.Println("math.Sqrt(2):", math.Sqrt(2))
}

练习:切片

题目(二)练习:切片

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
    a := make([][]uint8,dy)  //外层切片
    for x := range a{
        b := make([]uint8,dx)  //里层切片
        for y := range b{
            b[y] = uint8(x*y - 1)  //给里层切片里的每一个元素赋值。其中x*y可以替换成别的函数 形成不同图形
            // b[y] = uint8(x^y) 
        }
        a[x] = b  //给外层切片里的每一个元素赋值
    }
    return a
}

func main() {
    pic.Show(Pic)
}

练习:映射

题目(三)练习:映射

package main

import (
    "golang.org/x/tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    m := make(map[string]int)  // 创建映射
    c := strings.Fields(s)  // 以[]string形式返回
    for _, v := range c {  //每出现相同的单词(字符串)
        m[v] += 1  //出现次数就 + 1         
    }
    return m
}
func main() {
    wc.Test(WordCount)
}

练习:斐波纳契闭包

题目(四)练习:斐波纳契闭包

和js有点相似,函数使用外部的变量,使变量变成必成函数闭包变量, 变量拥有“临时存储”功能

package main

import "fmt"

// 返回一个“返回int的函数”
func fibonacci() func() int {
	a, b:= 0,1
	return func () int {
		temp := a
		a, b = b, (a+b)
		return temp
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

练习:Stringer

题目(五)练习:Stringer

对于fmt的Printf方法会调用类型关联的string方法,在前面类型的学习中可以知道 对于入参参数只有1个且为该类型的函数可以通过 typeName.xxx 去调用函数

package main
import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.

func (v IPAddr) String() string{  
    return fmt.Sprintf("%v.%v.%v.%v", v[0],v[1],v[2],v[3])  
}  

func main() {
    hosts := map[string]IPAddr{
        "loopback":  {127, 0, 0, 1},
        "googleDNS": {8, 8, 8, 8},
    }
    for name, ip := range hosts {
        fmt.Printf("%v: %v\n", name, ip)
    }
}

练习:错误

题目(六)练习:错误


// 待补充 为啥fmt.Sprintf 中e 直接写入会无限死循环

package main

import (
	"fmt"
	"math"
)
// 定义类型
type ErrNegativeSqrt float64

// 重写Error()
func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number:  %v", float64(e))
}

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}
	return math.Sqrt(x), nil
}

func main() {
	//这里只是简单的打印
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

练习:Reader

题目(七)练习:Reader

package main

import (
    "golang.org/x/tour/reader"

)

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.

func (r MyReader) Read(b []byte) (int, error) {
    // 赋值并返回
    b[0] = 'A'
    return 1, nil
}

func main() {
    reader.Validate(MyReader{})
}

练习:rot13Reader

题目(八)练习:rot13Reader

package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func (rot rot13Reader) Read(p []byte) (int, error) {
    // 读取字符串数组
    n, err := rot.r.Read(p)
    for i := 0; i < n; i++ {
        p[i] = rot13(p[i])
    }
    return n, err

}
func rot13(b byte) byte {
    if (b >= 'A' && b <= 'M') || (b >= 'a' && b <= 'm') {
        b += 13
    } else if (b >= 'N' && b <= 'Z') || (b >= 'n' && b <= 'z') {
        b -= 13
    }
    return b
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

练习:图像

题目(九)练习:图像

对类型定义不同的方法,这里有点像类的成员函数

package main

import (
    "golang.org/x/tour/pic"
    "image/color"
    "image"
)

type Image struct{}  //新建一个Image结构体

func (i Image) ColorModel() color.Model{  //实现Image包中颜色模式的方法
    return color.RGBAModel
}

func (i Image) Bounds() image.Rectangle{  //实现Image包中生成图片边界的方法
    return image.Rect(0,0,200,200)
}

func (i Image) At(x,y int) color.Color{  //实现Image包中生成图像某个点的方法
    return color.RGBA{uint8(x),uint8(y),uint8(255),uint8(255)}
}

func main() {
    m := Image{}
    pic.ShowImage(m)  //调用
}

练习:等价二叉查找树

题目(九)练习:等价二叉查找树

这个练习题主要让我们了解如何使用信道实现函数运行时的通信问题

package main

import (
"golang.org/x/tour/tree"
"fmt"
)
//  发送value,结束后关闭channel
func Walk(t *tree.Tree, ch chan int){
    sendValue(t,ch)
    close(ch)
}
//  递归向channel传值
func sendValue(t *tree.Tree, ch chan int){
    if t != nil {
        sendValue(t.Left, ch)
        ch <- t.Value
        sendValue(t.Right, ch)
   }
}

// 使用写好的Walk函数来确定两个tree对象  是否一样 原理还是判断value值
func Same(t1, t2 *tree.Tree) bool {
       ch1 := make(chan int)
       ch2 := make(chan int)
       go Walk(t1,ch1)
       go Walk(t2,ch2)
       for i := range ch1 {   // ch1 关闭后   for循环自动跳出
               if i != <- ch2 {
                      return false
               }
       }
      return true
}

func main() {
 
    // 打印 tree.New(1)的值
    var ch = make(chan int)
    go Walk(tree.New(1),ch)
    for v := range ch {
          fmt.Println(v)
    }
    
    //  比较两个tree的value值是否相等
    fmt.Println(Same(tree.New(1), tree.New(1)))
    fmt.Println(Same(tree.New(1), tree.New(2)))
}

练习:Web 爬虫

题目(十)练习:Web 爬虫 这个练习题是练习对信道和锁的使用

package main


import (
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
	Fetch(url string) (body string, urls []string, err error)
}

var (
	//  map  存放爬取的url
	m = make(map[string]int)
	// 互斥锁
	l sync.Mutex
	//   群组等待     当添加的任务没有完成时(done()), wait() 会一直等待    三个方法   Add()  Done()  Wait()
	i sync.WaitGroup
)

func main() {
	i.Add(1)
	Crawl("http://golang.org/", 4, fetcher)
	
	i.Wait()// 会一直等待直到子线程任务结束	
	
	for k, _ := range m {
		fmt.Println(k)
	}	
	fmt.Println("over")
}

// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {

	defer i.Done() //  和add相对应
	if depth <= 0 {
		return
	}
	_, urls, err := fetcher.Fetch(url)
	if err != nil {
	//	fmt.Println(err)
		return
	}
	// 存入数据  需要同步锁  因为这是在子线程中
	l.Lock()
	if m[url] == 0 { //  还未爬取过
		m[url]++ // 存入爬取的url  改变对应的标示
		depth--
		//		fmt.Printf("found: %s %q\n", url, body)
		for _, u := range urls {
			i.Add(1)
			go Crawl(u, depth, fetcher) // 继续爬取
		}
	}
	l.Unlock()

}

// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
	"http://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"http://golang.org/pkg/",
			"http://golang.org/cmd/",
		},
	},
	"http://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"http://golang.org/",
			"http://golang.org/cmd/",
			"http://golang.org/pkg/fmt/",
			"http://golang.org/pkg/os/",
		},
	},
	"http://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
	"http://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
}

后续

通过该课程学习和这些练习题,想必大家都能过了解GO语言怎么写了。在学习GO语言的过程,和前端js编码最大的不同是在编码之前对自己想要的东西必须认知比较的清晰,在后续使用都是类型推断辅助编码。还有一个特定就是信道、go程、锁的了解知道了为啥go语言能够拥有高性能。最后自己也尝试编写一个开源项目去锻炼自己的go语言编程能力