【初心者必見】Go言語の「配列とスライス」完全ガイド|違い・使い分け・実践コード付き

Go

Go言語を学び始めると、必ず出てくるのが「配列(array)」と「スライス(slice)」です。

一見似ているこの2つ、実は使い方も性質も大きく異なるため、初学者がつまずきやすいポイントでもあります。

この記事では、Goにおける配列とスライスの違い・使い分け・書き方の実例をやさしく解説します。

「結局、配列とスライスはどう違うの?」
「どっちを使えばいいの?」
という疑問をスッキリ解決します。

スポンサーリンク

Goの「配列」とは?基本の書き方と特徴

配列の基本概念

配列(array)は、固定長のデータ構造で、Goでは「長さも型の一部」として扱われます。

宣言の基本構文

package main

import "fmt"

func main() {
    // 長さ3のint型配列を宣言
    var arr [3]int
    fmt.Println(arr) // [0 0 0](ゼロ値で初期化)
}

説明: [3]intは「3つのint型を持つ配列」を意味します。

配列の初期化方法

// 方法1:値を指定して初期化
arr1 := [3]int{1, 2, 3}

// 方法2:長さを自動で決める
arr2 := [...]int{1, 2, 3, 4, 5}

// 方法3:一部の要素だけ指定
arr3 := [5]int{0: 10, 2: 20} // [10 0 20 0 0]

要素へのアクセスと変更

arr := [3]int{1, 2, 3}

// 要素の取得
fmt.Println(arr[0]) // 1

// 要素の変更
arr[0] = 10
fmt.Println(arr) // [10 2 3]

// 配列の長さ
fmt.Println(len(arr)) // 3

配列の特徴まとめ

  • 固定長 一度作ったら長さを変更できません
  • 値渡し 関数に渡すとき、配列全体がコピーされます
  • 型に長さが含まれる [3]int[4]intは別の型として扱われます
  • メモリ効率 サイズが決まっているため、メモリ使用量が予測しやすいです

配列の実用例

// 曜日を管理する配列
weekdays := [7]string{
    "月曜日", "火曜日", "水曜日", "木曜日", 
    "金曜日", "土曜日", "日曜日",
}

// RGB値を管理する配列
red := [3]int{255, 0, 0}

ポイント: Goの配列は「長さ固定・値渡し」という制限があるため、シンプルな用途に向いています。

Goの「スライス」とは?柔軟性のある配列風データ構造

スライスの基本概念

スライス(slice)は、可変長の配列のような構造で、Goで最もよく使われるデータ型です。

基本構文と初期化

package main

import "fmt"

func main() {
    // 方法1:リテラルで初期化
    nums := []int{10, 20, 30}
    fmt.Println(nums) // [10 20 30]
    
    // 方法2:make関数で作成
    s := make([]int, 5) // 長さ5、容量5
    fmt.Println(s)      // [0 0 0 0 0]
    
    // 方法3:長さと容量を別々に指定
    s2 := make([]int, 3, 10) // 長さ3、容量10
    fmt.Println(len(s2), cap(s2)) // 3 10
}

重要: 長さを指定しない[]intはスライスになります。

スライスの追加と削除

// 要素の追加
nums := []int{10, 20, 30}
nums = append(nums, 40)
fmt.Println(nums) // [10 20 30 40]

// 複数要素の追加
nums = append(nums, 50, 60)
fmt.Println(nums) // [10 20 30 40 50 60]

// 別のスライスを追加
other := []int{70, 80}
nums = append(nums, other...)
fmt.Println(nums) // [10 20 30 40 50 60 70 80]

スライスの特徴まとめ

可変長 append関数で要素を追加できます

参照渡し 関数に渡すとき、参照がコピーされるため効率的です

柔軟性 部分的な切り出しや結合が簡単にできます

実用性 Goのプログラムで最もよく使われるデータ構造です

スライスの内部構造

// スライスは以下の3つの情報を持っています
// - ポインタ:配列の先頭アドレス
// - 長さ:現在の要素数
// - 容量:内部配列の最大サイズ

s := make([]int, 3, 5)
fmt.Printf("長さ: %d, 容量: %d\n", len(s), cap(s))
// 長さ: 3, 容量: 5

覚えておこう: スライスはGoで最もよく使われる柔軟なデータ構造。
appendでの追加が可能で、扱いやすさが魅力です。

配列とスライスの違い・使い分けポイント

詳細比較表

比較項目配列(array)スライス(slice)
長さ固定可変
宣言形式[N]Type[]Type
渡し方値渡し(コピー)参照渡し
拡張性不可appendで可能
メモリ連続領域に確保内部配列への参照
型の扱い長さも型の一部長さは型に含まれない
使用頻度非常に高い

実際のコード例での比較

package main

import "fmt"

func main() {
    // 配列の例
    arr := [3]int{1, 2, 3}
    modifyArray(arr)
    fmt.Println("配列(元):", arr) // [1 2 3](変更されない)
    
    // スライスの例
    slice := []int{1, 2, 3}
    modifySlice(slice)
    fmt.Println("スライス(元):", slice) // [999 2 3](変更される)
}

func modifyArray(arr [3]int) {
    arr[0] = 999 // コピーが変更される
}

func modifySlice(s []int) {
    s[0] = 999 // 元のスライスが変更される
}

使い分けのガイドライン

配列を使う場面:

  • 固定サイズで高速性が必要
  • RGB値や座標など、要素数が決まっている
  • メモリ使用量を厳密に管理したい

