Go言語入門:言語仕様 -Vol.64-

スポンサーリンク
Go言語入門:言語仕様 -Vol.64- 用語解説
Go言語入門:言語仕様 -Vol.64-
この記事は約7分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

配列とスライスのリテラル(Array and slice literals)

配列とスライスのリテラルには、以下のルールが適用される:

  • 各要素は、配列内の位置を示す関連付けられた整数インデックスを持つ。
  • キーを持つ要素は、そのキーをインデックスとして使用する。キーは int 型の値で表現可能な非負の定数でなければならない。型付きの場合は整数型でなければならない。
  • キーを持たない要素は、前の要素のインデックスに1を加えたものを使用する。最初の要素にキーがない場合、そのインデックスは0である。

複合リテラルのアドレスを取得すると、リテラルの値で初期化された一意の変数へのポインタが生成される。

var pointer *Point3D = &Point3D{y: 1000}

スライスまたはmap型のゼロ値は、同じ型の初期化済みだが空の値と同一ではないことに注意せよ。したがって、空のスライスまたはmapの複合リテラルのアドレスを取得することは、new で新しいスライスまたはmapの値を割り当てることと同じ効果を持たない。

p1 := &[]int{}    // p1 は初期化済みの空のスライスを指す。値は []int{} で長さは 0
p2 := new([]int)  // p2 は未初期化のスライスを指す。値は nil で長さは 0

配列リテラルの長さは、リテラル型で指定された長さである。リテラルで提供される要素がその長さより少ない場合、不足する要素は配列要素型のゼロ値に設定される。配列のインデックス範囲外のインデックス値を持つ要素を提供するとエラーとなる。... 表記は、最大の要素インデックスに1を加えた長さの配列を指定する。

buffer := [10]string{}             // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5}       // len(intSet) == 6
days := [...]string{"Sat", "Sun"}  // len(days) == 2

スライスリテラルは、基底となる配列リテラル全体を記述する。したがって、スライスリテラルの長さと容量は、最大の要素インデックスに1を加えたものである。スライスリテラルは以下の形式を持ち

[]T{x1, x2, … xn}

これは配列に対するスライス操作の省略形である:

tmp := [n]T{x1, x2, … xn}
tmp[0 : n]

解説

配列・スライスリテラルの基本

前回の複合リテラルの話の続きで、今回は配列とスライスにフォーカスした詳しいルールです。

基本的な書き方はシンプルですね。

// 配列:サイズが固定
a := [3]int{10, 20, 30}

// スライス:サイズが可変
s := []int{10, 20, 30}

インデックスの自動採番

要素を並べると、自動的に 0, 1, 2, … とインデックスが振られます。

s := []int{10, 20, 30}
// s[0]=10, s[1]=20, s[2]=30

キー(インデックス番号)を明示的に書くこともでき、その場合は途中を飛ばせます。

s := []int{0: 10, 5: 60}
// s = [10, 0, 0, 0, 0, 60]  ← 指定しなかった部分はゼロ値

さらに、キーありとキーなしを混ぜることもできます。キーなしの要素は「直前のインデックス+1」になります。

s := []int{5: 100, 200, 300}
// s[5]=100, s[6]=200, s[7]=300

200 にはキーがないので、直前の 5 に1を足して 6 になり、300 はさらに 7 になる、という仕組みです。

& でポインタを直接作れる

複合リテラルの前に & をつけると、値を作ると同時にそのポインタを得られます。

// この2つは同じ意味
p := &Point3D{y: 1000}

// ↓ と同じこと
tmp := Point3D{y: 1000}
p := &tmp

& を使うと一行で済むので、ポインタを返す関数などでよく使われるパターンです。

func NewPoint(x, y float64) *Point {
    return &Point{x: x, y: y}  // 関数内で作ったポインタを返せる
}

&[]int{} と new([]int) は違う

ここは少しややこしいポイントです。空のスライスリテラルのポインタと、new で作ったスライスのポインタは意味が違います。

p1 := &[]int{}    // 初期化済みの空スライス。中身は [] で nil ではない
p2 := new([]int)  // 未初期化のスライス。中身は nil

イメージで言うと:

  • &[]int{} → 空っぽだけど、ちゃんとお皿が置いてある状態
  • new([]int)お皿すら置かれていない状態

実用上の違いは、JSON にシリアライズしたときなどに現れます。

data1, _ := json.Marshal(&[]int{})    // "[]"   ← 空配列
data2, _ := json.Marshal(new([]int))  // "null"  ← null

APIのレスポンスで []null のどちらを返したいかによって使い分ける必要がありますね。

配列リテラルの長さ

配列は固定長なので、リテラルで指定する要素が足りない場合はゼロ値で埋められます。

intSet := [6]int{1, 2, 3, 5}
// intSet = [1, 2, 3, 5, 0, 0]  ← 残り2つはゼロ値

逆に、配列のサイズを超えるインデックスを指定するとエラーです。

a := [3]int{5: 100}  // エラー! サイズ3の配列にインデックス5は入らない

... を使うと、要素の数に合わせてサイズを自動で決めてくれます。

days := [...]string{"Sat", "Sun"}  // [2]string になる

スライスリテラルの正体

最後に、スライスリテラルの裏側の仕組みについてです。実は、スライスリテラルを書くと内部的にはまず配列が作られ、それに対するスライス操作が行われています。

// これを書くと...
s := []int{10, 20, 30}

// 内部的にはこういうことが起きている
tmp := [3]int{10, 20, 30}   // まず配列を作る
s := tmp[0:3]                // その全体をスライスとして取り出す

だから、スライスリテラルの長さ(len)と容量(cap)は同じ値になります。

s := []int{10, 20, 30}
fmt.Println(len(s))  // 3
fmt.Println(cap(s))  // 3  ← 元の配列のサイズと同じ

普段はこの裏側を意識する必要はありませんが、「スライスは配列の一部を見ている窓」という Go の基本的な考え方を理解する手がかりになります。

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました