
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
Array types(配列型)
配列は、要素型と呼ばれる単一の型の要素の番号付きシーケンスです。要素数は配列の長さと呼ばれ、決して負になりません。
ArrayType = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .
長さは配列の型の一部です。これは、int型の値で表現可能な非負の定数として評価される必要があります。配列aの長さは、組み込み関数lenを使用して調べることができます。要素は、0からlen(a)-1までの整数インデックスでアドレス指定できます。配列型は常に一次元ですが、多次元型を形成するために組み合わせることができます。
[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64 // [2]([2]([2]float64))と同じ
配列型Tは、それらの含む型が配列または構造体型のみである場合、直接的または間接的に、型Tの要素、またはTをコンポーネントとして含む型の要素を持つことはできません。
// 無効な配列型
type (
T1 [10]T1 // T1の要素型がT1
T2 [10]struct{ f T2 } // T2が構造体のコンポーネントとしてT2を含む
T3 [10]T4 // T3がT4内の構造体のコンポーネントとしてT3を含む
T4 struct{ f T3 } // T4が構造体内の配列T3のコンポーネントとしてT4を含む
)
// 有効な配列型
type (
T5 [10]*T5 // T5がポインタのコンポーネントとしてT5を含む
T6 [10]func() T6 // T6が関数型のコンポーネントとしてT6を含む
T7 [10]struct{ f []T7 } // T7が構造体内のスライスのコンポーネントとしてT7を含む
)
解説
配列とは何か?
配列(array) は、同じ型の要素を固定個数並べたデータ構造です。
たとえ話: 配列は「ロッカー」のようなもので、同じサイズの箱が固定個数並んでいます。各箱には番号(インデックス)が付いていて、0番から始まります。一度作ったロッカーのサイズ(要素数)は変更できません。
package main
import "fmt"
func main() {
// 5個の整数を格納する配列
var numbers [5]int
numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50
fmt.Println(numbers) // [10 20 30 40 50]
}
1. 配列の基本
配列の宣言
package main
import "fmt"
func main() {
// 基本的な宣言
var arr1 [5]int // ゼロ値で初期化: [0 0 0 0 0]
// 初期値を指定
var arr2 [3]string = [3]string{"apple", "banana", "cherry"}
// 短縮構文
arr3 := [4]int{1, 2, 3, 4}
// 長さを自動計算
arr4 := [...]int{10, 20, 30} // 長さ3の配列
fmt.Println(arr1, arr2, arr3, arr4)
}
配列の長さ
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
// len()で長さを取得
length := len(arr)
fmt.Println("長さ:", length) // 5
// 長さは型の一部
var arr2 [5]int // arr と同じ型
var arr3 [10]int // arr とは異なる型
// これは代入できる
arr2 = arr // OK
// これはエラー(型が違う)
// arr3 = arr // cannot use arr (type [5]int) as type [10]int
_ = arr2
_ = arr3
}
2. インデックスによるアクセス
配列の要素は、0から始まるインデックスでアクセスできます。
package main
import "fmt"
func main() {
arr := [5]int{10, 20, 30, 40, 50}
// 読み取り
fmt.Println(arr[0]) // 10
fmt.Println(arr[2]) // 30
fmt.Println(arr[4]) // 50
// 書き込み
arr[1] = 25
arr[3] = 45
fmt.Println(arr) // [10 25 30 45 50]
// 範囲外アクセスはコンパイルエラー(定数の場合)
// fmt.Println(arr[5]) // invalid array index 5 (out of bounds for 5-element array)
// 実行時の範囲外アクセスはパニック
// i := 5
// fmt.Println(arr[i]) // panic: runtime error: index out of range [5] with length 5
}
3. 配列の初期化
部分的な初期化
package main
import "fmt"
func main() {
// 最初の3つだけ初期化、残りはゼロ値
arr1 := [5]int{1, 2, 3}
fmt.Println(arr1) // [1 2 3 0 0]
// インデックスを指定して初期化
arr2 := [5]int{0: 10, 2: 30, 4: 50}
fmt.Println(arr2) // [10 0 30 0 50]
// 混在も可能
arr3 := [5]int{1, 2, 4: 50}
fmt.Println(arr3) // [1 2 0 0 50]
}
配列のコピー
配列は値型なので、代入するとコピーされます。
package main
import "fmt"
func main() {
arr1 := [3]int{1, 2, 3}
// arr2にコピー
arr2 := arr1
// arr2を変更
arr2[0] = 10
// arr1は変わらない
fmt.Println("arr1:", arr1) // [1 2 3]
fmt.Println("arr2:", arr2) // [10 2 3]
}
4. 多次元配列
配列を組み合わせて、多次元配列を作れます。
2次元配列
package main
import "fmt"
func main() {
// 3行5列の配列
var matrix [3][5]int
// 初期化
matrix[0][0] = 1
matrix[0][1] = 2
matrix[1][0] = 3
fmt.Println(matrix)
// [[1 2 0 0 0] [3 0 0 0 0] [0 0 0 0 0]]
// 初期値を指定
matrix2 := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
fmt.Println(matrix2)
// [[1 2 3] [4 5 6]]
}
3次元配列
package main
import "fmt"
func main() {
// 2×2×2の配列
var cube [2][2][2]float64
cube[0][0][0] = 1.0
cube[1][1][1] = 8.0
fmt.Println(cube)
// 初期化
cube2 := [2][2][2]int{
{
{1, 2},
{3, 4},
},
{
{5, 6},
{7, 8},
},
}
fmt.Println(cube2)
// [[[1 2] [3 4]] [[5 6] [7 8]]]
}
5. 配列の走査
for文での走査
package main
import "fmt"
func main() {
arr := [5]int{10, 20, 30, 40, 50}
// 方法1: インデックスを使う
for i := 0; i < len(arr); i++ {
fmt.Printf("%d: %d\n", i, arr[i])
}
// 方法2: for-range
for i, v := range arr {
fmt.Printf("%d: %d\n", i, v)
}
// インデックスが不要な場合
for _, v := range arr {
fmt.Println(v)
}
}
多次元配列の走査
package main
import "fmt"
func main() {
matrix := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
// ネストしたループ
for i := 0; i < len(matrix); i++ {
for j := 0; j < len(matrix[i]); j++ {
fmt.Printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j])
}
}
// for-range版
for i, row := range matrix {
for j, val := range row {
fmt.Printf("matrix[%d][%d] = %d\n", i, j, val)
}
}
}
6. 配列と関数
配列を引数として渡す
配列は値型なので、コピーが渡されます。
package main
import "fmt"
func modify(arr [3]int) {
arr[0] = 100 // コピーを変更
fmt.Println("関数内:", arr)
}
func main() {
arr := [3]int{1, 2, 3}
modify(arr)
// 関数内: [100 2 3]
// 元の配列は変わらない
fmt.Println("main:", arr)
// main: [1 2 3]
}
ポインタを使って配列を変更
package main
import "fmt"
func modifyByPointer(arr *[3]int) {
arr[0] = 100 // 元の配列を変更
fmt.Println("関数内:", *arr)
}
func main() {
arr := [3]int{1, 2, 3}
modifyByPointer(&arr)
// 関数内: [100 2 3]
// 元の配列が変わる
fmt.Println("main:", arr)
// main: [100 2 3]
}
7. 配列 vs スライス
配列とスライスは似ていますが、重要な違いがあります。
| 特徴 | 配列 | スライス |
|---|---|---|
| 長さ | 固定(型の一部) | 可変 |
| 宣言 | [5]int | []int |
| 値型/参照型 | 値型 | 参照型 |
| サイズ変更 | 不可 | 可能(append) |
package main
import "fmt"
func main() {
// 配列(長さ固定)
arr := [3]int{1, 2, 3}
fmt.Printf("配列: %v, 型: %T\n", arr, arr)
// スライス(長さ可変)
slice := []int{1, 2, 3}
fmt.Printf("スライス: %v, 型: %T\n", slice, slice)
// スライスは要素を追加できる
slice = append(slice, 4, 5)
fmt.Println("追加後:", slice) // [1 2 3 4 5]
// 配列は追加できない
// arr = append(arr, 4) // エラー!
}
8. 再帰的な配列型の制限
配列型は、自分自身を要素として直接または間接的に含むことができません(配列・構造体経由の場合)。
無効な例
// ❌ 無効な配列型
// T1は自分自身を要素に持つ
// type T1 [10]T1
// T2は構造体経由で自分自身を含む
// type T2 [10]struct{ f T2 }
// T3とT4が相互に参照
// type T3 [10]T4
// type T4 struct{ f T3 }
有効な例
ポインタ、関数、スライス経由なら再帰的な定義が可能です。
package main
import "fmt"
// ✅ 有効な配列型
// ポインタ経由
type T5 [10]*T5
// 関数経由
type T6 [10]func() T6
// スライス経由
type T7 [10]struct{ f []T7 }
func main() {
var t5 T5
var t6 T6
var t7 T7
fmt.Printf("%T %T %T\n", t5, t6, t7)
}
9. 実用例
例1: 固定サイズのバッファ
package main
import "fmt"
const BufferSize = 1024
type Buffer [BufferSize]byte
func main() {
var buf Buffer
// データを書き込む
copy(buf[:], []byte("Hello, World!"))
// 最初の13バイトを表示
fmt.Printf("%s\n", buf[:13])
// Hello, World!
}
例2: ルックアップテーブル
package main
import "fmt"
func main() {
// 月の日数(平年)
daysInMonth := [12]int{
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31,
}
month := 2 // 3月(0始まりなので2)
fmt.Printf("3月の日数: %d日\n", daysInMonth[month])
}
例3: 行列計算
package main
import "fmt"
type Matrix [2][2]int
func AddMatrices(a, b Matrix) Matrix {
var result Matrix
for i := 0; i < 2; i++ {
for j := 0; j < 2; j++ {
result[i][j] = a[i][j] + b[i][j]
}
}
return result
}
func main() {
a := Matrix{{1, 2}, {3, 4}}
b := Matrix{{5, 6}, {7, 8}}
c := AddMatrices(a, b)
fmt.Println(c)
// [[6 8] [10 12]]
}
例4: 固定サイズのキュー
package main
import "fmt"
type Queue struct {
data [5]int
front int
rear int
size int
}
func (q *Queue) Enqueue(val int) bool {
if q.size == 5 {
return false // キューが満杯
}
q.data[q.rear] = val
q.rear = (q.rear + 1) % 5
q.size++
return true
}
func (q *Queue) Dequeue() (int, bool) {
if q.size == 0 {
return 0, false // キューが空
}
val := q.data[q.front]
q.front = (q.front + 1) % 5
q.size--
return val, true
}
func main() {
var q Queue
q.Enqueue(1)
q.Enqueue(2)
q.Enqueue(3)
val, ok := q.Dequeue()
if ok {
fmt.Println("取り出した値:", val) // 1
}
}
まとめ: 配列型で覚えておくべきこと
配列の基本
- 固定長: 長さは型の一部で変更不可
- 同じ型: すべての要素が同じ型
- 値型: 代入や引数渡しでコピーされる
- 0始まり: インデックスは0から
len(arr)-1まで
宣言と初期化
// 宣言
var arr1 [5]int
// 初期値付き
arr2 := [3]string{"a", "b", "c"}
// 長さ自動
arr3 := [...]int{1, 2, 3, 4}
// インデックス指定
arr4 := [5]int{0: 10, 4: 50}
配列 vs スライス
// 配列: 長さ固定
arr := [5]int{1, 2, 3, 4, 5}
// スライス: 長さ可変(推奨)
slice := []int{1, 2, 3, 4, 5}
slice = append(slice, 6) // 要素を追加可能
実用的なアドバイス
package main
import "fmt"
func main() {
// 1. 固定サイズが必要な場合のみ配列を使う
var buffer [1024]byte
// 2. 通常はスライスを使う方が柔軟
slice := []int{1, 2, 3}
slice = append(slice, 4)
// 3. 多次元データには配列が便利
matrix := [3][3]int{
{1, 0, 0},
{0, 1, 0},
{0, 0, 1},
}
// 4. 大きな配列は関数にポインタで渡す
largeArray := [1000000]int{}
processArray(&largeArray) // コピーを避ける
fmt.Println(buffer[0], slice, matrix[0][0])
}
func processArray(arr *[1000000]int) {
// 処理
}
配列は、固定サイズのデータ構造として重要ですが、Goではスライスの方が一般的に使われます。用途に応じて適切に使い分けましょう!
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント