비교 연산자
비교 연산자
두 값을 서로 비교하는 비교 연산자를 알아봅니다. 느슨한 비교(==)와 엄격한 비교(===)의 차이를 이해하는 게 핵심이에요.
본문
비교 연산자는 이름이 뜻하듯 두 값을 비교할 수 있게 해줘요. 다양한 타입 관련 비교 예시를 보여주는 타입 비교 표(type comparison tables)도 함께 살펴보면 도움이 돼요.
비교 연산자 (Comparison Operators)
| 예제 | 이름 | 결과 |
|---|---|---|
$a == $b |
같음(Equal) | 타입 저글링 후 $a가 $b와 같으면 true. |
$a === $b |
동일(Identical) | $a가 $b와 같고 타입도 같으면 true. |
$a != $b |
같지 않음(Not equal) | 타입 저글링 후 $a가 $b와 같지 않으면 true. |
$a <> $b |
같지 않음(Not equal) | 타입 저글링 후 $a가 $b와 같지 않으면 true. |
$a !== $b |
동일하지 않음(Not identical) | $a가 $b와 같지 않거나 타입이 같지 않으면 true. |
$a < $b |
작음(Less than) | $a가 $b보다 엄격히 작으면 true. |
$a > $b |
큼(Greater than) | $a가 $b보다 엄격히 크면 true. |
$a <= $b |
작거나 같음(Less than or equal to) | $a가 $b보다 작거나 같으면 true. |
$a >= $b |
크거나 같음(Greater than or equal to) | $a가 $b보다 크거나 같으면 true. |
$a <=> $b |
우주선(Spaceship) | $a가 $b보다 작거나 같거나 클 때 각각 0보다 작거나 같거나 큰 int. |
두 피연산자가 모두 숫자 문자열이거나, 한쪽이 숫자이고 다른 쪽이 숫자 문자열이면 비교는 숫자로 수행됩니다. 이 규칙은 switch 문에도 적용돼요. ===나 !== 비교는 타입과 값을 함께 비교하므로 타입 변환이 일어나지 않습니다.
경고: PHP 8.0.0 이전에는 문자열을 숫자나 숫자 문자열과 비교할 때, 비교를 수행하기 전에 문자열이 숫자로 변환됐어요. 이로 인해 다음 예제처럼 놀라운 결과가 나올 수 있었죠:
<?php
var_dump(0 == "a");
var_dump("1" == "01");
var_dump("10" == "1e1");
var_dump(100 == "1e2");
switch ("a") {
case 0:
echo "0";
break;
case "a":
echo "a";
break;
}
?>
PHP 7에서 위 예제의 출력:
bool(true)
bool(true)
bool(true)
bool(true)
0
PHP 8에서 위 예제의 출력:
bool(false)
bool(true)
bool(true)
bool(true)
a
<?php
// Integers
echo 1 <=> 1, ' '; // 0
echo 1 <=> 2, ' '; // -1
echo 2 <=> 1, ' '; // 1
// Floats
echo 1.5 <=> 1.5, ' '; // 0
echo 1.5 <=> 2.5, ' '; // -1
echo 2.5 <=> 1.5, ' '; // 1
// Strings
echo "a" <=> "a", ' '; // 0
echo "a" <=> "b", ' '; // -1
echo "b" <=> "a", ' '; // 1
echo "a" <=> "aa", ' '; // -1
echo "zz" <=> "aa", ' '; // 1
// Arrays
echo [] <=> [], ' '; // 0
echo [1, 2, 3] <=> [1, 2, 3], ' '; // 0
echo [1, 2, 3] <=> [], ' '; // 1
echo [1, 2, 3] <=> [1, 2, 1], ' '; // 1
echo [1, 2, 3] <=> [1, 2, 4], ' '; // -1
// Objects
$a = (object) ["a" => "b"];
$b = (object) ["a" => "b"];
echo $a <=> $b, ' '; // 0
$a = (object) ["a" => "b"];
$b = (object) ["a" => "c"];
echo $a <=> $b, ' '; // -1
$a = (object) ["a" => "c"];
$b = (object) ["a" => "b"];
echo $a <=> $b, ' '; // 1
// not only values are compared; keys must match
$a = (object) ["a" => "b"];
$b = (object) ["b" => "b"];
echo $a <=> $b, ' '; // 1
?>
다양한 타입에 대해 비교는 다음 표(순서대로)에 따라 수행됩니다.
다양한 타입과의 비교 (Comparison with Various Types)
| 피연산자1 타입 | 피연산자2 타입 | 결과 |
|---|---|---|
| null 또는 string | string | null을 ""로 변환, 숫자 또는 사전식 비교 |
| bool 또는 null | anything | 양쪽을 bool로 변환, false < true |
| object | object | 내장 클래스는 자체 비교를 정의할 수 있고, 서로 다른 클래스는 비교 불가, 같은 클래스는 Object Comparison 참고 |
| string, resource, int 또는 float | string, resource, int 또는 float | 문자열과 리소스를 숫자로 변환, 일반 수학 |
| array | array | 멤버 수가 적은 배열이 더 작음. 피연산자1의 키가 피연산자2에 없으면 배열은 비교 불가, 그 외에는 값 대 값 비교(다음 예제 참고) |
| object | anything | object는 항상 더 큼 |
| array | anything | array는 항상 더 큼 |
<?php
// Bool and null are compared as bool always
var_dump(1 == TRUE); // TRUE - same as (bool) 1 == TRUE
var_dump(0 == FALSE); // TRUE - same as (bool) 0 == FALSE
var_dump(100 < TRUE); // FALSE - same as (bool) 100 < TRUE
var_dump(-10 < FALSE);// FALSE - same as (bool) -10 < FALSE
var_dump(min(-100, -10, NULL, 10, 100)); // NULL - (bool) NULL < (bool) -100 is FALSE < TRUE
?>
<?php
// Arrays are compared like this with standard comparison operators as well as the spaceship operator.
function standard_array_compare($op1, $op2)
{
if (count($op1) < count($op2)) {
return -1; // $op1 < $op2
} elseif (count($op1) > count($op2)) {
return 1; // $op1 > $op2
}
foreach ($op1 as $key => $val) {
if (!array_key_exists($key, $op2)) {
return 1;
} elseif ($val < $op2[$key]) {
return -1;
} elseif ($val > $op2[$key]) {
return 1;
}
}
return 0; // $op1 == $op2
}
?>
경고: # 부동소수점 숫자의 비교
float가 내부적으로 표현되는 방식 때문에, 두 float가 같은지 검사해서는 안 돼요. 자세한 내용은 float 문서를 참고하세요.
참고: PHP의 타입 저글링은 서로 다른 타입의 값을 비교할 때, 특히 int를 bool과 또는 int를 string과 비교할 때 항상 명확하지 않다는 점을 알아두세요. 따라서 대부분의 경우
==와!=보다===와!==비교를 쓰는 게 일반적으로 권장됩니다.
참고: 비교 중 재귀(recursion)를 만나면, 예를 들어 자기 자신에 대한 참조를 담은 배열이나 객체를 비교하면 PHP 8.4.0부터 Error가 발생하고, 이전에는 치명적 오류로 끝났어요.
비교 불가능한 값
동일성 비교(===와 !==)는 임의의 값에 적용할 수 있지만, 다른 비교 연산자는 비교 가능한 값에만 적용해야 해요. 비교 불가능한 값을 비교한 결과는 정의되어 있지 않으므로 신뢰하면 안 됩니다.
참고: PHP 8.5.0부터 비교 불가능한 객체(예: enum, CurlHandle, 또는 다른 내부 클래스)를
==나!=로 bool과 느슨하게 비교하면, 그 객체가 bool로 캐스팅된 것, 즉(bool) $object처럼 항상 일관되게 평가돼요. 그런 객체는 항상true로 캐스팅되므로true와 같고false와는 같지 않습니다. PHP 8.5.0 이전에는 불리언 리터럴($object == true)에 비교할 때만 적용됐고, 변수에 담긴 불리언 값과 비교하면 항상false를 반환했어요. 자세한 내용은 마이그레이션 가이드를 참고하세요.
참고
- strcasecmp()
- strcmp()
- 배열 연산자
- Types
삼항 연산자
또 다른 조건 연산자로 "?:"(삼항) 연산자가 있어요.
<?php
// Example usage for: Ternary Operator
$action = (empty($_POST['action'])) ? 'default' : $_POST['action'];
// The above is identical to this if/else statement
if (empty($_POST['action'])) {
$action = 'default';
} else {
$action = $_POST['action'];
}
?>
(expr1) ? (expr2) : (expr3) 표현식은 expr1이 true로 평가되면 expr2로 평가되고, expr1이 false로 평가되면 expr3로 평가돼요.
삼항 연산자의 가운데 부분은 생략할 수 있어요. expr1 ?: expr3 표현식은 expr1이 true로 평가되면 expr1의 결과로, 그렇지 않으면 expr3으로 평가됩니다. 이때 expr1은 한 번만 평가돼요.
참고: 삼항 연산자는 표현식이며, 변수로 평가되는 게 아니라 표현식의 결과로 평가된다는 점을 알아두세요. 변수를 참조로 반환하려 할 때 이 점이 중요합니다. 참조 반환 함수 안의
return $var == 42 ? $a : $b;문은 따라서 동작하지 않고 경고가 발생해요.
참고: 삼항 표현식을 "쌓는(stacking)" 것은 피하는 게 좋아요. 하나의 표현식 안에 괄호 없는 삼항 연산자를 여러 개 쓰면 PHP의 동작이 다른 언어에 비해 명확하지 않거든요. 실제로 PHP 8.0.0 이전에는 삼항 표현식이 대부분의 다른 프로그래밍 언어처럼 오른쪽 결합이 아니라 왼쪽 결합으로 평가됐죠. 왼쪽 결합성에 기대는 것은 PHP 7.4.0부터 deprecated입니다. PHP 8.0.0부터 삼항 연산자는 비결합이에요.
<?php
// on first glance, the following appears to output 'true'
echo (true ? 'true' : false ? 't' : 'f');
// however, the actual output of the above is 't' prior to PHP 8.0.0
// this is because ternary expressions are left-associative
// the following is a more obvious version of the same code as above
echo ((true ? 'true' : false) ? 't' : 'f');
// here, one can see that the first expression is evaluated to 'true', which
// in turn evaluates to (bool) true, thus returning the true branch of the
// second ternary expression.
?>
참고: 다만 숏 삼항(
?:)의 연결(chaining)은 안정적이고 합리적으로 동작해요. 거짓이 아닌(non-falsy) 값으로 평가되는 첫 번째 인자로 평가됩니다. 정의되지 않은 값은 여전히 경고를 발생시킨다는 점에 주의하세요.
<?php
echo 0 ?: 1 ?: 2 ?: 3, PHP_EOL; //1
echo 0 ?: 0 ?: 2 ?: 3, PHP_EOL; //2
echo 0 ?: 0 ?: 0 ?: 3, PHP_EOL; //3
?>
널 병합 연산자
또 하나 유용한 축약 연산자는 "??"(널 병합) 연산자예요.
<?php
// Example usage for: Null Coalesce Operator
$action = $_POST['action'] ?? 'default';
// The above is identical to this if/else statement
if (isset($_POST['action'])) {
$action = $_POST['action'];
} else {
$action = 'default';
}
?>
(expr1) ?? (expr2) 표현식은 expr1이 null이면 expr2로, 그렇지 않으면 expr1로 평가돼요.
특히 이 연산자는 isset()처럼 왼쪽 값이 존재하지 않아도 notice나 warning을 발생시키지 않아요. 배열 키에 쓸 때 특히 유용하죠.
참고: 널 병합 연산자는 표현식이며, 변수로 평가되는 게 아니라 표현식의 결과로 평가된다는 점을 알아두세요. 변수를 참조로 반환하려 할 때 이 점이 중요합니다. 참조 반환 함수 안의
return $foo ?? $bar;문은 따라서 동작하지 않고 경고가 발생해요.
참고: 널 병합 연산자는 우선순위가 낮아요. 즉 문자열 결합이나 산술 연산자 같은 다른 연산자와 섞어 쓴다면 괄호가 필요할 가능성이 큽니다.
<?php
// Raises a warning that $name is undefined.
print 'Mr. ' . $name ?? 'Anonymous';
// Prints "Mr. Anonymous"
print 'Mr. ' . ($name ?? 'Anonymous');
?>
참고: 널 병합 연산자는 간단한 중첩을 허용한다는 점도 알아두세요.
<?php
$foo = null;
$bar = null;
$baz = 1;
$qux = 2;
echo $foo ?? $bar ?? $baz ?? $qux; // outputs 1
?>