쉬프트 연산
unsigned char byte = 5; // 0 0 0 0 0 1 0 1 -> 2^2 + 2^0
byte << 1; // 왼쪽으로 N(1)칸 이동 0 0 0 0 1 0 1 0 -> 2^2*2배 + 2^0*2배 = 2^N승 만큼 2배증가해서 10이됨
byte << 2; // 왼쪽으로 N(2)칸 이동 0 0 0 1 0 1 0 0 -> 2^2*4배 + 2^0*4배 = 4배증가 20
byte >> 1; // 오른쪽으로 N(1)칸 이동 0 0 0 0 0 0 1 0 -> 2^2/2 + 2^0/2 = 2^N승 만큼 줄어들어서 2가 됨
비트곱(&), 비트합(|), XOR(^), 반전(~)
---> 이렇게 비트연산자를 공부하면 금방 까먹음 와닿지도 않고 비트 연산자가 언제 쓰이는지도 모르기 때문에
그러면 비트 연산자는 언제 쓰이는가??
특정상태를 표현하는 경우)
게임 캐릭터의 상태값을 표현한다고 예시를 들어보자
// 캐릭터의 상태
#define HUNGRY 1
#define COLD 2
#define TIRED 4
// 캐릭터의 기본 상태
unsigned char isStatus = 0;
[0] [0] [0] [0] [0] [0] [0] [0]
TIRED COLD HUNGRY
// 캐릭터의 상태값 변경
isStatus = isStatus | HUNGRY; // 배고픈 상태로 변경
비트연산자의 비트합(|)을 이용해 캐릭터를 배고픈 상태로 변경한다.
기존의 캐릭터의 상태 : [0] [0] [0] [0] [0] [0] [0] [0]
배고픈 상태 값 : [0] [0] [0] [0] [0] [0] [0] [1]
변경된 캐릭터 상태 : [0] [0] [0] [0] [0] [0] [0] [1]
// 캐릭터가 배고픈 상태인지 체크
if(isStatus & HUNGRY)
{
"현재 캐릭터는 배고픈 상태입니다..."
"먹을것을 주지 않으면 10초 뒤에 HP가 10 감소됩니다."
}
기존의 캐릭터의 상태 : [0] [0] [0] [0] [0] [0] [0] [1]
배고픈 상태 값 : [0] [0] [0] [0] [0] [0] [0] [1]
--> 비트 곱(&) 연산을 통해 지정한 자리(배고픔)의 상태값이 배고픈지(1) 물어본다.
즉, 기존의 캐릭터가 배고픈 상태(1)일 경우에만 True를 반환한다.
Q1) 캐릭터의 배고픈 상태를 배고프지 않은 상태로 변경하고 싶을 때는 어떻게 할까?
// 1.비트 연산 XOR을 이용(같으면 0 다르면 1)
기존의 캐릭터의 상태 : [0] [0] [0] [0] [0] [0] [0] [1]
배고픔 여부 상태 값 : [0] [0] [0] [0] [0] [0] [0] [1]
변경된 상태 값 : [0] [0] [0] [0] [0] [0] [0] [0]
--> 기존에 캐릭터가 배고픈 상태였다면 배고프지 않은 상태로 변경이 되지만,
애초에 배고픈 상태가 아니였을 경우 XOR을 사용하게 되면 배고픈 상태로 바뀌므로 문제가 발생한다.
기존의 캐릭터의 상태 : [0] [0] [0] [0] [0] [0] [0] [0]
배고픔 여부 상태 값 : [0] [0] [0] [0] [0] [0] [0] [1]
변경된 상태 값 : [0] [0] [0] [0] [0] [0] [0] [1]
// 2.비트 곱으로 배고픈 상태인 경우를 체크한 뒤 XOR 연산을 사용해 배고프지 않은 상태로 변경시킨다.
// 캐릭터가 배고픈 상태인지 체크
if(isStatus & HUNGRY)
{
"현재 캐릭터는 배고픈 상태입니다..."
isStatus = isStatus XOR 1; // 배고프지 않은 상태로 변경
}
기존의 캐릭터의 상태 : [0] [0] [0] [0] [0] [0] [0] [1]
배고픈 상태 값 : [0] [0] [0] [0] [0] [0] [0] [1]
--> 비트 곱(&) 연산을 통해 지정한 자리(배고픔)의 상태값이 배고픈지(1) 물어본다.
즉, 기존의 캐릭터가 배고픈 상태(1)일 경우에만 True를 반환한다.
// 3. 반전비트(~)를 이용한 다음 비트곱(&) 연산을 수행한다.
isStatus &= ~HUNGRY
기존의 캐릭터의 상태 : [0] [0] [0] [0] [0] [0] [1] [1]
배고픈 상태 값 반전(~) : [1] [1] [1] [1] [1] [1] [1] [0]
비트곱 연산 수행 : [0] [0] [0] [0] [0] [0] [1] [0]
배고픔의 상태를 배고프지 않은 상태인 0으로 변경하고 싶으면 반전비트(~) 수행한 다음
비트곱(&) 연산을 수행하면 된다.
즉, 1인 비트를 0으로 바꾸고 싶은 경우 주로 사용됨
전처리기
전처리기 : 컴파일 하기전에 상단부분에 전처리기인 #define 등으로 선언된 구문을 보고 먼저 수행을 하는 것
Q1) 그러면 여기서 왜 #define을 사용하는 이유가 무엇인가?
# define POISON_STATE 10 // 독상태
int AcharacterStatus = 10; // define을 사용하지 않은 경우
int AcharacterStatus = POISON_STATE; // define을 사용한 경우
int BcharacterStatus = 10; // define을 사용하지 않은 경우
int BcharacterStatus = POISON_STATE; // define을 사용한 경우
int CcharacterStatus = 10; // define을 사용하지 않은 경우
int CcharacterStatus = POISON_STATE; // define을 사용한 경우
int DcharacterStatus = 10; // define을 사용하지 않은 경우
int DcharacterStatus = POISON_STATE; // define을 사용한 경우
--> define을 사용하는 경우는 'POISON_STATE'이 무엇인지 한눈에 파악하기 쉽다.
가독성인 측면에서 유용하다.
#define POISON_STATE 11 // 독상태
#define FATAL_POISON_STATE 12 // 치명적인 독상태
--> #define 부분만 수정을 해주면 됨
int AcharacterStatus = POISON_STATE;
int AcharacterStatus = FATAL_POISON_STATE;
int BcharacterStatus = POISON_STATE;
int BcharacterStatus = FATAL_POISON_STATE;
int CcharacterStatus = POISON_STATE;
int CcharacterStatus = FATAL_POISON_STATE;
int DcharacterStatus = POISON_STATE;
int DcharacterStatus = FATAL_POISON_STATE;
int EcharacterStatus = POISON_STATE;
int EcharacterStatus = FATAL_POISON_STATE;
--> 독 상태가 여러가지 상태로 분류되는 경우의 요구사항이 추가되어서 기존의 독상태에 대한 코드를 수정해야된다고 해보자.
#define을 사용하면 모든 코드를 수정할 필요없이 define에 설정된 상태에 대한 코드값만 수정을 하면 모든 캐릭터에 일괄 적용이 된다.
즉, 수정이 되어야 하는 모든 코드에 대해서 일일히 수정하지 않고도 한번에 처리가 가능하므로 코드 유지보수적인 측면에서 효율적이다.