計算機科学において、関係演算子(かんけいえんざんし、英: relational operator)または比較演算子(ひかくえんざんし、英: comparison operator)とは、プログラミング言語の演算子で、2つの対象の関係を調べるものをいう。たとえば、同値関係を調べる等号( の )や、順序関係を調べる不等号( の )などが含まれる。
JavaやC#など、独立したブーリアン型を型システムに持つ言語では、関係演算子は2つのオペランドの間に演算子が表す関係が成り立つかどうかによって真 (true) または偽 (false) を返す。一方で、C言語などのブーリアン型を持たない初期の言語では、関係演算子は整数 0(偽を意味する)または 1(真を意味する)を返す。
関係演算子を含む式は、関係式 (relational expression) または条件 (condition) と呼ばれる。また、技術的な文献において、関係を言葉で説明する代わりに関係演算子が用いられることもある。多くのプログラミング言語では、関係演算子は中置記法で記述される。たとえば、以下のC言語のコードは、x が y より小さい場合にメッセージを表示するものである。
if (x < y) {
printf("x is less than y in this example\n");
}
他方で前置記法を採用している言語もある。たとえば、Lispでは以下のように書く。しかしこれは演算子というよりも、Lispでは識別子に使える文字の範囲が緩くて、単に >=
という名前の関数であるというだけである。
(if (>= x y)
(display "x is greater than or equal to y in this example"))
多くのプログラミング言語で使用されている標準的な数値比較演算子を以下に示す。
様式 | 等しい | 等しくない | より大きい | より小さい | 以上 | 以下 |
---|---|---|---|---|---|---|
数学 | ||||||
Fortran[note 1] | .EQ.
|
.NE.
|
.GT.
|
.LT.
|
.GE.
|
.LE.
|
ALGOL 68[note 2] | =
|
≠
|
>
|
<
|
≥
|
≤
|
/=
|
>=
|
<=
| ||||
eq
|
ne
|
gt
|
lt
|
ge
|
le
| |
BASICライク[note 3] | =
|
<>
|
>
|
<
|
>=
|
<=
|
MUMPS | =
|
'=
|
>
|
<
|
'<
|
'>
|
Pascalライク[note 4] | =
|
<>
|
>
|
<
|
>=
|
<=
|
C言語ライク[note 5] | ==
|
!=
|
>
|
<
|
>=
|
<=
|
shライクなシェル[note 6] | -eq
|
-ne
|
-gt
|
-lt
|
-ge
|
-le
|
バッチファイル | EQU
|
NEQ
|
GTR
|
LSS
|
GEQ
|
LEQ
|
MATLAB[note 7] | ==
|
~=
|
>
|
<
|
>=
|
<=
|
eq(x,y)
|
ne(x,y)
|
gt(x,y)
|
lt(x,y)
|
ge(x,y)
|
le(x,y)
| |
Mathematica[1] | ==
|
!=
|
>
|
<
|
>=
|
<=
|
Equal[x,y]
|
Unequal[x,y]
|
Greater[x,y]
|
Less[x,y]
|
GreaterEqual[x,y]
|
LessEqual[x,y]
|
>=
or GE
instead of ≥
), platforms with no bold
emphasis (use 'ge'
), or platforms with only UPPERCASE (use .GE
or 'GE'
).
<
と >
はシェルではリダイレクトの記号として用いられるため別の記号を用いる必要がある。先頭のハイフンを除いたものはPerlでは文字列比較演算子として使用される。
!=
を用いない。MATLABにおいて、!
はシェルコマンドの記述に用いられるからである。上段の形式はSmalltalkでも用いられるが、等号は =
となる。
C言語から直接または間接的に派生したプログラミング言語では、同値関係の関係演算子として、直感的な =
ではなく ==
を用いる。一方、=
を用いる言語としては Pascal、BASIC、Ada、Standard ML、Objective Caml、SQL、VHDL などがある。
C言語は広範囲に普及したため、後発のプログラミング言語における構文や仕様はC言語のそれを参考に定義されたものも多いが、そのうちの一つがこの ==
演算子である。この独特の構文は、B言語開発の初期の段階で =
を別の意味に割り当てたことに端を発する。ALGOLとFORTRANの流れを汲むB言語の設計者は、タイピングを減らしたいという要望から、頻繁に記述される更新・代入操作のためのコピー演算子として =
を代用することを決定した(これは A = A + 1
のような、一見して「数学的には不成立」な式が許容されることを意味する)。代わりに、=
が本来担う役割である等号として ==
が使われることとなった。C言語はこれらの演算子をそのまま引き継ぎ、以後JavaやC#をはじめとする多くの言語がこの構文を採用したのである。
これらのC言語ファミリーにおける =
の用法はバグの温床になりうる。C言語にはブーリアン型がなく、if文やwhile文の条件式には真偽値に評価されうる任意の数値型の式を受け付ける(ゼロあるいはゼロ相当を偽、非ゼロを真とする)。またC言語における代入は文ではなく式であり値を持つ。そのため、プログラマーが if (x == y)
の代わりに、if (x = y)
とミスタイプしても構文的には合法となってしまうのである。C言語において、if (x == y)
は大雑把に言えば「x と y が等しければ後続の文を実行せよ」を意味する。しかし、if (x = y)
とミスタイプすると「x に y の値を割り当て、もし x の新しい値が0でなければ、後続の文を実行せよ」という意味になってしまう。たとえば、下記の例で if (x = y)
と書いてしまうと、y が x に代入され両方とも2になり、更に x の値2は0ではないので、常に if 文のブロックが実行される。したがって、以下のコードは "x is 2 and y is 2" を出力する[2]。
int x = 1;
int y = 2;
if (x = y) {
/* yが0でなければ以下のコードは常に実行される */
printf("x is %d and y is %d\n", x, y);
}
他の言語やコンパイラの中には、このようなミスを事前に防ぐように工夫されているものもある。
-Wparentheses
、C4706[3])。:=
、等値比較演算子は=
であり[4]、また式の途中に代入演算子は登場できないので、この種の誤りは排除できる。=
、等値比較演算子は==
であるものの、代入は式ではなく文であり[5]、この種の誤りは排除できる。Python 3.8では値を返す代入式が導入されたが、Pascalと類似の代入演算子:=
が使われるため、通常は比較演算子の==
と混同するようなことはない[6]。=
記号を使用する。BASIC系では代入は式ではなく文であり[9]、代入演算子としての=
のほうは式中に出現することがない。また、プログラマーの中には予防策として、定数に対する比較を記述するとき、以下のように直感とは逆の順でオペランドを記述する者もいる。定数は左辺値ではないので、このように比較演算子の左側に記述するスタイルにしておけば、たとえ誤って =
と書いてしまったとしても、そのコードは不適格となる。コンパイラは不適格コードに対してエラーメッセージを出力し、コンパイルを中断するので、記述ミスに気づくことができ、結果、適切な演算子に修正できるのである。このコーディングスタイルはヨーダ記法や left-hand comparison として知られている。ただしこの記法には、比較対象の少なくとも片方が左辺値を持たないような場合にしか使えない、多くの場合は重要な側(自然言語で考えたときに主語となる側)の式が後から現れることになる、といった欠点がある。コーディング規約として推奨しているプロジェクトもあれば、推奨していないプロジェクトもある。
if (2 == a) { /* 仮に = と == を誤用した場合はコンパイルエラーを引き起こす */
/* ... */
}
なお、C/C++では、以下のように意図的に条件式中に代入式が書かれることもある。ただし前述のように、コンパイラは通例このようなコードに対しても警告を出す。
FILE* fp = NULL;
if (fp = fopen("sample.txt", "r")) {
/* 指定したファイルを開くことができ、fp が non-null となった場合の処理 */
fclose(fp);
fp = NULL;
}
PHPでは、==
演算子をさらに拡張し、型が異なっても値が等しければ真を返す ==
演算子(たとえば 4 == "4"
は真である)と、値が等しくかつ同じ型を持っている場合に真を返す ===
演算子(たとえば 4 === 4
は真であるが 4 === "4"
は偽である)の2種類の演算子を持っている[10]。x == 0
は x が 0
、"0"
(文字 0 を含む文字列)または false
(PHPでは他の言語でも見られるように、false
は 0
と等しい)のときに真を返す。これは、変数に 0 の値が割り当てられているかを確認するのに便利であるが、必ずしも期待される動作とは限らない[10]。一方で、x === 0
は x が 0
のときのみ真を返す。
多くの現代的なプログラミング言語において、オブジェクトやデータ構造は参照を通じてアクセスされる。そのような言語では、2種類の異なる等価性を判定する必要性が生じる:
通常、前者の等価性は後者の等価性を含意しているが(自身に等しくないような NaN のようなものは除く)、逆は必ずしも真ではない。たとえば、2つの文字列オブジェクトは別個のオブジェクトであるかもしれない(前者の意味では等しくない)が、同じ文字の並びを持ちうる(後者の意味で等しい)。
次の表では、これらの2種類の等価性を判定するための異なる方法を、様々な言語において一覧できるようにしてある。
言語 | 物理的な等価 | 構造的な等価 | 備考 |
---|---|---|---|
C, C++ | a == b |
*a == *b |
a とb はポインタである
|
C# | object.ReferenceEquals(a, b) 1 |
a.Equals(b) 1 |
|
Common Lisp | (eq a b) |
(equal a b) |
|
Java | a == b |
a.equals(b) |
a とb は参照である
|
OCaml | a == b |
a = b |
|
Pascal | a^ = b^ |
a = b |
|
Perl | $a == $b |
$$a == $$b |
$a と$b はスカラーリファレンスである
|
PHP5 | N/A | $a == $b |
$a と$b はオブジェクトである
|
Python | a is b |
a == b |
|
Ruby | a.equal?(b) |
a == b |
|
Scheme | (eq? a b) |
(equal? a b) |
|
VB.NET | a Is b |
a = b |
|
Objective-C | a == b |
[a isEqual:b] |
a とb はオブジェクトへのポインタである
|
==
演算子は既定で ReferenceEquals()
メソッドの呼び出しと等価になるが、代わりに Equals()
メソッドを実行するように演算子オーバーロードをすることができる。このことによって、構造的な等価性の方がより直感的と思われる型において、==
で構造的な等価性を判定するようにできる。特に文字列比較において、このことが効果的である(Java で文字列比較は a.equals(b)
と書かなければならないが、C# では a == b
と書ける)。ただし、変更可能 (mutable) な参照型の場合は、==
演算子をオーバーロードすべきではないとされている[11]。多くの.NET言語では、参照型における組み込みの比較演算子は参照の等価性を判定するために用意されているものであり、驚き最小の原則の観点からも、一般的にオーバーロードは避けるべきとされている[12]。一見して自明ではないが、比較演算子は、互いにほかの比較演算子を用いて論理的に同値な命題を構成できる。これは、ちょうどブール論理の論理演算子 XOR、AND、OR、NOT の間で見られる関係に似ている。以下の4つの条件式は互いに論理的同値である。
さらに、等号も不等号を用いて表現することができる。
この性質をプログラミングに応用して、不等号 ≥ だけ(または、≥ と = の二つだけ)を真面目に実装し、ほかの比較演算子を ≥(または、≥ と =)を用いて定義することも行われる。
java.lang.Boolean
はboolean
に暗黙変換される。また、C#ではbool
への暗黙変換演算子や、true
/false
演算子をユーザー定義することもできる。このようなケースでは、JavaやC#であっても条件式を書くべきところに間違って代入式を書けてしまう。