짧은 설명
- 구조체 : 사용자가 정의해서 사용하는 데이터형
- 열거형, 구조체가 같은 정의이다.
- 물론, 사용하는 방법은 다르다.
- 멤버 변수, 함수 : 구조체, 함수에 선언된 변수 및 함수다.
- 구조체 크기 : 선언된 멤버 변수의 메모리 크기 합이다.
- 메모리 패딩 : 메모리는 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)
}
올바른 매크로 작성 규칙
- 매크로 전체를 괄호로 감싸기
- 각 인자도 괄호로 감싸기
- 복잡한 표현식은 피하기
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이 생긴다.