Go言語は、シンプルで洗練された設計思想を持つプログラミング言語として、多くの開発者に支持されています。
プログラムを書く上で避けては通れないのが「演算子」の存在であり、値の計算や比較、条件分岐の制御において中心的な役割を果たします。
本記事では、Go言語で利用可能なすべての演算子を網羅し、その具体的な使い方や注意点について解説します。
初心者の方には基礎の習得として、実務経験者の方には知識の再確認やリファレンスとして活用していただければ幸いです。
Go言語における演算子の基本分類
Go言語の演算子は、その機能に応じて大きくいくつかのカテゴリーに分類されます。
基本的な算術演算から、論理演算、ビット演算、さらにはGo言語特有のチャネル演算子まで、多岐にわたる種類が存在します。
プログラムの読みやすさを維持するため、Go言語の演算子は他の言語と比較してシンプルに保たれているのが特徴です。
まずは、日常的なプログラミングで最も頻繁に使用される算術演算子から見ていきましょう。
算術演算子(Arithmetic Operators)
算術演算子は、数値型(整数や浮動小数点数)に対して数学的な計算を行うために使用されます。
Go言語では、加減乗除といった基本的な演算に加えて、剰余を求める演算子が用意されています。
| 演算子 | 名称 | 例 | 説明 |
|---|---|---|---|
| + | 加算 | a + b | aとbを足し合わせます。文字列の結合にも使用できます。 |
| – | 減算 | a – b | aからbを引きます。 |
| * | 乗算 | a * b | aとbを掛け合わせます。 |
| / | 除算 | a / b | aをbで割ります。整数同士の除算は結果も整数になります。 |
| % | 剰余 | a % b | aをbで割ったときの余りを求めます。整数型にのみ適用可能です。 |
ここで注意が必要なのは、異なる型同士の計算は直接行えないという点です。
例えば、int32型とint64型の変数をそのまま足そうとすると、コンパイルエラーが発生します。
また、除算においては整数の割り算と浮動小数点の割り算で挙動が異なることに注意してください。
package main
import "fmt"
func main() {
// 整数の除算
fmt.Println("10 / 3 =", 10 / 3)
// 浮動小数点の除算
fmt.Println("10.0 / 3.0 =", 10.0 / 3.0)
// 文字列の結合
str := "Go" + " Language"
fmt.Println(str)
}
10 / 3 = 3
10.0 / 3.0 = 3.3333333333333335
Go Language
このように、整数同士の除算では小数点以下が切り捨てられるため、精度の高い計算が必要な場合は浮動小数点型へのキャストが必要です。
比較演算子(Relational Operators)
比較演算子は、2つの値を比較してその結果を真偽値(boolean)として返すために使用されます。
if文やfor文のループ条件などで非常に頻繁に利用される要素です。
| 演算子 | 名称 | 例 | 説明 |
|---|---|---|---|
| == | 等価 | a == b | aとbが等しい場合にtrueを返します。 |
| != | 不等 | a != b | aとbが等しくない場合にtrueを返します。 |
| > | より大きい | a > b | aがbより大きい場合にtrueを返します。 |
| < | より小さい | a < b | aがbより小さい場合にtrueを返します。 |
| >= | 以上 | a >= b | aがb以上である場合にtrueを返します。 |
| <= | 以下 | a <= b | aがb以下である場合にtrueを返します。 |
比較を行う際も、算術演算子と同様に左右のオペランドの型が一致している必要があります。
特に構造体や配列の比較については、その要素がすべて比較可能である場合に限り、== や != を使用することが可能です。
スライスやマップは直接 == で比較できないため、nil との比較以外では reflect.DeepEqual() などの利用を検討します。
論理演算子(Logical Operators)
論理演算子は、複数の条件式を組み合わせたり、条件を反転させたりする場合に使用します。
Go言語では「短絡評価(ショートサーキット)」が採用されており、不必要な計算をスキップする仕組みが備わっています。
| 演算子 | 名称 | 例 | 説明 |
|---|---|---|---|
| && | 論理積 (AND) | a && b | aとbがともにtrueであればtrueを返します。 |
| || | 論理和 (OR) | a || b | aまたはbのどちらかがtrueであればtrueを返します。 |
| ! | 論理否定 (NOT) | !a | aがtrueならfalse、falseならtrueを返します。 |
例えば、a && b という式において a が false である場合、b の値にかかわらず結果は必ず false になります。
このとき、Go言語は b の評価を行わずに処理を終了します。
これを短絡評価と呼びます。
条件式の左側で nil チェックを行い、右側でその変数にアクセスするといった安全なコードを書く際に非常に有用です。
ビット演算子(Bitwise Operators)
ビット演算子は、整数のビット単位での操作を行うために使用されます。
システムプログラミングや、フラグ管理、ネットワークプロトコルの実装など、低レベルな処理が必要な場面で活躍します。
| 演算子 | 名称 | 例 | 説明 |
|---|---|---|---|
| & | ビット論理積 | a & b | 各ビットのANDを取ります。 |
| | | ビット論理和 | a | b | 各ビットのORを取ります。 |
| ^ | ビット排他的論理和 | a ^ b | 各ビットのXORを取ります。 |
| << | 左シフト | a << n | aをnビット左にシフトします。 |
| >> | 右シフト | a >> n | aをnビット右にシフトします。 |
| &^ | ビットクリア | a &^ b | bで1が立っている箇所のビットをaから消去します。 |
Go言語独自の演算子として注目すべきなのがビットクリア演算子 (&^)です。
これは z = x &^ y と書いた場合、y でビットが1になっている位置に対応する x のビットを0にする操作を行います。
他言語では x & (^y) と表現されることが多い処理ですが、Goでは専用の演算子が用意されています。
package main
import "fmt"
func main() {
var x uint8 = 0b11110000
var y uint8 = 0b00111100
// ビットクリア: yが1のビットをxから消す
result := x &^ y
fmt.Printf("x: %08b\n", x)
fmt.Printf("y: %08b\n", y)
fmt.Printf("x &^ y: %08b\n", result)
}
x: 11110000
y: 00111100
x &^ y: 11000000
代入演算子(Assignment Operators)
代入演算子は、変数に値を格納するために使用されます。
単純な代入だけでなく、算術演算やビット演算と組み合わせた複合代入演算子も提供されています。
| 演算子 | 説明 |
|---|---|
| = | 右辺の値を左辺の変数に代入します。 |
| := | 短縮変数宣言。型推論を伴う変数宣言と代入を同時に行います。 |
| +=, -=, *=, /=, %= | 算術演算を行った結果を左辺の変数に再代入します。 |
| &=, |=, ^=, <<=, >>=, &^= | ビット演算を行った結果を左辺の変数に再代入します。 |
Go言語において非常に重要なのが := 演算子です。
これは関数内でのみ使用可能で、変数の初期化と宣言を一度に行うことができるため、コードの記述量を減らし可読性を高めます。
ただし、既存の変数に対して値を更新する場合は単なる = を使用する必要があるため、混同しないようにしましょう。
Go言語固有の演算子と特殊な動作
標準的なプログラミング言語に共通する演算子以外にも、Go言語には言語の特性を反映した特殊な演算子が存在します。
ポインタ操作や並行処理に関わる演算子は、Goの機能を最大限に活用するために不可欠な要素です。
ポインタ演算子
Go言語はメモリ管理を安全に行いつつ、必要に応じてメモリのアドレスを直接参照することができます。
ポインタに関連する演算子は以下の2つです。
&(アドレス演算子):変数のメモリ上のアドレスを取得します。*(デリファレンス演算子):ポインタが指し示しているアドレスにある実体の値にアクセスします。
C言語などとは異なり、Go言語ではポインタ演算(p++ などでアドレスを移動させること)は許可されていません。
これにより、バッファオーバーランなどのメモリ関連のバグを未然に防ぐ安全性が確保されています。
受信・送信演算子(チャネル演算子)
Go言語の大きな特徴である「ゴルーチン(Goroutine)」間の通信に使用されるのが、チャネル演算子 <- です。
この演算子の位置によって、チャネルへのデータの送信か、チャネルからのデータの受信かが決定されます。
ch <- v:チャネルchに値vを送信します。v := <-ch:チャネルchから値を受け取り、変数vに代入します。
この演算子は並行処理を同期させるための重要な役割を担っており、Goのプログラミングスタイルを象徴するものです。
インクリメントとデクリメント
Go言語にも ++(インクリメント)および --(デクリメント)演算子が存在します。
しかし、他の多くの言語とは決定的に異なる仕様があります。
Goにおいてこれらは式(Expression)ではなく文(Statement)として扱われます。
そのため、y = x++ のような代入式の中に組み込むことはできず、また ++x のような前置インクリメントもサポートされていません。
package main
import "fmt"
func main() {
i := 1
i++ // 許可される(文)
fmt.Println(i)
// j := i++ // コンパイルエラー:i++は値を返さない
// ++i // コンパイルエラー:前置インクリメントは存在しない
}
演算子の優先順位(Precedence)
複雑な式を記述する際、どの演算子が先に評価されるかを知っておくことは非常に重要です。
優先順位を誤解していると、意図しない計算結果を招く可能性があります。
Go言語の演算子優先順位は、大きく分けて5つのレベルに整理されています。
| 優先度 | 演算子 |
|---|---|
| 5 (最高) | * / % << >> & &^ |
| 4 | + – | ^ |
| 3 | == != < <= > >= |
| 2 | && |
| 1 (最低) | || |
同じ優先順位の演算子が並んでいる場合は、左から右へと評価が進みます(左結合)。
たとえば a + b - c は、まず a + b が計算され、その結果から c が引かれます。
コードの可読性を高めるためには、優先順位に頼りすぎず、カッコ ( ) を活用して評価順序を明示することをおすすめします。
カッコで囲まれた部分は最優先で評価されるため、誤解のないコードになります。
実務で役立つ演算子の豆知識
基本的な使い方のほかにも、Go言語の演算子を扱う上で知っておくと便利な知識がいくつかあります。
現場でのトラブルを避け、効率的な実装を行うためのヒントを紹介します。
三項演算子の不在
Go言語には、他言語にある condition ? true_val : false_val という形式の「三項演算子」が存在しません。
これはコードをシンプルに保ち、一つの処理に対して一つの書き方を推奨するというGoの哲学に基づいています。
同様の処理を行いたい場合は、標準的な if-else 文を使用します。
// 他言語の三項演算子的な書き方
var result string
if score >= 60 {
result = "合格"
} else {
result = "不合格"
}
行数は増えますが、誰が見ても挙動が明確であるというメリットがあります。
ゼロ除算の挙動
Go言語において、整数を0で割る(ゼロ除算)と実行時にパニック(Runtime Panic)が発生します。
一方で、浮動小数点数(float64など)で0除算を行うと、パニックにはならず +Inf(無限大)や NaN(非数)が返されます。
計算対象のデータ型によってエラーの発生仕方が異なるため、入力値のチェックは欠かせません。
文字列結合の効率
算術演算子の + を使って文字列を結合するのは非常に直感的です。
しかし、大量の文字列をループ内で結合する場合、+ 演算子を繰り返すとメモリの再確保が頻発し、パフォーマンスが低下します。
そのような場合は、strings.Builder 構造体を利用する方が効率的です。
数個の文字列を結合する程度であれば、+ 演算子を使用しても問題ありません。
まとめ
Go言語の演算子は、シンプルながらも強力な機能を備えています。
本記事では、算術、比較、論理、ビット、代入、そしてGo独自のチャネル演算子について解説しました。
演算子の優先順位を正しく理解し、型の一致に注意を払うことで、バグの少ない堅牢なコードを記述することができます。
また、インクリメントが文であることや三項演算子が存在しないことなど、Go独自の制約を把握しておくことも実務において重要です。
今回学んだ知識を土台として、より効率的で読みやすいGoプログラムの実装に取り組んでみてください。
基本を大切にすることが、将来的に複雑なシステムを構築する際の大きな力となるはずです。
