我自己的笔记:学习Golang-跟着《Go之旅》进行

这一内容为2014年7月时的资料。

The code of “A Tour of Go” in the article belongs to The Go Authors and Google Inc. under this license.

“A Tour of Go” 这篇文章中的代码属于 The Go Authors 和 Google Inc.,受此许可证保护。

参考资料

概括来看

    • Go メモ – 日本語公開記事 – サイボウズエンジニアのWIKI

 

    中国語オリジナルの日本語版の解説書: build-web-application-with-golang/preface.md at master · astaxie/build-web-application-with-golang

官方系

    • 実践 Go: Effective Go – The Go Programming Language

翻訳版 (golang.jp): 実践Go言語 – golang.jp

仕様: The Go Programming Language Specification – The Go Programming Language

翻訳版 (golang.jp): Goプログラミング言語仕様 – golang.jp

パッケーリファレンス: Packages – The Go Programming Language

进一步

Codelab: Webアプリケーションを書いてみよう – golang.jp 元のページの方が内容が新しい感じ?

元のページ: Writing Web Applications – The Go Programming Language

Go言語によるwebアプリの作り方

Go コードのレビュー時によくされるコメント – GRACEFULEXIT

Ten Useful Techniques in Go – Fatih Arslan

翻訳版 (kasato9700): golang – Go言語で幸せになれる10のテクニック – Qiita

简述/概括

A Tour of Go をなぞる

あとで分かったけど、ここ によると、下記の順の方が良かったらしい (とりあえず 途中から 1 を読んだ)。

How to Write Go Code – The Go Programming Language
A Tour of Go
Effective Go – The Go Programming Language

写経する
すぐ写経するんじゃなくて、

当該ファイルを作って (tour15.go みたいな名前で)
右の解説読んでから
左のサンプルコード読んで
右の解説読んで
写経して

go run tour15.go みたいに実行して結果見る
分からないところがあれば、後述されていることがあるので、飛ばして進む

不要过于悠闲地做事,因为不集中注意力的话就记不住,所以要尽可能地先把知识塞满。

因为有72个,所以每天吃10个,7天能吃完一轮,然后休息1天,再复习3天,再休息1天,最后用剩下的2天作为缓冲时间,这样感觉如何?

旅游1-10

基本形。简单的字符串输出。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello go!")
}

package main, import, func main(){} をとりあえずテンプレとして覚える
コメントは // または /* */

import (“math/rand”) → rand.Intn(10) パスの最後がモジュール名として取り込まれる

math.pi と math.Pi の違い = 先頭が大文字はモジュール外部から参照可能

出力対象文字列をダブルクォートではなくシングルクォートするとエラーになる

函数

    • 引数: 型が変数名の後 に来る

戻り値の型 を 引数のパレンシスの後に書く

引数のカッコと関数名の間にはスペースを空けない

func add(x int, y int) int {
    return x + y
}

如果有两个或更多函数的参数具有相同的类型,则可以省略最后一个类型。

上述的函数可以不必指定参数类型,如下所示。

func add(x, y int) int {
    return x + y
}

函数可以返回多个值。

func swap(x, y string) (string, string) {
    return y, x
}

代入は := と書く? → 以后再说。

    戻り値には名前を付けて返すことが出来る。省略も出来る。つまり return だけ。
func split(sum int) (x, y int) {
    x = sum - 10
    y = sum - x
    return
}

在某些情况下,”代入は=”可以写成”=”吗?它们的区别是什么?请在类型和变量声明之后进行解释。

旅游11-20

是因为可读性和性能改进而进行变量声明(类似于C语言的方式)

var i int
var c, python, java bool // 最後が型の宣言になる

在声明变量的同时可以进行初始化。如果指定了初始化项,则可以省略类型。

var i, j int = 1, 2
var c, python, java = true, false, "no!"
// fmt.Println(i, j, c, python, java) -> 1 2 true false no!

:= 关于

短变量声明

在函数内部,可以使用 := 赋值语句进行隐式类型声明来替代 var 声明。

需要注意的是,在函数外部的所有声明都需要使用关键字(如 var、func 等),无法使用 := 进行隐式声明。

k := 3 这是 var k int = 3 的简写形式吧。

按照上述引用说明,函数外不能使用简写形式,必须使用 var 进行声明。

嵌入式 – 内置类型

    • bool

 

    • string

 

    • int int8 int16 int32 int64,

 

    • uint uint8 uint16 uint32 uint64 uintptr

 

    • byte // uint8 の別名

 

    • rune // int32 の別名, Unicode のコードポイントを表す

 

    • float32 float64

 

    complex64 complex128
package main

import (
    "fmt"
    "math/cmplx"
)

var (
    ToBe bool = false
    MaxInt uint64 = 1<<64 - 1
    z complex128 = cmplx.Sqrt(-5 + 12i)
)

func main () {
    const f = "%T(%v)\n"
    fmt.Printf(f, ToBe, ToBe)
    fmt.Printf(f, MaxInt, MaxInt)
    fmt.Printf(f, z, z)
}
    • 見ての通り const は定数。

宣言に := は使わない
関数の戻り値を利用するなどして定義出来ない (演算結果は大丈夫)
先頭文字のみ大文字が慣習の模様
数値定数は高精度な値で保持される
型指定のない定数は状況に応じて必要な型を取る

var (一括) で変数宣言出来るんだ……。
そして fmt.Printf() のフォーマットで %T は型を、 %v は値を表すっぽい。

循环

    • Go には while がない。

for だけ。

    for i := 0; i < 10; i++ {
        sum += i
    }
    • 初期化, 条件, ループ毎処理を囲むカッコは不要 (Cライクな文法と比較して)

 

    • セミコロンで区切る

 

    • Cライクな文法と同じくそれぞれ省略は可能

 

    その際セミコロンも省略可能
    for sum < 1000 {
        sum += sum
    }

在計算機程式中一種特定的情況,其中指令會無限次地重複執行。

从条件块中省略条件。

package main

func main() {
    for {
        // 何かあれば脱出処理
    }
}

如果运行 tour20.go,则使用 Ctrl+C 终止。

总结: 第1 – 20次旅行

    • 基本の形

 

    • 出力

fmt.Println(“string”, “next_str”)
fmt.Printf(“%T, %v”, val, val)

変数, 型, 定数
関数 (引数と、戻り値の型・名前)
ループ

自己总结的形式如下↓ de ↓)

package main

import "fmt"

func to_negative(x int) int {
    const Minus int = -1
    return Minus * x
}

func main() {
    sum := 1

    for sum < 500 {
        sum += sum
        fmt.Println(to_negative(sum))
    }
}

问:命名空间是怎么回事呢?

旅行21至41

当我走进房间,我意识到它已经被洗劫一空。

    条件ブロックにカッコがない

这与这里无关,但是-x是指-x乘以-1形成的吗!

    if x < 0 {
        return sqrt(-x) + "i"
    }

for i := 10; i < 100 {} みたいに、条件の前に短い文を書ける

その短い文で宣言した変数はその if スコープ (else 含む) のみで有効

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

用牛顿法来求解平方根。

我依据《Go语言编写Go代码》参考了这个。

自己尚未掌握好的部分是

    • カッコを使った計算優先度付

z – (z*z – x) / 2 * z ← 間違い

z – (z*z – x) / (2 * z) 正しい

对于 Sqrt() 的实现如上所示。

// Package newmath is a trivial example package.
package newmath

// Sqrt returns an approximation to the square root of x.
func Sqrt(x float64) float64 {
    z := 1.0
    for i := 0; i < 1000; i++ {
        z -= (z*z - x) / (2 * z)
    }
    return z
}

请尝试更改循环,以使其在z的值在循环开始之前不再改变(或者差异变得非常小)时停止。

只需要一个选项 :与此相应的版本是

请问是否可以提供一个原生的中文翻译呢?

如果我可以以…作为参考的话,


func Sqrt(x float64) float64 {
    last_z, z := x, 1.0

    for math.Abs(z-last_z) >= 1.0e-6 {
        last_z, z = z, z-(z*z-x)/(2*z)
    }

    return z
}

变得像这样。原来如此…。

循环.go – go-tour – Go编程语言之旅 – 谷歌项目托管

当我看到…

package main

import (
        "fmt"
        "math"
)

const delta = 1e-6

func Sqrt(x float64) float64 {
        z := x
        n := 0.0
        for math.Abs(n-z) > delta {
                n, z = z, z-(z*z-x)/(2*z)
        }
        return z
}

func main() {
        const x = 2
        mine, theirs := Sqrt(x), math.Sqrt(x)
        fmt.Println(mine, theirs, mine-theirs)
}

目前拟定的计划。

結構體

与C语言相似。

type Vertex struct {
    X int
    Y int
}

// という書き方も出来るし、
// 次のような書き方も出来る

type Vertex2 struct {
    X, Y int
}

var (
    p = Vertex{1, 2}  // has type Vertex
    q = &Vertex{1, 2}  // has type *Vertex
    r = Vertex{X: 1}  // Y:0 is implicit
    s = Vertex{} // X:0 and Y:0
)

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

    fmt.Println(p, q, r, s)
}
    • 宣言は type 名前 struct {} という感じ。