スライスを使う場面:

  • 一般的なリスト処理(ほとんどの場合)
  • 要素数が変動する可能性がある
  • 関数間でデータを効率的に受け渡ししたい

実務での推奨: 迷ったらスライスを使いましょう。Goの標準ライブラリでもスライスが主流です。

スライスの便利な操作とテクニック集

その1:スライスの一部を取り出す(スライシング)

nums := []int{1, 2, 3, 4, 5}

// 基本的な切り出し
sub1 := nums[1:4]    // [2 3 4](1番目から3番目まで)
sub2 := nums[:3]     // [1 2 3](最初から2番目まで)
sub3 := nums[2:]     // [3 4 5](2番目から最後まで)
sub4 := nums[:]      // [1 2 3 4 5](全体のコピー)

fmt.Println(sub1, sub2, sub3, sub4)

その2:要素の削除

// 特定のインデックスの要素を削除
nums := []int{1, 2, 3, 4, 5}

// 2番目(インデックス1)の要素を削除
nums = append(nums[:1], nums[2:]...)
fmt.Println(nums) // [1 3 4 5]

// 最後の要素を削除
nums = nums[:len(nums)-1]
fmt.Println(nums) // [1 3 4]

その3:スライスのコピー

// 浅いコピー(参照は同じ)
original := []int{1, 2, 3}
shallow := original
shallow[0] = 999
fmt.Println(original) // [999 2 3](影響を受ける)

// 深いコピー(完全に独立)
original2 := []int{1, 2, 3}
deep := make([]int, len(original2))
copy(deep, original2)
deep[0] = 999
fmt.Println(original2) // [1 2 3](影響を受けない)

その4:スライスの長さと容量

s := make([]int, 3, 10)
fmt.Printf("初期状態 - 長さ: %d, 容量: %d\n", len(s), cap(s))
// 初期状態 - 長さ: 3, 容量: 10

s = append(s, 4, 5, 6, 7, 8)
fmt.Printf("追加後 - 長さ: %d, 容量: %d\n", len(s), cap(s))
// 追加後 - 長さ: 8, 容量: 10

s = append(s, 9, 10, 11)
fmt.Printf("容量超過後 - 長さ: %d, 容量: %d\n", len(s), cap(s))
// 容量超過後 - 長さ: 11, 容量: 20(自動で拡張)

その5:スライスの結合

slice1 := []int{1, 2, 3}
slice2 := []int{4, 5, 6}

// 2つのスライスを結合
combined := append(slice1, slice2...)
fmt.Println(combined) // [1 2 3 4 5 6]

// 複数のスライスを結合
slice3 := []int{7, 8, 9}
allCombined := append(append(slice1, slice2...), slice3...)
fmt.Println(allCombined) // [1 2 3 4 5 6 7 8 9]

その6:スライスの検索と存在確認

// 要素の存在確認
func contains(slice []int, item int) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

nums := []int{1, 2, 3, 4, 5}
fmt.Println(contains(nums, 3)) // true
fmt.Println(contains(nums, 6)) // false

その7:スライスの逆順

// スライスを逆順にする
func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

nums := []int{1, 2, 3, 4, 5}
reverse(nums)
fmt.Println(nums) // [5 4 3 2 1]

実践的な活用例

Webアプリケーションでのユーザーリスト管理

type User struct {
    ID   int
    Name string
    Age  int
}

// ユーザーリストの管理
users := []User{
    {1, "田中", 25},
    {2, "佐藤", 30},
    {3, "鈴木", 28},
}

// 新しいユーザーの追加
newUser := User{4, "高橋", 32}
users = append(users, newUser)

// 特定条件のユーザーを抽出
var adults []User
for _, user := range users {
    if user.Age >= 30 {
        adults = append(adults, user)
    }
}

ログデータの処理

// ログエントリの構造体
type LogEntry struct {
    Timestamp string
    Level     string
    Message   string
}

// ログデータの管理
logs := []LogEntry{
    {"2024-01-01 10:00:00", "INFO", "アプリケーション開始"},
    {"2024-01-01 10:01:00", "ERROR", "データベース接続エラー"},
    {"2024-01-01 10:02:00", "INFO", "接続復旧"},
}

// エラーログだけを抽出
var errorLogs []LogEntry
for _, log := range logs {
    if log.Level == "ERROR" {
        errorLogs = append(errorLogs, log)
    }
}

よくある質問と答え

Q: 配列とスライスはいつ使い分けるべきですか?
A: 99%の場合はスライスを使用します。
配列は固定サイズで高いパフォーマンスが必要な特殊な用途でのみ使用します。

Q: スライスの容量はいつ拡張されますか?
A: 長さが容量を超えたときに自動的に拡張されます。
通常は容量が2倍になります。

Q: nilスライスと空スライスの違いは何ですか?
A: nilスライスはvar s []int、空スライスはs := []int{}です。
どちらも長さは0ですが、nilスライスは内部配列も存在しません。

まとめ:Goの配列とスライスを使いこなそう!

重要ポイントまとめ:

  • 配列:固定長・値渡し・高速性重視
  • スライス:可変長・参照渡し・実用性が高い
  • ほとんどはスライスを使用
  • appendと スライシングを覚えれば大部分の操作が可能

Go言語では、配列とスライスが明確に区別されており、それぞれに用途と特徴があります。

基本的にはスライスを使いこなせばOKですが、配列との違いを知っておくことで、パフォーマンス改善やメモリ管理の理解も深まります

コメント

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