A Tour of Go 笔记

https://tour.golang.org/list

Basics

------ Packages, variables and functions -------

Packages

每个 Go 程序都由几个 package 构成。程序在 main package 中开始执行。

package main

import( 
	"fmt" // 使用了 fmy 和 math/rand 
	"math/rand" // 这个包的文件开头是 `package rand`
)

func main(){
	fmt.Println("My favorite number is", rand.Intn(10))
}

Imports

import 包的时候可以用 () 一块导入

package main

import (
	"fmt"
	"math"
)

func main(){
	fmt.Printf("now you have %g problems.\n",math.Sqrt(7))
}

以上 import 也可以写成:

import "fmt"
import "math"

但是最好合起来写。

Exported names

当一个 name 以大写字母开头时,说明它是 exported 的,在 import 一个 package 后,只能访问其 exported name ,比如以下代码会出错:

package main
import (
	"fmt"
	"math"
)
func main(){
	fmt.Println(math.pi)
}

在这里插入图片描述
需要把 math.pi 改成 math.Pi

package main
import (
	"fmt"
	"math"
)
func main(){
	fmt.Println(math.Pi)
}

在这里插入图片描述

Functions

一个函数可以由零个或者多个参数,注意参数的类型要写在变量名的后面(好奇葩)

package main
import "fmt"
func add(x int, y int )int{
	return x + y
}
func main(){
	fmt.Println(add(42,13))
}

当两个连续的参数类型相同时,可以省略掉前面的类型,只留最后一个,比如:

package main
import "fmt"
func add(x, y int) int {
	return x + y
}
func main(){
	fmt.Println(add(42,13))
}

额还要注意每句结尾没有分号。

Multiple results

一个函数可以返回多个结果

package main
import "fmt"
func swap(x, y string) (string, string) {
	return y, x
}
func main(){
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

Named return values

返回的变量可以有名字,这样的话在返回时直接 return (不带任何参数),如此返回的便是 named return values ,这叫做 “naked” return

package main
import "fmt"
func split(sum int) (x, y int){
	x = sum * 4 / 9
	y = sum - x 
	return 
}
func main(){
	fmt.Println(split(17))
}

这种写法只能在比较短的函数里用,太长的函数用这种写法的话很影响可读性。

Variables

var 声明一系列的变量,变量的类型也是在最后面写。

变量可以放在 package 和 function 所在的区域。

package main
import "fmt"
var c, python, java bool
func main(){
	var i int 
	fmt.Println(i, c, python, java)
}

Variables with initializers

var 进行声明变量时可以初始化,每个变量一个初始值。

假如存在初始化值,那么变量类型可以省略。

package main
import "fmt"
var i, j int = 1, 2
func main(){
	var c, python, java = true, false, "no!"
	fmt.Println(i, j, c, python, java)
}

Short variable declarations

在函数里面,可以用 :=(叫做 short assignment statement) 来代替 var 进行隐式类型声明。

但是在函数外面,每句话前面必须有一个关键字,比如 var,func 等,所以不能用 := (很关键!)

package main
import "fmt"
func main(){
	var i, j int = 1, 2
	k := 3
	c, python, java := true, false, "no!"
	fmt.Println(i, j, k, c, python, java)
}

Basic types

Go 的基本类型有:

bool 

string 

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32, represents a Unicode code point

float32 float64

complex64 complex128

变量也可以像 import 时那样用 () (factored into blocks)。

int,uint,uintptr 在 32 位机器上一般是 32 位,64 位机器上则是 64 位。如果需要整数的话一般用 int 就 ok 了。

package main
import (
	"fmt"
	"math/cmplx"
)
var (
	ToBe bool = false
	MaxInt uint64 = 1<<64 - 1
	z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main(){
	fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
	fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
	fmt.Printf("Type: %T Value: %v\n", z, z)
}

在这里插入图片描述

Zero values

没有显式给初值的变量会给 zero value

numeric types 的 zero value 是:0
boolean type 的 zero value 是:false
strings 的 zero value 是:"" (空串)

package main
import "fmt"
func main() {
	var i int 
	var f float64 
	var b bool 
	var s string 
	fmt.Printf("%v %v %v %q\n", i, f, b, s)
}

在这里插入图片描述

Type conversions

T(v) 可以将 v 转换成类型 T ,比如:

var i int = 42
var f float64 = float64(i)
vat u uint = uint(f)

也可以写为:

i := 42
f := float64(i)
u := uint(f)
package main
import (
	"fmt"
	"math"
)
func main(){
	var x, y int = 3, 4
	var f float64 = math.Sqrt(float64(x*x + y*y)) 
	var z uint = uint(f)
	fmt.Println(x, y, z)
}

在这里插入图片描述

和 C 不同的是,Go 在赋值的时候必须显式进行类型转化。

假如去掉的话,会报错:

package main

import (
	"fmt"
	"math"
)

func main() {
	var x, y int = 3, 4
	var f float64 = math.Sqrt((x*x + y*y))
	var z uint = f
	fmt.Println(x, y, z)
}

在这里插入图片描述

Type inference

初始化一个变量时,可以通过右边的初始值来推测变量类型。

i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

Constants

前面带有关键字 const 的为常量,可以是字符、串、布尔值、数值等。

不能通过 := 来声明

package main
import "fmt"
const Pi = 3.14
func main(){
	const World = "世界"
	fmt.Println("Hello", World)
	fmt.Println("Happy", Pi, "Day")
	const Truth = true 
	fmt.Println("Go rules?", Truth)
}

在这里插入图片描述

Numeric Constants

numeric constants 是高精度值

package main

import "fmt"

const (
	// Create a huge number by shifting a 1 bit left 100 places.
	// In other words, the binary number that is 1 followed by 100 zeroes.
	Big = 1 << 100
	// Shift it right again 99 places, so we end up with 1<<1, or 2.
	Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
	return x * 0.1
}

func main() {
	fmt.Println(needInt(Small))
	fmt.Println(needInt(Big))
	fmt.Println(needFloat(Small))
	fmt.Println(needFloat(Big))
}

fmt.Println(needInt(Big)) 这句会报错:

在这里插入图片描述

------ Flow control statements: for, if, else, switch and defer ------

For

Go 只有一种循环结构:for 循环

与其他语言不同的是,三部分之间没有括号。循环体必须有大括号 {}

package main

import "fmt"

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

而 初始条件和递增条件是可以省略的:

package main

import "fmt"

func main() {
	sum := 1
	for ; sum < 1000; {
		sum += sum
	}
	fmt.Println(sum)
}

把上面这种写法里的 ; ; 扔掉,得到的写法类似于 C 语言中的 while ,只不过是把 while 换成了 for

package main

import "fmt"

func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}

假如去掉循环的终止条件的话就会变成一个无限循环:

package main

func main() {
	for {
	}
}

If

for 类似,不需要小括号 () ,但是需要大括号 {}

package main

import (
	"fmt"
	"math"
)

func sqrt(x float64) string {
	if x < 0 {
		return sqrt(-x) + "i"
	}
	return fmt.Sprint(math.Sqrt(x))
}

func main() {
	fmt.Println(sqrt(2), sqrt(-4))
}

If with a short statement

for 类似,if 语句在判断条件之前可以加一个初始化语句,并且初始化语句中的变量只在这个 if 里的 scope 有效:

package main

import (
	"fmt"
	"math"
)

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}

