산술 연산자
데이터들을 실제로 "움직이고 계산하는" 방법에 대해 배울 차례입니다. 그 중심에는 바로 연산자(Operators) 가 있습니다.
연산자는 특정 연산을 수행하도록 컴파일러에 지시하는 기호입니다.
이 장에서는 우리가 일상생활에서 가장 흔히 사용하는 사칙연산을 비롯하여, 숫자를 계산하고 조작하는 데 사용되는 산술 연산자(Arithmetic Operators) 에 대해 자세히 알아보겠습니다.
연산자와 표현식의 개념
본격적으로 산술 연산자를 살펴보기 전에, '연산자'와 '표현식'이라는 개념을 명확히 하는 것이 중요합니다.
- 연산자 (Operator): 특정 연산을 수행하도록 컴파일러에 지시하는 기호입니다. (예:
+
,-
,*
,/
,%
) - 피연산자 (Operand): 연산의 대상이 되는 값이나 변수입니다. (예:
a + b
에서a
와b
) - 표현식 (Expression): 하나 이상의 연산자와 피연산자로 구성되어 최종적으로 하나의 값을 생성하는 코드 조각입니다. (예:
a + b
,score * 2
,(x + y) / 2
) 모든 표현식은 결과 값과 타입을 가집니다.
int result = 10 + 20; // '10', '20'은 피연산자, '+'는 연산자, '10 + 20'은 표현식, 'result'는 변수
기본 산술 연산자
C++은 우리가 수학에서 흔히 사용하는 사칙연산을 위한 기본적인 산술 연산자를 제공합니다.
연산자 | 이름 | 설명 | 예시 (결과) |
---|---|---|---|
+ | 덧셈 (Addition) | 두 피연산자를 더합니다. | 5 + 3 (8) |
- | 뺄셈 (Subtraction) | 왼쪽 피연산자에서 오른쪽 피연산자를 뺍니다. | 5 - 3 (2) |
* | 곱셈 (Multiplication) | 두 피연산자를 곱합니다. | 5 * 3 (15) |
/ | 나눗셈 (Division) | 왼쪽 피연산자를 오른쪽 피연산자로 나눕니다. | 5 / 3 (1) |
% | 나머지 (Modulo) | 왼쪽 피연산자를 오른쪽 피연산자로 나눈 나머지 값을 반환합니다. 정수형 피연산자에만 적용됩니다. | 5 % 3 (2) |
나눗셈 연산자(/
)의 특성 (정수 vs. 실수)
나눗셈 연산자 /
는 피연산자의 타입에 따라 다르게 동작합니다.
- 정수 / 정수: 결과도 정수입니다. 소수점 이하는 버려집니다 (내림).
정수 나눗셈 예시 int a = 10; int b = 3; int result_int = a / b; // 10 / 3 = 3 double result_double = a / b; // 10 / 3 = 3.0 (정수 나눗셈 후 3이 double로 변환) std::cout << "정수 나눗셈: " << result_int << std::endl; // 출력: 3 std::cout << "정수 나눗셈 후 실수 변환: " << result_double << std::endl; // 출력: 3.0
- 실수 / 실수 (또는 실수 / 정수, 정수 / 실수): 결과는 실수입니다. 어느 한쪽이라도 실수 타입이면 다른 쪽이 실수로 암시적 형 변환된 후 실수 나눗셈이 수행됩니다.
실수 나눗셈 예시 double c = 10.0; double d = 3.0; double result_float = c / d; // 10.0 / 3.0 = 3.333... double mixed_result = a / d; // a(int)가 double로 변환 후 10.0 / 3.0 = 3.333... std::cout << "실수 나눗셈: " << result_float << std::endl; // 출력: 3.333... std::cout << "혼합 나눗셈: " << mixed_result << std::endl; // 출력: 3.333...
정확한 실수 나눗셈 결과를 얻기 위해서는 최소한 하나의 피연산자를 실수 타입으로 명시적 형 변환해 주는 것이 좋습니다.
int total_items = 7;
int num_boxes = 2;
double items_per_box = static_cast<double>(total_items) / num_boxes;
std::cout << "상자당 물건 수: " << items_per_box << std::endl; // 출력: 3.5
나머지 연산자(%
)의 특성:
나머지 연산자는 오직 정수 타입 피연산자에만 사용할 수 있습니다. 실수에 사용하면 컴파일 오류가 발생합니다.
int quotient = 10 / 3; // 몫: 3
int remainder = 10 % 3; // 나머지: 1
std::cout << "10 나누기 3의 몫: " << quotient << std::endl; // 출력: 3
std::cout << "10 나누기 3의 나머지: " << remainder << std::endl; // 출력: 1
// double d = 10.5 % 3.2; // 오류! 실수에는 나머지 연산자 사용 불가
단항 산술 연산자
기본 산술 연산자 외에, 피연산자가 하나인 단항(Unary) 산술 연산자도 있습니다.
연산자 | 이름 | 설명 | 예시 (결과) |
---|---|---|---|
+ | 단항 덧셈 (Unary Plus) | 피연산자의 부호를 유지합니다. (거의 사용 안 함) | +5 (5) |
- | 단항 뺄셈 (Unary Minus) | 피연산자의 부호를 반전시킵니다. | -5 (-5) |
int num = 10;
int positive_num = +num; // num의 값을 그대로 사용
int negative_num = -num; // num의 부호를 반전 (10 -> -10)
int negative_negative_num = -negative_num; // (-10)의 부호를 반전 (-> 10)
std::cout << "단항 +: " << positive_num << std::endl; // 출력: 10
std::cout << "단항 -: " << negative_num << std::endl; // 출력: -10
std::cout << "단항 -를 다시 적용: " << negative_negative_num << std::endl; // 출력: 10
증감 연산자
변수의 값을 1 증가시키거나 1 감소시킬 때 사용하는 특수한 연산자입니다. 코드를 간결하게 만들고 성능상 이점을 제공하기도 합니다.
연산자 | 이름 | 설명 | 예시 (결과) |
---|---|---|---|
++ | 증가 (Increment) | 피연산자의 값을 1 증가시킵니다. | x++ , ++x |
-- | 감소 (Decrement) | 피연산자의 값을 1 감소시킵니다. | x-- , --x |
전위(Prefix) vs. 후위(Postfix):
증감 연산자는 피연산자의 앞(++x
, --x
)에 붙느냐, 뒤(x++
, x--
)에 붙느냐에 따라 동작 방식에 중요한 차이가 있습니다.
-
전위 연산자 (Prefix Operator):
++x
,--x
- 먼저 값을 변경한 후, 그 변경된 값을 다른 연산에 사용합니다.
전위 연산자 예시 int x = 5; int y = ++x; // x를 1 증가시켜 6으로 만든 후, 그 값 6을 y에 할당 std::cout << "x: " << x << ", y: " << y << std::endl; // 출력: x: 6, y: 6
-
후위 연산자 (Postfix Operator):
x++
,x--
- 먼저 현재 값을 다른 연산에 사용한 후, 그 다음에 값을 1 변경합니다.
후위 연산자 예시 int a = 5; int b = a++; // a의 현재 값 5를 b에 할당한 후, a를 1 증가시켜 6으로 만듦 std::cout << "a: " << a << ", b: " << b << std::endl; // 출력: a: 6, b: 5
언제 무엇을 사용할까?
단순히 변수의 값을 1 증가시키거나 감소시킬 목적이라면 전위, 후위 어느 것을 사용해도 결과적으로 변수의 값은 동일하게 변합니다. 하지만 다른 연산과 함께 사용할 때는 반드시 그 차이를 이해하고 사용해야 합니다. 일반적으로 전위 연산자(++x
, --x
)를 사용하는 것이 약간 더 효율적이거나 안전하다고 권장됩니다. 이는 후위 연산자가 원래 값의 복사본을 생성해야 하는 반면, 전위 연산자는 그렇지 않기 때문입니다.
연산자 우선순위와 결합 규칙
하나의 표현식에 여러 연산자가 포함될 경우, 어떤 연산이 먼저 수행될지는 연산자 우선순위(Operator Precedence) 에 의해 결정됩니다.
우선순위가 높은 연산자가 먼저 수행됩니다.
예를 들어 수학에서 곱셈과 나눗셈이 덧셈과 뺄셈보다 우선순위가 높은 것과 같습니다.
우선순위 | 연산자 | 설명 |
---|---|---|
높음 | ++ , -- | 증감 (단항) |
+ , - | 단항 덧셈, 뺄셈 | |
* , / , % | 곱셈, 나눗셈, 나머지 | |
낮음 | + , - | 덧셈, 뺄셈 (이항) |
= | 대입 연산자 (다음 장에서 다룸) |
int result = 5 + 3 * 2; // 곱셈(*)이 덧셈(+)보다 우선순위가 높아 3 * 2가 먼저 계산됩니다.
// 5 + (3 * 2) = 5 + 6 = 11
std::cout << "5 + 3 * 2 = " << result << std::endl; // 출력: 11
만약 같은 우선순위를 가진 연산자들이 함께 있을 때는 결합 규칙(Associativity) 에 따라 연산 순서가 결정됩니다.
대부분의 산술 연산자는 왼쪽에서 오른쪽으로(Left-to-Right) 결합합니다.
int result = 10 - 5 + 2; // 뺄셈과 덧셈은 우선순위가 같으므로 왼쪽부터 계산
// (10 - 5) + 2 = 5 + 2 = 7
std::cout << "10 - 5 + 2 = " << result << std::endl; // 출력: 7
괄호 ()
사용:
연산자 우선순위나 결합 규칙에 관계없이 원하는 순서대로 연산을 강제하려면 괄호 ()
를 사용합니다. 괄호 안의 표현식이 가장 먼저 계산됩니다.
int result = (5 + 3) * 2; // 괄호 안의 덧셈이 먼저 계산됩니다.
// (8) * 2 = 16
std::cout << "(5 + 3) * 2 = " << result << std::endl; // 출력: 16
괄호는 연산 순서를 명확하게 하여 코드의 가독성을 높이는 데도 매우 유용합니다.
복잡한 표현식에서는 우선순위를 일일이 기억하기보다 괄호를 적절히 사용하여 의도를 명확히 하는 것이 좋습니다.