연산자 우선순위 표
1 L->R | :: :: |
Global scope (unary) Namespace scope (binary) |
2 L->R | () () type() type{} [] . -> ++ –– typeid const_cast dynamic_cast reinterpret_cast static_cast sizeof… noexcept alignof |
Parentheses Function call Functional cast List init temporary object (C++11) Array subscript Member access from object Member access from object ptr Post-increment Post-decrement Run-time type information Cast away const Run-time type-checked cast Cast one type to another Compile-time type-checked cast Get parameter pack size Compile-time exception check Get type alignment |
3 R->L | + - ++ –– ! not ~ (type) sizeof co_await & * new new[] delete delete[] |
Unary plus Unary minus Pre-increment Pre-decrement Logical NOT Logical NOT Bitwise NOT C-style cast Size in bytes Await asynchronous call Address of Dereference Dynamic memory allocation Dynamic array allocation Dynamic memory deletion Dynamic array deletion |
4 L->R | ->* .* |
Member pointer selector Member object selector |
5 L->R | * / % |
Multiplication Division Remainder |
6 L->R | + - |
Addition Subtraction |
7 L->R | << >> |
Bitwise shift left / Insertion Bitwise shift right / Extraction |
8 L->R | <=> | Three-way comparison (C++20) |
9 L->R | < <= > >= |
Comparison less than Comparison less than or equals Comparison greater than Comparison greater than or equals |
10 L->R | == != |
Equality Inequality |
11 L->R | & | Bitwise AND |
12 L->R | ^ | Bitwise XOR |
13 L->R | | | Bitwise OR |
14 L->R | && and |
Logical AND Logical AND |
15 L->R | || or |
Logical OR Logical OR |
16 R->L | throw co_yield ?: = *= /= %= += -= <<= >>= &= |= ^= |
Throw expression Yield expression (C++20) Conditional Assignment Multiplication assignment Division assignment Remainder assignment Addition assignment Subtraction assignment Bitwise shift left assignment Bitwise shift right assignment Bitwise AND assignment Bitwise OR assignment Bitwise XOR assignment |
17 L->R | , | Comma operator |
애초에 연산자 우선순위가 너무 많아서 모두 기억하기 어려운게 당연하다. 주로 복합 표현식이 어떻게 평가되는지 이해하기 위해 쓴 표지만, 어차피 굳이 뭐 항상 검색할 필요는 없다.
정수의 지수화
C++에서 지수관련 연산을 계산하고 싶다면 <cmath> 헤더를 #include하고 pow() 함수를 사용해야 한다. (반환값은 double)
#include <cmath>
double x{ std::pow(3.0, 4.0) };
사실 이렇게 하기 보다 정수를 지수화 하고싶다면 그냥 자신만의 함수를 만들어서 사용하는 것이 가장 좋다.
하지만 대부분의 경우 정수 지수화는 정수 타입을 오버플로한다. 아마 이런 이유 때문에 표준 라이브러리에 포함되지 않은 것이겠지..
오버플로를 확인하는 정수 지수화의 조금 더 안전한 버전 예시
#include <cassert> // for assert
#include <cstdint> // for std::int64_t
#include <iostream>
#include <limits> // for std::numeric_limits
// A safer (but slower) version of powint() that checks for overflow
// note: exp must be non-negative
// Returns std::numeric_limits<std::int64_t>::max() if overflow occurs
constexpr std::int64_t powint_safe(std::int64_t base, int exp)
{
assert(exp >= 0 && "powint_safe: exp parameter has negative value");
// Handle 0 case
if (base == 0)
return (exp == 0) ? 1 : 0;
std::int64_t result { 1 };
// To make the range checks easier, we'll ensure base is positive
// We'll flip the result at the end if needed
bool negativeResult{ false };
if (base < 0)
{
base = -base;
negativeResult = (exp & 1);
}
while (exp > 0)
{
if (exp & 1) // if exp is odd
{
// Check if result will overflow when multiplied by base
if (result > std::numeric_limits<std::int64_t>::max() / base)
{
std::cerr << "powint_safe(): result overflowed\n";
return std::numeric_limits<std::int64_t>::max();
}
result *= base;
}
exp /= 2;
// If we're done, get out here
if (exp <= 0)
break;
// The following only needs to execute if we're going to iterate again
// Check if base will overflow when multiplied by base
if (base > std::numeric_limits<std::int64_t>::max() / base)
{
std::cerr << "powint_safe(): base overflowed\n";
return std::numeric_limits<std::int64_t>::max();
}
base *= base;
}
if (negativeResult)
return -result;
return result;
}
int main()
{
std::cout << powint_safe(7, 12) << '\n'; // 7 to the 12th power
std::cout << powint_safe(70, 12) << '\n'; // 70 to the 12th power (will return the max 64-bit int value)
return 0;
}
증감연산자와 부작용
Prefix increment (pre-increment) | ++ | ++x | Increment x, then return x |
Prefix decrement (pre-decrement) | –– | ––x | Decrement x, then return x |
Postfix increment (post-increment) | ++ | x++ | Copy x, then increment x, then return the copy |
Postfix decrement (post-decrement) | –– | x–– | Copy x, then decrement x, then return the copy |
prefix는 직관적이고 이해하기도 쉽다.
먼저 피연산자가 증가하거나 감소한 다음 표현식이 피연산자의 값으로 평가된다.
하지만 후위증감이 조금 헷갈리는 경우가 있다.
먼저 피연산자의 복사본이 만들어진다. 그런 다음 피연산자(복사본이 아님)가 증가하거나 감소한다.
마지막으로 원본이 아닌 사본이 평가된다.
int main()
{
int x { 5 };
int y { x++ }; // x is incremented to 6, copy of original x is evaluated to the value 5, and 5 is assigned to y
std::cout << x << ' ' << y << '\n';
return 0;
}
위 코드의 결과값으로
6 5
이런 결과가 출력된다.
int y 행의 계산 과정을 알아보자면, 먼저 x 와 동일한 값(5)으로 시작하는 x 의 임시 복사본이 y에 만들어진다. 그런 다음 실제 x는 5 에서 6 으로 증가한다 . 그 후 x 의 복사본 (여전히 값이 5 임)이 반환되어 y 에 할당된다.
그런 다음 임시 복사본이 삭제된다.
결과적으로 y 는 값 5 (사전 증가된 값) 로 끝나고 x는 값 6 (사후 증가된 값)으로 끝난다.
다른 예시
int main()
{
int x { 5 };
int y { 5 };
std::cout << x << ' ' << y << '\n';
std::cout << ++x << ' ' << --y << '\n'; // prefix
std::cout << x << ' ' << y << '\n';
std::cout << x++ << ' ' << y-- << '\n'; // postfix
std::cout << x << ' ' << y << '\n';
return 0;
}
위 코드의 출력은 다음과 같다.
5 5
6 4
6 4
6 4
7 3
prefix 부분에서 x와 y 는 해당 값이 std::cout으로 전송되기 전에 증가/감소하므로 std::cout에 반영된 업데이트된 값을 볼 수 있다.
postfix 줄에서는 x 와 y 의 복사본이 std::cout으로 전송되므로 여기에 반영된 증가 및 감소를 볼 수 없다. 이러한 변경 사항은 x 와 y가 다시 평가되는 다음 줄까지 표시되지 않는다 .
postfix 버전은 훨씬 더 많은 단계를 거치므로 prefix 버전만큼 성능이 좋지 않을 수 있다. 애초에 동일한 결과를 생성하는데 접두사 버전의 성능이 더 뛰어나고 예상치 못한 일이 발생할 가능성이 적기 때문에 일반적으론 그냥 접두사를 선호한다.
평가 순서로 인한 부작용 side effect
이제 부작용 side effect에 대해 알아보자.
여기에서 부작용이란, 표현식에서 우리가 관찰할 수 있는 효과가 있는 경우를 부작용이라고 부른다.
x = 5; // the assignment operator has side effect of changing value of x
++x; // operator++ has side effect of incrementing x
std::cout << x; // operator<< has side effect of modifying the state of the console
그냥 이런 것들 전부 콘솔창에 변화가 있거나 변수값이 바뀌었으므로 부작용으로 처리한다.
아무튼 이런 부작용들로 인해 평가 순서 문제가 발생할 수 있다.
int add(int x, int y)
{
return x + y;
}
int main()
{
int x { 5 };
int value{ add(x, ++x) }; // undefined behavior: is this 5 + 6, or 6 + 6?
// It depends on what order your compiler evaluates the function arguments in
std::cout << value << '\n'; // value could be 11 or 12, depending on how the above line evaluates!
return 0;
}
여기서 add()값을 5 + 6인 11로 평가할 지, 6 + 6인 12로 평가할 지 부작용이 있다. 그냥 문제일 뿐 딱히 별 의미는 없다.
이런 부작용이 발생하는데도 컴파일러는 주어진 아키텍쳐에 대해 가장 자연스러운(성능이 가장 뛰어난) 작업을 수행할 수 있도록 굳이 평가 순서를 의도적으로 정의하진 않는다.
쉼표 연산자
예를들어 ( x , y)같은 경우가 있다. 이 연산자는 x를 평가한 다음 y를 평가하고 y의 값을 반환한다.
int main()
{
int x{ 1 };
int y{ 2 };
std::cout << (++x, ++y) << '\n'; // increment x and y, evaluates to the right operand
return 0;
}
먼저 쉼표 연산자의 왼쪽 피연산자가 평가되어 x가 1 에서 2 로 증가한다. 다음으로, 오른쪽 피연산자가 평가되어 y가 2 에서 3 으로 증가한다 . 쉼표 연산자는 오른쪽 피연산자( 3 ) 의 결과를 반환하며 이후에 콘솔에 출력한다.
쉼표는 모든 연산자 중 할당보다 우선순위가 가장 낮기 때문에 다음 두 줄의 코드는 서로 다른 작업을 수행한다.
z = (a, b); // (a, b)를 먼저 평가하여 b의 결과를 얻은 다음 해당 값을 변수 z에 할당
z = a, b; // "(z = a), b"로 평가하므로 z는 a의 값을 할당받고 b는 평가 후 삭제됨
이러한 이유 때문에 쉼표 연산자를 사용하는 것은 다소 위험하다. 그냥 사용하지 말자.
참고로 우리가 보통 사용했던 구분의 의미를 가진 쉼표 기호는 연산자를 호출하지 않는다.
추가사항 (참고)
부동 소수점
부동 소수점 연산에서 관계연산자 (>및 <) 또는 항등연산자 (== 및 !=)를 사용하는 것을 피해야 한다.
각각의 피연산자들의 차이가 크면 상관없지만, 차이가 크지 않은 경우 반올림 오류로 인해 예상치 못한 값을 반환하기 때문이다.
즉, 부동 소수점 값을 비교하지 말자.
(정밀도가 낮은(유효 자릿수가 적은)경우랑, 리터럴임을 보장할 수 있는 const 또는 constexpr 변수는 괜찮긴 함)
논리 연산자 , 드모르건의 법칙
논리연산자에서 AND와 OR를 혼합하여 사용하는 경우, AND가 더 우선순위가 높기 때문에 먼저 수행된다.
value1 || value2 && value3 표현식의 경우 value1 || (value2 && value3) 로 평가된다.
드모르건의 법칙에 의해 논리연산자 NOT을 우리가 원하는 방향으로 분배해줄 수 없다.
!(x && y) 이것이 !x && !y 라고 분배되리라 예상하지만 실제로는 그렇지 않다.
드모르건의 법칙은 다음과 같다.
!(x && y) 은 !x || !y 와 같고,
!(x || y) 은 !x && !y 와 같다.
즉, 논리 NOT 을 분배할 때는 논리 AND 를 논리 OR 로, 그 반대로 뒤집어야 한다 .
드모르건의 법칙은 복잡한 표현식을 더 쉽게 읽을 수 있도록 도와주기 때문에 알아두는 것이 좋다.
x | y | !x | !y | !(x && y) | !x || !y |
false | false | true | true | true | true |
false | true | true | false | true | true |
true | false | false | true | true | true |
true | true | false | false | false | false |
반대 경우
x | y | !x | !y | !(x || y) | !x && !y |
false | false | true | true | true | true |
false | true | true | false | false | false |
true | false | false | true | false | false |
true | true | false | false | false | false |