golang_基本コード解説
はい、承知いたしました。「golang」の「文法」について、初心者が理解できるように最適なコード例を交えながら網羅的に解説します。
Go言語の文法を基礎から徹底解説!初心者向け網羅ガイド
Go言語は、シンプルで効率的なプログラミング言語として、クラウドエンジニアリングの分野で広く利用されています。この記事では、Go言語の文法について、プログラミング初心者の方でも理解できるように、コード例を豊富に交えながら丁寧に解説します。
1. Go言語の基本構成
まずは、Go言語で書かれたプログラムの基本的な構成要素を見ていきましょう。
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
コード解説
-
package main
:- Go言語のプログラムは、必ず何らかのパッケージに属している必要があります。
-
package main
は、実行可能なプログラムのエントリーポイントとなるパッケージを意味します。
-
import "fmt"
:-
import
は、外部のパッケージをプログラムに取り込むためのキーワードです。 -
"fmt"
は、フォーマットされた入出力を行うための標準パッケージで、fmt.Println()
関数などが含まれています。
-
-
func main() { ... }
:-
func
は、関数を定義するためのキーワードです。 -
main()
関数は、プログラム実行時に最初に呼び出される特別な関数です。 -
{ ... }
で囲まれた部分が、関数の処理内容(ブロック)を記述する箇所です。
-
-
fmt.Println("Hello, Go!")
:-
fmt.Println()
は、fmt
パッケージに含まれる関数で、引数に与えられた文字列を標準出力に出力します。 -
"Hello, Go!"
は、出力する文字列リテラルです。
-
実行結果
Hello, Go!
このコードは、Go言語で最も基本的なプログラムの一つで、「Hello, Go!」というメッセージを画面に出力するものです。
2. 変数とデータ型
プログラムでデータを扱うためには、変数を宣言し、適切なデータ型を割り当てる必要があります。
2.1. 変数宣言
Go言語では、変数を宣言する方法がいくつかあります。
(1) var
キーワードを使った宣言
var message string
message = "Hello, World!"
fmt.Println(message)
-
var message string
は、message
という名前の変数を宣言し、そのデータ型をstring
(文字列型)と指定しています。 - 変数の宣言と代入を分離することも可能です。
(2) 短縮変数宣言 :=
message := "Hello, World!"
fmt.Println(message)
-
:=
は、変数の宣言と初期値の代入を同時に行うための短縮記法です。 - データ型は、代入される値から自動的に推論されます(型推論)。
- 関数内でのみ使用可能です。
(3) 複数変数の宣言
var name, age = "太郎", 30
fmt.Println(name, age)
var (
firstName = "太郎"
lastName = "山田"
)
fmt.Println(firstName, lastName)
- 複数の変数をまとめて宣言することも可能です。
-
var()
ブロックを使うと、複数行にわたる宣言を整理して記述できます。
2.2. 主要なデータ型
Go言語には、様々なデータ型が用意されています。
-
整数型:
-
int
: 符号付き整数(環境によってbit数が変わる) -
int8
,int16
,int32
,int64
: bit数指定の符号付き整数 -
uint
,uint8
,uint16
,uint32
,uint64
: 符号なし整数
-
-
浮動小数点数型:
-
float32
: 単精度浮動小数点数 -
float64
: 倍精度浮動小数点数
-
-
複素数型:
-
complex64
: 単精度複素数 -
complex128
: 倍精度複素数
-
-
文字列型:
-
string
: 文字列
-
-
真偽値型:
-
bool
: 真理値 (true
またはfalse
)
-
データ型とコード例
package main
import "fmt"
func main() {
// 整数型
var integer int = 10
fmt.Printf("型: %T, 値: %v\n", integer, integer) // %T: 型, %v: 値
// 浮動小数点数型
var float float64 = 3.14
fmt.Printf("型: %T, 値: %v\n", float, float)
// 文字列型
var message string = "Hello, Go!"
fmt.Printf("型: %T, 値: %v\n", message, message)
// 真偽値型
var boolean bool = true
fmt.Printf("型: %T, 値: %v\n", boolean, boolean)
}
実行結果
型: int, 値: 10
型: float64, 値: 3.14
型: string, 値: Hello, Go!
型: bool, 値: true
fmt.Printf()
関数の %T
は変数の型、%v
は値を表示するための書式指定子です。
3. 演算子
Go言語では、様々な演算子を使って値の計算や比較を行います。
3.1. 算術演算子
演算子 | 説明 | 例 |
---|---|---|
+ |
加算 | a + b |
- |
減算 | a - b |
* |
乗算 | a * b |
/ |
除算 | a / b |
% |
剰余 | a % b |
++ |
インクリメント |
a++ (後置) |
-- |
デクリメント |
a-- (後置) |
コード例
package main
import "fmt"
func main() {
a := 10
b := 3
fmt.Println("a + b =", a+b)
fmt.Println("a - b =", a-b)
fmt.Println("a * b =", a*b)
fmt.Println("a / b =", a/b)
fmt.Println("a % b =", a%b)
a++
fmt.Println("a++ =", a) // インクリメント
b--
fmt.Println("b-- =", b) // デクリメント
}
実行結果
a + b = 13
a - b = 7
a * b = 30
a / b = 3
a % b = 1
a++ = 11
b-- = 2
3.2. 比較演算子
演算子 | 説明 | 例 |
---|---|---|
== |
等しい | a == b |
!= |
等しくない | a != b |
> |
より大きい | a > b |
< |
より小さい | a < b |
>= |
以上 | a >= b |
<= |
以下 | a <= b |
コード例
package main
import "fmt"
func main() {
a := 10
b := 5
fmt.Println("a == b:", a == b)
fmt.Println("a != b:", a != b)
fmt.Println("a > b:", a > b)
fmt.Println("a < b:", a < b)
fmt.Println("a >= b:", a >= b)
fmt.Println("a <= b:", a <= b)
}
実行結果
a == b: false
a != b: true
a > b: true
a < b: false
a >= b: true
a <= b: false
比較演算子は、2つの値を比較し、その結果を真偽値 (bool
) で返します。
3.3. 論理演算子
演算子 | 説明 | 例 |
---|---|---|
&& |
論理積 (AND) | a && b |
|| |
論理和 (OR) | a || b |
! |
論理否定 (NOT) | !a |
コード例
package main
import "fmt"
func main() {
a := true
b := false
fmt.Println("a && b:", a && b)
fmt.Println("a || b:", a || b)
fmt.Println("!a:", !a)
fmt.Println("!b:", !b)
}
実行結果
a && b: false
a || b: true
!a: false
!b: true
論理演算子は、真偽値を組み合わせて、より複雑な条件を表現するために使用します。
4. 制御構造
プログラムの流れを制御するために、Go言語には様々な制御構造が用意されています。
if
文 (条件分岐)
4.1. if
文は、条件式の結果によって処理を分岐させるための構文です。
package main
import "fmt"
func main() {
age := 20
if age >= 20 {
fmt.Println("成人です")
} else {
fmt.Println("未成年です")
}
}
コード解説
-
if age >= 20 { ... }
は、age >= 20
という条件式が真 (true
) の場合に、{ ... }
内の処理を実行します。 -
else { ... }
は、条件式が偽 (false
) の場合に、{ ... }
内の処理を実行します (省略可能)。
実行結果
成人です
else if
を使うと、複数の条件を順に評価できます。
package main
import "fmt"
func main() {
score := 75
if score >= 80 {
fmt.Println("優")
} else if score >= 70 {
fmt.Println("良")
} else if score >= 60 {
fmt.Println("可")
} else {
fmt.Println("不可")
}
}
実行結果
良
for
文 (繰り返し)
4.2. for
文は、処理を繰り返し実行するための構文です。Go言語には while
文はありませんが、for
文で同様の処理を記述できます。
(1) カウンター付き for
文
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}
コード解説
-
for i := 0; i < 5; i++ { ... }
は、初期化 (i := 0
)、条件式 (i < 5
)、後処理 (i++
) を指定する標準的なfor
ループです。 -
i
が 0 から 4 まで変化しながら、fmt.Println(i)
が繰り返し実行されます。
実行結果
0
1
2
3
4
(2) 条件式のみの for
文 (whileループ)
package main
import "fmt"
func main() {
count := 0
for count < 3 {
fmt.Println(count)
count++
}
}
コード解説
-
for count < 3 { ... }
は、条件式のみを指定したfor
ループで、while
ループと同様の動作をします。 -
count < 3
が真である限り、{ ... }
内の処理が繰り返し実行されます。
実行結果
0
1
2
(3) range
を使った for
文
range
は、配列、スライス、マップなどの要素を順番に取り出すために使用します。
package main
import "fmt"
func main() {
numbers := []int{1, 2, 3, 4, 5} // スライス
for index, value := range numbers {
fmt.Printf("インデックス: %d, 値: %d\n", index, value)
}
}
コード解説
-
for index, value := range numbers { ... }
は、numbers
スライスの要素を順番に取り出し、index
にインデックス、value
に要素の値を代入します。 -
range
は、インデックスと値のペアを返します。インデックスが不要な場合は、_
(ブランク識別子) で無視できます (for _, value := range numbers { ... }
)。
実行結果
インデックス: 0, 値: 1
インデックス: 1, 値: 2
インデックス: 2, 値: 3
インデックス: 3, 値: 4
インデックス: 4, 値: 5
switch
文 (多岐分岐)
4.3. switch
文は、変数の値に応じて複数の処理を分岐させるための構文です。
package main
import "fmt"
func main() {
signal := "blue"
switch signal {
case "red":
fmt.Println("赤信号")
case "yellow":
fmt.Println("黄信号")
case "blue":
fmt.Println("青信号")
default:
fmt.Println("不明な信号")
}
}
コード解説
-
switch signal { ... }
は、変数signal
の値に基づいて分岐します。 -
case "red":
,case "yellow":
,case "blue":
は、それぞれの値に対応する処理を記述します。 -
default:
は、どのcase
にも一致しない場合の処理を記述します (省略可能)。 - Go言語の
switch
文では、break
を明示的に記述する必要はありません (自動的にbreak
される)。
実行結果
青信号
5. 関数
関数は、処理をまとまりに分割し、再利用性を高めるための重要な構文です。
5.1. 関数の定義
package main
import "fmt"
// greeting 関数 (引数なし、戻り値なし)
func greeting() {
fmt.Println("こんにちは!")
}
// add 関数 (引数あり、戻り値あり)
func add(a int, b int) int {
return a + b
}
func main() {
greeting() // 関数の呼び出し
sum := add(5, 3) // 関数の呼び出しと戻り値の受け取り
fmt.Println("5 + 3 =", sum)
}
コード解説
-
func greeting() { ... }
は、greeting
という名前の関数を定義しています。- 引数リスト
()
内は空なので、引数はありません。 - 戻り値の型指定がないので、戻り値もありません。
- 引数リスト
-
func add(a int, b int) int { ... }
は、add
という名前の関数を定義しています。- 引数リスト
(a int, b int)
は、a
とb
という2つのint
型の引数を受け取ることを指定しています。 - 戻り値の型指定
int
は、関数がint
型の値を返すことを指定しています。 -
return a + b
は、関数の戻り値を指定しています。
- 引数リスト
-
greeting()
とadd(5, 3)
は、それぞれの関数を呼び出しています。
実行結果
こんにちは!
5 + 3 = 8
5.2. 複数の戻り値
Go言語の関数は、複数の戻り値を返すことができます。
package main
import "fmt"
// divide 関数 (複数の戻り値)
func divide(a int, b int) (int, int) {
quotient := a / b // 商
remainder := a % b // 剰余
return quotient, remainder
}
func main() {
q, r := divide(10, 3) // 複数の戻り値を受け取る
fmt.Printf("商: %d, 剰余: %d\n", q, r)
quotient, _ := divide(10, 3) // 剰余を無視する場合
fmt.Println("商:", quotient)
}
コード解説
-
func divide(a int, b int) (int, int) { ... }
は、divide
関数が2つのint
型の戻り値を返すことを指定しています。 -
return quotient, remainder
は、2つの戻り値を指定しています。 -
q, r := divide(10, 3)
は、複数の戻り値をq
とr
で受け取っています。 -
quotient, _ := divide(10, 3)
のように、不要な戻り値は_
(ブランク識別子) で無視できます。
実行結果
商: 3, 剰余: 1
商: 3
6. データ構造 (配列、スライス、マップ)
Go言語には、複数のデータをまとめて扱うためのデータ構造がいくつか用意されています。
6.1. 配列 (Array)
配列は、固定長のデータ列です。
package main
import "fmt"
func main() {
// 配列の宣言と初期化
var numbers [5]int = [5]int{1, 2, 3, 4, 5}
fmt.Println("配列:", numbers)
// 要素へのアクセス (インデックス指定)
fmt.Println("numbers[0]:", numbers[0]) // 最初の要素
// 要素の変更
numbers[0] = 100
fmt.Println("変更後:", numbers)
// 配列の長さ
fmt.Println("長さ:", len(numbers))
}
コード解説
-
var numbers [5]int = [5]int{1, 2, 3, 4, 5}
は、長さ 5 のint
型配列numbers
を宣言し、初期値を設定しています。 -
numbers[0]
のように、インデックス (0始まり) を指定して要素にアクセスします。 -
len(numbers)
で配列の長さを取得します。 - 配列は固定長であるため、要素数を後から変更することはできません。
実行結果
配列: [1 2 3 4 5]
numbers[0]: 1
変更後: [100 2 3 4 5]
長さ: 5
6.2. スライス (Slice)
スライスは、可変長のデータ列です。内部的には配列を指しており、柔軟にサイズを変更できます。
package main
import "fmt"
func main() {
// スライスの宣言と初期化 (make関数)
names := make([]string, 3) // 長さ3、容量3のスライス
names[0] = "Alice"
names[1] = "Bob"
names[2] = "Charlie"
fmt.Println("スライス:", names)
// スライスのリテラル (初期値指定)
fruits := []string{"apple", "banana", "orange"}
fmt.Println("スライス:", fruits)
// 要素の追加 (append関数)
fruits = append(fruits, "grape") // 末尾に "grape" を追加
fmt.Println("追加後:", fruits)
// スライスの長さと容量
fmt.Println("長さ:", len(fruits))
fmt.Println("容量:", cap(fruits))
// スライスの一部を取り出す (スライス式)
subFruits := fruits[1:3] // インデックス 1 から 3 (3は含まない)
fmt.Println("部分スライス:", subFruits)
}
コード解説
-
names := make([]string, 3)
は、make([]string, 3)
で長さ 3、容量 3 のstring
型スライスnames
を作成しています。 -
fruits := []string{"apple", "banana", "orange"}
は、スライスのリテラルで初期値を指定してスライスを作成しています。 -
append(fruits, "grape")
は、fruits
スライスの末尾に "grape" を追加し、新しいスライスを返します (元のスライスは変更されないため、再代入が必要)。 -
len(fruits)
でスライスの長さ、cap(fruits)
で容量を取得します。- 長さ はスライスに含まれる要素数、容量 は内部配列のサイズ (追加可能な要素数) を表します。
-
fruits[1:3]
は、スライス式でfruits
のインデックス 1 から 3 (3は含まない) の要素を取り出し、新しいスライスsubFruits
を作成します。
実行結果
スライス: [Alice Bob Charlie]
スライス: [apple banana orange]
追加後: [apple banana orange grape]
長さ: 4
容量: 6
部分スライス: [banana orange]
6.3. マップ (Map)
マップは、キーと値のペアを格納するデータ構造です。キーを使って値を高速に検索できます (ハッシュテーブル)。
package main
import "fmt"
func main() {
// マップの宣言と初期化 (make関数)
ages := make(map[string]int) // キー: string型, 値: int型
ages["Alice"] = 30
ages["Bob"] = 25
fmt.Println("マップ:", ages)
// マップのリテラル (初期値指定)
colors := map[string]string{
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
}
fmt.Println("マップ:", colors)
// 要素へのアクセス (キー指定)
fmt.Println("Alice の年齢:", ages["Alice"])
// 要素の追加・更新
ages["Charlie"] = 28 // 追加
ages["Alice"] = 31 // 更新
fmt.Println("更新後:", ages)
// 要素の削除 (delete関数)
delete(ages, "Bob")
fmt.Println("削除後:", ages)
// キーの存在確認
age, ok := ages["Bob"] // ok が true ならキーが存在する
if ok {
fmt.Println("Bob の年齢:", age)
} else {
fmt.Println("Bob は存在しません")
}
// マップの長さ
fmt.Println("長さ:", len(ages))
}
コード解説
-
ages := make(map[string]int)
は、make(map[string]int)
でキーがstring
型、値がint
型のマップages
を作成しています。 -
colors := map[string]string{ ... }
は、マップのリテラルで初期値を指定してマップを作成しています。 -
ages["Alice"]
のように、キーを指定して値にアクセスします。 -
ages["Charlie"] = 28
のように、キーと値を指定して要素を追加・更新します。 -
delete(ages, "Bob")
は、ages
マップからキー "Bob" の要素を削除します。 -
age, ok := ages["Bob"]
は、キー "Bob" の値を取得し、ok
にキーの存在 (真偽値) を返します。マップに存在しないキーでアクセスした場合、値はゼロ値 (int
の場合は0
) が返りますが、キーが存在するかどうかはok
で確認する必要があります。 -
len(ages)
でマップの長さを取得します (キーと値のペアの数)。
実行結果
マップ: map[Alice:30 Bob:25]
マップ: map[blue:#0000FF green:#00FF00 red:#FF0000]
Alice の年齢: 30
更新後: map[Alice:31 Bob:25 Charlie:28]
削除後: map[Alice:31 Charlie:28]
Bob は存在しません
長さ: 2
7. 構造体 (Struct)
構造体は、複数の異なるデータ型をまとめて定義できる複合データ型です。
package main
import "fmt"
// Person 構造体の定義
type Person struct {
FirstName string
LastName string
Age int
}
func main() {
// 構造体のインスタンス作成
person1 := Person{
FirstName: "太郎",
LastName: "山田",
Age: 30,
}
fmt.Println("person1:", person1)
// ドット演算子でフィールドにアクセス
fmt.Println("名前:", person1.FirstName, person1.LastName)
fmt.Println("年齢:", person1.Age)
// フィールドの値の変更
person1.Age = 31
fmt.Println("年齢変更後:", person1)
// 構造体のゼロ値
var person2 Person // 初期値未設定
fmt.Println("person2 (ゼロ値):", person2) // 各フィールドはゼロ値で初期化される
}
コード解説
-
type Person struct { ... }
は、Person
という名前の構造体を定義しています。-
FirstName string
,LastName string
,Age int
は、構造体のフィールド (メンバ変数) を定義しています。
-
-
person1 := Person{ ... }
は、Person
構造体のインスタンス (オブジェクト)person1
を作成し、各フィールドに初期値を設定しています。 -
person1.FirstName
のように、ドット演算子 (.
) で構造体のフィールドにアクセスします。 -
var person2 Person
は、Person
構造体のインスタンスperson2
を宣言していますが、初期値を設定していません。この場合、各フィールドはそれぞれのデータ型のゼロ値 (string型は空文字列""
、int型は0
など) で初期化されます。
実行結果
person1: {太郎 山田 30}
名前: 太郎 山田
年齢: 30
年齢変更後: {太郎 山田 31}
person2 (ゼロ値): { 0}
8. ポインタ (Pointer)
ポインタは、メモリ上のアドレスを格納する変数です。ポインタを使うと、変数の値を直接操作したり、関数間で効率的にデータを共有したりできます。
package main
import "fmt"
func main() {
var num int = 10
var ptr *int // int型ポインタの宣言 (初期値は nil)
ptr = &num // num 変数のアドレスを ptr に代入 (&演算子: アドレス取得)
fmt.Println("num の値:", num)
fmt.Println("num のアドレス:", &num)
fmt.Println("ptr の値 (num のアドレス):", ptr)
fmt.Println("ptr が指す先の値 (*ptr):", *ptr) // *演算子: 間接参照 (ポインタが指す先の値を取得)
// ポインタ経由で値を変更
*ptr = 20
fmt.Println("num の値 (ポインタ経由で変更後):", num) // num の値も変更される
}
コード解説
-
var ptr *int
は、int
型のポインタ変数ptr
を宣言しています。*int
は「int型へのポインタ」という意味です。ポインタ変数の初期値はnil
(どのメモリ位置も指していないことを表す特別な値) です。 -
ptr = &num
は、&num
で変数num
のメモリアドレスを取得し、ptr
に代入しています。 -
*ptr
は、ptr
が指すメモリ位置にある値 (つまりnum
の値) を取得します (間接参照)。 -
*ptr = 20
は、ptr
が指すメモリ位置に20
を代入します。これは、num = 20
と同じ効果があり、num
の値も変更されます。
実行結果
num の値: 10
num のアドレス: 0xc0000120a8
ptr の値 (num のアドレス): 0xc0000120a8
ptr が指す先の値 (*ptr): 10
num の値 (ポインタ経由で変更後): 20
ポインタを理解することは、Go言語の効率的なプログラミングに不可欠です。特に、関数で引数を参照渡しする場合や、大きなデータ構造を扱う場合に重要になります。
9. エラーハンドリング
Go言語では、エラー処理を言語仕様として重視しています。関数がエラーを返す場合、呼び出し元で適切に処理する必要があります。
package main
import (
"fmt"
"errors"
)
// divide 関数 (エラーを返す例)
func divide(a int, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero") // エラーを作成して返す
}
return a / b, nil // 正常時は nil エラーを返す
}
func main() {
result, err := divide(10, 2) // 戻り値を2つ受け取る (result, error)
if err != nil {
fmt.Println("エラー:", err)
} else {
fmt.Println("結果:", result)
}
result, err = divide(10, 0) // ゼロ除算エラーが発生
if err != nil {
fmt.Println("エラー:", err) // エラーメッセージを表示
} else {
fmt.Println("結果:", result)
}
}
コード解説
-
func divide(a int, b int) (int, error) { ... }
は、divide
関数がint
型の値とerror
型の値を返すことを指定しています。- Go言語では、関数がエラーを返す場合、慣例として最後の戻り値に
error
型を指定します。
- Go言語では、関数がエラーを返す場合、慣例として最後の戻り値に
-
if b == 0 { ... }
は、b
が 0 の場合 (ゼロ除算エラー) の処理です。-
errors.New("division by zero")
で新しいerror
オブジェクトを作成し、return 0, errors.New(...)
でエラーを返します。 - 正常時は、
return a / b, nil
のように、計算結果とnil
エラー (エラーなしを表す) を返します。
-
-
result, err := divide(10, 2)
は、divide
関数の戻り値をresult
とerr
で受け取っています。 -
if err != nil { ... }
は、エラーが発生した場合の処理です。-
err
がnil
でない (エラーオブジェクトである) 場合、エラーメッセージ (err.Error()
で取得可能) を表示します。 - エラーが発生しなかった場合は、
else { ... }
内の処理 (正常時の処理) を実行します。
-
実行結果
結果: 5
エラー: division by zero
Go言語では、エラーを error
型の値として明示的に扱い、関数呼び出し元でエラーチェックを行うことで、堅牢なプログラムを開発できます。
10. まとめ
この記事では、Go言語の文法について、基礎から網羅的に解説しました。
-
基本構成:
package
,import
,func main()
-
変数とデータ型:
var
,:=
,int
,string
,bool
など - 演算子: 算術、比較、論理演算子
-
制御構造:
if
,for
,switch
- 関数: 関数の定義、引数、戻り値、複数戻り値
- データ構造: 配列、スライス、マップ
-
構造体:
struct
-
ポインタ:
&
,*
-
エラーハンドリング:
error
型、errors.New()
Go言語の文法はシンプルで覚えやすく、効率的なプログラミングが可能です。この記事を参考に、ぜひGo言語プログラミングに挑戦してみてください。
さらに深く学ぶためには、以下の公式ドキュメントや書籍も参考になるでしょう。
- The Go Programming Language Specification: https://go.dev/ref/spec (Go言語仕様書)
- A Tour of Go: https://go.dev/tour/welcome/1 (Go言語チュートリアル)
- プログラミング言語Go: https://www.amazon.co.jp/dp/4873117585 (書籍)
Go言語の世界へようこそ!
Discussion