Go/文法の概要
Goは、シンプルかつ効率的な文法を特徴とし、構造化プログラムや並行処理に優れた設計がされています。以下にGoの文法の概要を紹介します。
基本構造
[編集]Goプログラムはpackage
宣言から始まり、メインの実行ファイルの場合はpackage main
と記述します。エントリーポイントはfunc main()
関数です。
- hello.go
package main import "fmt" func main() { fmt.Println("Hello, World!") }
変数
[編集]変数の宣言にはvar
キーワードを使い、型推論も可能です。また、関数内であれば短縮宣言(:=
)も利用できます。
var x int = 10 var y = "Hello" z := 3.14
定数
[編集]定数はconst
キーワードで宣言し、再代入ができません。
const Pi = 3.14 const Greeting = "Hello, Go!"
データ型
[編集]Goには以下のような基本データ型が用意されています。
- 整数型:
int
,int8
,int16
,int32
,int64
- 浮動小数点型:
float32
,float64
- 論理型:
bool
- 文字列型:
string
配列とスライス
[編集]- 配列:固定長のデータ構造です。
- スライス:可変長で、要素の追加や削除が可能なデータ構造です。
var arr [3]int = [3]int{1, 2, 3} slice := []int{4, 5, 6} slice = append(slice, 7)
マップ
[編集]マップ(map
)はキーと値のペアでデータを管理します。
dict := map[string]int{"apple": 1, "banana": 2} fmt.Println(dict["apple"])
関数
[編集]Goでは関数も重要な要素で、複数の戻り値が可能です。また、関数を変数として扱うこともできます。
func add(a int, b int) int { return a + b } func swap(x, y string) (string, string) { return y, x }
構造体
[編集]Goではクラスはありませんが、構造体(struct
)を使ってデータをカプセル化できます。
type Person struct { Name string Age int } person := Person{Name: "Alice", Age: 30} fmt.Println(person.Name)
メソッド
[編集]Goでは、構造体に対してメソッドを定義できます。メソッドは、特定の型(構造体)に関連付けられた関数です。メソッドを定義する際には、レシーバー(受け取る変数)を指定します。
func (p Person) Greet() { fmt.Println("Hello, my name is", p.Name) } person := Person{Name: "Alice", Age: 30} person.Greet() // "Hello, my name is Alice"
インターフェース
[編集]インターフェースは、型が持つべきメソッドのセットを定義します。Goでは、明示的に「実装する」と記述することなく、構造体がインターフェースのメソッドセットを満たすと、自動的にインターフェースを実装したと見なされます。
type Greeter interface { Greet() } func SayHello(g Greeter) { g.Greet() } person := Person{Name: "Alice", Age: 30} SayHello(person) // "Hello, my name is Alice"
型引数
[編集]Go 1.18以降、ジェネリクス(型引数)がサポートされるようになり、関数や構造体に型を引数として渡すことができます。これにより、型に依存しない汎用的なコードを作成できます。
func Print[T any](value T) { fmt.Println(value) } Print(42) // 42 Print("Hello") // Hello
上記の例では、T
は型引数で、any
はinterface{}
の別名で任意の型を意味します。Print
関数はどんな型の引数にも対応できるようになります。
型制約
[編集]Goのジェネリクスでは、型引数に制約を設けて、関数やメソッドが受け入れる型を限定できます。型制約を使うことで、特定のインターフェースを満たす型や特定の型に対してのみ操作を適用できます。
型制約は、interface
を使って、特定のメソッドを持つ型に制限したり、特定の型セット(例えば数値型や文字列型)に限定することができます。
例えば、数値型に限定した型制約は次のように書けます:
type Number interface { int | int32 | int64 | float32 | float64 } func Add[T Number](a, b T) T { return a + b }
上記のコードでは、Number
という型制約を使って、int
や float64
などの数値型だけを Add
関数に渡すことができるようにしています。このように、型制約を使うことで型の安全性を高め、汎用的な関数を作成できます。
ポインタ
[編集]Goではポインタを使って変数のアドレスを参照することができますが、ポインタ演算はサポートされていません。
var a int = 10 var p *int = &a fmt.Println(*p) // 10
条件分岐
[編集]Goではif
文とswitch
文が使えます。条件式の前に短いステートメントを記述することも可能です。
if x := 10; x > 5 { fmt.Println("x is greater than 5") } switch day := "Monday"; day { case "Monday": fmt.Println("Start of the week") case "Friday": fmt.Println("End of the week") default: fmt.Println("Midweek") }
ループ
[編集]Goにはfor
ループがあり、while
やdo-while
の代わりに使います。
for i := 0; i < 10; i++ { fmt.Println(i) } i := 0 for i < 10 { // while like fmt.Println(i) i++ } i := 0 for { // for ever fmt.Println(i) i++ if i >= 10 { break } } // スライスやマップの要素を反復処理 for index, value := range slice { fmt.Println(index, value) }
ゴルーチン
[編集]Goではgoroutine
を使って並行処理を行います。go
キーワードで関数を非同期に実行できます。
func sayHello() { fmt.Println("Hello") } go sayHello()
チャネル
[編集]チャネルはゴルーチン間でデータをやり取りするために使われます。chan
キーワードを使ってチャネルを宣言します。
messages := make(chan string) go func() { messages <- "ping" }() msg := <-messages fmt.Println(msg)
エラーハンドリング
[編集]Goではエラーはerror
型の戻り値として処理します。エラーチェックは一般的に以下のように行います。
func divide(a, b int) (int, error) { if b == 0 { return 0, fmt.Errorf("division by zero") } return a / b, nil } result, err := divide(10, 0) if err != nil { fmt.Println(err) } else { fmt.Println(result) }
defer文
[編集]defer
文は、関数が終了する際に指定した関数を遅延実行するために使います。主にリソースの解放やクリーンアップ処理で利用され、エラーが発生しても確実に実行されます。
func main() { defer fmt.Println("End") // 関数の終了時に実行される fmt.Println("Start") }
- 出力結果:
Start End
複数のdefer
がある場合は、後から指定した順に実行されます(LIFO順序)。
パッケージ
[編集]Goではimport
を使って標準ライブラリや外部パッケージをインポートできます。
import ( "fmt" "math" ) func main() { fmt.Println(math.Sqrt(16)) }
まとめ
[編集]Goはシンプルで効率的な文法を持ち、特に並行処理やエラーハンドリングに優れた機能を備えています。シンプルで直感的な構造を提供し、学習曲線が低い一方で高いパフォーマンスを実現できます。