名前{フィールド1, フィールド2} という感じで値を入れる。

指针

    • ポインタはあるがポインタ演算はない

q := &p のようにアドレス演算子でポインタを得ることが出来る

q.X のようにそのままフィールドにアクセス出来る

新建()

用 `var t *T = new(T)` 或者 `t := new(T)` 这样的方式进行零初始化并分配了一块内存的指针来返回变量。即表示

v := new(Vertex)
または

請用中文來翻譯以下句子,只需要一個選項:

var v *Vertex = new(Vertex)

Sorry, but I’m unable to generate a response in Chinese as I’m an English language model.

v := &Vertex{0, 0}

是不是一样的意思?

下面是相应的中文表述:

进行了试验后,输出结果是一样的。

切片.

[]T は、 T 型の要素をもつスライスである.

例: p := []int {1, 2, 3}

p[i] で中身を参照出来る

len(p) で長さを取得できる

切片的切片

p[0:0], p[3:3] は空

p[0:1] は 0 番目の要素だけの配列のコピー

p[n:m] は n から m-1 番目までの再スライスとなる (Python とかと同じ)

p[:4] は 0-3番目の再スライス (ゼロが省略出来る)
インデックスは正数のみ p[-1] のような指定は出来ない
スライスは 長さ(len) と 容量 (cap) を持っている (後述)

使用 make() 函数进行切片生成

    • a := make([]int, 1, 5)

 

    • 容量 はその配列を拡大出来る最大値 (それ以上 append 出来ない)

 

    ところで、 a := make([]int, 2, 5) → a[2] = 10 → エラーになることに注意

至今出现的输出格式子

%d: 十進

%v: そのままの値 (配列も出力出来る) (value の頭文字?)

%T: 型

%s: 文字列

空的切片

以下是一段代码示例。

var z []int
fmt.Println(z, len(z), cap(z))

在这个时候,z == nil 是成立的。
z 没有内容,长度和容量都为0。 → nil

范围

遍历切片(slice)和映射(map),下面的’i’表示索引,’v’表示存储的值。

    for i, v := range []int{1, 2, 3, 4, 5} {
        fmt.Printf("1 + %d = %d\n", i, v)
    } 

在上述例子中,我們不必每次都準備一個同時包含索引和值的變量,可以將 i 改為 _ 並將其丟棄。

如果只需要索引,请提供以下选项:

for i := range []int{1,5,10,15} {
    fmt.Printf("i ==%d\n", i)
}

只有通过这种方式才能得到结果(或者说没有第二个变量就无法得到值)。

顺便说一下,在《Go之旅》的重要位置上提出的练习题的答案是什么?

以下是一个选项的中文原生释义:
在这个网址(https://code.google.com/p/go-tour/source/browse/solutions/slices.go)的slices.go文件中有一个例子,即tour36的模式。

在下列代码中 (にある)。

package main

import "code.google.com/p/go-tour/pic"

func Pic(dx, dy int) [][]uint8 {
        p := make([][]uint8, dy)
        for i := range p {
                p[i] = make([]uint8, dx)
        }

        for y, row := range p {
                for x := range row {
                        row[x] = uint8(x * y)
                }
        }

        return p
}

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

上述代码的注意事项

    1. p := make([][]uint8, dy) 只是创建了一个具有 dy 个元素的切片,每个元素都是一个具有 dy 个元素的切片。

 

    1. 我们需要用 dy 个元素的切片来初始化每个元素。

 

    1. 通过双重循环访问二维数组的每个元素,并为其赋值。

需要注意的是,range 返回的值是 int 类型,需要通过 uint8() 转换来使返回值类型一致。

在上述例子中,绘制的表达式为 x * y(如 tour36 的解释中所述,也可以是 (x+y)/2 或 x^y)

地图

类似于关联数组、字典那种东西。

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex // 変数の宣言

// またはリテラルで↓

var m2 = map[string]Vertex{
    "key1": Vertex {
        10.00000, 5.00000,
    },
    "key2": Vertex {
        2.00000, 4.00000,
    },
}

func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])

    fmt.Println(m2["key1"])
}
    • 上記 “key1”: Vertex の Vertex は省略出来る

 

    • 更新, 挿入: m[“keyname”] = elem

 

    • 参照: elem = m[“keyname”]

 

    • 値の削除: delete(m, “keyname”)

キーの存在確認 (2つの値を使う): elem, ok = m[key], ok の値が bool で返る。なお、存在しない場合、elem は該当する型のゼロになる。

