Skip to content

Instantly share code, notes, and snippets.

@tokubass
Last active July 24, 2018 15:40
Show Gist options
  • Save tokubass/385a7c3504ebf7dd22770ec22a5f8655 to your computer and use it in GitHub Desktop.
Save tokubass/385a7c3504ebf7dd22770ec22a5f8655 to your computer and use it in GitHub Desktop.
The Go Programming Language ch4

4 Composite Types

  • array,structはサイズ固定

  • maps,slicesはサイズ変動

  • sliceの前にarrayを学ぶこと

4.1 Array

  • []でアクセス
  • 0始まり
  • lenが配列の長さを返す組み込み関数
  • 明示的に初期化しない要素は型のzero valueで初期化される。

var [3]int a[0]からa[len(a)-1]までアクセスできる。 a[len(a)]はエラーになる。

rangeで順次アクセス(ここはaでもaのポインタ&aでもいけるようだ) for i, v := range(a) { fmt.Printf("%d %d\n", i,v); }

[...]int{1 ,2 3} としたら、長さを勝手に3にしてくれる。 長さ情報も配列の型のうち。[3]intと[4]intは異なる型. 配列の長さはコンパイル時に決定していなければならず、変更できない。

a := [...]int{9: -1} とした場合0-8は0で初期化され、9だけ-1. 型は[10]int

要素の型がcomparableであるならば、配列もcomparableである。 配列の==は要素が全て同じ値であることを示す。否定は!=

配列の型が違うとコンパイルエラー

関数に渡される配列はcopy.なので、関数内で配列を変更しても呼び出し元は影響しない。 copyが嫌ならポインタを渡す。ただし配列を書き換えると呼び出し元に影響する。

// coolな初期化 func zero(ptr *[32]byte) { *ptr = [32]byte{}; }

呼び出し元の変数を書き換えられるといっても型は変更できない。 つまり配列に要素を追加したり、削除したりはできない。 SHA256の場合など特殊ケースを除けば固定サイズのhash,arrayを関数引数にわたすことはない。かわりにslicesを使うから。

4.2 Slices

sliceは可変長の連続した要素で、すべての要素の型が同じ。

宣言は[]T

長さのない配列のように見える。

配列配列の部分列にアクセスするための軽量データ構造

3つのコンポーネントからなる。

  • pointer
    • sliceが指している最初の要素
  • length
    • sliceの要素数.capacityは超えられない。len関数で取得できる。
  • capacity
    • sliceの最初の要素から配列末尾までの長さ。cap関数で取得。

複数のsliceは一つの配列を共有することができる。(ポインタだからね)

  • s[i:j]は0<=i<=j<=cap(s)
  • s[i:i+1]だとiの値を取得できる
  • s[i:] はi以降すべて
  • cap(s)を超えるとpanicになる。
  • stringをスライスしたら型はstring
  • []byteなら[]byteになる。

sliceと配列の違い

sliceリテラルは暗黙的に正しいサイズの型で配列と、ポインタを生成する。

値は順番にならんでいるし、indexでアクセスできる。

異なる点は全ての要素の値が同じであるか==で比較できないこと。(==はポインタ比較になる。shallow testには使える)

2つのsliceが等しいか比較するには高度に最適化されたbytes.Equalを使うが、これは[]byte用で、それ以外では自作する必要がある。

deppにネストしたデータ構造をtestするのはよくない。

一つに、sliceの要素は配列の要素とちがい、参照型。

slice自身を含んだsliceを作れる。(無限ループになる)

にもかかわらずこの方法をとるのはシンプルでも効率的でもないのは明らか。 http://stackoverflow.com/questions/36077566/how-can-a-slice-contain-itself

2つ目の理由も参照型であること。基本配列のデータをみるタイミングが違えば、その間にデータが変更されているかもしれない。Goのmap型のようなhash tableはキーのshallow copyだけするから。 it requires that equality for each key remain the same throughout the lifetime of the hash table. それゆえmapのkeyにsliceを使うのは適していない。 http://golang.jp/go_faq#map_keys

用語: shallow copy ポインタのコピー、deep copy: 値のコピー(メモリ上のデータをコピー)

arrayとsliceの==の挙動の違いを理解して使うのは紛らわしいので、sliceについては==を全く使わないのが安全。

