文章目录
- Basics
- ------ Packages, variables and functions -------
- Packages
- Imports
- Exported names
- Functions
- Multiple results
- Named return values
- Variables
- Variables with initializers
- Short variable declarations
- Basic types
- Zero values
- Type conversions
- Type inference
- Constants
- Numeric Constants
- ------ Flow control statements: for, if, else, switch and defer ------
- For
- If
- If with a short statement
- If and else
- Exercise: Loops and Functions
- Switch
- Switch with no condition
- Defer
- Stacking defers
- ------ More types: structs, slices, and maps ------
- Pointers
- Structs
- Struct Fields
- Pointers to structs
- Struct Literals
- Arrays
- Slices
- Slices are like references to arrays
- Slice literals
- Slice defaults
- Slice length and capacity
- Nil slices
- Creating a slice with make
- Slices of slices
- Appending to a slice
- Range
- Exercise: Slices
- Maps
- Map literals
- Map literals continued
- Mutating Maps
- Exercise: Maps
- Function values
- Function closures
- Exercise: Fibonacci closure
- Methods and interfaces
- Methods
- Methods are functions
- Pointer receivers
- Pointers and functions
- Methods and pointer indirection
- Choosing a value or pointer receiver
- Interfaces
- Interfaces are implemented implicitly
- Interface values
- Interface values with nil underlying values
- Nil interface values
- The empty interface
- Type assertions
- Type switches
- Stringers
- Exercise: Stringers
- Errors
- Exercise: Errors
- Readers
- Exercise: Readers
- Exercise: rot13Reader
- Images
- Exercise: Image
- Concurrency
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
给一个数 x x x ,计算 z z z 使得 z 2 z^2 z2 尽可能靠近 x x x 。
可以使用迭代法,从一个初始的 z z z 开始:
z = z − z 2 − x 2 z z=z-\frac{z^2-x}{2z} z=z−2zz2−x
(这个是牛顿迭代法)设 f ( z ) = z 2 − x f(z)=z^2-x f(z)=z2−x,即求该方程的零点。取某一点 ( z n , f ( z n ) ) (z_n,f(z_n)) (zn,f(zn)) ,则该点的切线方程为
f ( z n + 1 ) − f ( z n ) = f ′ ( z n ) ( z n + 1 − z n ) f(z_{n+1})-f(z_n)=f'(z_n)(z_{n+1}-z_n) f(zn+1)−f(zn)=f′(zn)(zn+1−zn)
以该切线方程和 z z z 轴的交点作为下一个估计的 z n + 1 z_{n+1} zn+1 ,即:
z n + 1 = z n − z n 2 − x 2 z n z_{n+1}=z_n-\frac{z_n^2-x}{2z_n} zn+1=zn−2znzn2−x
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} 2:
计算
3
\sqrt{3}
3:
计算
4
\sqrt{4}
4:
计算
5
\sqrt{5}
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 的原因有两个:
- 使得 method 可以修改 receiver 指向的值
- 防止在每次调用 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.Reader
有 Read
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/",
},
},
}