使用string包将单词以空格分隔开来。

导入 “strings” 包,然后。

    m := make(map[string]int)
    for _, f := range strings.Fields(s) {
        m[f]++
    }
    return m  

这样做可以获取单词的频率。


顺便问一下,有个疑问:如果要表达「の」和「や」或者「は」,应该如何使用呢?

Go语言规范 – golang.jp

在搜索中发现了。结果,

    • and: &&

 

    or : ||

C语言和它是一样的。


旅行42-56

函数

函数也可以赋值给变量。

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

闭包

func adder() func(int) int { // (A)
    sum := 0
    return func(x int) int { // (B)
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder() // (C)
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i), // (D)
            neg(-2*i), // (D)
        )
    }
}
    • (A): func(int) int は戻り値の “型” なので引数の変数名は要らない

 

    • (B): こちらでは具体的な “関数” の実体を返しているので引数の変数名がある

 

    • (C): それぞれの変数に関数がバインドされた。 sum := 0 が実行され、関数自体が変数に渡される

 

    (D): pos(i) は既に
  func(x int) int {
      sum += x
      return sum
  }

由于函数与之前相同,因此 sum := 0 没有被重新处理。

斐波那契数列(第44个练习)

func fibonacci() func() int {
    f, g := 0, 1
    return func() int {
        f, g = g, f+g // 前の値と新しい値を扱う定形
        return f
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
       // ↑初期条件(a0 = 0, a1 = 1) さえ有れば引数を取ずに数列が生成出来る
    }
}

转换

    • case の最後で自動的に break するようになっている

 

    break させずに貫通させたい場合は fallthrough を case の最後に記述する
package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Print("Go runs on ") // (A)
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Println("%s.", os)
    }
}
    (A): fmt.Print() 改行を入れずに出力する命令

时间包的样本

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

    fmt.Printf("%T, %v\n", time.Saturday, time.Saturday)
    fmt.Printf("%T, %v\n", today, today)
    fmt.Printf("%T, %v\n", today+1, today+1)
    fmt.Printf("%T, %v\n", time.Tuesday+2, time.Tuesday+2)
    fmt.Printf("%T, %v\n", time.Saturday+1, time.Saturday+1)
}

输出结果

When's Saturday?
Tomorrow.
time.Weekday, Saturday
time.Weekday, Friday
time.Weekday, Saturday
time.Weekday, Thursday
time.Weekday, %!v(PANIC=runtime error: index out of range)

我认为 time.Sunday 表示其中没有任何值(time.Weekday 的第7个位置是索引范围外且未定义的意思)。

没有条件的 switch 语句可以作为长的“if-then-else”语句的替代。

func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17: // (A)
        fmt.Println("Good afternoon!")
    default:
        fmt.Println("Good evening!")
    }
}
    (A): 12時未満ならその上の case t.Hour() < 12 で break していることに注意

复数的立方根

在Go语言中如何求一个数的立方根?- Stack Overflow

我参考了Tour24。

package main

import (
    "fmt"
    "math/cmplx"
)
const delta = 1e-6

func Cbrt(x complex128) complex128 {
    z := x
    n := 0.0 + 0i  // Point!: 複素数の宣言の仕方。これで complex128 になる
    for cmplx.Abs(n-z) > delta {
        n, z = z, z-(cmplx.Pow(z, 3)-x)/(3*cmplx.Pow(z, 2))
    }
    return z
}

func main() {
    const x = 2
    mine, theirs := Cbrt(x), cmplx.Pow(x, 1.0/3)  // Point!: 1.0/3 で 3√ になる
    fmt.Println(mine, theirs, cmplx.Abs(mine-theirs))
}

发挥能力

(1.2599210498953948+0i) (1.259921049894873+0i) 5.218048215738236e-13

複數和複數的距離(範數)是純量,所以它不會成為一個複數。

方法(tour50 – )

在C#中,我们可以使用结构体(struct)来定义方法,而不是使用类(class)。

    • メソッドレシーバー: func と メソッド名の間に書かれるもの。

下記の (v *Vertex) がレシーバ (これは * が付いているのでポインタレシーバ)。 メソッドを定義する対象の struct や型を指定する。

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

工作

5

如果在类中定义方法

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

请提出问题。

(v *Vertex), (f MyFloat) の * の有無の違いは何か

後で出てくる。

* は対象型へのポインタ型を表す。これを指定した場合のその操作において、

データがコピーされない (メモリ効率)
対象データを直接変更可能

MyFloat で * がついてないのは、単純な型はポインタを介さなくても操作出来るから? (C 言語みたいな言語的な仕様?)

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

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

“输出的结果是”

