我自己的笔记:学习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)
}
上述代码的注意事项
-
- p := make([][]uint8, dy) 只是创建了一个具有 dy 个元素的切片,每个元素都是一个具有 dy 个元素的切片。
-
- 我们需要用 dy 个元素的切片来初始化每个元素。
-
- 通过双重循环访问二维数组的每个元素,并为其赋值。
需要注意的是,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 {
...
}
-
- 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))
}
-
- 在(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 内を実行する。
以下是我的理解记录。我不太有信心。
-
- (A)由于存在channel,不会被阻塞(不会等待后续命令),会在当前处理线程之外以另一个goroutine执行,所以(B)也会立即并行执行。
-
- 在(A)中,由于c仍为空,所以在 fmt.Println(<-c) 的处理过程中会进入阻塞状态(等待数据接收可用)。
-
- 与(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 の使い所
暂时就这样吧。接下来想边写应用程序边复习。哎。