If and else

if 初始化语句中的变量在 else 中也有效:

package main

import (
	"fmt"
	"math"
)

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	} else {
		fmt.Printf("%g >= %g\n", v, lim)
	}
	// can't use v here, though
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}

Exercise: Loops and Functions

给一个数 xx ,计算 zz 使得 z2z^2 尽可能靠近 xx

可以使用迭代法,从一个初始的 zz 开始:

z=zz2x2z z=z-\frac{z^2-x}{2z}

(这个是牛顿迭代法)设 f(z)=z2xf(z)=z^2-x,即求该方程的零点。取某一点 (zn,f(zn))(z_n,f(z_n)) ,则该点的切线方程为

f(zn+1)f(zn)=f(zn)(zn+1zn) f(z_{n+1})-f(z_n)=f'(z_n)(z_{n+1}-z_n)

以该切线方程和 zz 轴的交点作为下一个估计的 zn+1z_{n+1} ,即:

zn+1=znzn2x2zn z_{n+1}=z_n-\frac{z_n^2-x}{2z_n}

package main

import (
	"fmt"
)

func Sqrt(x float64) float64 {
	z := 1.0 
	for i:=0; i<10; i++ {
		z -= (z*z - x) / (2*z)
		fmt.Println(i+1,":",z)
	}
	return z
}

func main() {
	fmt.Println(Sqrt(2))
}

计算 2\sqrt{2}

在这里插入图片描述
计算 3\sqrt{3}

在这里插入图片描述
计算 4\sqrt{4}
在这里插入图片描述
计算 5\sqrt{5}

在这里插入图片描述
改成当变化小于一定值时停止:

package main

import (
	"fmt"
)
var eps = 1e-6

func Sqrt(x float64) float64 {
	z := 1.0 
	for i:=0; i<10; i++ {
		d := (z*z - x) / (2*z)
		z -= d
		if d < 0 {
			d = -d
		}
		if d < eps {
			fmt.Println(i+1,":",z,"early termination")
			break
		}
		fmt.Println(i+1,":",z)
		
	}
	return z
}

func main() {
	fmt.Println(Sqrt(2))
}

在这里插入图片描述
可以看出收敛还是很快的。

Switch