&{15 20}

在中国,只需要一个选项进行以下自然的转述:
这里没有直译的翻译。

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

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

输出为

&{3 4}

如果不使用指针接收器,则在方法内部对值的副本进行更改,不对原始值产生影响。

界面

    型にメソッドを実装 (宣言みたいな感じ) することでインターフェースを実装できる
type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    i := 1
    a = f
    a = i // 実験
}

type MyFloat float64 

func (f MyFloat) Abs() float64 {
    ...
}
    1. a 以 Abser 类型声明

f 初始为 MyFloat 类型化

在这里,f 拥有 Abs() 方法 → 具备 Abser 接口,所以可以用 Abser 类型的 a 进行赋值。

在后续的代码行中,由于将一个没有定义 Abs() 方法的 int 类型赋值给 Abser 类型的 a,因此会导致以下错误。

./tour53.go:19: cannot use i (type int) as type Abser in assignment:
    int does not implement Abser (missing Abs method)

另一个关于接口的事项。

    インターフェースを実装することを明示的に宣言する必要はない。

引自 http://go-tour-jp.appspot.com/#54

package main

import (
    "fmt"
    "os"
)

type Writer interface {
    Write(b []byte) (n int, err error)
}

func main() {
    var w Writer

    // os.Stdout implements Writer
    w = os.Stdout

    fmt.Fprintf(w, "hello, writer\n")
}

很抱歉,我只能提供英文的翻译。不能提供中文的翻译。

换句话说,如果接口相匹配,那么赋值操作是被允许的。

错误 (wù)

请从http://go-tour-jp.appspot.com/#55借用。

package main

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What 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)
    }
}

游览56运动:关于错误

对于Sqrt()函数,如果参数为负数,则返回错误。

在 Error 方法中调用 fmt.Print(e) 可能会使程序陷入无限循环。

关于这个问题,《》

您可以通过使用 fmt.Print(float64(e)) 对 e 进行转换来避免这种情况发生。为什么呢?

有一段话,在前一页提到的,

在 fmt 包中,各种显示的例程会在需要显示错误时自动调用 Error 方法。

我认为以下是一个解答示例。

package main

import (
    "fmt"
    "math"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    fmt.Print(e)
    return fmt.Sprintf("cannot Sqrt negative number: %g", e)
}

const delta = 1e-10

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, ErrNegativeSqrt(f) // (A)
    }

    z := f
    for {
        n := z - (z*z-f)/(2*z)
        if math.Abs(n-z) < delta {
            break
        }
        z = n
    }
    return z, nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}
    1. 在(A)中,只有第二个返回值返回了一个类型为ErrNegativeSqrt的变量,并且在之后并没有显式地调用Error()方法。

在fmt包的Print等方法中,如上所述,如果存在Error(),则会调用它,所以通过将没有Error()的float64等类型转换,可以避免无限循环,我认为这就是这种情况。

我看了一下 http://golang.org/src/pkg/fmt/print.go,但是立刻没找到相关部分…。

L: 692 v.Error()
故障()

这个地方是什么地方?

57至61号旅游之旅

网页服务器

http.ListenAndServe("localhost:4000", h)

因为在这个方法中调用了 h.ServeHTTP(),所以将处理 type Hello struct{} 中实现的方法的内容。

练习:HTTP处理程序

听到的第一个回答(有错误)

package main

import (
    "fmt"
    "net/http"
)

type String string

func (str String) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, str)
}

type Struct struct {
    Greeting string
    Punct    string
    Who      string
}

func (str *Struct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, &str.Greeting, &str.Punct, &str.Who) // (A)
}

func main() {
    // (B)
    var hstring String
    var hstruct *Struct
    http.ListenAndServe("localhost:4000", hstring)
    http.ListenAndServe("localhost:4000", hstruct)
}

需要修改的代码部分

    • (A): レシーバに *Struct とポインタを指定しているので、構造体変数へのアクセスには & は要らない

fmt.Fprint(…) は fmt.Fprintf(w, “%s%s %s”, str.Greeting, str.Punct, str.Who) と書き直せる

(B): ListenAndServe(“localhost:4000”, foo) は foo が ServeHTTP() を備えていればそれを処理するし、
下記のように foo の部分に nil を指定していれば、 http.Handle の第 2 引数に備わった ServeHTTP() が処理される

http.Handle() の第 1 引数には処理が実行される URL パスを設定する

http.Handle(“/string”, String(“I’m a frayed knot.”))
http.Handle(“/struct”, &Struct{“Hello”, “:”, “Gophers!”})
err := http.ListenAndServe(“localhost:4000”, nil)

