ゼロから学ぶGo言語プログラミング(12) A Tour of Go 55~60
A Tour of Goの続き。 メソッドから。
55.Errors
なんとなく見てきたコードでも登場していましたが、Goではエラーの通知に複数の戻り値を活用しているようです。 こんな感じのコードでしょうか。
f, err := foo() if err != nil { fmt.Println(err) }
これは errorインターフェースが文字列を返すよう定義されているから、と説明してますが、ここだけでは「へー、そうなのか」という感じ。 次で、自分でエラーを実装。
56.Exercise: Errors
エラーを扱う課題。 が、複素数は概念自体が身についていない。 仕方ないのでもっと単純な文字列の判定を課題代わりにしよう。
性別を表す文字列"M"または"F"のみを受け、maleやfemaleという文字列を返す関数を実装してみる。
package main import ( "fmt" ) type ErrMonthNum uint func (err ErrMonthNum) Error() string { return fmt.Sprintf("Need 1...12 uint. You gave %d.", uint(err)) } func MonthName(m uint) (string, error) { if m < 1 || m > 12 { return "", ErrMonthNum(m) } ms := []string{ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", } return ms[m-1], nil } func main() { fmt.Println(MonthName(6)) fmt.Println(MonthName(21)) }
これで一応期待通りにエラーが出力されるけど、これで合っているのかどうか…。 エラーを返す際に、自分でエラー用の型を作って、それにErrorメソッドを実装する、という辺りまでは理解できた。 まあ、色んなコード見て勉強しよう。
57.Web servers
やっと身近な話題が。 と思ったら、これはさすがにPlaygroundでは動かせない。 取り敢えずで作っておいたGoの環境を使って、ローカルで動かすことに。
ガイド通りコピーしてローカルで go run すると、localhost:4000 できちんと"Hello!"。 やはり恐ろしく手軽。
コードの方はというと、Hello型のstructを定義し、そこに ServeHTTPメソッドを実装してる。 ServerHTTPメソッドは、net/httpパッケージを使ったResponseWriterと、ポインタ渡しのRequestが引数。 処理はFprintのみ。
サーバーの実行はhttp.ListenAndServe()で、引数に"localhost:4000"を与えてポートを指定してる。 ポート込みのホスト名を指定って面白いな。 そしてHello型の変数も渡す。
多分http.ListenAndServe()は、ServHTTPメソッドを実装した構造体を受け取って、指定したポートで待ち受けるもの。 go runすると、ローカルの4000番で待ち続ける。
58.Exercise: HTTP Handlers
HTTPな課題。 任意の型にServeHTTPメソッドを実装し、任意のパスをそれらで受け取る、というもの。
型にServeHTTPメソッドを実装するのは、一つ前の解説通り。 問題はパスの扱いだが、課題の初期コードにそのまま「// your http.Handle calls here」とコメントがあり、解説中のハンドラ例をそのまま転記で問題なく動く。
http.ListenAndServe()の第2引数は、特定の型に結びつけたくない場合nilを渡す、っていうのはちょっとわかりにくいかな。
package main import ( "fmt" "net/http" ) type String string func (s String) ServeHTTP( w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, s) } type Struct struct { Greeting string Punct string Who string } func (s Struct) ServeHTTP( w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, s) } func main() { http.Handle("/string", String("I'm a String!")) http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"}) http.ListenAndServe("localhost:4000", nil) }
物凄く手軽。 でもこれ、Goの特徴っていうより、net/httpが手軽なんでないだろうか。 もちろんGo自体の言語設計がこういうパッケージを書きやすくはしてるんだろうけど、これだけの課題では、便利な組み込みパッケージがあるんだなー、程度。
59.Images
画像の取り扱い。
imageパッケージがGoでの画像の基本らしい。
そしてimageパッケージにはImageインターフェースが定義されていて、カラーモデルやカラー値の取得、矩形での画像サイズを満たす必要がある。 しかし通常は用意されているRGBAなどが使えるので、独自実装する必要はない。 また、image.NewRGBA(image.Rect(0, 0, 100, 100)) のような手軽な生成用関数も用意されている。
60.Exercise: Images
画像取り扱いの課題。
自分でImage型を実装し、必要なメソッドを用意する。 必要なメソッドは以下。
- 画像のカラーモデル:color.Modelを返すColorModel()
- 画像のサイズ:Rectangleを返すBounds()
- 任意の座標のカラー:color.Colorを返すAt(x, y int)
image - The Go Programming Languageやcolor - The Go Programming Languageを見つつ、課題のコメントや解説を参考に完成。
package main import ( "code.google.com/p/go-tour/pic" "image" "image/color" ) type Image struct{ W int H int color uint8 } func (m Image) ColorModel() color.Model { model := color.RGBAModel return model } func (m Image) Bounds() image.Rectangle { return image.Rect(0, 0, m.W, m.H) } func (m Image) At(x,y int) color.Color { return color.RGBA{uint8(x), uint8(y^x), uint8(x^y), uint8(255)} } func main() { m := Image{255,255,100} pic.ShowImage(m) }
型を作り、インターフェースに基いてメソッドを実装し、それを投げればあとはよしなに…、という感覚が少し進んだ気がする。
でもこのコード、メソッドの実装を全部値渡しでしてるけど、負荷を考えるとポインタ渡しにすべきなんじゃないだろうか。 単純にポインタレシーバに書き換えただけでは動かないのは、何かを見落としているか、勘違いをしているんだろうなあ。
ゼロから学ぶGo言語プログラミング(11) A Tour of Go 49~54
A Tour of Goの続き。 メソッドから。
49.Methods and Interfaces
オブジェクト指向というのでなく、「メソッドとインターフェース」となっている。 クラスベースではないということ?
50.Methods
いきなり答えが。
Goには、クラス( class )のしくみはありませんが、struct型にメソッド( method )を定義できます。
どうやらstructが重要らしい。
structはfieldをもったデータ構造、という漠然としたイメージ。 このシンプルな構造体に何らかの形で紐付けられた関数が、メソッドということだろうか。
メソッドレシーバ ( method receiver )は、 func キーワードとメソッド名の間で、それ自身の引数リストで表現します。 (訳注:この例では、 (v *Vertex) のことを言っている)
んー、このコードらしい。
func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
func の後、関数名との間に (v *Vertex) という見慣れない書き方。 これはその関数が、何らかのstructに属することを表現するんだろうか。 関数名の前に来る辺り、それを意図している気がする。 この部分のことを「メソッドレシーバ」と呼ぶらしい。
メソッドレシーバに書いてあるのは、vという値名と、*VertexというVertexへのポインタ。 これはどう理解すべきなのか。
ポインタの概念しか知らず、きっちり実用した経験がないため、メモリの番地としか認識していなかった。 でも調べてみると、どうもメモリの操作を直接するためというより、値の渡し方の制御のために、Goではポインタが採用されているらしい。
Goでは関数への値は常に参照ではなく値渡しになる。 とすると、もしメソッドレシーバにポインタを使わなければ、メソッドを使うたびにそのstruct自体がコピーされて、メソッドに渡るということだろうか。
Goでポインタが使われるのは、原則値渡しのGoで参照渡しを実現するため、というのがひとつの大きな理由にありそうだ。
51.Methods continued
なんと、structでなくてもメソッドを持てるらしい。
試してみる。
package main import ( "fmt" ) type Foo string func (f Foo) bar () { fmt.Println(f) } func main() { ff := Foo("asdfghjkl") ff.bar() }
実行すると"asdfghjkl"が出力された。 けど、 type Foo string ではなく単なる int にメソッドを割り当てようとすると、エラーが。
cannot define new methods on non-local type int
これが以下の記述で述べていることのようだ。
しかし、他のパッケージからの型や基本型にはメソッドを定義できません。
基本型だけでなく、他のパッケージで定義されている型も、例えエクスポートされていようがメソッドは定義できない。 確かに基本型や他のパッケージの型には触れない方が不要な上書きのトラブルを防げる。
さらに、このコードをポインタで渡すように変えてみると、今度は出力がおかしい。 メモリの番地を吐いているっぽい。
ところがこれを以下のように、structを用いると、正しく出力される。
package main import ( "fmt" ) type Foo struct { a float64 } func (f *Foo) bar () { fmt.Println(f.a) } func main() { ff := Foo{10} ff.bar() }
intやstringで型を宣言して、それをポインタ渡しでメソッドに関連付け、メソッドから型を参照すると、それは当然ポインタ。 出力すればポインタの番地が返ってくる。 ということは、structが特殊なのか。 色々読んでみる。
そうか、ポインタは var という風に、付きでアクセスすると、そのポインタが示す変数への参照となる。 メソッドレシーバで渡されたintのポインタが示す値を使うには、*が必要だった。 そしてstructの場合、. がそれに似た役割を果たしている、と。
わかったようなわかっていないような。
52.Methods with pointer receivers
メソッドは、名前付きの型( named type )や、名前付きの型へのポインタに関連付けられます。
名前付きの型とは、typeキーワードで宣言するもののことだろう。 メソッドはそうやって宣言した型そのものか、そのポインタに関連付けることが出来る。
解説にしたがってポインタへの参照に書き換えると、確かにScaleメソッドでの変更が反映されなくなった。
メソッドを型に関連付ける際、実体で渡せば値渡しとなり、メモリの消費が増え、変更の直接反映ができない。 ポインタで渡せば参照渡しとなり、メモリ消費を抑え、変更も直接反映される。
なんか色々わかった気がする。
53.Interfaces
インターフェースも、これまでの経験でしっかり扱ってこなかったもの。 満たすべき実装を示しているとか、ポリモーフィズムに使えるとか、概要程度は理解していても活用はしていない。
インターフェース型( interface type )は、メソッド群で定義されます。
というのが以下のことだろう。
type Abser interface { Abs() float64 }
structを使って型を宣言する場合と同じようだけど、structの代わりにinterface。 そして中身は関数名と戻り値の型?
コード全体は interface に慣れていないため追いかけにくい。 a = v で、aがAbserインターフェースとなり、そこにMyFloatやVertexを代入しようとして、Vertexの方ではエラー。 ここまでは解説やコメントにある通りなのでわかる。 しかしなぜ、Vertexの方のメソッドレシーバが (v *Vertex) でポインタ渡しだと、エラーになるのか。
ポインタレシーバだからエラーだということは、インターフェース時点で、これが値渡しで実装されるよう定義されているはずなのに、それがどの箇所かわからない。
interfaceはGoで重要なはずだけれど、ここだけ見ていても進みそうにないので、次へ。
54.Interfaces are satisfied implicitly
言ってることはなんとなくわかる。
インターフェースという多態性を担保する仕組みがあり、Goではメソッドの実装そのものが即ちインターフェースの実装であって、別途宣言は要らない。
しかし、なんとなく止まり。
インターフェースは非常に重要そうなわりに、このツアーではこれで終わり。 どこかでみっちり理解できるよう取り組まないと。
ゼロから学ぶGo言語プログラミング(10) A Tour of Go 40~48
A Tour of Goの続き。 mapの途中から。
40.Mutating Maps
m := make(map[string]int)
という風に、mをstringがキーでintが値のmapとして初期化したとして、
m["foo"] = 100 //挿入・更新 v := m["foo"] //取得 delete(m, "foo") //削除
という操作が出来る。
キーの確認は
v, ok := m["foo"] //取得
という風に、取得時の書き方に2番めの値を付けて真偽を受け取る。
辞書的なデータ構造の扱いは、非常に多用しそうなので、自然と書けるようになっておきたいです。
41.Exercise: Maps
mapを使った課題。
ヒント通り、strings.Fieldsのリファレンスを見ると、与えられた文字列に含まれる単語を、空白文字で分割したsliceを返す関数だった。
切り出したsliceを受けて、後はforでmapに単語単位でカウントしていけば、テストは全てパスできた。
package main import ( "code.google.com/p/go-tour/wc" "strings" ) func WordCount(s string) map[string]int { fs := strings.Fields(s) ret := make(map[string]int) for i := 0; i < len(fs); i++ { ret[fs[i]] = ret[fs[i]] + 1 } return ret } func main() { wc.Test(WordCount) }
日本語利用者としては、strings.Fields()の挙動が気になったので、日本語を与えた場合も試してみた。
package main import ( "code.google.com/p/go-tour/wc" "strings" "fmt" ) func WordCount(s string) map[string]int { st := "私はGo言語を学習している者です。" fs := strings.Fields(st) ret := make(map[string]int) for i := 0; i < len(fs); i++ { ret[fs[i]] = ret[fs[i]] + 1 } fmt.Printf("%s", ret) return ret } func main() { wc.Test(WordCount) }
結果は、単語単位でsliceされず、人連なりの文字列が単独の要素になったsliceを得られただけだった。 UCAや漢字コードなどを元に、単語の自動分かちを期待したが、stringsという文字列処理の基本的パッケージらしいものがカバーすべきとは思わないので、当然かも知れない。
ちなみに、strings.Fields()は、全角の空白文字であっても分割対象にしている。 基本がUnicodeベースになっている恩恵で、楽で嬉しい。
42.Function values
関数の値渡しをサポートしている、という話。 これは少し前、関数を引数に取る画像生成の課題があったため、予測していた。
43.Function closures
クロージャは、JavasScriptでしっかり踏み込まずにやり過ごしてきた概念。 せっかくなのでGoできちんと学ぼう。
サンプルコードと、ツアーからもリンクされていたWikipediaの解説を見ながら進めます。
まずサンプルコード。
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), ) } }
adder()がクロージャを実現している関数。 戻り値としてintを引数に取る関数を使うことが宣言されている。
adder()の中では変数sumが0で初期化されている。 さらにadder()は、returnで無名関数を返している。 adder()から返す無名関数は、adder()が持つ変数sumに引数xを加算し、変数sumを返すもの。
main()の中で変数posとnegは、"adder()"で初期化されている。 つまりadder()の戻り値である無名関数で初期化されていることになる。 この2つの変数それぞれをforループの中でintを渡しながら呼び出すと、次の結果が出力される。
0 0 1 -2 3 -6 6 -12 10 -20 15 -30 21 -42 28 -56 36 -72 45 -90
関数adderがもつsumの値は、0で初期化されるだけで、後は無名関数で加算されるだけ。 なのでposの出力は 0,1,2,3,4... となっていく筈。 しかし実際にはsumの値を保持しており、0,1,3,6,10... となっている。 また、negの値はposの場合と異なる結果を返しており、posとnegそれぞれが独立したsumを保持していることになる。
これがGoのクロージャのようである。
漠然とした印象では、オブジェクト指向におけるインスタンスの挙動のように見える。 これが関数のスコープで実現できるメリットは、まだいまいち気付けていない。 クロージャとオブジェクト指向の関係を探すと、MDNの以下の記述があった。
感じた類似性そのものは誤りでないらしい。 そしてここにあるように
メソッドを 1 つだけ持つオブジェクトを使いたくなるような状況ならば、どんな時でもクロージャを使う事ができます。 という点も、returnを使ってクロージャが実装されている点から、理解できた気がする。
44.Exercise: Fibonacci closure
早速クロージャを使った課題。 フィボナッチ数をクロージャで返そう、というもの。
初期のコードはこれ。
package main import "fmt" // fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
fibonacci()は関数を返すようになっている。 ここで返す関数が、クロージャということだろう。 Wikipediaを見ると、こういったクロージャの外側のスコープをエンクロージャと呼ぶらしい。
エンクロージャがクロージャを実現するためのスコープをまず用意し、クロージャを無名関数として実装してエンクロージャから返すことで、あたかもオブジェクト指向のインスタンスのように、返された無名関数ごとに状況を引き継ぐ、独立した環境が手に入る、という理解を現時点ではしておく。
最終的に課題はこう書いた。
package main import "fmt" // fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { a := 0 b := 1 return func() int { fib := a + b a = b b = fib return fib } } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
期待通りの結果にはなるが、これがクロージャの用途として適しているのか…。 扱っている関数がクロージャかどうか、注意が必要そうだという事は理解できたし、シンプルにインスタンスっぽいものを持ちたい場合、確かに楽な状況もありそう。
クロージャが活躍する状況は大いにあるのだろうから、もっと積極的に使って慣れておきたい。
45.Switch
ifやforからしばらく経ったここにきて、制御構造switch。
pythonにはswitchが無く、欲しいなあと思っていたけど、breakを忘れてバグに繋がりやすいというのが理由らしい。 goではどうなんだろう。
switch部分だけ抜き出すと、こうなっている。
switch os := runtime.GOOS; os { case "darwin": fmt.Println("OS X.") case "linux": fmt.Println("Linux.") default: // freebsd, openbsd, // plan9, windows... fmt.Printf("%s.", os) }
重要なのは以下の解説。
Goでのswitchは、 case の最後で自動的にbreakします。 もし、brakeせずに通したい場合は、 fallthrough 文をcaseの最後に記述します。
つまりバグの温床になりやすいbreakを自動化し、むしろ意図的な特殊な制御の方をfallthroughという文で実現したらしい。 非常に明確で素晴らしい仕様だと思う。
46.Switch evaluation order
caseは上から順に評価、一致で停止、全てのcaseで一致しなければdefault。 うん明確。
Goのswitchは、評価に式が書けるらしい。 あっちにfallthroughは無いけれど、FileMakerのCase()に似ている。
47.Switch with no condition
switchの条件文を省略することで、caseが比較すべき対象が無くなり、caseの式の真偽で評価される。 確かに条件の多いifを連ねるなら、こちらの方が見通しが良さそう。
48.Advanced Exercise: Complex cube roots
complexに関するAdvancedな課題。
complex型は、複素数だったらしい。 しかし複素数や虚数の素養が、せいぜい概念程度の理解であり、この課題そのものを大きく誤解してしまいそう。 ということで、ひとまずここはスキップする。
次はメソッドから。
ゼロから学ぶGo言語プログラミング(9) A Tour of Go 34~39
A Tour of Goの続き。 rangeとmap。
34.Range
これもpythonっぽいrange機能。 mapはまだツアーに登場してないけど、辞書みたいなものかな。
for i, v := range foo { }
これでfooが持つインデックスがiに、値がvに渡される。
ただ、"range フォーム"という表現が引っかかる。 翻訳の揺れなどではなく、元の英文でも"range form"となってる。
35.Range continued
インデックスや値を " _ "(アンダーバー) へ代入することで破棄することができます。
破棄することで、受け取って使わないよりも不要なリソースを確保せず済むんでしょうか。
for _, _ := range foo { }
こうすると、fooの長さ分処理ができると思ったら、":="での型推論がひっかかった。 コロンを外せば動いたので、アンダースコアも減らす。
for _ = range foo { }
さてこれが、len()でカウント取ってのループとどっちが効率良いのか。 range使った方が好みだけど、"_"を省略できないのはちょっと残念。
36.Exercise: Slices
バイト列を使った画像の生成の課題。 色々わかりにくい。
まず、"code.google.com/p/go-tour/pic"からのimport。 恐らく受け取ったバイト列を画像として出力するためのパッケージ。 pic.show()がそれに当たるらしい。
pic.show()はPic関数を引数として受け取るようだけど、課題の開始時点でPicには引数がない。 と思ったら、pic.show()にはPicを関数として渡していた。 つまり、pic.show()側で渡されたPicに、生成するサイズとなるdxやdyが渡されるということらしい。
次に分からないのが、"uint8"というPicの戻り値の指定。 まだまだ目がなれないので一見でわからないけれど、sliceはの後に、そのsliceが保持する中身の指定を行うようなので、Picの戻り値はuint8が連なったslice、ということになるようだ。 と思ったのだけど、それだとsliceを用意するmakeの書き方がわからない。 結局試行錯誤して、コードは次のようになった。
package main import "code.google.com/p/go-tour/pic" func Pic(dx, dy int) [][]uint8 { ret := make([][]uint8, dx, dy) for i := 0; i < dy; i++ { row := make([]uint8, dx) for k := 0; k < dx; k++ { row[k] = uint8(i ^ k) } ret[i] = row } return ret } func main() { pic.Show(Pic) }
"uint8"のように、""を連ねると、多次元のsliceを示すようで、make()での長さ指定は引数を増やして対応するらしい。 多次元配列を"[]uint8"という風に初期化しないといけないのは、動的に次元の増減は難しいということだろうか。
ついでに、呼び出しているpic.Show()のコードも見てみました。
やはり、int2つを引数に取る関数がShow()の引数でしたが、引数として関数を与える場合、
func Show(f func(int, int) [][]uint8) { }
というように、引数の場所に関数名、引数となる関数の引数、そして引数となる関数の戻り値まで記述するようです。 こういう風に関数を引数に取れるのは、Goがモダンなところなんでしょうか。
Sho()の結果は、imageパッケージを使ってRGBA画像としておき、それを更にShowImage()でbase64として返しています。 最終的な出力時に"IMAGE:"という文字列を先頭に付加していますが、これはplaygroundが画像としてbase64のデータを出力するためのフラグのようです。
sliceによる多次元配列の扱いは、まだ全然慣れませんが、使いこなせると強力そうです。
37.Maps
mapはやはり辞書のようです。
mapの初期化はsliceと同じmake()で、キーへのアクセスは"m["キー"]"です。 mapもsliceと同じく、保持する値の型を、make時に指定する必要があるようです。
38.Map literals
リテラルとしてのmapは"キー: 値"というペアでダイレクトに指定でき、make()で準備する必要がないようです。 stringのmapであれば、こんな風に定義できました。
var m2 = map[string]string{ "a": "b", "c": "d", "e": "f", }
39.Map literals continued
これ、38で試したコードではすでにstringを個々のmap要素に書いてませんが、Vertexの場合と同じように考えていいんでしょうか。
"トップレベル"とは、Vertexがmainパッケージの直下に指定されている事と同義と捉えていいのかどうか…。
ゼロから学ぶGo言語プログラミング(8) A Tour of Go 25~33
A Tour of Goの続き。 データ型や構造あたりから。
25.Structs
(また、 type 宣言は皆さんが知っているものです。)
struct というのが、構造を持ったデータのようです。 内容は名前と型を並べたシンプルなものですが、もっと複雑な入れ子なども可能なのでしょうか。 これを定義し、型として扱うための宣言が"type"のように見えます。
type宣言の出てこないスクリプト言語ばかりやってきたため、残念ながら知っていません。 Goで慣れましょう。
26.Struct Fields
type 宣言した structは、以下のように変数で使うようです。
v := Vertex{1, 2} v.X = 4
{}による内容の指定は、名前付きでは出来ないんでしょうか。
ドットシンタックスでのアクセスは、パッケージへのそれと同じですが、実際には何がどう違ってくるのか。
27.Pointers
ポインタ。 Cですこーし触れて以来、概念は理解していても、まともに使った経験がありません。
さらにこの事例では、ポインタを利用するメリットがピンとこない。
func main() { p := Vertex{1, 2} q := &p q.X = 1e9 fmt.Println(p) }
このポインタを通じた間接的なアクセスで、とてもわかりやすくなります。
とあるんですが、なぜp.Xへのアクセスでは分かりにくいのか。 structが持つ、まだ言及の無い性質に関係あるんでしょうか。
28.Struct Literals
structを使って宣言した型Vertexが、変数へ割り当てる際に、リテラルを渡して初期化できることは理解出来ました。 またリテラルを渡す際、"名前: 値" という構文を用いれば、特定のフィールドだけ指定できることや、名前付きの割り当てと名前なしの割り当てが混在できないことも、確認しました。
問題は前項に引き続きポインタで、
q = &Vertex{1, 2} // has type *Vertex
とすることで、初期化したVertexのポインタが直接手に入るのは良いんですが、このメリットが見えません。
29.The new function
golangにはnew()があり、これを使うと"ゼロ初期化"したタイプが手に入る。 タイプと型を分けて考えないと、おかしいのかな。
なんにせよ"ゼロ初期化"は、structが持つfieldを、数値であればゼロ、文字列であれば空っぽといったデータで初期化すること。 newはそういった初期化を行った構造体の、ポインタを返すという感じでしょうか。
またポインタ絡みなので、飲み込み切れていません。
30.Slices
golangでの配列的な構造は、sliceということのようです。
sliceは
p := []string{}
という風に取得でき、{}にはカンマ区切りで値を入れられる。 [] がsliceであることを表し、後ろに型を続け、更にその内容を {} で示す。
ということは、型の混在したsliceはだめということでしょうか。
31.Slicing slices
p := []int{2, 3, 5, 7, 11, 13} fmt.Println("p[1:4] ==", p[1:4])
こういった書き方で、"p[1:4] == [3 5 7]"が出力されるのは理解できます。 しかし解説がしっくりこない。
sliceは、同じ配列を参照する新しいsliceを作成することで、再sliceすることができます。
これはつまり、p[x:y]という書き方で、新たなsliceが作成されていることを示しているんでしょうか。
32.Making slices
make が現れました。
sliceは、make 関数で生成します。 これは、ゼロに初期化した配列をメモリに割り当て、その配列を参照したsliceを返す働きをします。
これを読む限り、配列とsliceは別物のようです。
33.Nil slices
slice はgolangでかなり重要そうですが、このツアーだけではいまいち飲み込めませんでした。
別途解説などを見て、じっくり理解しようと思います。
次は 「 34.Range 」から。