연산자 우선순위
연산자 우선순위
연산자가 서로 얼마나 '단단히' 결합하는지를 정하는 우선순위와 결합 방향에 대해 알아봅니다. 표현식이 실제로 어떻게 묶여 계산되는지가 여기서 결정돼요.
본문
연산자의 우선순위는 두 표현식을 얼마나 '단단히' 묶을지 지정해요. 예를 들어 1 + 5 * 3이라는 표현식에서 답은 18이 아니라 16이에요. 곱셈("*") 연산자가 덧셈("+") 연산자보다 우선순위가 높기 때문이죠. 필요하면 괄호를 써서 우선순위를 강제할 수도 있어요. 예를 들어 (1 + 5) * 3은 18로 평가됩니다.
연산자의 우선순위가 같을 때는 결합 방향(associativity)이 연산자를 어떻게 묶을지 결정해요. 예를 들어 "-"는 왼쪽 결합이라서 1 - 2 - 3은 (1 - 2) - 3으로 묶이고 -4로 평가됩니다. 반면 "="는 오른쪽 결합이라서 $a = $b = $c는 $a = ($b = $c)로 묶여요.
우선순위가 같으면서 비결합(non-associative)인 연산자는 서로 나란히 쓸 수 없어요. 예를 들어 1 < 2 > 1은 PHP에서 불법입니다. 반면 1 <= 1 == 1이라는 표현식은 합법인데, == 연산자가 <= 연산자보다 우선순위가 낮기 때문이에요.
결합 방향은 이항(그리고 삼항) 연산자에만 의미가 있어요. 단항 연산자는 접두(prefix) 또는 접미(postfix)라서 이 개념이 적용되지 않죠. 예를 들어 !!$a는 !(!$a)로만 묶일 수 있어요.
엄밀히 필요하지 않더라도 괄호를 쓰면 묶임을 명시적으로 만들어 암묵적인 연산자 우선순위와 결합 방향에 기대지 않게 되므로 코드 가독성이 자주 높아집니다.
다음 표는 연산자를 우선순위 순서로 나열한 것이며, 맨 위가 가장 높은 우선순위예요. 같은 줄에 있는 연산자는 우선순위가 같고, 그 경우 결합 방향이 묶임을 결정합니다.
연산자 우선순위 (Operator Precedence)
| 결합 방향 | 연산자 | 추가 정보 |
|---|---|---|
| (n/a) | clone new |
clone과 new |
| 오른쪽 | ** |
산술 |
| (n/a) | + - ++ -- ~ (int) (float) (string) (array) (object) (bool) @ |
산술(단항 +, -), 증가/감소, 비트, 타입 캐스팅 및 오류 제어 |
| 왼쪽 | instanceof |
타입 |
| (n/a) | ! |
논리 |
| 왼쪽 | * / % |
산술 |
| 왼쪽 | + - . |
산술(이항 +, -), 배열 및 문자열(.은 PHP 8.0.0 이전) |
| 왼쪽 | << >> |
비트 |
| 왼쪽 | . |
문자열(PHP 8.0.0부터) |
| 왼쪽 | ` | >` |
| 비결합 | < <= > >= |
비교 |
| 비결합 | == != === !== <> <=> |
비교 |
| 왼쪽 | & |
비트 AND 및 참조 |
| 왼쪽 | ^ |
비트 |
| 왼쪽 | ` | ` |
| 왼쪽 | && |
논리 |
| 왼쪽 | ` | |
| 오른쪽 | ?? |
널 병합(null coalescing) |
| 비결합 | ? : |
삼항(PHP 8.0.0 이전에는 왼쪽 결합) |
| 오른쪽 | = += -= *= **= /= .= %= &= ` |
= ^= <<= >>= ??=` |
| (n/a) | yield from |
yield from |
| (n/a) | => |
키 있는 yield |
| (n/a) | yield |
yield |
| (n/a) | print |
|
| 왼쪽 | and |
논리 |
| 왼쪽 | xor |
논리 |
| 왼쪽 | or |
논리 |
| (n/a) | include include_once require require_once |
include, include_once, require, require_once |
| (n/a) | fn (...) => |
fn 화살표 구분 |
| (n/a) | throw |
throw |
참고: 캐스트 연산자와 같은 줄에 있는 단항 연산자(
~,@, 단항+/-)는instanceof보다 더 단단히 결합하는 반면,!는 덜 단단히 결합해요. 따라서(int) $x instanceof Foo는((int) $x) instanceof Foo로 묶이고,!$x instanceof Foo는!($x instanceof Foo)로 묶입니다.
<?php
$a = 3 * 3 % 5; // (3 * 3) % 5 = 4
var_dump($a);
$a = 1;
$b = 2;
$a = $b += 3; // $a = ($b += 3) -> $a = 5, $b = 5
var_dump($a, $b);
?>
삼항 연산자는 우선순위를 명확히 구분하기 위해 특별히 괄호 사용을 요구합니다.
<?php
$a = true ? 0 : (true ? 1 : 2);
var_dump($a);
// this is not allowed since PHP 8
// $a = true ? 0 : true ? 1 : 2;
?>
연산자 우선순위와 결합 방향은 표현식이 어떻게 묶이는지 결정할 뿐, 평가 순서(evaluation order)를 지정하지는 않아요. PHP는 (일반적인 경우) 표현식이 어떤 순서로 평가되는지 지정하지 않으므로, 특정 평가 순서를 가정하는 코드는 피해야 합니다. 동작이 PHP 버전 사이에서 또는 주변 코드에 따라 바뀔 수 있기 때문이에요.
<?php
$a = 1;
echo $a + $a++; // may print either 2 or 3
$i = 1;
$array[$i] = $i++; // may set either index 1 or 2
?>
<?php
$x = 4;
// this line might result in unexpected output:
echo "x minus one equals " . $x-1 . ", or so I hope\n";
// the desired precedence can be enforced by using parentheses:
echo "x minus one equals " . ($x-1) . ", or so I hope\n";
// this is not allowed, and throws a TypeError:
echo (("x minus one equals " . $x) - 1) . ", or so I hope\n";
?>
-1, or so I hope
-1, or so I hope
Fatal error: Uncaught TypeError: Unsupported operand types: string - int
PHP 8 이전에는 +, -, .이 같은 우선순위였어요.
<?php
$x = 4;
// this line might result in unexpected output:
echo "x minus one equals " . $x-1 . ", or so I hope\n";
// because it is evaluated like this line (prior to PHP 8.0.0):
echo (("x minus one equals " . $x) - 1) . ", or so I hope\n";
// the desired precedence can be enforced by using parentheses:
echo "x minus one equals " . ($x-1) . ", or so I hope\n";
?>
-1, or so I hope
-1, or so I hope
x minus one equals 3, or so I hope
참고:
=은 대부분의 다른 연산자보다 우선순위가 낮음에도, PHP는 여전히if (!$a = foo())같은 표현식을 허용해요. 이 경우foo()의 반환값이$a에 들어갑니다. 할당의 왼쪽은 반드시 변수여야 하므로, 할당은 주변의 더 높은 우선순위 접두 연산자와가 아니라 그 변수와 묶이기 때문이에요. 표현식 피연산자를 받는 다른 접두 연산자(clone, 캐스트 연산자,@,~)에도 같은 게 적용됩니다. 예를 들어clone $a = $b는(clone $a) = $b가 아니라clone ($a = $b)로 묶여요.
변경 이력
| 버전 | 설명 |
|---|---|
| 8.0.0 | 문자열 결합(.)이 이제 산술 덧셈/뺄셈(+, -) 및 비트 시프트 좌/우(<<, >>)보다 낮은 우선순위를 갖습니다. 이전에는 +, -와 같은 우선순위이고 <<, >>보다 높은 우선순위였어요. |
| 8.0.0 | 삼항 연산자(? :)가 이제 비결합입니다. 이전에는 왼쪽 결합이었죠. |
| 7.4.0 | 산술 덧셈/뺄셈(+, -) 또는 비트 시프트 좌/우(<<, >>)에 상대적인 문자열 결합(.)의 우선순위에 기대는 것, 즉 괄호 없이 함께 쓰는 것은 deprecated입니다. |
| 7.4.0 | 삼항 연산자(? :)의 왼쪽 결합성에 기대는 것, 즉 괄호 없는 삼항 연산자를 여러 개 중첩하는 것은 deprecated입니다. |