Go言語入門:言語仕様 -Vol.85-

スポンサーリンク
Go言語入門:言語仕様 -Vol.85- 用語解説
Go言語入門:言語仕様 -Vol.85-
この記事は約7分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

算術演算子(Arithmetic operators)

算術演算子は数値に適用され、最初のオペランドと同じ型の結果を生成する。4つの標準算術演算子(+-*/)は整数型、浮動小数点型、および複素数型に適用される。+ は文字列にも適用される。ビット論理演算子およびシフト演算子は整数にのみ適用される。

+    和                      整数, 浮動小数点数, 複素数値, 文字列
-    差                      整数, 浮動小数点数, 複素数値
*    積                      整数, 浮動小数点数, 複素数値
/    商                      整数, 浮動小数点数, 複素数値
%    剰余                    整数

&    ビットAND               整数
|    ビットOR                整数
^    ビットXOR               整数
&^   ビットクリア(AND NOT)  整数

<<   左シフト                整数 << 0以上の整数
>>   右シフト                整数 >> 0以上の整数

オペランドの型が型パラメータである場合、演算子はその型集合の各型に適用可能でなければならない。オペランドは型パラメータがインスタンス化される型引数の値として表現され、演算はその型引数の精度で計算される。たとえば、以下の関数が与えられたとき:

func dotProduct[F ~float32|~float64](v1, v2 []F) F {
	var s F
	for i, x := range v1 {
		y := v2[i]
		s += x * y
	}
	return s
}

x * y と加算 s += x * y は、F の型引数に応じて、それぞれ float32 または float64 の精度で計算される。


解説

算術演算子の全体像

Go の算術演算子は大きく3グループに分かれます。

1. 基本の四則演算(+ – * /)

数値ならどれでも使えます。さらに + は文字列の連結にも使えます。

3 + 5           // 8(整数)
3.14 * 2.0      // 6.28(浮動小数点数)
(1+2i) + (3+4i) // (4+6i)(複素数)
"Hello" + " Go" // "Hello Go"(文字列連結)

2. 剰余(%)

整数専用です。割り算のあまりを求めます。

7 % 3    // 1
-7 % 3   // -1(Go では被除数の符号に従う)

3. ビット演算・シフト(& | ^ &^ << >>)

整数専用です。

0b1100 & 0b1010   // 0b1000(AND)
0b1100 | 0b1010   // 0b1110(OR)
0b1100 ^ 0b1010   // 0b0110(XOR)
0b1100 &^ 0b1010  // 0b0100(AND NOT = ビットクリア)
1 << 3             // 8(左シフト)
16 >> 2            // 4(右シフト)

結果の型は最初のオペランドと同じ

算術演算の結果は、最初のオペランドと同じ型になります。これは Go の「暗黙の型変換をしない」という方針と一致しています。

var a int32 = 10
var b int32 = 3
c := a / b    // c は int32 型で値は 3(小数部分は切り捨て)

異なる型同士では演算できません。

var a int32 = 10
var b int64 = 3
c := a + b    // コンパイルエラー! 型が違う
c := int64(a) + b  // OK! 明示的に変換する

整数の除算は切り捨て

整数同士の / は、小数部分が切り捨てられます(ゼロに向かって切り捨て)。

7 / 3      // 2(3.5 ではない)
-7 / 3     // -2(-3 ではない。ゼロに向かう)

浮動小数点の結果が欲しい場合は、先に変換します。

float64(7) / float64(3)  // 2.3333...

&^(ビットクリア)は Go 独自

&^ は他の言語にはあまり見られない Go 独自の演算子です。「右オペランドで指定したビットをクリア(0にする)」という意味です。

x := 0b1111    // 15
mask := 0b0110 // クリアしたいビット
result := x &^ mask  // 0b1001 = 9

x & (^mask) と同じ結果になりますが、1つの演算子で書けるので便利です。権限フラグの管理などでよく使われます。

// 権限フラグの例
const (
    Read    = 1 << iota  // 1
    Write                // 2
    Execute              // 4
)

perm := Read | Write | Execute  // 7(全権限)
perm = perm &^ Write           // 5(Write 権限を除去)

文字列の + 連結

+ は文字列の連結にも使えますが、大量の連結にはパフォーマンス上の注意が必要です。文字列は不変なので、+ のたびに新しい文字列が作られます。

// 少量の連結なら問題なし
greeting := "Hello" + " " + "World"

// ループ内で大量に連結するのは非効率
var s string
for i := 0; i < 10000; i++ {
    s += "a"  // 毎回新しい文字列が作られる → 遅い
}

// 代わりに strings.Builder を使う
var b strings.Builder
for i := 0; i < 10000; i++ {
    b.WriteString("a")  // 内部バッファに追記 → 速い
}
s := b.String()

型パラメータと算術演算の精度

ジェネリック関数で算術演算を行うとき、実際の計算精度はインスタンス化時の型引数によって決まります。

func dotProduct[F ~float32|~float64](v1, v2 []F) F {
    var s F
    for i, x := range v1 {
        y := v2[i]
        s += x * y  // F の型に応じた精度で計算される
    }
    return s
}

// float32 で呼ぶと、計算精度は float32(約7桁)
v1 := []float32{1.0, 2.0, 3.0}
v2 := []float32{4.0, 5.0, 6.0}
dotProduct(v1, v2)  // float32 精度

// float64 で呼ぶと、計算精度は float64(約15桁)
v3 := []float64{1.0, 2.0, 3.0}
v4 := []float64{4.0, 5.0, 6.0}
dotProduct(v3, v4)  // float64 精度

同じソースコードでも、型引数によって計算結果が微妙に異なる場合があります。浮動小数点の丸め誤差は精度が低いほど大きくなるので、精度が重要な計算では float64 を使うのが一般的です。

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました