본문 바로가기
C++

[C++] 비트 연산자

by arirang_ 2023. 2. 22.

비트 연산자

- 비트 단위로 연산이 진행될 때 사용하는 연산자

- 비트 쉬프트(bit shift) : << , >>


화살표가 가리키는 방향으로 민다.
비트를 왼쪽, 오른쪽으로 민다!!

 

ex)

unsigned char byte = 1;

byte << 1 

왼쪽으로 한칸씩 민다!!라는 의미

 

- 2진수에서 한칸 올라간다는 것은 2배 증가한다는 뜻이다. (10진수를 예로 들어서 이해해보기)

(비트가 한자리 올라간다는 것은 2배가 된다는 의미이다.)

0 0 0 0 1 0 1
더보기

위의 2진수를 10진수로 표현하면 10이다 (2의 3제곱 + 2이니까)

이게 한칸씩 왼쪽으로 이동하면 

0 0 0 1 0 1 0 0
더보기

10의 2배인 20이다. (2의 4네제곱 + 2의 2제곱이니까) 

즉 각각이 한칸씩 올라갔기 때문에 전체의 값이 2배가 된 것이다!!

 

byte <<= 1;
//서로 같은 의미
byte = byte << 1

값이 증가하는 경우 예시

byte <<= 2;  //값이 4배 늘어남
byte <<= 3;  //값이 8배 늘어남

값이 감소하는 경우 예시

byte >>= 1; //값이 1/2 감소,절반으로 줄어든다

근데 만약 byte가 13(홀수)이라면??

unsigned char byte = 13;
byte >>= 1;

>>1이 2로 나누는거라면 13을 2로 나누면 나머지는 어떻게 되는가...?

→ 나머지는 오른쪽으로 밀려 소실되므로 2로 나눈 몫이라고 생각하면 된다!!!

 

자세히 살펴보자.

2진수에서 홀수는 2의 0승 자리에 1이 올 것이다

              1

그럼 당연히 13도 2의 0승, 즉 일의 자리에 1이 오겠지. 13을 2진수로 표현하면 다음과 같다.

0 0 0 0 1 1 0 1

여기서 한칸씩 오른쪽으로 밀면  가장 오른쪽에 있던 1이 소실된다. 

따라서 오른쪽으로 한칸 이동한 것은 2로 나눈 몫이라고 생각하면 된다!

 

정리해보면

더보기

왼쪽으로 이동시      : 2^n 배수

오른쪽으로 이동시  : 2^n으로 나눈 몫

예시

byte <<= 3;    //2^3배수, 8배 증가
byte >>= 2;    //2^2 나눈 몫만 남게 된다.

 

비트 곱, 합, xor

- 비트 곱 : &

- 비트 합 : |

- xor : ^

- 반전 : ~

 

비트 연산 : 비트 단위로 하나씩 연산하는 것, 자리대로, 자릿수 맞춰서 진행함.

 

★ 비트 합(|) 연산

- 2개의 비트를 비트 단위로 자리대로 연산했을 때 둘 중 적어도 하나 1일 경우 1이 나오고, 둘 다 0일 경우 0이 나온다.

1 0 1 1 0 1 0 0

|

1 1 1 0 0 1 0 0

1 1 1 1 0 1 0 0

 

★ 비트 곱(&) 연산

- 2개의 비트를 비트 단위로 자릿수대로 연산했을 때 둘 다 1일 경우 1이 나오고, 적어도 하나가 0일 경우 0이 된다.

1 1 0 1 1 0 1 0

&

1 1 0 1 1 1 0 1

1 1 1 1 1 0 0 0

 

★ 비트 XOR(^) 연산

- 2개의 비트가 같으면 0, 다르면 1

1 1 0 1 0 1 0 1

^

0 0 1 1 0 1 0 0

1 1 1 0 0 0 0 1

 

 

★ 비트 반전(~) 

- 1은 0으로, 0은 1로 반전

1 1 0 1 0 1 0 1

~

0 0 1 0 1 0 1 0

 

define

-비트 단위 연산은 많이 사용한다. 게임에서 많이 사용함. 잘 기억하고 있어야 한다.

# : 전처리 단계에서 수행될 수 있는 명령어. 모든 컴파일 과정 중에 제일 먼저 수행된다.

 

#define : 내가 지정한 구문을 특정 숫자로 치환해준다. 나중에 함수같은 느낌으로 쓸 수 있음.

#define HUNGRY 1


...

int iStatus = HUNGRY;     //iStatus는 1이 된다. 그리고 컴파일이 진행됨

- 장점

여러가지 상태가 중첩됨.

1) 가독성 

- 배고픔 수치를 20 이런식으로 적어놓는 것보다  HUNGRY라는 것으로 직관적으로 코드의 의미를 알 수 있다.

2) 코드 유지 보수 측면에서 좋음.

- 값이 변화할 때 하나하나 사용된 그 코드를 찾아다니면서 수정할 필요가 없다. define에서 정의된 값만 변경해주면 된다.


 

int에서 한번에 표현할 수 있는 상태가 32가지 (32bit니까)

... ... ... ... ... ... ... 피곤한 상태
(0 or 1)
목마름
상태
(0 or 1)
배고픔
상태
(0 or 1)

32비트 변수로는 한번에 표현할 수 있는 상태가 32가지인 것이다.

(1을 준비해 놓았다가 딱 끼워넣는 것! - 상태의 값을 넣다 뺐다 넣다 뺐다)

 

각 자리 비트에 대응하는 숫자를 특정 하나의 상태로 본다!!!!

 

목마른 상태는 비트의 2번째 칸에 위치하므로 2로 저장         
(각 한자리에 맡는 숫자여야 하니까 2로, 2 부분만 1이고 나머지 자리는 다 0으로 하기 위해서..)

피곤한 상태는 비트의 3번째 칸에 위치하므로 4로 저장

 

#define HUNGRY 1
#define THIRSTY 2
#define TIRED 4

아무 상태도 없을 때

unsigned int iStatus = 0;

 

배가 고픈 상태

unsigned int iStatus = 0;
int iStatus |= HUNGRY;

 

배도 고픈데 목도 마른 상태 

(상태를 점점 합쳐주면 된다) - 비트 합 사용

unsigned int iStatus = 0;
iStatus |= HUNGRY;
iStauts |= THIRSTY;

THIRSTY 상태를 넣어주면서 iStatus의 다른 상태를 건드리지 않는다. 각 자리에 대해서만 비트 연산을 하기 때문에

원래 상태는 원래 상태대로 나올 것이고, THIRSTY에 대해서만 추가되는 것!!!

넣어주고 싶은 상태만 그 자리에 넣어주면 되는 것!!!

 

비트로 보자.

아무 상태 아닐 때 (32bit)

... ... 0 0 0 0 0 0 0 0

배고픈 상태 추가 (32bit)

... ... 0 0 0 0 0 0 0 0

|

... ... 0 0 0 0 0 0 0 1

... ... 0 0 0 0 0 0 0 1

 

자 여기서 이제 목마름 상태도 추가해보자 

(배고프고 목마른 상태)

... ... 0 0 0 0 0 0 0 1

|

... ... 0 0 0 0 0 0 1 0

... ... 0 0 0 0 0 0 1 1

각 자리에 대해서만 상태가 추가되었다가 제거되었다 하는 것이지 다른 상태의 영향을 주지 않는다!

 

 

상태를 넣어주었으면 물어볼 수도 있다!

지금 배고픈 상태인가?

지금 목마른 상태인가? 등등.. 

각 상태가 어떠한지 물어볼 수 있다. (상태값 체크)

 

if(iStatus & THIRSTY){

...

}

비트 곱 연산자는 비교하는 두 비트가 모두 1일 때만 1이다. 

따라서 내가 궁금한 상태를 제외한 나머지가 어떠하든간에

THIRSTY는 2^1자리 제외하고 나머지가 다 0이기 때문에 비트곱 연산을 하면 내가 궁금한 상태 부분을 제외하고는 다 0인 환경이 된다.

따라서 내가 궁금한 상태의 비트 연산 결과 값만 조건문에 반영할 수 있다.

내가 지정한 자리만 비트 1인지 물어보는 것!!!!!

 

(1)

(현재 상태 - 종합적 상태 다 존재: 목마름, 배고픔...)

... ... 0 1 1 0 1 0 1 1

&

(개발자가 궁금한 상태 - 목마름 상태)

... ... 0 0 0 0 0 0 1 0

0 0 0 0 0 0 0 0 1 0

- 현재 캐릭터는 지금 목마른 상태이네~

 

(2)

(현재 캐릭터 상태 - 종합적 상태 다 존재: 목마름, 배고픔...)

... ... 1 0 0 1 0 1 0 1

&

(개발자가 궁금한 상태 - 목마름 상태)

... ... 0 0 0 0 0 0 1 0

... ... 0 0 0 0 0 0 0 0

- 현재 캐릭터는 목마르지 않은 상태네~

 

 

if(iStatus & 화상상태){
  //화상 상태면 hp를 몇초에 몇칸씩 뺀다
  //이런거 들어갈 수 있음

}

 

그러면 해당 자리만 비트를 제거하고 싶다면 어떻게 해야 할까?

캐릭터 화상상태 1 -> 화상상태 0으로 만들고 싶다면?

혹은 

캐릭터 피곤한 상태 1 -> 많이 쉬었음 -> 피곤한 상태 0으로 피곤한 상태 제거하고 싶다면?

 

특정 자리 비트 제거

xor 연산을 사용하지 않음 → 문제가 있다 

//특정 자리 비트 제거
iStauts &= ~THIRSTY;

 

현재 캐릭터 상태

... ... 1 1 1 0 1 0 1 0

위에 형광표 친 부분의 상태(목마름1 -> 목마름0) 뺄거임

-THIRSTY 준비

... ... 0 0 0 0 0 0 1 0

 

- 비트 곱을 진행하기 전에 내가 원하는 상태(THIRSTY)를 반전시킨다.

... ... 1 1 1 1 1 1 0 1

 

- 현재 캐릭터 상태와 ~THIRSTY 비트곱을 진행한다!

(현재 캐릭터 상태)

... ... 1 1 1 0 1 0 1 0

&

... ... 1 1 1 1 1 1 0 1

... ... 1 1 1 0 1 0 0 0

 

결과 - 목마름 상태(1) - > 목마르지 않은 상태(0)으로 변경됨

캐릭터의 목마름 상태가 0이였어도 그대로 0으로 내려온다. 1로 바뀌지 않는다(XOR의 문제였음)

즉 목마른 상태가 1이였으면 0으로 바뀌고 0이였으면 그대로 0으로 유지됨!

 

//특정 자리 비트 제거
iStauts &= ~THIRSTY;

이 구문은 외우고 "특정 비트가 빠졌구나" 이렇게 바로 알아볼 수 있으면 된다!!!

 

#define HUNGRY 1
#define THIRSTY 2
#define THIRED 4
#define FIRE 8
#define COLD 16

 

코드로 상태를 나열할 때는 보통 16진수로 쓴다.

c++에서는 16진수 앞에 0x를 붙인다.

#define HUNGRY    0x001
#define THIRSTY   0x002
#define THIRED    0x004
#define FIRE      0x008

#define COLD      0x010        //16은 16진수에서 10
#define POSITION1 0x020        //32는 16의 두묶음이니까
#define POSITION2 0x040         
#define POSITION3 0x080         

#define POSITION4 0x100         
#define POSITION5 0x200         
#define POSITION6 0x400         
#define POSITION7 0x800         



//16진수는 1,2,3,4,5,6,7,8,9,a,b,c,d,e,f

 


아래 강의로 보고 개념 정리한 것입니다

https://www.youtube.com/watch?v=hjAqjGYmQnA&list=PL4SIC1d_ab-aOxWPucn31NHkQvNPHK1D1&index=12 

 

 

'C++' 카테고리의 다른 글

[C++] 함수  (0) 2023.02.22
[C++] 변수  (0) 2023.02.22
[C++] 조건문  (0) 2023.01.06
7. 논리 연산자  (0) 2023.01.06
6. 증감 연산자  (0) 2023.01.06