Notice
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Today
Total
관리 메뉴

Study Note

[C/C++ Syntax] #005 수식(Expression)과 연산자(Operator) 본문

- 프로그래밍 언어(Programming Language)/C\C++

[C/C++ Syntax] #005 수식(Expression)과 연산자(Operator)

mymir 2021. 1. 3. 03:26

앞선 글에서 변수, 자료형을 컴퓨터 하드웨어 입장에서 살펴보면 메모리에 가까운 파트라고 할 수 있고, 이번 글에서 살펴볼 수식과 연산자는 계산을 담당하는 CPU에 가까운 파트라고 볼 수 있습니다. C/C++ 언어의 연산은 어떤 메커니즘으로 돌아가는지 살펴봅시다.

 

 

 

수식이란?


수식(expression)이란 데이터를 연산 기호로 연결한 식으로 항상 그 결과값을 갖는 형태를 취합니다. 이때, 연산을 하는 주체인 연산 기호는 연산자(operator)라고 말하며 연산을 당하는 객체인 데이터를 피연산자(operand)라 부릅니다. 연산자는 필요한 피연산자의 개수에 따라 단항 연산자, 이항 연산자, 삼항 연산자로 나뉘고 연산의 진행방향이 각각 존재합니다.

 

수식에서 중요하게 이해하고 있어야 할 부분이 있는데, 하나의 연산자는 반드시 하나의 수식값을 갖는다는 점입니다. 여러 개의 연산자로 이루어진 하나의 수식일지라도 각 연산자에 의한 결과마다 수식값을 반환합니다.

 

또한 다중 연산자로 이루어진 하나의 수식에서 괄호에 의한 결합이 없다면, 연산자 우선순위(precedence)에 따라 결합법칙이 자동적으로 일어나 연산들을 수행하게 됩니다. (우선순위가 같다면 결합 방향의 순서로 계산합니다.) 예를 들어 사칙 연산에서 덧셈보다 곱셈이 우선순위가 높기 때문에 1+2×3의 결과가 9가 아니라 7이 되는 것처럼 말입니다. 따라서 다음 표를 통해 우선순위를 파악하고 있어야 하며, 헷갈리는 경우 수식을 분리시키거나 괄호를 이용하여 연산을 해주는 것이 안전하며 코드를 읽을 때에도 편리합니다.

 

우선순위 연산자 기호 기능 결합 방향
1

[ ] 배열 쳠자 연산자
-> 간접 멤버 선택(참조) 연산자
( ) 함수 호출 연산자
. 직접 멤버 선택(참조) 연산자
++ 후위 증가 연산자
-- 후위 감소 연산자
2 ! 논리 부정 연산자
~ 보수 연산자
+ 양의 부호 연산자
- 음의 부호 연산자
++ 전위 증가 연산자
-- 전위 감소 연산자
& 주소 연산자
sizeof 크기 연산자
(type) 형 변환 연산자
3 * 곱셈 연산자
/ 나눗셈 연산자
% 나머지 연산자
4 + 덧셈 연산자
- 뺄셈 연산자
5 << 좌측 비트 이동 연산자
>> 우측 비트 이동 연산자
6 < 비교 연산자(작다)
<= 비교 연산자(작거나 같다)
> 비교 연산자(크다)
>= 비교 연산자(크거나 같다)
7 == 비교 연산자(같다)
!= 비교 연산자(같지 않다)
8 & 비트 단위 AND 연산자
9 ^ 비트 단위 XOR 연산자
10 | 비트 단위 OR 연산자
11 && 논리 AND 연산자
12 || 논리 OR 연산자
13 ? : 조건 연산자
14 = 대입 연산자
+= 치환 대입 연산자(덧셈)
-= 치환 대입 연산자(뺄셈)
*= 치환 대입 연산자(곱셈
/= 치환 대입 연산자(나눗셈)
%= 치환 대입 연산자(나머지)
&= 치환 대입 연산자(비트 단위 AND)
|= 치환 대입 연산자(비트 단위 OR)
^= 치환 대입 연산자(비트 단위 XOR)
<<= 치환 대입 연산자(좌측 비트 이동)
>>= 치환 대입 연산자(우측 비트 이동)
15 , 콤마 연산자

 

그럼 이 중에서 뒤에 배울 개념에 포함된 연산자를 제외하고 몇 가지 연산자를 알아보도록 하겠습니다.

 

 

 

산술 연산자


3 + 1; //4
3 - 1; //2
3 * 2; //6
7 / 2; //3
7.0 / 2.0; //3.5
7 % 2; //1

산술 연산자는 기본적으로 수학에서의 의미와 동일하게 적용됩니다. 다만 몇 가지 주의할 점과 일반적으로 사용하지 않는 나머지 연산자(%)에 대하여 알아보겠습니다.

 

우선 수학에서 x와 y를 곱하는 경우에 종종 곱셈 기호를 생략하여 xy로 표기하곤 하는데, C/C++에선 허용되지 않습니다. 반드시 곱셈 연산자(*)를 이용하여 x * y로 표현해주어야 합니다.

 

다음으로 나눗셈 연산자(/)는 피연산자의 자료형이 모두 정수형인 경우 몫을 연산합니다. 예를 들어, 5 / 2를 연산하는 경우 수식값은 2.5가 아닌 2가 됩니다.

 

마지막으로 나머지 연산자(%)는 좌측 피연산자를 우측 피연산자로 나눈 나머지를 수식값으로 반환합니다. 이때, 두 피연산자는 모두 정수형이어야 합니다.

 

 

대입연산자


x = 10;

대입연산자(=)는 변수에 특정 값을 저장하는 연산자로 저장될 변수는 좌측에, 저장할 값은 우측에 위치하는 구조를 갖고 있습니다. 수학에서의 '같다'라는 의미와 혼용하지 않아야 합니다. ('같다'라는 의미는 등가 연산자(==)로 구현되어 있습니다.)

 

앞서 모든 수식은 항상 수식값을 갖는다고 말했는데요, 대입연산자도 마찬가지입니다. 대입 연산자는 변수에 저장된 수식값을 반환합니다. 그렇기 때문에 다음과 같은 구조를 가질 수 있게 됩니다.

x = y = 2;

 

 

 

부호 연산자


int a = 10;
-a // -10

부호 연산자(+, -)는 단항 연산자로 하나의 피연산자의 앞에 위치하여 변수나 상수의 부호를 나타내거나 변경하는 연산자입니다. 산술 연산자의 덧셈과 뺄셈과 같은 기호를 사용하지만 피연산자의 개수가 다르므로 구분되어 사용됩니다.

 

 

 

증감 연산자


증감 연산자(++, --)또한 단항 연산자로 변수의 값을 1 증가(++) 혹은 1 감소(--)시키는 연산자입니다. 이렇게 결과는 간단하지만 피연산자의 위치에 따라 전위(prefix)와 후위(postfix)로 나뉘며 증감 연산자를 포함해 여러 연산자를 사용하는 수식의 경우 연산의 과정이 상이하므로 사용에 주의해야 합니다.

 

먼저 전위 증감연산자의 경우 '변수의 값을 먼저 증감시키고 사용한다'라는 의미로 사용되므로 아래의 두 문장씩 동일한 의미를 갖습니다.

++x;
x = x + 1;

--y;
y = y - 1;

 

다음으로 후위 증감연산자는 의미적으로 '변수를 먼저 사용한 뒤 증감시킨다'라는 의미로 사용되는데, 과정이 조금 복잡하기에 간단하게만 정의하겠습니다. (더욱 정확한 의미를 보려면 side effects에 대한 이해가 필요합니다.) 의미로 보면 전위 증감연산자보다 우선순위가 낮아야 할텐데 그렇지 않은 것으로 보아 일부 추측이 가능합니다. 반환값은 늦지만 처리는 가장 빠르다. 즉, 변수의 값을 뒤에 언젠가 반드시 증감시킬 것이라고 암시를 해두면 증감되지 않은 변수를 자유롭게 사용하고 나중에 증감시키는 방법을 사용한 것입니다. (반환 시기는 컴파일러마다 다릅니다..)

 

visual studio에서의 예제를 살펴보겠습니다.

#include <stdio.h>

int main() {
	int a = 10;
	int b = a++ + a;
	printf("%d", b);
}

변수 b의 초기화 식 a++ + a 를 대체 코드로 보면 다음과 같습니다.

b = a + a;
a = a + 1;

즉, visual studio에선 후위 연산자의 반환이 대입 연산자보다 늦다는 것을 알 수 있습니다.

 

하지만 다른 컴파일러에선

b = (a+1) + a;
a = a + 1;

와 같이 해석될 수 있으므로 자신의 개발환경에 맞게 사용할 수 있어야 합니다.

 

 

 

논리 연산자


논리 연산자는 연산의 논리적 조합값이 참인지 거짓인지를 판별하는 연산자입니다. AND를 뜻하는 &&, OR를 뜻하는 ||, NOT을 뜻하는 !로 이루어져 있고 진리표는 다음과 같습니다.

x y x && y x || y !x
true true true true false
true false false true false
false true false true true
false false false false true

이때, 참과 거짓을 나타내는 값은 bool형 자료로 앞서 언급하진 않았지만 true와 false를 갖는 자료라고 이해하면 됩니다.

 

한 가지 독특한 점은, C/C++에서 0은 false에 대응되고 0이 아닌 모든 값은 true에 대응된다는 것인데, 이를 활용한 수식을 작성할 수도 있습니다.

 

 

 

비교 연산자


int a = 10;

a < 10; // false
a <= 10; // true
a > 10; // false
a >= 10; // true
a == 10 // true
a != 10 // false

비교 연산자는 두 개의 피연산자의 대소를 비교하는 연산자로 관계 연산자라고도 불립니다. 수식값은 true  또는 false로 논리 연산자와 같이 사용되는 경우가 많습니다. 수학에서의 등호, 부등호와 같은 의미로 사용되며, 앞서 말한 등가연산자 ==를 대입 연산자 =와 혼용하지 않도록 주의해야 합니다.

 

하지만, 수학에서 흔히 사용되는 1<x<2와 같은 식은 허용되지 않습니다. 'x는 1보다 크고, 2보다 작다'를 반드시 구분하여 (1<x) && (x<2)와 같이 작성해주어야 합니다.

 

또한 앞선 글에서 부동소수점형의 오차에 따라 비교 연산자의 수식값이 생각한 것과 다르게 반환될 수 있으니 이런 사용에도 주의해야 합니다.

 

 

비트 단위 연산자


비트 단위 연산자는 논리 연산을 한 비트 단위로 각각 연산시켜주는 연산자입니다. 위 그림과 같이 4비트 크기의 0b0001과 0b0110을 AND 논리 연산자를 이용하면 수식값이 1이지만 AND 비트 단위 연산자를 이용하면 수식값이 0이 되는 것입니다.

0b0001 && 0b0110; // 1
0b0001 & 0b0110; // 0

 

이런 비트 단위 연산자는 정수형 자료만 피연산자로 사용할 수 있으며 각 연산자의 자세한 기능은 다음과 같습니다.

연산자 기호 기능
& 각 비트별 AND
| 각 비트별 OR
^ 각 비트별 XOR
~(단항) 각 비트별 NOT
<< (연산자 기준) 좌측 피연산자를 우측 피연산자 만큼 왼쪽으로 비트 이동
>> (연산자 기준) 좌측 피연산자를 우측 피연산자 만큼 오른쪽으로 비트 이동

 

여기서 새롭게 보여지는 비트 이동 연산자(<<. >>)가 있습니다. 비트를 해당 방향으로 해당 양만큼 밀어내는 연산자인데 우리가 사용하는 자료는 비트의 수가 제한되어 있기때문에 비트를 밀게 되면 범위 밖으로 밀려난 비트는 소멸되고, 소멸된 만큼 빈 공간에 비트를 채워야 합니다.

 

먼저 왼쪽 비트 이동 연산자 <<의 경우 4비트 크기의 0b0010를 2칸 미는 수식으로 0b0010 << 2 로 작성하면 수식값은 0b1000, 즉 unsigned인 경우 8, signed인 경우 -8이 됩니다. 0b0010의 세 번째 비트와 네 번째 비트가 밀려나 소멸하고 빈 공간인 첫 번째 비트와 두 번째 비트에 0을 채워준 것을 확인할 수 있습니다. 이를 의미적으로 해석하면 비트를 왼쪽으로 한 칸 이동한다는 것은 2를 곱한다는 것과 같은 의미로 이해할 수 있습니다. (물론 오버플로우도 적용됩니다.)

 

다음으로 오른쪽 비트 이동 연산자 >>의 경우 4비트 크기의 0b0110과 0b1110을 각각 1칸 미는 수식을 살펴보면 0b0110 >> 1의 수식값은 0b0011, 0b1110 >> 1의 수식값은 0b1111이 됩니다. 오른쪽으로 밀려난 비트가 버려지는 것은 동일하지만 채워지는 4번째 비트가 다른 것을 확인할 수 있습니다. 이는 부호를 유지하기 위해 이동 전의 최상위 비트(MSB)를 유지하여 비트를 채워주는 방식으로, 2로 나눈 몫과 같은 의미를 갖는다는 것을 알 수 있습니다.

 

 

 

치환 대입 연산자


x += y; // x = x + y
x -= y; // x = x - y
x *= y; // x = x * y
x /= y; // x = x / y
x %= y; // x = x % y
x &= y; // x = x & y
x |= y; // x = x | y
x ^= y; // x = x ^ y
x >>= y; // x = x >> y
x <<= y; // x = x << y

복합 연산자라고도 불리는 이 연산자는 변수에 담겨 있는 값을 변경하여 다시 변수에 대입하는 경우에 주로 사용됩니다. 예를 들어, x = x + 1에서 x의 중복 사용을 줄이고자 복합연산자 +=를 사용하여 x += 1과 같이 사용할 수 있습니다.

 

 

 

조건 연산


a = 10;
(a == 10)? 1:2; // 1
a = 100;
(a == 10)? 1:2; // 2

유일하게 세 개의 피연산자를 요구하므로 삼항 연산자라고도 부르며 조건에 따라 수식값을 선택할 수 있는 연산자입니다. (피연산자1)? (피연산자2) : (피연산자3)의 형태로 사용되며 조건에 해당하는 피연산자1의 값이 참인 경우 피연산자2의 값을 수식값으로 선택, 거짓인 경우 피연산자3의 값을 수식값으로 선택합니다.

 

 

 

콤마 연산자


콤마 연산자는 두 문장을 하나의 문장으로 결합하는 연산자로 가장 낮은 우선순위를 갖는 연산자입니다. 간단한 예로 설명해봅시다.

 

x = (1 + 1, 7 - 6);

이렇게 콤마 연산자를 사용하면 '1+1'과 'x = 7 - 6'의 두 문장을 합친 것과 같은 의미를 갖는 하나의 문장을 작성할 수 있습니다. 여기서 연산의 순서에 따른 수식값 처리가 두드러지므로 조금 더 살펴보겠습니다.

 

위와 같이 1순위인 괄호 연산자 내부에서 결합 방향 순서 상 덧셈 연산자를 먼저 처리한 수식값 2와, 그 다음 순위인 뺄셈 연산자를 처리한 수식값 1을 콤마 연산자의 결합 방향으로 보면 우측 피연산자인 1이 수식값으로 선택됩니다. 그리고 괄호를 벗어나 대입 연산자를 통해 변수 x에 1이 저장되는 구조임을 알 수 있습니다. 이렇듯 연산자 우선 순위와 결합 방향을 오용할 경우 치명적인 결과를 낳게되므로 주의하여 사용해야 합니다.

 

또한 위 식에서 1+1가 무의미함 혹은 이질적으로 느껴질 수 있을 것입니다. 보통 그런 경우엔 뭔가 잘못된 경우가 많지요. 이런 경우는 한 문장에서 대입 연산자를 통하지 않은 수식값 혹은 데이터는 문장이 종료됨과 동시에 소멸한다는 구조에서 나온 상황입니다.

Comments