Go 里面默认每个 case 后都有 break ,所以不会往后执行多个 case ,另一点不同是:case 变量不一定是常数,不一定是整数。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("OS X.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("When's Saturday?")
	today := time.Now().Weekday()
	switch time.Saturday {
	case today + 0:
		fmt.Println("Today.")
	case today + 1:
		fmt.Println("Tomorrow.")
	case today + 2:
		fmt.Println("In two days.")
	default:
		fmt.Println("Too far away.")
	}
}

Switch with no condition

switch 可以没有条件,等价于 switch true ,如:

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

Defer

一个 defer 语句会在包含该语句的函数返回时才执行,它的参数是立即算好的(而不是执行时算好的)

package main

import "fmt"

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

Stacking defers

defer 的语句是依次被放到栈上的,因此最后的执行顺序是后进先出

package main

import "fmt"

func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}

	fmt.Println("done")
}

在这里插入图片描述

------ More types: structs, slices, and maps ------

Pointers

Go 也有指针,指针保存的是一个值的地址。

类型 *T 是指向类型 T 的一个指针,其零值为 nil

var p *int

操作符 & 可以生成指向操作数的一个指针:

i := 42
p = &i

操作符 * 表示指针指向的值:

fmt.Println(*p) // read i through the pointer p
*p = 21 // set i through the pointer p

这叫做 dereferencing 或 indirecting 。

与 C 不同的是,Go 没有关于指针的运算。

package main

import "fmt"

func main() {
	i, j := 42, 2701

	p := &i         // point to i
	fmt.Println(*p) // read i through the pointer
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i

	p = &j         // point to j
	*p = *p / 37   // divide j through the pointer
	fmt.Println(j) // see the new value of j
}

Structs

struct 是一个集合:

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}

Struct Fields

通过 . 来访问 struct 的一个成员变量:

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}

Pointers to structs

可以通过 struct pointer 来访问 struct 的成员

一般来说要写 (*p).X ,但是 Go 可以直接写成 p.X (相当于 C 中的 p->x

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}

Struct Literals

相当于初始化吧,看例子:

package main

import "fmt"

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
	fmt.Println(v1, p, v2, v3)
}

在这里插入图片描述

Arrays

类型 [n]T 表示一个含有 n 个类型为 T 的元素的数组

var a [10]int

声明了一个有 10 个整数的数组

一个数组的长度是其类型的一部分,因此数组不能 resize,这看起来很受限,但是 Go 提供了一种很方便的方式来使用数组。

package main

import "fmt"

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}

在这里插入图片描述

Slices

一个数组的大小是固定的,一个 slice 则是动态大小的,在实际中 slice 用得比 array 要多。

类型 []T 表示类型为 T 的一个 slice

a[low : high]

这样便取了 a[low,high) 部分的元素形成 slice (和 python 很像)

package main

import "fmt"

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}

在这里插入图片描述

Slices are like references to arrays

slice 并不存储任何数据,它只描述一个数组的一部分。

改变 slice 中的元素也会改变其对应的数组中的元素。

其他 slice 如果也也用了这个数组,那么他们的值也会改变。

package main

import "fmt"

func main() {
	names := [4]string{
		"John",
		"Paul",
		"George",
		"Ringo",
	}
	fmt.Println(names)

	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}

在这里插入图片描述

Slice literals

一个 slice literal 就像一个没有长度的 array literal

array literal 如下:

[3]bool{true,true,false}

slice literal 如下:

[]bool{true,true,false}
package main

import "fmt"

func main() {
	q := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(q)

	r := []bool{true, false, true, true, false, true}
	fmt.Println(r)

	s := []struct {
		i int
		b bool
	}{
		{2, true},
		{3, false},
		{5, true},
		{7, true},
		{11, false},
		{13, true},
	}
	fmt.Println(s)
}

在这里插入图片描述

Slice defaults

可以省略 slice 时的边界,默认的下界是 0,默认的上界是长度:

var a [10]int
a[0:10]
a[:10]
a[0:]
a[:]
package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}

	s = s[1:4]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}

在这里插入图片描述

Slice length and capacity

一个 slice 既有长度,也有容量。

一个 slice 的长度是它含有的元素个数,其容量是其底下的数组的元素个数,从 slice 中的第一个元素开始数。

len(s),cap(s) 可以得到其长度和容量。

可以通过 reslice 来延长 slice 的长度,但是要保证其有足够的容量

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)

	// Slice the slice to give it zero length.
	s1 := s[:0]
	printSlice(s1)

	// Extend its length.
	s2 := s1[:4]
	printSlice(s2)

	// Drop its first two values.
	s3 := s[2:]
	printSlice(s3)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

在这里插入图片描述

Nil slices

一个 slice 的零值是 nil ,一个 nil 的 slice 的长度和空间都是 0 并且没有其对应的数组。

