공부/C++

const, #define, 함수 매크로, enum, 비트 연산, 시프트 연산, 비트 연산자 활용

월러비 2025. 6. 17. 18:10

짧은 설명

  • 구조체 : 사용자가 정의해서 사용하는 데이터형
    • 열거형, 구조체가 같은 정의이다.
    • 물론, 사용하는 방법은 다르다.
  • 멤버 변수, 함수 : 구조체, 함수에 선언된 변수 및 함수다.
  • 구조체 크기 : 선언된 멤버 변수의 메모리 크기 합이다.
    • 메모리 패딩 : 메모리는 4바이트 단위로 잘리기 때문에 4바이트에 맞춰지지 않으면 빈 공간을 추가한 후 다음 변수의 메모리를 이어붙인다.
  • 멤버 함수 : 구조체 안쪽에 선언 및 저으이해야한다.
    • 멤버 변수를 사용할 수 있다.

C 기능들

const 상수

  • 변수 : 사용자가 메모리에 접근하는 수단
    • 읽고 쓸 수 있다.
  • 상수 : 변수와 동일하지만, 읽기만 가능하고 쓸 수가 없다.
    • 값 할당 타이밍 : 상수 선언때 한번만 값 할당이 가능하다.
    • 쓰려고 하면 ‘컴파일 에러’가 발생한다.
    • 선언 시 반드시 초기화해야 함
    • 컴파일러가 타입 검사를 수행하여 안전함
const double PI = 3.14159265358979;  // PI는 상수로 선언

double getArea(double r) {
    return PI * r * r;
}

int main() {
    printf("%f", getArea(10));  // 반지름이 10인 원의 넓이를 출력
}
  • 상수로 선언하는 데이터 : 바뀌지 않아야하는 값들
    • ex) PI : 3.14

#define 매크로

#define PI 3.14159265358979

double getArea(double r) {
    return PI * r * r;
}

int main() {
    printf("%f", getArea(10));  // 반지름이 10인 원의 넓이를 출력
}
  • #: 전처리기
    • 전처리기가 코드 컴파일 전에 치환
  • 매크로를 사용하면 컴파일 전에 미리 정의한 변수에 값을 넣는다.
    • 변수가 사용된 모든 곳에 값이 복사되는 것이다.
  • 매크로는 단순 텍스트 치환
  • 세미콜론(;)을 붙이지 않음
  • 대문자로 작성하는 것이 관례

매크로 활용 - 코드 간소화

#define MAIN int main
#define ULL unsigned long long 
#define NUM 1234
#define PLUS +
#define PNT printf

MAIN() {  // int main()
    ULL m = NUM PLUS 1;  // unsigned long long m = 1234 + 1;
    PNT("%d", NUM);  // printf("%d", 1234);
}
  • 예시로 극단적으로 사용했다.
  • 코드를 간소화할 수 있다는 예시

매크로 활용 - 함수 매크로

  • 패턴을 CV(복사) 할 수 있다.
#define SQUARE(X) X * X

int main() {
    int a = 5;

    printf("%d", SQUARE(a));  // 25 출력
}
  • 주의사항 : 연산 우선순위를 주의해야한다.
#define SQUARE(X) X * X
//#define SQUARE(X) ((X) * (X)) //올바른 방법

int main() {
    int a = 5;
		
		//a + 1 * a + 1 로 계산된다.
    printf("%d", SQUARE(a + 1));  // 11 출력 (예상과 다름!)
    
    //100 / 5 * 5
    printf("%d", 100 / SQUARE(a));  // 100 출력 (예상: 4)
}

올바른 매크로 작성 규칙

  1. 매크로 전체를 괄호로 감싸기
  2. 각 인자도 괄호로 감싸기
  3. 복잡한 표현식은 피하기

enum 열거형

enum {
    MAINMENU_STATE,
    PLAYING_STATE,
    PAUSED_STATE,
    GAMEOVER_STATE
};

int main() {
    printf("%d\\n", MAINMENU_STATE);  // 0
    printf("%d\\n", PLAYING_STATE);   // 1
    printf("%d\\n", PAUSED_STATE);    // 2
    printf("%d\\n", GAMEOVER_STATE);  // 3
}
  • enum 클래스와 enum (C++)
    • enum 클래스가 더 제약이 강하다.
  • enum은 이름도 제외할 수 있다.
    • enum의 요소만 사용 가능하다.
  • 값을 명시하지 않으면 0부터 자동 할당된다.
  • 특정 값을 지정할 수 있다.
    • 지정 값부터 다시 순서대로 값이 지정된다.
  • 인덱스를 enum의 요소로 형변환이 가능하고, 반대로 enum의 요소를 index 정수로 형변환이 가능하다.

비트 연산

논리 연산

int a = 106, b = 83;

printf("a & b = %d\\n", a & b);  // 비트 AND
printf("a | b = %d\\n", a | b);  // 비트 OR
printf("a ^ b = %d\\n", a ^ b);  // 비트 XOR
printf("~a    = %d\\n", ~a);     // 비트 NOT

a = 106 = 01101010
b = 83  = 01010011

a & b = 01000010 = 66  (둘 다 1인 비트만 1)
a | b = 01111011 = 123 (하나라도 1인 비트는 1)
a ^ b = 00111001 = 57  (서로 다른 비트만 1)
~a    = 10010101 = -107 (모든 비트 반전)

시프트 연산

printf("%d\\n", 1 << 4);      // 00000001 -> 00010000 = 16
printf("%d\\n", (-3) << 2);   // 11111101 -> 11110100 = -12
printf("%d\\n", 31 >> 2);     // 00011111 -> 00000111 = 7
printf("%d\\n", (-24) >> 3);  // 11101000 -> 11111101 = -3
  • << : 왼쪽 시프트 (2를 곱하는 효과)
  • >> : 오른쪽 시프트 (2로 나누는 효과)
  • 빠른 곱셈/나눗셈에 활용
  • 새로 들어오는 비트(0 또는 1)는 밀리는 자리의 비트값으로 정해진다.
    • 밀리는 자리가 0이라면 0이 생기고, 1이라면 1이 생긴다.
    • 다만, 왼쪽으로 시프트는 반드시 ‘0’이다.

비트 연산자 활용

  • 플래그 설정/해제
  • 특정 비트 검사
  • 효율적인 곱셈/나눗셈 (2의 거듭제곱)
  • 스킬 시스템
    • 입력받은 스킬 번호를 받고 해당 번호만큼 시프트 연산을 사용 : 해당 위치의 비트만 1로 활성화된다.
    • 좌측 마지막 비트만 0인지 1인지 주의해야한다.
      • 우측 시프트는 해당 자리의 비트에 따라 바뀌기 때문이다.
      • 0이면 0이 생기고, 1이면 1이 생긴다.