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

相等性

编辑

用法

编辑

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

物件相等与内容等同性

编辑

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

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

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

  • 实质同等性:如果有两个参照A和B来自引用同一个物件,以A与物件进行的互动,跟借由B与物件进行的互动,两者其实就是相同作用而无法区别,特别是以A去改变物件的异动会反映在B之上。当讨论为值而非物件时,实质同等性并不适用。
  • 语义同等性:如果两个参照物件或两个值在某种意义上是等价的:
    • 结构等式(即它们的内容是相同的),或浅薄地(仅测试目前部份)或深入地(递归地测试其所有部份的相等性)。实现这一点的简易方法是通过代表等式:检查参照的值是否有相同的代表式。
    • 其它特制的同等性,保留外部行为。例如将  视为有理数时,被判断是相等的。除了反射性对称性传递性之外,对 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(全为真或全为假):

 

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

标准关系运算符

编辑

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

Common relational operators
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]
  1. ^ Including FORTRAN II, III, IV, 66 and 77.
  2. ^ ALGOL 68: stropping regimes are used in code on platforms with limited character sets (e.g., use >= or GE instead of ), platforms with no bold emphasis (use 'ge'), or platforms with only UPPERCASE (use .GE or 'GE').
  3. ^ Including Visual Basic .NET, OCaml, SQL, Standard ML, Excel, and others.
  4. ^ Including ALGOL, Simula, Modula-2, Object Pascal (Delphi), OCaml, Standard ML, Eiffel, APL, and others.
  5. ^ Including C, C++, C#, Go, Java, JavaScript, Perl (numerical comparison only), PHP, Python, Ruby, and R.
  6. ^ 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.
  7. ^ 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 =.
  8. ^ 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
  1. ^ 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.

另见

编辑

参考

编辑