연산자 우선순위와 결합 방향
연산자 우선순위와 결합 방향 (Operator precedence)
표현식 하나에 연산자가 여럿 섞여 있으면, 컴파일러는 어느 연산을 먼저 묶어야 할지 정해야 해요. 이때 기준이 되는 게 **연산자 우선순위(operator precedence)**예요. 같은 줄에 적힌 연산자끼리는 우선순위가 같고, 우선순위가 같으면 **결합 방향(associativity)**이 다음 기준이 돼요. 아래 표는 C++ 연산자의 우선순위와 결합 방향을 위에서 아래로, 우선순위가 높은 순서대로 정리한 거예요. a, b, c는 피연산자(operand)를 뜻해요.
출처: cppreference
본문
우선순위 표
| 우선순위 | 연산자 | 설명 | 결합 방향 |
|---|---|---|---|
| 1 | a::b |
스코프 해석(Scope resolution) | 왼쪽→오른쪽 → |
| 2 | a++ a-- |
후위(접미사) 증가·감소 | |
type (a) type {a} |
함수 스타일 캐스트(Functional cast) | ||
a() |
함수 호출 | ||
a[] |
첨자(Subscript) | ||
a.b a->b |
멤버 접근 | ||
| 3 | ++a --a |
전위 증가·감소 | 오른쪽→왼쪽 ← |
+a -a |
단항 플러스·마이너스 | ||
!a ~a |
논리 NOT·비트 NOT | ||
( type )a |
C 스타일 캐스트 | ||
*a |
역참조(Indirection) | ||
&a |
주소 취하기(Address-of) | ||
^^a |
반사(reflection) 연산자(C++26부터) | ||
sizeof |
크기(Size-of)[^1] | ||
co_await |
await-표현식(C++20) | ||
new – new[] |
동적 메모리 할당 | ||
delete – delete[] |
동적 메모리 해제 | ||
| 4 | a.*b a->*b |
멤버 포인터(Pointer-to-member) | 왼쪽→오른쪽 → |
| 5 | a * b a / b a % b |
곱셈·나눗셈·나머지 | |
| 6 | a + b a - b |
덧셈·뺄셈 | |
| 7 | a << b a >> b |
비트 왼쪽·오른쪽 시프트 | |
| 8 | a <=> b |
삼중 비교 연산자(C++20부터) | |
| 9 | a < b a <= b a > b a >= b |
관계 연산자<, <=, >, >= 각각에 대해 |
|
| 10 | a == b a != b |
동등 연산자==, != 각각에 대해 |
|
| 11 | a & b |
비트 AND | |
| 12 | a ^ b |
비트 XOR(배타적 논리합) | |
| 13 | `a | b` | 비트 OR(포괄적 논리합) |
| 14 | a && b |
논리 AND | |
| 15 | `a | b` | |
| 16 | a ? b : c |
삼항 조건부[^2] | 오른쪽→왼쪽 ← |
throw |
throw 연산자 | ||
co_yield |
yield-표현식(C++20) | ||
a = b |
직접 대입(C++ 클래스에 기본 제공) | ||
a += b a -= b |
합·차에 의한 복합 대입 | ||
a *= b a /= b a %= b |
곱·몫·나머지에 의한 복합 대입 | ||
a <<= b a >>= b |
비트 왼쪽·오른쪽 시프트에 의한 복합 대입 | ||
a &= b a ^= b `a |
= b` | 비트 AND·XOR·OR에 의한 복합 대입 | |
| 17 | a, b |
쉼표(Comma) | 왼쪽→오른쪽 → |
[^1]: sizeof의 피연산자는 C 스타일 타입 캐스트가 될 수 없어요. sizeof (int) * p라는 표현식은 모호함 없이 (sizeof(int)) * p로 해석되지, sizeof((int)*p)로 해석되지 않아요.
[^2]: 조건부 연산자의 가운데 표현식(?와 : 사이)은 괄호로 감싼 것처럼 파싱돼요. 즉 ?:에 대한 그 표현식의 우선순위는 무시돼요.
우선순위가 어떻게 적용되나
표의 어떤 행에 우선순위와 함께 적힌 연산자는, 그보다 아래 행에 있는 더 낮은 우선순위의 연산자보다 자기 피연산자에 더 꽉 붙어(마치 괄호로 감싼 것처럼) 바인딩돼요. 예를 들어 std::cout << a & b와 *p++는 각각 (std::cout << a) & b와 *(p++)로 파싱되지, std::cout << (a & b)나 (*p)++로 파싱되지 않아요.
우선순위가 같은 연산자는 결합 방향에 따라 피연산자에 바인딩돼요. 예를 들어 a = b = c는 대입의 오른쪽→왼쪽 결합 때문에 a = (b = c)로 파싱되지 (a = b) = c로 파싱되지 않아요. 반면 a + b - c는 덧셈·뺄셈의 왼쪽→오른쪽 결합 때문에 (a + b) - c로 파싱돼요.
결합 방향 표기는 단항 연산자에는 사실상 불필요하지만 완전성을 위해 보여 주는 거예요. 전위 단항 연산자는 항상 오른쪽→왼쪽(delete ++*p는 delete(++(*p))), 후위 단항 연산자는 항상 왼쪽→오른쪽(a[1][2]++는 ((a[1])[2])++)으로 결합해요. 멤버 접근 연산자는 후위 단항 연산자와 묶여 있지만 결합 방향이 의미 있다는 점만 기억하면 돼요. a.b++는 a.(b++)이 아니라 (a.b)++로 파싱돼요.
참고로 연산자 우선순위는 연산자 오버로딩에 영향받지 않아요. 예를 들어 std::cout << a ? b : c;는 산술 왼쪽 시프트의 우선순위가 조건부 연산자보다 높기 때문에 (std::cout << a) ? b : c;로 파싱돼요.
주의할 점(Notes)
우선순위와 결합 방향은 컴파일 타임 개념이고, 런타임 개념인 평가 순서(order of evaluation)와는 별개예요.
표준 자체는 우선순위 단계를 직접 명시하지 않아요. 그것들은 문법(grammar)에서 유도된 거예요.
const_cast, static_cast, dynamic_cast, reinterpret_cast, typeid, sizeof..., noexcept, alignof는 절대 모호해지지 않아서 우선순위 표에 포함되지 않아요.
일부 연산자에는 대체 표기(alternate spelling)가 있어요. 예를 들어 &&는 and, ||는 or, !는 not으로 쓸 수 있죠.
C에서는 삼항 조건부 연산자가 대입 연산자보다 우선순위가 높아요. 그래서 C++에서 e = ((a < d) ? (a++) : (a = d))로 파싱되는 e = a < d ? a++ : a = d 같은 표현식은, C의 문법·의미상 제약 때문에 C에서는 컴파일되지 않아요. 자세한 내용은 C 페이지를 참고해요.
더 알아보기
- 연산자 오버로딩(operator overloading): 연산자를 사용자 정의 타입에 맞게 재정의하기.
- 평가 순서(order of evaluation): 우선순위와는 별개인 런타임 평가 순서 개념.
- 대체 연산자 표기(alternative operator syntax):
and,or,not같은 대체 철자. - C 연산자 우선순위: C에서의 우선순위 문서.