关系运算子

关系运算子在计算机科学的编程语言中，是测试或定义两个实体之间某种关系的构造或操作符。一共有六种关系，分别为：`小于(<)`、`大于(>)`、`小于或等于(<=)`、`大于或等于(>=)`、`等于(==)`和`不等于(<>)`。在具备布尔型别的编程语言中（如 Pascal，Ada 或 Java），这些运算符通常根据两个操作变量之间的条件关系是否成立，判定为真(True)或假(False)。诸如 C 语言中关系运算子传回整数 0 或 1，其中 0 表示假，任何非零值表示真。使用关系运算子创建的表达式，形成所谓的关系表达式或条件。

关系运算子可以被视为谓词逻辑的特殊情况。

## 相等性

### 用法

许多编程语言的构造和资料型别中都使用到相等性，用于测试元素是否已存在于集合中，或者借由键来存取值。它在切换（switch）语句，以及编程的逻辑并联过程中，用于将控制流调度到正确的分支。相等性的可能含义之一是“如果 a 等于 b，那么我们可以在任何情况下互换 a 或 b，而不会产生任何差异。”但这样的声明不一定成立，尤其在将可变性和内容等同性一起考虑时。

### 物件相等与内容等同性

有时，特别是在物件导向编程中，对资料型别和继承物件进行比对时，出现了相等性和辨别的问题。以下情况通常需要区别：

- 相同型别的两个不同物件，例如两只手
- 两个物件相等但不同，例如两张10元钞票
- 两个物件相等但有不同的呈现，例如$1元纸钞和$1元硬币
- 对同一物件的两个不同参照，例如，同一人的两个昵称

在许多现代编程语言中会借由参照来存取物件和资料结构。在这些语言中，需要测试两种相等性质：

- 实质同等性：如果有两个参照A和B来自引用同一个物件，以A与物件进行的互动，跟借由B与物件进行的互动，两者其实就是相同作用而无法区别，特别是以A去改变物件的异动会反映在B之上。当讨论为值而非物件时，实质同等性并不适用。
- 语义同等性：如果两个参照物件或两个值在某种意义上是等价的：
- 结构等式（即它们的内容是相同的），或浅薄地（仅测试目前部分）或深入地（递归地测试其所有部分的相等性）。实现这一点的简易方法是通过代表等式：检查参照的值是否有相同的代表式。
- 其它特制的同等性，保留外部行为。例如将$\frac{1}{~{}2~{}}$和$\frac{2}{~{}4~{}}$视为有理数时，被判断是相等的。除了反射性、对称性和传递性之外，对 A = B 特制的定义可能是“当且仅当对于物件A和物件B之上的所有操作，都将具有相同的结果时，则 A = B ”。

第一种同等性质通常蕴涵着第二种同等性质（除了非数字类（not a number, NaN），它们不等于自身），但反向的同等性质并不一定成立。例如两个字串物件可以是不同物件（第一种意义不相等），但它们包含相同的字元序列（第二种意义上相等）。有关此问题的更多信息，请参阅识别（identity）。

实数中包括许多简分数，无法以浮点算数精确地表示，所以需要在给定误差范围内来测试相等性。但这样的误差范围将打破一些例如传递性、反身性的要求性质：IEEE浮点标准是判断 Nan ≠ NaN 成立（NaN不等于自身）。

其他编程元素例如可计算的函数，可能没有相等性的意义，或者相等性是不能计算的。由于这些原因，一些语言以基础类别、界面、特点（trait）或协定的形式，定义了“可比较”的明确概念，以源码中的显式声明，被借由型别的结构，来使用关系运算。

### 比较不同类型的值

JavaScript，PHP 和一些其它动态型别的语言中，如果两个值相等，等号运算符将计算为真，即使它们实际上为不同型别的物件，例如以数值4和字串"4"相比较，结果会是相等。在这类语言中通常也会提供型别相等运算子，仅对具有相同或等价型别的物件比较返回真（在PHP 5中 4 ==="4"为假，但 4 =="4" 为真）。而在将数值0也当作布尔值为假的编程语言中，该运算子可化简为检查物件是否为数值零（例如，对于数值0或字串"0"的x物件，使用型别相等运算子，则 x == 0 判断传回真值）。

## 次序比较

非数值资料的次序比较（大于或小于）运算是根据排序惯例（例如字串依照编程语言内定的字典次序，和/或可由开发人员设定的）。当两个资料项 a 和 b 之间的比较结果，要和数值关联时，通常惯例是如果 a b 则为 1。例如C语言的函数`strcmp`执行三方向比较，并根据此惯例返回 -1, 0 或 1，而`qsort`预期比较函数依此惯例返回值。在排序算法中比较方法源码的效率至为关键，因为它是排序性能的主要因素之一。

开发人员定义的资料型别（不是编程语言内建的型别）的比较，可以编写自订的或使用函式库的函数（如上文的`strcmp`）来执行，或者在某些语言中通过重载比较运算符-即以开发人员的定义指派给比较运算子，来比较特定资料型别。另一个选择是使用某些惯例，例如成员比较。

## 逻辑等价

虽然一开始可能不那么显而易见，像布尔逻辑运算符 XOR，AND，OR 和 NOT，这些关系运算子可以设计为具有逻辑等同性，使得它们都可以相互定义。对于任何给定的 x 和 y 值，以下四个条件语句都有相同的逻辑等价性 E（全为真或全为假）：

$$E = \begin{cases} x < y \\ y > x \\ x \ngeq y \\ y \nleq x \end{cases}$$

这依赖于域是良好排序的。

## 标准关系运算符

在编程语言中最常见到的数值关系运算子如下所示。

Convention｜equal to｜not equal to｜greater than｜less than｜greater thanor equal to｜less thanor equal to

