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ですが、配列との違いを知っておくことで、パフォーマンス改善やメモリ管理の理解も深まります。
コメント