비트 연산자를 이용하여 비트 단위로 연산할 수 있습니다.
비트 연산자 중 시프트 연산자라는 것이 있습니다.
<<, >>로 씁니다.
<<는 비트를 왼쪽으로 옮깁니다.
아래와 같은 코드를 입력했을 때 어떻게 작동하는지
살펴보면서 설명하겠습니다.
unsigned char num = 1;
num << 1; //비트를 왼쪽으로 한 칸 옮긴다.
위 코드에서 char형 변수 num의 비트를 보면 아래와 같을 것입니다.
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
그 다음 시프트 연산 <<를 통해 비트를 1칸 옮깁니다.
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
비트를 한 칸 옮기면서 1이었던 값이 2가 되었습니다.
이번에는 두 칸 옮겨 볼까요?
unsigned char num = 1;
num << 2; //비트를 왼쪽으로 두 칸 옮긴다.
위 코드에서 char형 변수 num의 비트 상태는 아래와 같을 것입니다.
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
그 다음 시프트 연산 <<를 통해 비트를 두 칸 옮기게 됩니다.
0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
처음 1이었던 값이
시프트 연산 <<를 통해 비트를 두 칸 옮기게 되었고
결과적으로 4가 되었습니다.
여기서 알 수 있는 사실은
비트를 왼쪽으로 한 칸 옮길 때마다
값이 두 배가 된다는 것입니다.
십진수에서도 보면 02라는 값에서
2를 왼쪽으로 한 칸 옮기면 20이 되죠.
십진수에서 기존 2라는 값을 왼쪽으로 한 칸 옮기니
10배가 증가한 20이 되었습니다.
그렇다면 이진수는 어떨까요?
왼쪽으로 한 칸 옮기면 값이 2배가 증가하겠죠.
이번에는 비트 연산자 중
시프트 연산 >>은 어떻게 작동하는지 살펴보겠습니다.
아래 코드를 통해 살펴보죠.
unsigned char num = 10;
num >> 1; //비트를 오른쪽으로 한 칸 옮긴다.
값이 10인 char형 변수 num이 있습니다.
0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
값 10을 시프트 연산 >>를 수행하였습니다.
아래와 같이 될 것입니다.
0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
계산하면 5가 나옵니다.
예를 하나 더 들어보죠.
unsigned char num = 4;
num >> 2; //비트를 오른쪽으로 두 칸 옮긴다.
값 4를
0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
>> 연산을 통해 비트를 오른쪽으로 두 칸 옮겼습니다.
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
계산하면 1이 나옵니다.
여기서 알 수 있는 점은
한 칸 옮길 때 값이 2배가 되는 << 연산과 달리,
>> 연산을 하면 한 칸 옮길 때마다 값이 1/2배가 됩니다.
바꿔 말해
>> 연산을 통해 비트를 오른쪽으로 한 칸 옮길 때마다
기존 값이 절반으로 줄어듭니다.
즉, 오른쪽으로 한 칸 옮길 때마다 기존 값을 2로 나누는 것이죠.
그럼 홀수를 >> 연산하면 나머지는 어떻게 될까요?
아래와 같이 3을 표현하는 비트가 있다고 합시다.
0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
3을 >> 연산을 통해 오른쪽으로 한 칸 옮겨봅니다.
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
계산하니 1이 나왔습니다.
>> 연산을 하면서 몫 부분만 남고
나머지 부분은 사라지는 것을 알 수 있습니다.
추가적으로 시프트 연산 후 그 값을
바로 대입할 수도 있습니다.
<< 연산을 한 후에 대입하고 싶다면 <<=를
>> 연산을 한 후에 대입하고 싶다면 >>=를 사용하면 됩니다.
기존 2의 값을 가진 변수 num을
<< 연산을 통해 왼쪽으로 한 칸 옮긴 후 대입하고
그 값을 확인해 보니 4가 대입된 것을 확인할 수 있습니다.
이번에는
기존 8의 값을 가진 변수 num을
>> 연산을 통해 왼쪽으로 두 칸 옮긴 후 대입하고
그 값을 확인해 보니 2가 대입된 것을 확인할 수 있습니다.
비트 연산자의 또 다른 종류로는
&(곱) 연산자, |(합) 연산자, ~(반전) 연산자, ^(xor) 연산자가 있습니다.
& 연산은 비트가 둘 다 1인 경우만 1이 됩니다.
0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
&
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
=
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 연산은 비트가 둘 중 하나라도 1이면 1이 됩니다.
0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
|
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
=
0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
~ 연산은 모든 비트를 반전시킵니다.
~
0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
=
1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 |
^ 연산은 비트를 비교하여 같으면 0, 다르면 1이 됩니다.
0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
^
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
=
0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
비주얼 스튜디오에서 C++ 개발을 할 때
'전처리기'라는 과정이 있습니다.
이는 모든 컴파일 과정 중 가장 먼저 수행됩니다.
전처리기 지시문 중 하나인 #define 지시문을 살펴보겠습니다.
#define 지시문은 매크로를 정의합니다.
즉, 코드 작성자가 정한 구문을 특정 숫자로 치환해 주는 역할을 합니다.
아래 코드를 보면
코드 맨 위에 #define 지시문이 있는 것을 확인할 수 있습니다.
전처리기 지시문은 코드 가장 맨 위에 작성합니다.
작성자가 임의로 정한 HUNGRY라는 구문을 1로 보겠다는 의미입니다.
컴파일을 수행하면 컴파일러는 "#define HUNGRY 1"을 보고
코드로 내려가 HUNGRY 구문을 1로 바꾸는 과정을 먼저 수행합니다.
실행 결과 변수 status에 들어간 수를 보면 1이 대입된 것을 확인할 수 있습니다.
#define 지시문은 왜 사용하는 걸까요?
첫 번째 장점은 가독성입니다.
게임을 예로 들겠습니다.
전투 중, 배고픔, 죽음, 비행, 달리는 중, 걷는 중 등등
게임에서 플레이어 상태는 다양하게 존재합니다.
지시문을 사용하지 않는다면 각각 상태에 대응하는 수를
코드 작성자가 외워야 합니다.
전투 중은 0, 배고픔은 1, 죽음은 2...
그러나 아래 코드처럼 #define을 통해 정의해 놓으면
#define BATTLE 0
#define HUNGRY 1
#define DEAD 2
플레이어 상태를 표현할 때 구문을 대입하면
단순히 숫자를 쓰는 것보다 훨씬 알아보기 쉽습니다.
두 번째 장점은 유지보수를 하기에 있어 용이하다는 점입니다.
예를 들어 자판기 시스템을 구축한다고 봅시다.
이 자판기는 음료수의 개수가 최대 20개까지 들어가는 자판기입니다.
그래서 20이라는 숫자를 음료수의 최대 개수로 생각하고
코드를 작성하였습니다.
그런데 A 자판기가 업그레이드되어 음료수를 최대 40개까지 넣을 수 있게 되었습니다.
음료수의 최대 개수를 20으로 생각하고 코드 내에서 20으로 작성하였는데
40개로 수정하기 위해 20이라는 수를 일일이 수정해야 하는 번거로움이 생깁니다.
만약 처음부터 #define 지시문을 통해
아래와 같이 작성하고
이를 코드 내에서 활용했다면 어땠을까요?
#define MAX_QUANTITY 20
음료수의 최대 개수가 40개로 늘어나면
아래와 같이 20을 40으로, 숫자만 바꿔 주면 간단하게 해결됩니다.
#define MAX_QUANTITY 40
강의 출처 : https://www.youtube.com/watch?v=PFc4g8mxOiI&list=PL4SIC1d_ab-aOxWPucn31NHkQvNPHK1D1&pp=iAQB
'C++ > 기초' 카테고리의 다른 글
C++ 기초 : 변수 / 함수 (1) (0) | 2024.02.18 |
---|---|
C++ 기초 : 비트 연산자 (2) (0) | 2024.02.16 |
C++ 기초 : switch 구문 / 삼항 연산자 (0) | 2024.02.12 |
C++ 기초 : if / else 구문 (0) | 2024.02.10 |
C++ 기초 : 비교 연산자 (0) | 2024.02.10 |