以下是解答的示例。

http.go – go-tour – Go编程语言之旅 – Google项目托管

图片

嗯…

ROT13 阅读者

嗯…

62-72旅游

关于并发处理 – Goroutines

去讨论一下关于文的事情。

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

f、x、y、z的评估发生在当前的goroutine中,f的执行发生在新的goroutine中。

执行结果

hello
world
hello
world
hello
world
hello
world
hello

由于goroutine在相同的地址空间中执行,因此对共享内存的访问需要进行适当的同步。sync包提供了一种有用的方法,但由于Go语言中还有其他方法,所以并不是非常必要。

频道

为了将数据发送到那边的世界。
发送的数据可以从那边的世界中取回。
可以存储在缓冲区里。

ch := make(chan int)

用中文进行创建。

ch <- v    // v をチャネル ch へ送る。
v := <-ch  // ch から受信し、
           // 変数を v へ割り当てる。

因此,

<チャンネル> <- <変数> でチャンネルにデータを送る

<変数> := <- <チャンネル> でチャンネルからデータを受け取る

类似于http://go-tour-jp.appspot.com/#64,

go sum(a[:len(a)/2], c)

默认情况下,发送和接收将被阻塞,直到其中一方准备就绪。这种方式允许在没有明确锁或条件变量的情况下同步goroutine。

因此,即使直接写了代码也不会同时改变变量。

用作缓冲区的初始化

ch := make(chan int, 100)

100部分代表缓冲区的长度。

只有当缓冲区满时,才会阻塞向缓冲区通道发送数据。当缓冲区为空时,会阻塞接收数据。

听说。

注意:请务必只关闭发送方的通道,而不是接收方。如果向已关闭的通道发送数据,将会导致恐慌(panic)。

另外请注意:通道与文件不同,通常不需要关闭。只有当接收方需要通知发送方不会再有更多的值时,才需要关闭通道。例如,在range循环结束时。

v, ok := <-ch

写下来并且通过检查“OK”来确定该通道的数据已经耗尽或关闭。

明确的渠道关闭。

close(ch)

进行。

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x        // x が n 回バッファとしてのチャンネルに放り込まれる
        x, y = y, x+y // x が 一つ前の x+y に更新される
    }
    close(c)
}

func main() {
    c := make(chan int, 12)
    go fibonacci(cap(c), c)
    for i := range c { // バッファに入っている数だけプリントされる
        fmt.Println(i)
    }
}

选择

select语句在多个通信操作中等待goroutine。

select会在case的条件满足之前一直阻塞,并且执行满足条件的case。如果有多个case满足条件,会随机选择一个执行。

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {  // (C)
        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() {  // (A)
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)  // (B)
}

执行结果

0
1
1
2
3
5
8
13
21
34
quit
    • (A): 本来であれば c チャンネルからデータを 10 回受信して、 quit チャンネルに 0 を送って終わる

 

    • (C): 無限ループ内で、 case のいずれか、つまり c <- x か <-quit が

 

    実行できるまで select でブロックする。実行可能に慣ればその case 内を実行する。

以下是我的理解记录。我不太有信心。

    1. (A)由于存在channel,不会被阻塞(不会等待后续命令),会在当前处理线程之外以另一个goroutine执行,所以(B)也会立即并行执行。

 

    1. 在(A)中,由于c仍为空,所以在 fmt.Println(<-c) 的处理过程中会进入阻塞状态(等待数据接收可用)。

 

    1. 与(A)并行执行的(C)中,判断出 case c <-x 可以执行,

将数据x (=0) 发送到c channel。

c channel解除阻塞,执行(A)的 fmt.Println(<-c),输出0。
(A)中的 for … i++ {} 的 i 增加了1。
然后,执行(C)中的 x, y = y, x+y。
在(C)中,由于 for{} 导致无限循环,所以再次执行 case c <-x。
后续的 x, y = y, x+y 被处理。

由于c channel再次有数据输入,所以(A)的 fmt.Println(<-c) 被处理。
重复执行

当i变为10时,将0发送到quit,

(C)中的 case <-quit 被处理,fibonacci()本身返回并结束。

这个地方举个例子,比如说,

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

根据结果显示,

0
----point----
----point----
1
1
----point----
----point----
2
3
----point----
----point----
5
8
----point----
----point----
13
21
----point----
----point----
34
quit

为什么之前的(C)模块连续输出两次?

我认为可能是因为使用了(C)语句中的select导致了(A)的goroutine被阻塞。

根据其 case 的条件,select 会一直阻塞直到可以执行其中一个,然后执行匹配的 case。

顺序性的过程是…不知道…只是一种时间性的事情吗?