例外として、nilとの比較に==を使う。

sliceのzero valueがnilのとき、基本配列をもっていない。

nil sliceはlenもcapも0だが、non-nil sliceの場合([]int{})も0

どのような型もnil値を持てる。

特定のslice typeのnil値は[]int(nil)のような変換式を使って書くことができる

sliceが空かどうかの判定はlen(s) = 0かs==nil nil sliceにおいてnil以外との比較は、zero lengthのsliceと同じ振る舞いをする。

用語: 'conversion expression' 変換式

蛇足 Underlying array? 基本型(Underlying type)とは、配列ならばその要素の型。構成要素の型ってことかな。 この上で、基本配列とは?

makeは空のsliceを作る。 capを指定しない場合はlenと同じ値になる。 裏でmakeは匿名配列を作成し、そのsliceを返す。 その配列にアクセスできるのは、そのsliceのみ。 make([]T, len)は(lenとcapが同じなので)配列全体をみているが、 make([]T,len,cap)はlenからみている。しかしcapは配列全体を示す。 make([]T, cap)[:len]と同じだから。

4.2.1 The append Function

[]rune("hoge"); で一文字ずつ分解できるのでappendとloopを組み合わせる必要はない。

  • appendはsliceを理解するうえで重要 appendはcapを超えないなら、同じ基本配列のsliceを作るが、 capを超えると、新規に配列を作成して、そのsliceを作る。 元のsliceが参照していた配列とは別物になる。

copy関数はslice間でcopyができる。copyの戻り値はcopyした要素の数。 copyのdest,srcは同じsliceであることもある。例: copy(s[1:2],s[4:5])

s := []int{1,2,3,4,5};
s2 := []int{10,9,8,7,6,5,4,3,2,1}
num := copy(s[0:2],s2[0:10]); //<- copy元のsrcがdestのcapより大きくても安全。

この場合s2[0:10]の先頭二文字しかcopyされない。

overlapでも安全

num := copy(s[2:3],s);
fmt.Println(s); //[1 2 1 4 5]
fmt.Println(num); //1

appendで新規に作られる基本配列のサイズは appendのたびにreallocatonしなくていいように ある程度大きめのサイズを確保してくれる。

sliceを正しく使うために、以下の点を覚えておく。 配列はdirectだが、sliceはpointer.

to update them requires an assignment like the one above. この点において、pureな参照型ではないが、しかし structのような集合型と共有点がある。

type InSlice struct { ptr *int len, cap int }

自作関数のappendIntはひとつずつしかappendできなかったが、 可変長引数にすることで対応できる。 この記法は以下。 func appendInt(x []int, y ...int) []int {}

4.2.2 In-Place Slice Techniques

rotate,reverse,nonemptyを実装してみよう。 sliceはstackを実装するのに使われる。

4.3 Maps

map [K] V Kがkeyのタイプ、Vがvalueのタイプ。すべて同じタイプである 必要があるが、K,Vは同じである必要はない。 Kは==で比較する。 floating-pointで==比較はしてはいけない。chapter3参照。 得にNaNとはダメ。 makeはmapを作れる。

初期値を伴う宣言の場合はmakeはいらない。

ages := make(map[string]int)
ages := map[string]int{ // makeなし
   "alice": 31,
   "charlie": 34,
}
ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34

ages := map[string]int{} // empty map. makeなし。

delete(ages, "alice")

存在しないkeyの場合、zero valueになっている。 本当に存在しなかったかどうかはokをチェック

val, ok := ages["hoge"]
if !ok {}

ages["hoge"] = ages["hoge"] + 1
ages["hoge"] += 1
ages["hoge"]++

&ages["alice"]はコンパイルエラー。map elementのアドレスは得られない。 理由として、rehashでアドレスが変化するから。

range-based for loopででkey,valueがとれる。

for name,age := range ages {

}

しかし順番はランダム。(rangeを実行する度に順番かわる)

delete,len,range はnilなmapで安全.空のmapと同じように振る舞う。 しかし、makeでallocateする前に代入しようとするとpanicになる。

mapどうしの比較はできない。唯一 == nilだけ。 loopで比較する必要がある。

