關係運算子
關係運算子在電腦科學的程式語言中,是測試或定義兩個實體之間某種關係的構造或運算子。一共有六種關係,分別為:小于(<)
、大于(>)
、小于或等于(<=)
、大于或等于(>=)
、等于(==)
和不等于(<>)
。在具備布爾型別的程式語言中(如 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.