package main
import "fmt"
func main(){
	var s []int
	fmt.Println(s, len(s), cap(s)) 
	if s == nil {
		fmt.Println("nil!")
	}
}

在这里插入图片描述

Creating a slice with make

slices 可以通过内置的 make 函数来进行创建,这个方法是帮助你如何创建动态长度的数组。

函数 make 分配一个长度为零的数组,然后返回引用该数组的 slice :

a := make([]int, 5) // len(a)=5

如果想要确定其容量,则传第三个参数给 make

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
package main

import "fmt"

func main() {
	a := make([]int, 5)
	printSlice("a", a)

	b := make([]int, 0, 5)
	printSlice("b", b)

	c := b[:2]
	printSlice("c", c)

	d := c[2:5]
	printSlice("d", d)
}

func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}

在这里插入图片描述

Slices of slices

Slices 可以包含任何类型,包括其他的 slices

package main

import (
	"fmt"
	"strings"
)

func main() {
	// Create a tic-tac-toe board.
	board := [][]string{
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
	}

	// The players take turns.
	board[0][0] = "X"
	board[2][2] = "O"
	board[1][2] = "X"
	board[1][0] = "O"
	board[0][2] = "X"

	for i := 0; i < len(board); i++ {
		fmt.Printf("%s\n", strings.Join(board[i], " "))
	}
}

在这里插入图片描述

Appending to a slice

可以在一个 slice 后面加上一个新元素:

func append(s []T, vs ...T) []T

第一个参数 s 是一个类型为 T 的 slice ,剩下的参数是要加到 slice 后面的元素。

函数 append 的结果是一个包含所有原来 slice 再并上新加的元素的 slice

假如说 s 对应的 underlying 的数组过小,那么会分配给它一个更大的数组,返回的 slice 会指向新分配的数组。

package main
import "fmt"
func main(){
	var s []int 
	printSlice(s)
	// append works in nil slices
	s = append(s, 0)
	printSlice(s)
	// The slice grows as needed
	s = append(s, 1)
	printSlice(s)
	// We can add more than one element at a time
	s = append(s, 2, 3, 4)
	printSlice(s)
}
func printSlice(s []int){
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

在这里插入图片描述

Range

range 形式的 for 循环可以在一个 slice 或 map 中遍历迭代所有元素(和 python 很像)

当在 slice 上执行 range 操作时,每次迭代会返回两个值,第一个值是其下标,第二个值是该下标对应的元素的一份拷贝。

package main
import "fmt"
var pow = []int{1,2,4,8,16,32,64,128}
func main(){
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}

在这里插入图片描述
可以用 _ 跳过 index 或者 value:

for i, _ := range pow
for _, value := range pow

假如说只想要 index 的话,第二个可以完全不写:

for i := range pow
package main
import "fmt"
func main(){
	pow := make([]int, 10)
	for i := range pow {
		pow[i] = 1 << uint(i) // == 2**i
	}
	for _, value := range pow {
		fmt.Printf("%d\n", value)
	}
}

在这里插入图片描述

Exercise: Slices

Slices 的一个练习

package main

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

func Pic(dx, dy int) [][]uint8 {
	res := make([][]uint8, dy)
	d := make([]uint8, dx)
	for i := range res{
		for j := range d{
			d[j] = uint8((j+i)/2)
		}
		res[i] = d
	}
	return res
}

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

在这里插入图片描述

Maps

一个 map 将 key 映射为 value

map 的零值是 nil ,其没有任何 key ,也不能添加 key

make 函数返回一个给定类型的 map ,并且已经初始化好了

package main
import "fmt"
type Vertex struct {
	Lat, Long float64
}
var m map[string]Vertex
func main(){
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m["Bell Labs"])
}

在这里插入图片描述

Map literals

map 常量类似于 struct 常量,但是 key 是必须有的

package main
import "fmt"
type Vertex struct {
	Lat, Long float64
}
var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}
func main(){
	fmt.Println(m)
}

Map literals continued

假如说最顶层的类型只是一个类型名,那么可以忽略掉它。

