关系运算子
关系运算子在计算机科学的编程语言中,是测试或定义两个实体之间某种关系的构造或操作符。一共有六种关系,分别为:小于(<)
、大于(>)
、小于或等于(<=)
、大于或等于(>=)
、等于(==)
和不等于(<>)
。在具备布尔型别的编程语言中(如 Pascal,Ada 或 Java),这些运算符通常根据两个操作变量之间的条件关系是否成立,判定为真(True)或假(False)。诸如 C 语言中关系运算子传回整数 0 或 1,其中 0 表示假,任何非零值表示真。使用关系运算子创建的表达式,形成所谓的关系表达式或条件。
关系运算子可以被视为谓词逻辑的特殊情况。
相等性
用法
许多编程语言的构造和资料型别中都使用到相等性,用于测试元素是否已存在于集合中,或者借由键来存取值。它在切换(switch)语句,以及编程的逻辑并联过程中,用于将控制流调度到正确的分支。相等性的可能含义之一是“如果 a 等于 b,那么我们可以在任何情况下互换 a 或 b,而不会产生任何差异。”但这样的声明不一定成立,尤其在将可变性和内容等同性一起考虑时。
物件相等与内容等同性
有时,特别是在物件导向编程中,对资料型别和继承物件进行比对时,出现了相等性和辨别的问题。以下情况通常需要区别:
- 相同型别的两个不同物件,例如两只手
- 两个物件相等但不同,例如两张10元钞票
- 两个物件相等但有不同的呈现,例如$1元纸钞和$1元硬币
- 对同一物件的两个不同参照,例如,同一人的两个昵称
在许多现代编程语言中会借由参照来存取物件和资料结构。在这些语言中,需要测试两种相等性质:
- 实质同等性:如果有两个参照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,如果 a = b 则为 0,如果 a > b 则为 1。例如C语言的函数strcmp
执行三方向比较,并根据此惯例返回 -1, 0 或 1,而qsort
预期比较函数依此惯例返回值。在排序演算法中比较方法源码的效率至为关键,因为它是排序性能的主要因素之一。
开发人员定义的资料型别(不是编程语言内建的型别)的比较,可以编写自订的或使用函式库的函数(如上文的strcmp
)来执行,或者在某些语言中通过重载比较运算符-即以开发人员的定义指派给比较运算子,来比较特定资料型别。另一个选择是使用某些惯例,例如成员比较。
逻辑等价
虽然一开始可能不那么显而易见,像布尔逻辑运算符 XOR,AND,OR 和 NOT,这些关系运算子可以设计为具有逻辑等同性,使得它们都可以相互定义。对于任何给定的 x 和 y 值,以下四个条件语句都有相同的逻辑等价性 E(全为真或全为假):
这依赖于域是良好排序的。
标准关系运算符
在编程语言中最常见到的数值关系运算子如下所示。
Convention | equal to | not equal to | greater than | less than | greater than or equal to |
less than or equal to |
---|---|---|---|---|---|---|
In print | = | ≠ | > | < | ≥ | ≤ |
FORTRAN[note 1] | .EQ.
|
.NE.
|
.GT.
|
.LT.
|
.GE.
|
.LE.
|
ALGOL 68[note 2] | =
|
≠
|
>
|
<
|
≥
|
≤
|
/=
|
>=
|
<=
| ||||
eq
|
ne
|
gt
|
lt
|
ge
|
le
| |
APL | =
|
≠
|
>
|
<
|
≥
|
≤
|
BASIC-like, spreadsheet formulas[note 3] | =
|
<>
|
>
|
<
|
>=
|
<=
|
MUMPS | =
|
'=
|
>
|
<
|
'<
|
'>
|
Lua | ==
|
~=
|
>
|
<
|
>=
|
<=
|
Pascal-like[note 4] | =
|
<>
|
>
|
<
|
>=
|
<=
|
C-like[note 5] | ==
|
!=
|
>
|
<
|
>=
|
<=
|
Bourne-like shells[note 6] | -eq
|
-ne
|
-gt
|
-lt
|
-ge
|
-le
|
Batch file | 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)
| |
Fortran 90[note 8] | ==
|
/=
|
>
|
<
|
>=
|
<=
|
Mathematica[1] | ==
|
!=
|
>
|
<
|
>=
|
<=
|
Equal[x,y]
|
Unequal[x,y]
|
Greater[x,y]
|
Less[x,y]
|
GreaterEqual[x,y]
|
LessEqual[x,y]
|
- ^ Including FORTRAN II, III, IV, 66 and 77.
- ^ ALGOL 68: stropping regimes are used in code on platforms with limited character sets (e.g., use
>=
orGE
instead of≥
), platforms with nobold
emphasis (use'ge'
), or platforms with only UPPERCASE (use.GE
or'GE'
). - ^ Including Visual Basic .NET, OCaml, SQL, Standard ML, Excel, and others.
- ^ Including ALGOL, Simula, Modula-2, Object Pascal (Delphi), OCaml, Standard ML, Eiffel, APL, and others.
- ^ Including C, C++, C#, Go, Java, JavaScript, Perl (numerical comparison only), PHP, Python, Ruby, and R.
- ^ Including Bourne shell, Bash, Korn shell, and Windows PowerShell. The symbols
<
and>
are usually used in a shell for redirection, so other symbols must be used. Without the hyphen, is used in Perl for string comparison. - ^ MATLAB, although in other respects using similar syntax as C, does not use
!=
, as!
in MATLAB sends the following text as a command line to the operating system. The first form is also used in Smalltalk, with the exception of equality, which is=
. - ^ Including FORTRAN 95, 2003, 2008 and 2015.
其他较少见的:Common Lisp的不等关系运算子是 /=,Macsyma/Maxima 的不等关系运算子是 #。旧的Lisp使用equal,greaterp 和 lessp; 而以not运算子作逻辑否定。
语法
关系运算子也用于技术文献而不是单词,如果编程语言支援通常以中缀表示法,亦即出现在其操作变量(两个表达式是相关的)之间。 举例而言如果 x 小于 y,在Python中的表达式将印出句子:
if x < y:
print("x is less than y in this example")
其他编程语言如 Lisp 使用前缀表示法,如下所示:
(>= X Y)
操作符链接
链接关系在数学中是普遍的写法,例如 3 < x < y < 20 表示 3 < x 而且 x < y 而且 y <20。语义是很清楚的,因为数学中这些关系运算是有传递性的。然而,许多最近的编程语言会把 3 < x < y 的表达式,看作两个左(或右)关系运算子的组合,而解译为(3 < x ) < y
。如果我们设 x = 4 则得到(3 < 4 )< y
,而运算式变成true < y
,这是无意义的。但它却可能通过 C/C++ 和一些其它语言的编译(因为 true 会以数值1代表)。
有些编程语言如Python和Perl 6 能正确给出x < y < z
表达式所代表的数学意义,其它种语言则不,
部份是因大多数运算符在C语言种类中,以中缀表示法的运作方式有所不同。D编程语言保持与C的一些兼容性,而“允许C语言表达式却有微妙不同的语义(虽然可说是方向正确),与便利性比起来造成更多的混淆”。
有些语言如 Common Lisp,对此则使用多参数谓词。当 x 在 1 和 10 之间时,评估比较运算式
(<= 1 x 10)
结果为真。
与赋值运算子的混淆情况
早期(西元1956-57年)FORTRAN编程语言受限于有限的字集,其中等号“=”是唯一的关系运算子,没有数学上通用的大于“<”或小于“>”关系符号(当然也就没有不大于“≤”或不小于“≥”之类的关系符号),迫使设计者定义如.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,执行以下语句”。
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等,赋值运算子不能出现在表达式中)。
有些程序员习惯于逆向(一般从左到右条件判断)写一个常数的比较:
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[inequality 1] | 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
|
- ^ Patent application: On May 14, 2003, US application 20,040,230,959 "IS NOT OPERATOR" was filed for the
ISNOT
operator by employees of Microsoft. This patent was granted on November 18, 2004.