In print｜=｜≠｜>｜”关系符号（当然也就没有不大于“≤”或不小于“≥”之类的关系符号），迫使设计者定义如`.GT.`、`.LT.`、`.GE.`、`.EQ.`这样的关系符号，随后等号“=”字符被人借用来执行复制，尽管此用法与数学意义明显不一致（X = X + 1 在数理是不能成立的）。

因此国际代数语言（IAL，ALGOL 58）和 ALGOL（1958和1960）引入了“:=”表示赋值操作，留下等号“=”字符作为相等关系的标准，遵循这个惯例的编程语言有CPL，ALGOL W，ALGOL 68，BCPL，Simula，SET（SETL），Pascal，Smalltalk，Modula-2，Ada，Standard ML，OCaml，Eiffel，Object Pascal（Delphi），Oberon，Dylan，VHSIC（VHDL）等。

### B 和 C 编程语言

大多数编程语言遵循的这种事实标准，后来被名为B的极简编译语言间接改变。它唯一的应用目标是作为（一个非常原始的）Unix的最初移植版本，但它也演变成非常有影响力的 C 编程语言。

B 最初是系统编程BCPL的语法变体，简化（无型别）的CPL版本。在描述为 “拆解” 过程的情况下，BCPL的交集和联集运算子被替换为`&`和`|`（后来变成`&&`和`

`）。

同样的过程中，原来具有ALGOL风格在BCPL语言中表示赋值操作的`:=`符号，在B语言中被替换为`=`。导致这种演变过程的原因未知。由于变量赋值在B语言中没有特殊语法（例如 let 或类似），而在表达式中允许这个操作，所以等号的传统语义（相等关系）和非标准涵义（变量赋值）另外相关联在一起。为了区分这两种意义，因此Ken Thompson使用了特别的双等号`==`组合取代相等关系判断。

一个小的型别系统后来被引入，B接着演变成C。C语言的普及与Unix的关联，使Java，C＃和许多其他语言沿用这种语法，虽然已经大不相同于等号的数学关系涵义。

### 编程语言

C编程的赋值语句会有返回值，由于任何非零值在条件运算式中被解译为真，源码`if(x = y)`是合法的，但与`if(x == y)`的意义完全相异。前者语义为“将 y 赋值给 x，如果 x 的新值不为 0，则执行以下语句”;后者语义则为“如果仅当 x 等于 y，执行以下语句”。

```c
  int x = 1;
  int y = 2;
  if (x = y) {
      /* This code will always execute if y is anything but 0*/
      printf("x is %d and y is %d\n", x, y);
  }
```

虽然Java和C＃具有与C相同的运算子，但这种错误通常会导致这些编程的编译错误，因为条件式必须是布林型别，而且没有隐式方法能从其它类型（如数值）转为布林型别。

因此，除非被赋值的变量具有布林型别（或包装为布林型别），否则会产生编译错误。

ALGOL类的语言中例如Pascal，Delphi和Ada（允许其编程可定义嵌套函数），Python和许多函数语言中，赋值运算子不可出现在表达式中（包括if子句），排除了这种错误。一些编译器如GNU编译器集合（GCC），则在编译`if`语句中包含赋值运算子的源码时，提供了警告，虽然在if条件中可以有一些赋值的合法使用。在此情况下赋值语句必须对额外的括号特别声明，以避免警告。

同样地，一些语言如BASIC使用“=”等号同时代表赋值操作和相等关系两者，因为在语法上它们是分开的（如Pascal，Ada，Python等，赋值运算子不能出现在表达式中）。

有些程序员习惯于逆向（一般从左到右条件判断）写一个常数的比较：

```c
  if (2 == a) {   /* Mistaken use of = versus == would be a compile-time error */
  }
```

如果意外使用了`=`，因为 2 不是变量则源码的编译无效，编译器会产生一个错误讯息，指出在等号的位置应该以适当的运算子替换。这种编程写法被称为左手比较或尤达条件式。

下表列出了各种编程测试型别相等的不同机制：

Language｜Physical equality｜Structural equality｜Notes

ALGOL 68｜`a :=: b` or `a ''is'' b`｜`a = b`｜when `a` and `b` are pointers

C, C++｜`a == b`｜`*a == *b`｜when `a` and `b` are pointers

C#｜`object.ReferenceEquals(a, b)`｜`a.Equals(b)`｜The `==` operator defaults to `ReferenceEquals`, but can be overloaded to perform `Equals` instead.

Common Lisp｜`(eq a b)`｜`(equal a b)`

Go｜`a == b`｜`reflect.DeepEqual(*a, *b)`｜when a and b are pointers

Java｜`a == b`｜`a.equals(b)`

JavaScript｜`a === b`｜`a == b`｜when a and b are two string objects containing equivalent characters, the === operator will still return true.

OCaml, Smalltalk｜`a == b`｜`a = b`

Pascal｜`a^ = b^`｜`a = b`

Perl｜`$a == $b`｜`$$a == $$b`｜when `$a` and `$b` are references to scalars

PHP5｜`$a === $b`｜`$a == $b`｜when `$a` and `$b` are objects

Python｜`a is b`｜`a == b`

Ruby｜`a.equal?(b)`｜`a == b`

Scheme｜`(eq? a b)`｜`(equal? a b)`

Swift｜`a === b`｜`a == b`｜when a and b have class type

Visual Basic .NET｜`a Is b` or `object.ReferenceEquals(a, b)`｜`a = b` or `a.Equals(b)`｜Same as C#

Objective-C (Cocoa, GNUstep)｜`a == b`｜`[a isEqual:b]`｜when `a` and `b` are pointers to objects that are instances of `NSObject`