package main
import "fmt"
type Vertex struct{
	Lat, Long float64
}
var m = map[string]Vertex{
	"Bell Labs": {{40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

func main() {
	fmt.Println(m)
}

在这里插入图片描述

Mutating Maps

在一个 map 中插入或更新元素:

m[key] = elem

取一个元素:

elem = m[key]

删除一个元素:

delete(m, key)

测试一个键是否存在:

elem, ok = m[key]

假如 m 里面有 key 的话,ok=true ,否则 ok=false,并且 elem 是元素类型的零值

假如 elem,ok 还没声明过,那可以直接这样写:

elem, ok := m[key]
package main

import "fmt"

func main() {
	m := make(map[string]int)

	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"])

	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"])

	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok)
}

在这里插入图片描述

Exercise: Maps

package main

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

func WordCount(s string) map[string]int {
	res := make(map[string]int)
	for _,v := range strings.Fields(s) {
		_, ok := res[v]
		if ok == true {
			res[v] += 1
		} else {
			res[v] = 1
		}
	}
	return res
}

func main() {
	wc.Test(WordCount)
}

在这里插入图片描述

Function values

函数也是值,它可以像值一样被传递。

函数值可以用作函数的参数以及返回值。

这里就是函数式编程嘛。

package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

在这里插入图片描述

Function closures

Go 函数可以是闭包,一个闭包是一个引用了其范围外面值的一个函数值。这个函数可以访问并修改该引用变量,从这种意义来看,函数和这个变量被绑在了一起。

比如下面这个例子,函数 adder 返回的就是一个闭包,每一个闭包都和自己的 sum 变量所绑定:

package main
import "fmt"
func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum 
	}
}
func main(){
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

在这里插入图片描述

Exercise: Fibonacci closure

实现一个 fibnacci 函数,该函数返回一个函数(闭包),可以返回连续的斐波那契数(0,1,1,2,3,5)

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
	pre := 0
	cur := 1
	return func() int {
		res := pre
		tmp := pre + cur
		pre = cur
		cur = tmp
		return res
	}
}

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

在这里插入图片描述

Methods and interfaces

Methods

Go 没有类!但是可以定义类型上的方法。

一个 method 试有一个特殊的叫做 receiver 的参数的函数

在以下例子中,方法 Abs 有着叫做 v 的类型 Vertex

package main
import (
	"fmt"
	"math"
)
type Vertex struct {
	X, Y float64
}
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}

Methods are functions

method 只是有着 receiver 参数的方程。

以下是用普通的方程写成的 Abs

package main
import (
	"fmt"
	"math"
)
type Vertex struct{
	X, Y float64
}
func Abs(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
	v := Vertex{3, 4}
	fmt.Println(Abs(v))
}

可以在非结构体上的类型声明 method

在以下例子中可以看到一个带有 method Abs 的数值类型 MyFloat

method 中的 receiver 的类型只能是和该 method 在同一个 package 中的类型,而不能是其他 package 中的类型(比如 built-in 类型 int

package main
import (
	"fmt"
	"math"
)
type MyFloat float64
func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}
func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}

比如以下写法就是错的:

package main

import (
	"fmt"
	"math"
)



func (f float64) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

func main() {
	f := float64(-math.Sqrt2)
	fmt.Println(f.Abs())
}

在这里插入图片描述

Pointer receivers

可以定义带有 pointer receivers 的 method

这意味着 receiver 的类型对于一些类型 T 有语法 *T

比如,method Scale 便是定义在 *Vertex 上的。

带有 pointer receivers 的 methods 可以更改 receiver 指向的值,因为 method 通常要修改他们的 receiver,因此 pointer receivers 比 value receiver 更加常见

在 value receiver 的情况下,Scale method 只是在原始 Vertex 值的一份拷贝上进行操作的。

package main
import (
	"fmt"
	"math"
)
type Vertex struct {
	X, Y float64
}
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs()) 
}

在这里插入图片描述
假如把 * 去掉的话,那么 Vertex 的值是不会变化的:

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())
}

在这里插入图片描述

Pointers and functions

此处可以看到把 Abs,Scale 写成方法的形式

package main
import (
	"fmt"
	"math"
)
type Vertex struct {
	X, Y float64
}
func Abs(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
func main() {
	v := Vertex{3, 4}
	Scale(&v, 10)
	fmt.Println(Abs(v))
}

在这里插入图片描述

Methods and pointer indirection

一个接受指针的函数在调用时必须给它一个指针:

var v Vertex
ScaleFunc(v, 5) // Compile error!
ScaleFunc(&v, 5) // OK

而对于一个有着指针 receiver 的 method 来说,该 method 被调用时既可以给他值,也可以给他指针:

var v Vertex
v.Scale(5) // OK
p := &v 
p.Scale(10) // OK

对于 v.Scale(5) ,尽管 v 是一个值而不是一个指针,有着 pointer receiver 的 method 被自动调用。即:出于方便起见,Go 将语句 v.Scale(5) 解释成了 (&v).Scale(5) ,因为 Scale method 需要一个 pointer receiver

package main
import "fmt"
type Vertex struct {
	X, Y float64
}
func (v *Vertex) Scale(f float64) { 
	v.X = v.X * f
	v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
func main() {
	v := Vertex{3, 4}
	v.Scale(2) 
	ScaleFunc(&v, 10)
	p := &Vertex{4, 3}
	p.Scale(3)
	ScaleFunc(p, 8)
	fmt.Println(v, p)
}

在这里插入图片描述
反过来也是一样:

以值作为参数的函数必须接受一个值:

var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // Compile error!

然而以值作为 receiver 的既可以接受值,也可以接受指针:

var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK

在这种情况下,p.Abs() 被解释成了 (*p).Abs()

package main
import (
	"fmt"
	"math"
)
type Vertex struct {
	X, Y float64
}
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func AbsFunc(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
	fmt.Println(AbsFunc(v))
	p := &Vertex{4, 3}
	fmt.Println(p.Abs())
	fmt.Println(AbsFunc(*p))
}

在这里插入图片描述

Choosing a value or pointer receiver

使用 pointer receiver 的原因有两个:

  1. 使得 method 可以修改 receiver 指向的值
  2. 防止在每次调用 method 时都要 copy 一下值,当 receiver 是一个大 struct 时会更有效

以下例子中,Scale,Abs 的 receiver 的类型都是 *Vertex ,尽管 Abs method 不需要修改它的 receiver

总之,在一个给定类型上的所有 method 都必须要么是 value receiver ,要么是 pointer receiver ,但是不能混着用。

package main
import (
	"fmt"
	"math"
)
type Vertex struct {
	X, Y float64 
}
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
	v := &Vertex{3, 4}
	fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
	v.Scale(5)
	fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

在这里插入图片描述

Interfaces

一个 interface 的类型是一系列 method 的签名(signatures)

一个接口类的值可以为任何实现这些 method 的值。

package main

import (
	"fmt"
	"math"
)

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f  // a MyFloat implements Abser
	a = &v // a *Vertex implements Abser

	// In the following line, v is a Vertex (not *Vertex)
	// and does NOT implement Abser.
	a = v

	fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

在这里插入图片描述
报错是因为 Vertex 没有实现 Abser 因为 Abs 只在 *Vertex 上进行了定义(为其指针类型)

Interfaces are implemented implicitly

一个类型通过实现其 method 来实现一个接口,不需要 implements 的关键字。

隐式接口把接口的定义和其实现进行了解耦,由此可以出现在任何 package 中。

package main
import "fmt"
type I interface {
	M()
}
type T struct {
	S string
}
func (t T) M() {
	fmt.Println(t.S)
}
func main() {
	var i I = T{"hello"}
	i.M()
}

Interface values

interface value 可以看作一个有着一个值和一个实际类型的 tuple :

(value, type)

一个 interface value 为一个确定的实在的类型的值

在一个 interface value 上调用一个 method 会执行其对应类型的 method

package main
import (
	"fmt"
	"math"
)
type I interface {
	M()
}
type T struct {
	S string
}
func (t *T) M() {
	fmt.Println(t.S)
}
type F float64
func (f F) M() {
	fmt.Println(f)
}
func main() {
	var i I
	i = &T{"Hello"}
	describe(i)
	i.M()
	i = F(math.Pi)
	describe(i)
	i.M() 
}
func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

在这里插入图片描述

Interface values with nil underlying values

假如接口中的实值是 nil,则这个 method 会以一个 nil receiver 被调用。

在一些语言中,这会导致一个 null pointer 的 exception ,但是在 Go 中并不会这样。

注意:一个 interface value 承载一个 nil 实值,其本身不是 nil

package main
import "fmt"
type I interface {
	M()
}
type T struct {
	S string
}
func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return 
	}
	fmt.Println(t.S)
}
func main() {
	var i I
	var t *T
	i = t
	describe(i)
	i.M()
	i = &T{"hello"}
	describe(i)
	i.M()
}
func describe(i I) {
	fmt.Printf("(%v, &T)\n", i, i)
}

在这里插入图片描述

Nil interface values

一个 nil interface 保存的既不是值也不是实类型

假如调用一个 nil interface 的话将造成一个 run-time error ,因为 interface 下并没有一个实在的 method 可供调用。

package main
import "fmt"
type I interface {
	M()
}
func main(){
	var i I
	describe(i)
	i.M()
}
func describe(i I){
	fmt.Printf("(%v, %T)\n", i, i)
}

在这里插入图片描述

The empty interface

一个规定了零个 method 的接口类型叫做 empty interface

interface()

一个 empty interface 可以保存任何类型的值(因为所有的类型都至少有 0 个 method)

empty interfaces 主要用了处理未知类型的变量,比如 fmt.Print 可以接受任意类型为 interface{} 的参数:

package main
import "fmt"
func main() {
	var i interface{}
	describe(i)
	i = 42
	describe(i)
	i = "hello"
	describe(i)
}
func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

在这里插入图片描述

Type assertions

type assertion 提供了获取接口底下的具体实值的方法:

t := i.(T)

这句话 assert 接口值 i 要有类型 T ,并将 T 的值给了变量 i

假如 i 不存放 T 的话,该语句会触发一个 panic

为了测试一个接口是否存着一个特定的类型,可以:

t, ok := i.(T)

