👶

Goの基礎 これわかる? - クイズで確認するGoの基礎 -

2024/10/24に公開

ねらい

Go言語の基礎について把握してもらうことです。
本などで学習するまえにざっと知っておいたら良さそうなことを書きます。

対象読者

  • 自分と同じようにGo言語を今から書こうと思っている人
  • すでに他の言語は書いたことがある人

基本的に「初めてのGo言語」で勉強したことを書いています。
もし、同じチームの人に読んでもらうとしたら以下を勧めると思います。

  1. この記事を読んでざっとGoの基礎について知る
  2. 気になった、深堀りたい部分を「初めてのGo言語」などで勉強する
  3. プロジェクトのコードを書いてみる

https://www.oreilly.co.jp/books/9784814400041/

クイズで確認するGoの基礎

文字列とrune

Q-1

Q: Go言語において、文字列リテラルを表す方法として正しいものを選んでください。

*「`」バッククォートについて
「`」バッククォートを使い、生(raw)文字列リテラルを表すことができますが、
「'」、「"」の使い方を理解する問題だと捉えてください。

1. 「'」を使う
2. 「"」を使う
3. 「'」、「"」どちらも使用できる

A-1

正解:2
文字列リテラルには「"」を使います。
「'」はruneリテラルです。
runeリテラルは文字列ではなく文字リテラルです。
以下のコードを参照してください。
runeはUnicodeコードポイントを表すエイリアスでもあり、1文字ずつ正確に扱う場合などにruneを用いることがあります。

package main

import (
	"fmt"
)

func main() {
	r1 := 'G'
	r2 := 'o'
	r3 := '言'
	r4 := '語'

	fmt.Printf("%c unicode: %d\n", r1, r1) // G unicode: 71
	fmt.Printf("%c unicode: %d\n", r2, r2) // o unicode: 111
	fmt.Printf("%c unicode: %d\n", r3, r3) // 言 unicode: 35328
	fmt.Printf("%c unicode: %d\n", r4, r4) // 語 unicode: 35486

	str := "Go言語" // 文字列

	byteLength := len(str) // 文字列の長さではなくバイト数が返る
	runeLength := len([]rune(str)) // 文字列の長さを調べるときはruneに変換する

	fmt.Printf("文字列: %s\n", str) // 文字列: Go言語
	fmt.Printf("バイト数: %d\n", byteLength) // バイト数: 8
	fmt.Printf("文字数 (rune): %d\n", runeLength) // 文字数 (rune): 4
}

https://go.dev/play/p/yYxbph7wK8p

スライス

Q-2

Q: 以下のmain関数において出力される、変数sliceGとして正しい組み合わせはどれか

1. replacing-main[ごうだ みなもと できすぎ ほねかわ できすぎ]
2. replacing-main[ごうだ みなもと のび ほねかわ できすぎ]
3. replacing-main[ごうだ みなもと できすぎ ほねかわ]
3. replacing-main[ごうだ みなもと のび ほねかわ]
package main

import "fmt"

func addMember(s []string) {
	s = append(s, "できすぎ")
	fmt.Println("adding-func", s)
}

func replaceMember(s []string) {
	s[2] = "できすぎ"
	fmt.Println("replacing-func", s)
}

func main() {
	sliceG := []string{"ごうだ", "みなもと", "のび", "ほねかわ"}

	addMember(sliceG)
	fmt.Println("adding-main", sliceG)

	replaceMember(sliceG)
	fmt.Println("replacing-main", sliceG)
}

A-2

正解:3

https://go.dev/play/p/o12YxeO6ud-

スライスとは

Go言語において、配列とスライスは区別されます。スライスは可変長で、配列は固定長であるなどの違いはあります。一般的にはスライスがよく使われるようです。

スライスは、基底配列、長さ、容量という3つの要素で構成され、実際に格納するデータは基底配列に保存されます。容量はその要素の数です。

// 配列
var arr [3]int = [3]int{1, 2, 3} // 長さが3

// スライス
var s []int = []int{1, 2, 3}

// スライス(長さと容量を指定する)
var s = make([]int, 5, 10)
// 長さ5
// 容量10
// intのゼロ値である0が5つのスライスができあがる

答えの解説

appendはmain関数のスライスに影響しません。
replaceを使うとmain関数のスライスに影響があります。

容量と長さという観点からappendが変更されない理由を説明できます。
スライスの容量を超える場合は基底配列は新しく作成され、main関数から参照している基底配列とは別のものになります。あらかじめ容量を多めに確保し、appendしても基底配列が変更されないようにしたとしても、appendする関数内で長さが変わるだけでありmain関数内のスライスの長さは変更できません。

一方でreplaceは容量の増加が無いために基底配列の変更がなく、main関数とreplaceMember関数の長さが同じスライスなので同じ出力結果となります。

スライスは基底配列を参照していることや容量という概念があることなどを念頭に置き学習すると理解ができる部分があると思いました。

マップ

Q-3

Q: 以下のmain関数において出力される、変数mapGとして正しいのはどれか

1. replacing-main map[0:ごうだ 1:みなもと 2:できすぎ 3:ほねかわ 4:できすぎ]
2. replacing-main map[0:ごうだ 1:みなもと 2:できすぎ 3:ほねかわ]
3. replacing-main map[0:ごうだ 1:みなもと 2:のび 3:ほねかわ 4:できすぎ]
4. replacing-main map[0:ごうだ 1:みなもと 2:のび 3:ほねかわ]

package main

import "fmt"

func addMember(m map[int]string) {
	m[4] = "できすぎ"
	fmt.Println("adding-func", m)
}

func replaceMember(m map[int]string) {
	m[2] = "できすぎ"
	fmt.Println("replacing-func", m)
}

func main() {
	mapG := map[int]string{
		0: "ごうだ",
		1: "みなもと",
		2: "のび",
		3: "ほねかわ",
	}

	addMember(mapG)
	fmt.Println("adding-main", mapG)

	replaceMember(mapG)
	fmt.Println("replacing-main", mapG)
}

A-3

正解:1

https://go.dev/play/p/q7I9ZZOA6Bq

マップとは

他言語では連想配列やハッシュと呼ばれるものです。

答えの解説

マップはポインタを使って実装されており、関数に渡すとそのアドレスを参照している値を書き返ることになるため、main関数のマップにも影響があります。

Go言語はポインタにより値を変更する、もしくは変更しないを意識しながら書ける言語ですが、マップのように実はポインタで実装されているということもあるので注意が必要です。

ポインタについてまだ知らなくても、ここではマップにはこのような性質があることを覚えておくとよいかと思います。

まとめ

今回はruneと文字列、マップ、 スライスの挙動について確認しました。
まだまだ学ぶべき基礎もたくさんあると思いますが、「Goのリテラル全部知る」、「マップやスライスについて全て理解する」といった時間は取れなくても、「ここは他言語と違うけど知ってる?」って確認がとれるようなものが書けたらいいなと思いました。

次は今回できなかったポインタについても簡単にまとめてみたいです!

ちなみに「できすぎ」の野球能力はチート級とのことで、ガキ大将チームに参加している場面があまりないそうです。

参考

https://www.oreilly.co.jp/books/9784814400041/

NE株式会社の開発ブログ

Discussion