キーワード

Go言語のrange文の基本的な使い方について解説

Go言語のrange文は、配列やスライス、マップ、チャネルなど、さまざまなデータ集合をシンプルに反復処理するための便利な構文です。

インデックスと要素が自動で取得できるため、コードの記述と可読性が向上します。

この記事では、具体例を交えてrange文の基本的な活用方法を説明します。

基本構文

range文の基本書式

range文は、配列、スライス、マップ、チャネルといったデータ型の要素を簡単に反復処理するために使用されます。

以下は基本的な書式の例です。

package main
import "fmt"
func main() {
	// スライスの作成
	nums := []int{1, 2, 3, 4, 5}
	// インデックスと値を取得して出力する
	for index, value := range nums {
		fmt.Println("Index:", index, "Value:", value)
	}
}
Index: 0 Value: 1
Index: 1 Value: 2
Index: 2 Value: 3
Index: 3 Value: 4
Index: 4 Value: 5

インデックスと要素の自動取得

range文を使うと、データ型のインデックスとその値を自動的に取得できます。

以下は、スライスの各要素に対して、インデックスと要素を出力するコード例です。

package main
import "fmt"
func main() {
	months := []string{"1月", "2月", "3月"}
	for idx, month := range months {
		fmt.Printf("インデックス:%d, 月:%s\n", idx, month)
	}
}
インデックス:0, 月:1月
インデックス:1, 月:2月
インデックス:2, 月:3月

空白識別子の活用

不要な値を取得したくない場合、もしくはどちらか一方だけが必要な場合、空白識別子 _ を使用して値を無視することができます。

以下は、インデックスを無視して要素のみを取得する例です。

package main
import "fmt"
func main() {
	names := []string{"太郎", "花子", "次郎"}
	// インデックスを無視して要素だけを表示
	for _, name := range names {
		fmt.Println("名前:", name)
	}
}
名前: 太郎
名前: 花子
名前: 次郎

配列・スライスにおけるrangeの使用

配列の例

Goでは、配列に対してもrange文を利用することができます。

以下は、配列の各要素を取り出す例です。

package main
import "fmt"
func main() {
	// 配列の作成
	colors := [3]string{"赤", "緑", "青"}
	for index, color := range colors {
		fmt.Printf("インデックス:%d 色:%s\n", index, color)
	}
}
インデックス:0 色:赤
インデックス:1 色:緑
インデックス:2 色:青

スライスの例

スライスも配列と同様にrange文で反復処理が可能です。

以下は、スライス内の値を出力するコード例です。

package main
import "fmt"
func main() {
	// スライスの作成
	fruits := []string{"りんご", "バナナ", "オレンジ"}
	for i, fruit := range fruits {
		fmt.Printf("位置:%d 果物:%s\n", i, fruit)
	}
}
位置:0 果物:りんご
位置:1 果物:バナナ
位置:2 果物:オレンジ

要素の変更と反映

range文で取得される値はコピーであるため、値を直接変更しても元のスライスには影響がありません。

正しくスライスの要素を変更する方法の例を示します。

package main
import "fmt"
func main() {
	nums := []int{10, 20, 30}
	// この処理ではコピーされた値に対して変更しているため、元のスライスには反映されない
	for _, num := range nums {
		num += 5
	}
	fmt.Println("変更後のスライス:", nums)
	// インデックスを使用して実際の要素を変更する
	for i := range nums {
		nums[i] += 5
	}
	fmt.Println("正しく変更後のスライス:", nums)
}
変更後のスライス: [10 20 30]
正しく変更後のスライス: [15 25 35]

マップにおけるrangeの使用

キーと値の取得方法

マップに対してrange文を使うと、キーとその値を同時に取得できます。

以下はマップの各要素を出力する例です。

package main
import "fmt"
func main() {
	// マップの作成
	ages := map[string]int{
		"山田": 30,
		"佐藤": 25,
		"鈴木": 40,
	}
	for key, value := range ages {
		fmt.Printf("名前:%s 年齢:%d\n", key, value)
	}
}
名前:山田 年齢:30
名前:佐藤 年齢:25
名前:鈴木 年齢:40

反復順序の特性

マップのrangeによる反復は、順序が保証されない特徴があります。

以下のコード例は、要素の出力順序が毎回変わる可能性があることを示します。

package main
import "fmt"
func main() {
	// マップの作成
	data := map[string]int{
		"a": 1,
		"b": 2,
		"c": 3,
	}
	for k, v := range data {
		fmt.Printf("キー:%s 値:%d\n", k, v)
	}
}
キー:a 値:1
キー:b 値:2
キー:c 値:3