假如 i 存放的是 T ,那么会返回对应的值,ok=true

package main
import "fmt"
func main() {
	var i interface{} = "hello"
	s := i.(string)
	fmt.Println(s)
	s, ok := i.(string)
	fmt.Println(s, ok)
	f, ok := i.(float64)
	fmt.Println(f, ok)
	f = i.(float64) // panic
	fmt.Println(f)
}

在这里插入图片描述

Type switches

switch v := i.(type) {
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}
package main

import "fmt"

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
}

在这里插入图片描述

Stringers

无处不在的一个接口是由 fmt 定义的 Stringer

type Stringer interface {
	String() string
}

一个 Stringer 是一个可以把自己描述成 string 的类型。

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}
	fmt.Println(a, z)
}

在这里插入图片描述

Exercise: Stringers

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (i IPAddr) String() string {
	return fmt.Sprintf("%v.%v.%v.%v", i[0], i[1], i[2], i[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)
	}
}

在这里插入图片描述

Errors

Go 程序通过 error 来表示错误状态

类型 error 是一个类似于 fmt.Stringer 的内置接口

type error interface {
	Error() string
}

fmt.Stringer 一样,fmt 包在输出值的时候会查找 error 这个接口

函数经常会返回一个 error 值,可以通过判断这个返回的值是否是 nil 来对错误进行处理

i, err := strconv.Atoi("42")
if err != nil {
	fmt.Printf("coun't convert number: %v\n", err)
	return 
}
fmt.Println("Converted integer:", i)

一个 error=nil 时说明执行成功。

package main
import (
	"fmt"
	"time"
)
type MyError struct {
	When time.Time
	Watch string
}
func (e *MyError) Error() string {
	return fmt.Sprintf("at %v, %s", 
		e.When, e.What)
}
func run() error {
	return &MyError{
		time.Now(),
		"it didn't work",
	}
}
func main() {
	if err := run(); err != nil {
		fmt.Println(err)
	}
}

在这里插入图片描述

Exercise: Errors

package main

import (
	"fmt"
)

type ErrNegativeSqrt float64 

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.0, ErrNegativeSqrt(x)
	}
	z := 1.0 
	for i:=0; i<10; i++ {
		d := (z*z - x) / (2*z)
		z -= d		
	}
	return z, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

在 method 中 print e 的时候要进行类型转换,要不然会形成无限递归

Readers

io 包定义了 io.Reader 接口,表示要读到一个数据流的结尾。

Go 的标准库有很多这一接口的实现。

接口 io.ReaderRead method:

func (T) Read(b []byte) (n int, err error)

其返回的是读入的字节数以及一个错误信息。

到流结束时会返回一个 io.EOF 的错误信息。

package main
import (
	"fmt"
	"io"
	"strings"
)
func main() {
	r := strings.NewReader("Hello, Reader!")
	b := make([]byte, 8)
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}
}

在这里插入图片描述

Exercise: Readers

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 = b[:cap(b)]
	for i := range(b) {
		b[i] = 'A'
	}
	return cap(b), nil
}

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

在这里插入图片描述

Exercise: rot13Reader

package main

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

type rot13Reader struct {
	r io.Reader
}

func (t rot13Reader) Read(k []byte) (int, error) {
	n, err := t.r.Read(k)
	for i := range(k) {
		if (k[i] >= 'A' && k[i] <= 'M') || (k[i] >= 'a' && k[i] <= 'm') {
			k[i] = byte(int(k[i]) + 13)
		} else if (k[i] > 'M' && k[i] <= 'Z') || (k[i] > 'm' && k[i] <= 'z') {
			k[i] = byte(int(k[i]) - 13)
		}
	}
	return n, err
}

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

在这里插入图片描述

Images

package image 定义了 Image 接口:

package image
type Image interface {
	ColorModel() color.Model 
	Bounds() Rectangle
	At(x, y int) color.Color
}
package main

import (
	"fmt"
	"image"
)

func main() {
	m := image.NewRGBA(image.Rect(0, 0, 100, 100))
	fmt.Println(m.Bounds())
	fmt.Println(m.At(0, 0).RGBA())
}

Exercise: Image

package main

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

import (
	"image"
	"image/color"
)
type Image struct{}

func (i Image) ColorModel() color.Model {
	return color.RGBAModel
}

func (i Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, 250, 250)
}

func (i Image) At(x, y int) color.Color {
	return color.RGBA{uint8(x),uint8(y),255,255}
}
func main() {
	m := Image{}
	pic.ShowImage(m)
}

在这里插入图片描述

Concurrency

Goroutines

goroutine 是一个轻量级线程。go f(x, y, z)

开始一个开始运行一个新的 goroutine:f(x, y, z)

f,x,y,z 是在当前 goroutine 下 evaluate 的,f 的执行发生在新的 goroutine 中。Goroutines 在相同的地址空间中运行,因此访问共享内存时需要同步。