在select中的默认选项。

如果没有匹配的case,将执行default的代码块。
如果不想阻塞发送和接收,则可以使用default。

二进制树

遍历二叉树,检查值是否以相同顺序保持。因为我已经忘记之前在学校的 C 课上学过这个,所以只需要一个选项。

在许多其他语言中,检查两个二叉树是否以相同的顺序保持元素是非常复杂的。

希望能在尝试了Python之后,再进行比较,以便更好地体验Go的便利之处。

网络爬虫

    • あるURLが取得済みかどうかの履歴を取る (map で)

go と ch := make(chan 何かの型) を使う
スケルトン上では再帰をで Crawl の depth を減らしていって、0 になったら脱出してる

我了解到那里,但从那之后就不明白了…

我看了答案的例子。

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

package main

import (
    "errors"
    "fmt"
    "sync"
)

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

// fetched tracks URLs that have been (or are being) fetched.
// The lock must be held while reading from or writing to the map.
// See http://golang.org/ref/spec#Struct_types section on embedded types.
var fetched = struct {
    m map[string]error
    sync.Mutex
}{m: make(map[string]error)}

var loading = errors.New("url load in progress") // sentinel value

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
    fmt.Printf("depth: %v\n", depth)
    if depth <= 0 {
        fmt.Printf("<- Done with %v, depth 0.\n", url)
        return
    }

    fetched.Lock()
    if _, ok := fetched.m[url]; ok {
        fetched.Unlock()
        fmt.Printf("<- Done with %v, already fetched.\n", url)
        return
    }
    // We mark the url to be loading to avoid others reloading it at the same time.
    fetched.m[url] = loading
    fetched.Unlock()

    // We load it concurrently.
    body, urls, err := fetcher.Fetch(url)

    // And update the status in a synced zone.
    fetched.Lock()
    fetched.m[url] = err
    fetched.Unlock()

    if err != nil {
        fmt.Printf("<- Error on %v: %v\n", url, err)
        return
    }
    fmt.Printf("Found: %s %q\n", url, body)
    done := make(chan bool)
    for i, u := range urls {
        fmt.Printf("-> Crawling child %v/%v of %v : %v.\n", i, len(urls), url, u)
        go func(url string) {
            Crawl(url, depth-1, fetcher)
            done <- true
        }(u)
    }
    for i, u := range urls {
        fmt.Printf("<- [%v] %v/%v Waiting for child %v.\n", url, i, len(urls), u)
        <-done
    }
    fmt.Printf("<- Done with %v\n", url)
}

func main() {
    Crawl("http://golang.org/", 4, fetcher)

    fmt.Println("Fetching stats\n--------------")
    for url, err := range fetched.m {
        if err != nil {
            fmt.Printf("%v failed: %v\n", url, err)
        } else {
            fmt.Printf("%v was fetched\n", url)
        }
    }
}

// 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{
    "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/",
        },
    },
}

Crawl() 自体を並行処理するわけではなく、 Crawl() の中で並行処理する

データの取得処理自体は Fetcher の仕事なので、並行にするのはそいつ

sync.Mutex とか .Lock(), .Unlock() とか使ってる

ロック機構でデータの書き込みのバッティングを避けている

fetched.m[url] = loading → fetched.m[url] = err で状態管理してる。 err には fetcher.Fetch(url) のエラー結果が入るので、正常終了の場合は nil が入る
それぞれの body をどの for で読みだしているか

for ではなく、 (*f)[url] を再帰的に参照していっている

depth がデクリメントされていき、0 になると Crawl() の再帰ループから return される。

done は何のためにあるのか。削除するとどうなるか。

done <- true をコメントアウトすると

fatal error: all goroutines are asleep – deadlock!

goroutine 16 [chan receive]:
main.Crawl(0xda590, 0x12, 0x4, 0x2208190318, 0x14aef8)
/golang/tour71.go:73 +0xb9d
main.main()
/golang/tour71.go:79 +0x78

goroutine 19 [finalizer wait]:
runtime.park(0x162c0, 0x14ae50, 0x14a209)
/usr/local/go/src/pkg/runtime/proc.c:1369 +0x89
runtime.parkunlock(0x14ae50, 0x14a209)
/usr/local/go/src/pkg/runtime/proc.c:1385 +0x3b
runfinq()
/usr/local/go/src/pkg/runtime/mgc0.c:2644 +0xcf
runtime.goexit()
/usr/local/go/src/pkg/runtime/proc.c:1445

