Go言語多次元配列
多次元配列は配列の配列であり、それは1つのデータテーブル或行列として考えることができ、その各要素は複数のインデックスでアクセスできます。
Go言語の多次元配列は、表形式データ、行列演算、ゲームボードなどの構造化された情報を処理するために使用できます。
基本概念
- 1次元配列:直線上の点のようなもので、1つの座標(インデックス)だけで特定の要素を見つけることができます
- 2次元配列:テーブルのようなもので、行と列の2つの座標が必要です
- 3次元配列:立方体のようなもので、縦・横・高さの3つの座標が必要です
- より高次元:理論的には多次元にできますが、実際のプログラミングでは2次元と3次元が最もよく使われます
身近な駐車スペースに例えることができます:
- 1次元配列= 1列の駐車スペース(駐車番号だけが必要)
- 2次元配列= 多層駐車場(フロアと駐車番号が必要)
- 3次元配列= 複数の多層駐車場(駐車場番号、フロア、駐車番号が必要)
Go言語は多次元配列をサポートしています。以下は一般的な多次元配列の宣言方法です:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
宣言と初期化:
// 2次元配列を宣言 var 数组名 [行数][列数]元素类型 // 宣言と初期化 var 数组名 [行数][列数]元素类型 = [行数][列数]元素类型{初始化值}以下の例では、3次元の整数配列を宣言しています:
var threedim [5][10][4]int
2次元配列
2次元配列は最も単純な多次元配列です。2次元配列は本質的に1次元配列から構成されます。2次元配列の定義方法は次のとおりです:
var arrayName [ x ][ y ] variable_type
variable_type は Go言語のデータ型、arrayName は配列名です。2次元配列はテーブルと考えることができ、x は行、y は列です。下図は、3行4列の2次元配列 a を示しています:

2次元配列の要素は、a[ i ][ j ]によりアクセスできます。
例
import "fmt"
func main() {
// Step 1: 配列を作成
values := [][]int{}
// Step 2: append() 関数を使用して、空の2次元配列に1次元配列を2行追加
row1 := []int{1, 2, 3}
row2 := []int{4, 5, 6}
values = append(values, row1)
values = append(values, row2)
// Step 3: 2行のデータを表示
fmt.Println("Row 1")
fmt.Println(values[0])
fmt.Println("Row 2")
fmt.Println(values[1])
// Step 4: 最初の要素にアクセス
fmt.Println("最初の要素は:")
fmt.Println(values[0][0])
}
上記の例の実行出力結果は次のとおりです:
Row 1 [1 2 3] Row 2 [4 5 6] 第一个元素为: 1
2次元配列の初期化
多次元配列は波括弧を使用して初期化できます。以下の例は、3行4列の2次元配列です:
a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}, /* 第三行索引为 2 */
}
注意:上記のコードの最後から2行目の}にはカンマが必要です。なぜなら最後の行の}を単独の行にすることはできません。次のように書くこともできます:a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}} /* 第三行索引为 2 */
以下の例では、2行2列の2次元配列を初期化します:
例
import "fmt"
func main() {
// 2次元配列を作成
sites := [2][2]string{}
// 2次元配列に要素を追加
sites[0][0] = "Google"
sites[0][1] = "サンプル"
sites[1][0] = "Taobao"
sites[1][1] = "Weibo"
// 結果を表示
fmt.Println(sites)
}
上記の例の実行出力結果は次のとおりです:
[[Google サンプル] [Taobao Weibo]]
2次元配列へのアクセス
2次元配列は座標を指定してアクセスします。配列の行インデックスと列インデックスのように、例えば:
val := a[2][3] 或 var value int = a[2][3]
上記の例では、2次元配列 val の3行目の4番目の要素にアクセスしています。
2次元配列は、入れ子ループを使用して要素を出力できます:
例
import "fmt"
func main() {
/* 配列 - 5行2列 */
var a = [5][2]int{ {0,0}, {1,2}, {2,4}, {3,6},{4,8}}
var i, j int
/* 配列要素を出力 */
for i = 0; i < 5; i++ {
for j = 0; j < 2; j++ {
fmt.Printf("a[%d][%d] = %d\n", i,j, a[i][j] )
}
}
}
上記の例の実行出力結果は次のとおりです:
a[0][0] = 0 a[0][1] = 0 a[1][0] = 1 a[1][1] = 2 a[2][0] = 2 a[2][1] = 4 a[3][0] = 3 a[3][1] = 6 a[4][0] = 4 a[4][1] = 8
以下の例では、各次元の要素数が異なる多次元配列を作成します:
例
import "fmt"
func main() {
// 空の2次元配列を作成
animals := [][]string{}
// 3つの1次元配列を作成、各配列の長さは異なる
row1 := []string{"fish", "shark", "eel"}
row2 := []string{"bird"}
row3 := []string{"lizard", "salamander"}
// append() 関数を使用して1次元配列を2次元配列に追加
animals = append(animals, row1)
animals = append(animals, row2)
animals = append(animals, row3)
// ループで出力
for i := range animals {
fmt.Printf("Row: %v\n", i)
fmt.Println(animals[i])
}
}
上記の例の実行出力結果は次のとおりです:
Row: 0 [fish shark eel] Row: 1 [bird] Row: 2 [lizard salamander]
配列要素のアクセスと変更
要素へのアクセス
例
import "fmt"
func main() {
// 3x3のマトリックスを作成
matrix := [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
// 単一の要素にアクセス
fmt.Println("1行目2列目:", matrix[0][1]) // 出力: 2
fmt.Println("3行目3列目:", matrix[2][2]) // 出力: 9
// 行全体にアクセス
fmt.Println("2行目:", matrix[1]) // 出力: [4 5 6]
// すべての要素を走査
fmt.Println("\nすべての要素を走査:)
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j])
}
}
}
要素の変更
例
import "fmt"
func main() {
// 2x2のゼロ値マトリックスを作成
var grid [2][2]int
fmt.Println("変更前のマトリックス:", grid)
// 特定の位置の要素を変更
grid[0][0] = 10
grid[0][1] = 20
grid[1][0] = 30
grid[1][1] = 40
fmt.Println("変更後のマトリックス:", grid)
// 1行を一括変更
grid[0] = [2]int{100, 200}
fmt.Println("1行目を変更後:", grid)
}
3次元およびそれ以上の多次元配列
3次元配列の例
例
import "fmt"
func main() {
// 2x3x4の3次元配列を宣言
// 2つの平面があり、各平面が3行4列と考えることができます
var cube [2][3][4]int
// 3次元配列を初期化
cube = [2][3][4]int{
{ // 最初の平面
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
},
{ // 2番目の平面
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24},
},
}
// 3次元配列の要素にアクセス
fmt.Println("cube[0][1][2] =", cube[0][1][2]) // 出力: 7
fmt.Println("cube[1][2][3] =", cube[1][2][3]) // 出力: 24
// 3次元配列を走査
fmt.Println("\n3次元配列の内容:")
for i := 0; i < 2; i++ {
fmt.Printf("平面 %d:\n", i)
for j := 0; j < 3; j++ {
for k := 0; k < 4; k++ {
fmt.Printf("%3d ", cube[i][j][k])
}
fmt.Println()
}
fmt.Println()
}
}
下図を参照してください:

多次元配列の一般的な操作
1. rangeを使用した走査
例
import "fmt"
func main() {
// 2次元配列を作成
scores := [3][4]int{
{85, 90, 78, 92},
{88, 76, 95, 89},
{92, 85, 88, 90},
}
fmt.Println("学生成績表:")
// rangeを使用して2次元配列を走査
for i, row := range scores {
fmt.Printf("学生 %d の成績: ", i+1)
for j, score := range row {
fmt.Printf("%d ", score)
// インデックスと値の両方を使用する場合
_ = j // 未使用変数の警告を回避
}
fmt.Println()
}
// インデックスを気にせず、値だけを気にする
total := 0
count := 0
for _, row := range scores {
for _, score := range row {
total += score
count++
}
}
fmt.Printf("\n平均点: %.2f\n", float64(total)/float64(count))
}
2. 配列の長さの取得
例
import "fmt"
func main() {
// 不規則な多次元配列を作成
jagged := [3][3]int{
{1, 2, 3},
{4, 5},
{6, 7, 8, 9}, // 注意:ここでは各行の長さが一致している必要があるため、コンパイルエラーになります
}
// 正しい例:配列の次元を取得
matrix := [4][5]int{}
// 行数を取得
rows := len(matrix)
fmt.Println("行数:", rows) // 出力: 4
// 最初の行の列数を取得(すべての行の長さは同じ)
cols := len(matrix[0])
fmt.Println("列数:", cols) // 出力: 5
// 総要素数を取得
totalElements := rows * cols
fmt.Println("総要素数:", totalElements) // 出力: 20
}
3. 配列の比較
例
import "fmt"
func main() {
// 同一の2次元配列を2つ作成
a := [2][2]int{{1, 2}, {3, 4}}
b := [2][2]int{{1, 2}, {3, 4}}
c := [2][2]int{{1, 2}, {3, 5}}
// 配列は直接比較できます(次元が完全に同じ場合のみ)
fmt.Println("a == b:", a == b) // 出力: true
fmt.Println("a == c:", a == c) // 出力: false
// 注意:異なる次元の配列は比較できない
// d := [2][3]int{{1, 2, 3}, {4, 5, 6}}
// fmt.Println(a == d) // コンパイルエラー:型が一致しない
}
実際の応用シーン
シーン1:ゲームボード(三目並べ)
例
import "fmt"
func main() {
// 3x3の三目並べボードを初期化
var board [3][3]string
// 空に初期化
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
board[i][j] = " "
}
}
// ゲームをシミュレーション
board[0][0] = "X"
board[1][1] = "O"
board[2][2] = "X"
// ボードを出力
fmt.Println("三目並べボード:")
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Printf(" %s ", board[i][j])
if j < 2 {
fmt.Printf("|")
}
}
fmt.Println()
if i < 2 {
fmt.Println("---+---+---")
}
}
}
シーン2:学生成績管理システム
例
import "fmt"
func main() {
// 定義:3人の学生、各学生に4つのコース
var grades [3][4]float64
// 学生の成績を入力
grades = [3][4]float64{
{85.5, 90.0, 78.5, 92.0}, // 学生1の成績
{88.0, 76.5, 95.0, 89.5}, // 学生2の成績
{92.5, 85.0, 88.5, 90.0}, // 学生3の成績
}
// 各学生の平均点を計算
fmt.Println("学生の成績統計:")
for i, studentGrades := range grades {
sum := 0.0
for _, grade := range studentGrades {
sum += grade
}
average := sum / float64(len(studentGrades))
fmt.Printf("学生 %d: 平均点 = %.2f\n", i+1, average)
}
// 各コースの平均点を計算
fmt.Println("\nコース平均点:")
for j := 0; j < 4; j++ {
sum := 0.0
for i := 0; i < 3; i++ {
sum += grades[i][j]
}
average := sum / 3.0
fmt.Printf("コース %d: 平均点 = %.2f\n", j+1, average)
}
}
シーン3:画像ピクセル処理
例
import "fmt"
func main() {
// シンプルな3x3グレースケール画像をシミュレーション
// 各ピクセル値の範囲:0(黒) ~ 255(白)
var image [3][3]int
// 画像を初期化(シンプルなグラデーション)
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
image[i][j] = (i + j) * 50
}
}
// 元の画像を表示
fmt.Println("元の画像:")
displayImage(image)
// 画像処理:輝度を上げる
fmt.Println("\n輝度を上げた後の画像:")
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
// 輝度を上げるが、255を超えない
newValue := image[i][j] + 50
if newValue > 255 {
newValue = 255
}
image[i][j] = newValue
}
}
displayImage(image)
}
func displayImage(img [3][3]int) {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Printf("%3d ", img[i][j])
}
fmt.Println()
}
}
注意事項とベストプラクティス
1. 配列の長さは型の一部である
例
import "fmt"
func main() {
// これらは異なる型です!
var a [2][3]int
var b [3][2]int
// 以下のコードはコンパイルエラーになる
// a = b // エラー:型が一致しない
fmt.Printf("a の型: %T\n", a) // [2][3]int
fmt.Printf("b の型: %T\n", b) // [3][2]int
}
2. 値型 vs 参照型
例
import "fmt"
func main() {
// 配列は値型です
original := [2][2]int{{1, 2}, {3, 4}}
// 代入するとコピーが作成されます
copy := original
// コピーを変更しても元の配列には影響しません
copy[0][0] = 100
fmt.Println("元の配列:", original) // [[1 2] [3 4]]
fmt.Println("コピーの配列:", copy) // [[100 2] [3 4]]
// 参照セマンティクスが必要な場合は、スライスを使用できます(後続の記事で紹介)
}
3. パフォーマンスの考慮
- メモリ連続:多次元配列はメモリ内で連続して格納されるため、アクセス速度が速い
- 固定サイズ:配列の長さはコンパイル時に決定され、動的に変更できない
- 適したシナリオ:データサイズが既知で固定されている場合、配列が最適な選択です
よくある質問(FAQ)
Q1: 多次元配列とネストされたスライスの違いは何ですか?
| 特性 | 多次元配列 | ネストされたスライス |
|---|---|---|
| サイズ | 固定、コンパイル時に決定 | 動的、実行時に変更可能 |
| メモリ | 連続割り当て | 非連続の可能性 |
| パフォーマンス | アクセス速度が速い | やや遅い、追加オーバーヘッドあり |
| 使用シナリオ | データサイズが既知 | データサイズが可変 |
Q2: 不規則な2次元配列を作成するには?
Goの配列は各行の長さが同じである必要があります。不規則な構造が必要な場合は、スライスを使用する必要があります:
例
irregular := [][]int{
{1, 2, 3},
{4, 5}, // この行は2つの要素のみ
{6, 7, 8, 9}, // この行は4つの要素
}
Q3: 多次元配列を関数の引数として使用できますか?
はい、ただし配列は値型であり、大きな配列を渡すとパフォーマンスのオーバーヘッドが発生することに注意してください:
例
// 行列を処理...
matrix[0][0] = 100
return matrix
}
// より良い方法はポインタまたはスライスを使用することです
func processMatrixPtr(matrix *[3][3]int) {
matrix[0][0] = 100
}