package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}

在这里插入图片描述

Channels

可以通过 <- 符号来向 channel 收发信息:

ch <- v // Send v to channel ch.
v := <-ch // Receive from ch and assign value to v.

在使用之前必须声明:

ch := make(chan int)

默认情况下,发送和接收都会 block 另一边,因此不需要显式的锁或者条件变量来进行同步操作。

package main
import "fmt"
func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}
func main() {
	s := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c
	fmt.Println(x, y, x+y)
}

在这里插入图片描述

Buffered Channels

可以通过 make 来初始化 channel 的缓冲区:ch := make(chan int, 100)

Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.

package main
import "fmt"
func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

if overfill:

package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	ch <- 3
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

在这里插入图片描述

Range and Close

一个发送者可以关掉 channel 来说明他不会再发数据了,接收者可以通过以下方式来确定一个 channel 是否已经被关上:

v, ok := <-ch

假如 channel 已关,ok = false

只有发送者才能关 channel。Sending on a closed channel will cause a panic.

注:channel 和文件不太一样,关闭并不是必要的。

package main
import (
	"fmt"
)
func fibnacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}
func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}

在这里插入图片描述

Select

select 可以让一个 goroutine 在多个通讯操作上进行等待

A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

在这里插入图片描述

Default Selection

假如没有 case 准备好了,那么就会运行 default case

select {
case i := <- c:
	// use i
default:
	// receiving from c would block
}
package main
import (
	"fmt"
	"time"
)
func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <- tick:
			fmt.Println("tick.")
		case <- boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}

在这里插入图片描述

Exercise: Equivalent Binary Trees

tree package 定义了类型:

type Tree struct {
	Left *Tree
	Value int
	Right *Tree
}

在这里插入图片描述
练习解答如下:

package main

import "golang.org/x/tour/tree"
import (
	"fmt"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int){
	if t==nil {
		return 
	}
	Walk(t.Left, ch)
	ch <- t.Value
	Walk(t.Right, ch)
}

// Same determines whether the trees
// t1 and t2 contain the same values.
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 := 0; i < 10; i++ {
		c1,c2 := <-ch1, <-ch2
		if c1 != c2 {
			return false
		}
	}
	return true
}

func main() {
	fmt.Println(Same(tree.New(1), tree.New(1)))
	fmt.Println(Same(tree.New(1), tree.New(2)))
}

sync.Mutex

我们已经看到了 channel 很适合于 goroutines 之间的通信

但是假如我们不需要通信呢?假如每一次只能有一个 goroutine 能访问变量呢?

这个概念叫做 mutual exclusion (互斥) ,提供互斥的数据结构一般叫做 mutex

通过 sync.Mutex 来提供互斥,以及其两个 methods :

Lock
Unlock

可以通过在代码块加上 Lock,Unlock 来使得这部分互斥执行

也可以使用 defer 来确保 mutex will be unlocked

package main

import (
	"fmt"
	"sync"
	"time"
)

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mux.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mux.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mux.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

Exercise: Web Crawler

package main

import (
	"fmt"
	"sync"
	"time"
)

type Fetcher interface {
	// Fetch returns the body of URL and
	// a slice of URLs found on that page.
	Fetch(url string) (body string, urls []string, err error)
}
type SafeCounter struct {
	v map[string] int
	mux sync.Mutex
}
var c = SafeCounter{v: make(map[string]int)}
func (c *SafeCounter) Inc(key string){
	c.mux.Lock()
	c.v[key]++
	c.mux.Unlock()
}
func (c *SafeCounter) Value(key string) (int, bool){
	c.mux.Lock()
	elem, ok := c.v[key]
	defer c.mux.Unlock()
	return elem, ok
}
// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
	// TODO: Fetch URLs in parallel.
	// TODO: Don't fetch the same URL twice.
	// This implementation doesn't do either:
	if depth <= 0 {
		return
	}
	_, ok := c.Value(url)
	if ok == true {
		return
	}
	c.Inc(url)
	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	for _, u := range urls {
		go Crawl(u, depth-1, fetcher)
	}
	return
}

func main() {
	Crawl("https://golang.org/", 4, fetcher)
	time.Sleep(5*time.Second)
}

// fakeFetcher is Fetcher that returns canned results.
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 is a populated fakeFetcher.
var fetcher = fakeFetcher{
	"https://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"https://golang.org/pkg/",
			"https://golang.org/cmd/",
		},
	},
	"https://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"https://golang.org/",
			"https://golang.org/cmd/",
			"https://golang.org/pkg/fmt/",
			"https://golang.org/pkg/os/",
		},
	},
	"https://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
	"https://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
}

在这里插入图片描述

posted @ 2020-05-19 20:18  winechord  阅读(251)  评论(0编辑  收藏  举报