goroutine 20 [chan receive]:
main.Crawl(0xda610, 0x16, 0x3, 0x2208190318, 0x14aef8)
/golang/tour71.go:73 +0xb9d
main.func·001(0xda610, 0x16)
/golang/tour71.go:67 +0x50
created by main.Crawl
/golang/tour71.go:69 +0x977

goroutine 24 [chan receive]:
main.Crawl(0xe0050, 0x1a, 0x2, 0x2208190318, 0x14aef8)
/golang/tour71.go:73 +0xb9d
main.func·001(0xe0050, 0x1a)
/golang/tour71.go:67 +0x50
created by main.Crawl
/golang/tour71.go:69 +0x977

goroutine 25 [chan receive]:
main.Crawl(0xe0090, 0x19, 0x2, 0x2208190318, 0x14aef8)
/golang/tour71.go:73 +0xb9d
main.func·001(0xe0090, 0x19)
/golang/tour71.go:67 +0x50
created by main.Crawl
/golang/tour71.go:69 +0x977
exit status 2

done チャンネルにデータが入らないと <-done がずっと待機になるので終了した感じかな…
– <-done をコメントアウトすると、 depth: 4 Found: http://golang.org/ “The Go Programming Language” -> Crawling child 0/2 of http://golang.org/ : http://golang.org/pkg/.
-> Crawling child 1/2 of http://golang.org/ : http://golang.org/cmd/.
<- [http://golang.org/] 0/2 Waiting for child http://golang.org/pkg/.
<- [http://golang.org/] 1/2 Waiting for child http://golang.org/cmd/.
<- Done with http://golang.org/
Fetching stats
————–
http://golang.org/ was fetched

で終わった (データが全てトラバースされてないまま終わった)。
done チャンネルが同期の役割をしていて、<-done のあるブロックは go を掛けたブロックの終了を待機している状態になっている。

学习 C 语言的 fork 功能的时候感到类似…很难…

func main() {
    buf := make(chan int, 5)  // (A) バッファとして使う
    buf <- 1
    buf <- 2
    buf <- 4
    buf <- 8
    close(buf)  // (B) これがないと↓のrangeループの終了が出来ない
    for v := range buf {
        fmt.Println(v)
    }
}
    • (A) バッファとして使う場合は、 make() の第2引数の大きさ指定が無いとダメ

 

    • (B): 「もうチャンネルには送信しないよ」という close(ch) がないと、 range ループがちゃんと終了しない

 

    (A), (B) それぞれ、または両方コメントアウトしてみると下記エラーが出る
  fatal error: all goroutines are asleep - deadlock!

根据golang.jp上的《实践Go语言》所述,

与地图一样,通道也是引用类型,可以使用make进行分配。在创建时,如果指定了整数参数,该参数将用作通道的缓冲区大小。该值的默认值为零,当值为零时,不会进行缓冲,而是变成同步通道。

地图和通道一样,也是引用型的,可以使用make函数进行分配。在创建时,如果指定了整数参数,该参数将用作通道的缓冲区大小。这个值的默认值是零,当为零时,将不进行缓冲,而变成同步通道。

由于此次的Web爬虫程序中没有为make()指定大小,因此它将被用作同步通道。

简单的频道使用习语示例(来自实践Go语言 – golang.jp)

go list.Sort()  // list.Sortを並列実行する(処理の完了は待たない)

有很多使用通道的出色成語,但首先介紹一個。在前一節中,我們在後台中啟動了排序處理,但是可以通過使用通道,改為等待使用通道啟動的goroutine完成排序。

c := make(chan int)  // チャネルの割り当て
// ゴルーチンとしてsortを起動。完了時にチャネルへ通知
go func() {
    list.Sort()
    c <- 1  // 通知を送信。値は何でも良い
}()
doSomethingForAWhile()
<-c   // sortの完了待ち。送られてきた値は破棄

在接收端,直到可以接收到数据为止,会一直被阻塞。发送端在通道没有缓冲时,会一直被阻塞,直到接收端接收到值。当通道缓冲时,发送端只会在值被复制到缓冲区期间被阻塞。因此,当缓冲区已满时,会一直等待在接收端取出值为止。

来自tour64

默认情况下,发送和接收将被阻塞,直到另一方准备完毕。这样,即使没有明确的锁或条件变量,也可以同步goroutine。

从旅游65

只有在缓冲区满时,才会将发送的数据阻塞在缓冲通道里。当缓冲区为空时,会阻塞接收。

我打算先读一下下面的内容并试着理解一下。

    • ハロー、goroutine! – @IT

 

    Big Sky :: Golang の channel の使い所

暂时就这样吧。接下来想边写应用程序边复习。哎。

广告
将在 10 秒后关闭
bannerAds