Goはset型を提供していない。(集合とは、重複する要素をもたない、順序づけられていない要素の集まり) しかしmapはkeyの重複がないことを利用して、目的を果たせる。 それを説明するためにdedupというプログラムを紹介。 各行をreadしてprint、ただし重複した行コンテンツはprintしない。 https://github.com/adonovan/gopl.io/blob/master/ch4/dedup/main.go (if err := input.Err(); err != nil っていつ発生するんだろ) ただしいつもmap[string]boolがシンプルなset型にはならないことに注意。 true,false両方とも含まれることがある(?) (あー、セット型ってリストだからか。単純にkeyをとりだしただけだと、falseの keyも取得できてしまう)

時々map,setのkeyにsliceを使いたくなるが、map keyは比較可能でなければならない。sliceは直接比較には使えない。以下のように2stepでやる。 mapのkeyを連結して、一つの文字列として比較する。 これはsliceだけではなく、比較できないkey typeで同じようなアプローチが可能。 比較可能なkeyでも使える。 たとえばcase-insensitiveな比較、それにkeyは文字列以外でもいい。integer,array,struct.

inputのUnicode code pointをカウントする(これ、だたhashでカウントしましたって言ってるだけだな) r, n, err := in.ReadRune() // returns rune, nbytes, error で読み込み失敗すると、変わりの文字(ReplacementChar=U+FFFD)がrに入るようだ。 if r == unicode.ReplacementChar && n == 1 {

mapの中にmapを入れるサンプルコード https://github.com/adonovan/gopl.io/blob/master/ch4/graph/main.go

4.4 Structs

名前付きの値の集合を表すdata type. それぞれの値をfiledと呼ぶ。 filedにはdotでアクセスできる。

var dilbert Employee
dilbert.Name
dilbert.Name = "foo"

pointerを通じてアクセスもできる。

position := &dilbert.Position
*position = "Senior " + *position

構造体ポインタで宣言しても暗黙のドットアクセスができる

var employeeOfTheMonth *Emploee = &dilbert
// もしくは employeeOfTheMonth := &dilbert
employeeOfTheMonth.Position += "hoge "
(*employeeOfTheMonth).Position += "hoge "

同じ型ならまとめてかける

type Hoge struct {
  Name,Address string
}

filed名が大文字、小文字かでアクセスコントロール

同じStruct名を入れ子にはできないがポインタは大丈夫

type Hoge struct {
 Field1 *Hoge
}

なので、再帰構造が作れる。linkリストや木構造。 https://github.com/adonovan/gopl.io/blob/master/ch4/treesort/sort.go

フィールドの並び順が変わると別の型とされることに注意

struct {}は空の構造体。sizeが0で、何の情報ももってない。 mapでset型を表現したように、何かしら値を代入して、bool値の変わりにすることがある。この値を利用すると、メモリなど容量が抑えれる。

seen := make(map[string]struct{}) // set of strings
 if _, ok := seen[s]; !ok {
       seen[s] = struct{}{} // trueの代わり

4.4.1 Struct Literals

type Point struct { X,Y int}
p := Point{1,2} // p := Point; p.X =1; p.Y = 2; としなくていい。

名前付きだとfieldがかけていてもok. zero valueになる。 p := Point{X:1} // Y:0

filedが小文字だと別packageにimportしてもexportされない。

関数の引数にとれる。structが大きい場合はpointerでもいい。

pp := &Point{1,2}
pp := new(Point)
*pp = Point{1,2}

4.4.2 Comparing Structs

すべてのFieldが比較可能ならstructは比較可能 ==,!=以外にもmapのkeyに指定できる。

type address struct {
  hostname string
  post     int
}
hits := make(map[address]int)
hits[address{"glang", 443}]++

4.4.3 Struct Embedding and Anonymous Fields

アノニマスフィールドにすることで、名前を省略できる。 でもstructリテラルでは構造を省略できない。 https://github.com/adonovan/gopl.io/blob/master/ch4/embed/main.go

アノニマスに指定した型が小文字始まりの場合は明示的に別パッケージから呼べない。

w.X = 8 //ok
w.circle.X = 8 //ng

構造体以外のものもアノニマスにできるが、別にほしくないでしょ。ー>この答えはmethodの組み込み。

4.5 JSON

4.6 Text and HTML Template

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment