トラック
/
Go
Go
/
演習
/
選挙の日
選挙の日

選挙の日

学習演習

はじめに

他の多くの言語と同じように、Goにもポインタがあります。 ポインタに初めて触れると、少し不思議に感じるかもしれません。でも、慣れてしまえばとてもシンプルです。 ポインタはGoの非常に重要な要素なので、時間をかけてしっかり理解しましょう。

詳細に入る前に、ポインタの使い道を理解しておきましょう。 ポインタは、プログラムの他の部分とメモリを共有する方法であり、主に2つの理由で役立ちます:

  1. 大量のデータがあるとき、関数間で渡すためにコピーを作るのは非常に非効率です。 代わりに、データが保存されているメモリの位置を渡すことで、プログラムのリソース消費量を劇的に減らせます。
  2. 関数間でポインタを渡すと、データの単一のコピーに直接アクセスして変更できます。つまり、ある関数で行われた変更は、その関数が終了した時点で、プログラムの他の部分からすぐに参照できるようになります。

変数とメモリ

ここで、通常の整数変数aがあるとしましょう:

var a int

変数を宣言すると、Goはその値を保存するためのメモリ上の場所を見つける必要があります。 この仕組みはほとんど私たちから抽象化されています。そのメモリに保存されている値を取得したいときは、変数名で参照するだけで済みます。

たとえば、a + 2と書くと、変数aに対応するメモリに保存された値を取り出し、それに2を足していることになります。

同様に、aのメモリに入っている値を変更したいときは、変数名を使って代入できます:

a = 3

aに対応するメモリには、今度は値3が保存されます。

ポインタ

変数を使うとメモリ内の値を参照できますが、その変数が指しているメモリアドレスを知りたいときもあります。ポインタは、そうした値のメモリアドレスを保持します。 ポインタ型の変数を宣言するには、基底型の前にアスタリスクを付けます:

var p *int // 'p' contains the memory address of an integer

ここでは、型が「intへのポインタ」(*int)である変数pを宣言しています。 これは、pが整数のメモリアドレスを保持することを意味します。 ポインタのゼロ値はnilです。nilポインタはメモリアドレスを何も保持しないからです。

変数へのポインタを取得する

変数の値のメモリアドレスを見つけるには、&演算子を使います。 たとえば、変数aのメモリアドレスを見つけてポインタpに保存したい場合は、次のようにします:

var a int
a = 2

var p *int
p = &a // the variable 'p' contains the memory address of 'a'

ポインタ経由で値にアクセスする(デリファレンス)

ポインタがあるとき、そのポインタが表すメモリアドレスに保存されている値を知りたいことがあります。 それには*演算子を使います:

var a int
a = 2

var p *int
p = &a // the variable 'p' contains the memory address of 'a'

var b int
b = *p // b == 2

*pという操作は、pに保存されたメモリアドレスにある値を取り出します。 この操作はよく「デリファレンス」と呼ばれます。

デリファレンス演算子を使って、ポインタが参照するメモリアドレスに新しい値を代入することもできます:

var a int        // declare int variable 'a'
a = 2            // assign 'a' the value of 2

var pa *int
pa = &a          // 'pa' now contains to the memory address of 'a'
*pa = *pa + 2    // increment by 2 the value at memory address 'pa'

fmt.Println(a)   // Output: 4
                 // 'a' will have the new value that was changed via the pointer!

*paへ代入すると、paが保持するメモリアドレスに保存されている値が変わります。 paはaのメモリアドレスを保持しているので、*paへ代入することは、実質的にaの値を変更していることになります!

ただし、注意点があります。デリファレンスする前に、ポインタがnilでないことを必ず確認しましょう。 nilポインタをデリファレンスすると、実行時にプログラムがクラッシュします!

var p *int // p is nil initially
fmt.Println(*p)
// panic: runtime error: invalid memory address or nil pointer dereference

構造体へのポインタ

ここまでは、プリミティブ値へのポインタだけを見てきました。 構造体へのポインタも作成できます:

type Person struct {
    Name string
    Age  int
}

var peter Person
peter = Person{Name: "Peter", Age: 22}

var p *Person
p = &peter

新しいPersonを作成し、それへのポインタをすぐに保存することもできます:

var p *Person
p = &Person{Name: "Peter", Age: 22}

構造体へのポインタがある場合、フィールドにアクセスする前にポインタをデリファレンスする必要はありません:

var p *Person
p = &Person{Name: "Peter", Age: 22}

fmt.Println(p.Name) // Output: "Peter"
                    // Go automatically dereferences 'p' to allow
                    // access to the 'Name' field

スライスとマップはすでにポインタ

スライスとマップは、その実装にすでにポインタが含まれている特殊な型です。 そのため、これらの型では、値のメモリアドレスを共有するためにポインタを作成しなくてよいことがほとんどです。 マップ内のキーの値をインクリメントする関数を考えてみましょう:

func incrementPeterAge(m map[string]int) {
	m["Peter"] += 1
}

マップを作成してこの関数を呼び出すと、関数がマップに加えた変更は、関数の終了後も保持されます。 これはポインタを使った場合と似た動作ですが、この例では参照やデリファレンスなど、ポインタの構文を一切使っていないことに注目してください:

ages := map[string]int{
  "Peter": 21
}
incrementPeterAge(ages)
fmt.Println(ages)
// Output: map[Peter:22]
// The changes the function 'incrementPeterAge' made to the map are visible after the function ends!

スライス内の既存の要素を変更する場合も同じです。

ただし、appendのように新しいスライスを返す操作は特殊で、関数の外側のスライスを変更しないことがあります。 これはスライスの内部動作によるものですが、より高度な話題なので、この演習では詳しく扱いません。 もっと知りたい場合は、Go Blog: Mechanics of 'append'で詳しく読めます。

説明

近所の学校には、とても活発な生徒会があります。 生徒会は会長によって運営され、2年ごとに新しい会長を選ぶ選挙が行われます。

今年の選挙では、票を数えるための新しいデジタルシステムが必要だということになりました。 学校は、この新しいシステムの構築に助けを求めています。

1. 投票カウンターを作りましょう

新しい投票システムに最初に必要なものの1つが、票を数えるカウンターです。 このカウンターは、特定の候補者が獲得した票を把握しておくためのものです。

NewVoteCounterという関数を作りましょう。この関数は、候補者の初期の票数を受け取り、その票数で初期化されたintを指すポインタを返します。

var initialVotes int
initialVotes = 2

var counter *int
counter = NewVoteCounter(initialVotes)
*counter == initialVotes // true

2. カウンターから票数を取得しましょう

これで新しいカウンターを作れるようになりました! しかし、新しいシステムにはカウンターから票数を取得する方法も必要だということに気づきます。

VoteCountという関数を作りましょう。この関数はカウンター(*int)を引数に取り、そのカウンターの票数を返します。 カウンターがnilの場合は、票がないものとして扱います。

var votes int
votes = 3

var voteCounter *int
voteCounter = &votes

VoteCount(voteCounter)
// => 3

var nilVoteCounter *int
VoteCount(nilVoteCounter)
// => 0

3. カウンターの票を増やしましょう

いよいよ票を数えるときです! 次に必要なのは、カウンターの票を増やす方法です。

IncrementVoteCountという関数を作りましょう。この関数はカウンター(*int)と票数を引数に取り、その票数だけカウンターを増やします。 渡されるポインタは決してnilにならないと仮定してかまいません。

var votes int
votes = 3

var voteCounter *int
voteCounter = &votes

IncrementVoteCount(voteCounter, 2)

votes == 5          // true
*voteCounter == 5   // true

4. 選挙結果を作りましょう

すべての票を数え終えたので、次は学校全体への結果発表を準備するときです。 ただ、ここで、票のカウンターだけでは足りないことに気づきます。 票数を特定の候補者と結びつける方法が必要です。

NewElectionResultという関数を作りましょう。この関数は候補者の名前とその票数を受け取り、新しい選挙結果を返します。

var result *ElectionResult
result = NewElectionResult("Peter", 3)

result.Name == "Peter"  // true
result.Votes == 3       // true

選挙結果の構造体はすでに用意されており、次のように定義されています:

type ElectionResult struct {
    // Name of the candidate
    Name    string
    // Votes of votes the candidate had
    Votes   int
}

5. 結果を発表しましょう

新しい会長を学校に発表するときです! 会長は、学校にある小さなデジタル掲示板で発表されます。 メッセージには、新しい会長の名前と獲得した票数を、<candidate_name> (<votes>)という形式で表示します。 そのようなメッセージの例は、"Peter (51)"です。

DisplayResultという関数を作りましょう。この関数は*ElectionResultを引数に取り、表示するメッセージの文字列を返します。

var result *ElectionResult
result = &ElectionResult{
    Name: "John",
    Votes: 32,
}

DisplayResult(result)
// => John (32)

6. 票の数え直し

最終結果が正確かどうかを確かめるために、票が数え直されました。 数え直しでは、一部の候補者の票数が1票ずれていることが分かりました。

DecrementVotesOfCandidateという関数を作りましょう。この関数は最終結果と、票数を1減らす対象の候補者の名前を受け取ります。 最終結果はmap[string]intの形式で渡され、キーは候補者の名前、値はその候補者の総票数です。

var finalResults = map[string]int{
    "Mary":  10,
    "John":  51,
}

DecrementVotesOfCandidate(finalResults, "Mary")

finalResults["Mary"]
// => 9
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Go Exercism

選挙の日を始める準備はできましたか?

Exercismに登録すれば、34個のコンセプト165個の演習、そして本物の人間によるメンタリングとともに、Goを学んでマスターできます。すべて無料です。