並列処理時の注意点

マップを並列処理で利用する場合、rangeによる反復順序が保証されず、また同時に書き換えが行われると結果が予測できなくなる可能性があります。

以下は、読み取り専用の例ですが、書き換えの場合はロックなどの同期処理が必要です。

package main
import (
	"fmt"
	"sync"
)
func main() {
	data := map[string]int{
		"x": 10,
		"y": 20,
		"z": 30,
	}
	var wg sync.WaitGroup
	// 並列処理でマップを読み取る例
	for key, value := range data {
		wg.Add(1)
		go func(k string, v int) {
			defer wg.Done()
			// 並列処理内で値を出力
			fmt.Printf("ゴルーチン - キー:%s 値:%d\n", k, v)
		}(key, value)
	}
	wg.Wait()
}
ゴルーチン - キー:x 値:10
ゴルーチン - キー:y 値:20
ゴルーチン - キー:z 値:30

チャネルとrange

チャネルからの値受信

チャネルに対するrange文は、チャネルが閉じられるまで値を受信します。

以下は、チャネルから値を読み取るサンプルコードです。

package main
import "fmt"
func main() {
	ch := make(chan string)
	// 非同期で値を送信するゴルーチン
	go func() {
		ch <- "メッセージ1"
		ch <- "メッセージ2"
		ch <- "メッセージ3"
		close(ch) // チャネルを閉じる
	}()
	// チャネルから値を受信
	for msg := range ch {
		fmt.Println("受信:", msg)
	}
}
受信: メッセージ1
受信: メッセージ2
受信: メッセージ3

ループ終了条件の確認

チャネルが閉じられると、range文は自動的にループを終了します。

以下のコードは、チャネルが閉じられた際にループが終了する様子を示しています。

package main
import "fmt"
func main() {
	ch := make(chan int)
	go func() {
		for i := 0; i < 3; i++ {
			ch <- i * 10
		}
		close(ch)
	}()
	// チャネルが閉じられるまで値を受信
	for val := range ch {
		fmt.Println("値:", val)
	}
	fmt.Println("チャネルが閉じられたためループ終了")
}
値: 0
値: 10
値: 20
チャネルが閉じられたためループ終了

その他の活用例と注意点

ネストしたrangeの使い方

多次元配列やスライスのネスト構造に対して、range文を入れ子にして利用することができます。

以下は、2次元スライスで各要素の位置と値を出力する例です。

package main
import "fmt"
func main() {
	matrix := [][]int{
		{1, 2, 3},
		{4, 5, 6},
		{7, 8, 9},
	}
	for i, row := range matrix {
		for j, value := range row {
			fmt.Printf("行:%d 列:%d 値:%d\n", i, j, value)
		}
	}
}
行:0 列:0 値:1
行:0 列:1 値:2
行:0 列:2 値:3
行:1 列:0 値:4
行:1 列:1 値:5
行:1 列:2 値:6
行:2 列:0 値:7
行:2 列:1 値:8
行:2 列:2 値:9

range文と従来のfor文の違い

range文は、データ型の要素をシンプルかつ直感的に反復処理できる特徴があります。

従来のforループとの違いとして、例えば以下の点が挙げられます。

・インデックスと要素が自動的に取得できる

・値はコピーされるため、直接変更する場合は注意が必要

以下に、従来のfor文とrange文による反復処理の例を示します。

package main
import "fmt"
func main() {
	// スライスの作成
	nums := []int{1, 2, 3}
	// 従来のfor文による反復処理
	for i := 0; i < len(nums); i++ {
		fmt.Printf("従来のfor - インデックス:%d 値:%d\n", i, nums[i])
	}
	// range文による反復処理
	for i, value := range nums {
		fmt.Printf("range - インデックス:%d 値:%d\n", i, value)
	}
}
従来のfor - インデックス:0 値:1
従来のfor - インデックス:1 値:2
従来のfor - インデックス:2 値:3
range - インデックス:0 値:1
range - インデックス:1 値:2
range - インデックス:2 値:3

まとめ

この記事では、Go言語のrange文を使って配列、スライス、マップ、チャネルなどの各データ型の反復処理方法を解説しました。

range文の基本構文とそれぞれの特性が把握できる総括的な内容です。

ぜひ、実際にコードを書き、range文の活用にチャレンジしてみてください。

関連記事

Back to top button
目次へ