C 언어 정리
1. C언어의 동작은 컴파일러마다 다르게 동작함.
EX) malloc : 반환값이 void포인터 -> C컴파일러에서는 자동으로 다른 타입의 포인터로 캐스팅함.
BUT C++컴파일러에서는 자동 형변환 해주지 않음.
2. C에 없는 타입 -> boolean (정수로 참/거짓 표현)
예상 문제)
A. C는 자동으로 실수 연산을 수행한다.
B. C에는 boolean 타입이 존재한다.
C. 정수끼리 연산하면 정수 결과가 나온다.
D. 타입 캐스트는 선택 사항이다.
3. 괄호 순서 & 타입 캐스트
핵심 개념 : C 언어에서는 연산이 먼저인지, 캐스팅이 먼저인지가 괄호에 따라 달라진다.
| 표현 | 계산 순서 | 결과 |
|---|---|---|
5 / 2 |
정수 / 정수 | 2 |
(float)5 / 2 |
실수 / 정수 : 캐스트 -> 연산 | 2.5 |
(float)(5 / 2) |
정수 / 정수 → 캐스트 | 2.0 |
예상 문제) Q. 다음 중 결과가 2.5가 되는 것은?
A. 5 / 2 = 2
B. (float)(5 / 2) = 2.0
C. (float)5 / 2 = 2.5
D. 5 / (float)2 = 2.5
4. 증감 연산자
핵심 개념 : ++가 변수 앞에 오느냐, 뒤에 오느냐에 따라 값이 “언제 증가하느냐”가 달라진다.
| 표현 | 동작 순서 |
|---|---|
++a |
증가 → 대입 |
a++ |
대입 → 증가 |
예상 문제)
int a = 5; printf(“%d”, a++);
출력값 : 5, a값 : 6
int a = 5; printf(“%d”, ++a);
출력값 : 6, a값 : 6
5. C 언어의 문자열
핵심 개념 : C 언어에는 string 타입이 없다.
| 구분 | char str[] |
char *p |
|---|---|---|
| 메모리 위치 | 배열 영역 | 문자열 상수 영역 |
| 내용 변경 | 가능 | 불가능 |
| 문자열 저장 | 직접 저장 | 주소만 저장 |
6. 구조체를 포인터로 전달
핵심 개념 : 함수의 매개변수로 구조체를 포인터 형태로 전달할 수 있다. 왜 포인터로 받나?
- 구조체 전체 복사를 피할 수 있음 → 성능 좋음
- 원본 구조체를 직접 수정 가능
struct Point {
int x;
int y;
};
void print(struct Point *p) { // Point 구조체의 주소를 받는 포인터 변수
// 포인터이므로 -> 사용
printf("%d\n", p->x);
printf("%d\n", p->y);
}
int main() {
struct Point p; // 구조체 변수 선언 - struct 구조체이름 변수
p.x = 10;
p.y = 20;
print(&p); // 주소를 넘겨줌
}
p->x == (*p).x
| 상황 | 사용 연산자 |
|---|---|
| 구조체 변수 | . |
| 구조체 포인터 | -> |
void print(struct Point p) { … } : 복사 전달 => call by value
void print(struct Point *p) { … } : 주소 전달 => call by reference
7. switch문과 break
핵심 개념 : switch문에서 break가 없으면, 해당 case부터 아래 모든 case가 순서대로 실행된다.
동작 원리
- switch의 값과 일치하는 case를 찾는다
- 그 지점부터 break를 만날 때까지 실행
- break가 없으면 끝까지 실행
switch (3) {
case 1:
printf("A");
case 2:
printf("B");
case 3:
printf("C"); // 여기부터 실행
case 4:
printf("D"); // 여기까지 실행
}
// 실행 결과 : CD
8. 재귀 vs 반복문
재귀 함수는 스택을 많이 사용하므로, 가능하면 반복문이 더 안전하다.
왜 재귀가 안 좋은가?
- 재귀 함수는
- 함수 호출마다 스택(stack) 메모리 사용
- 호출이 깊어질수록 스택이 계속 쌓임
- 너무 깊어지면 -> 스택 오버플로우(Stack Overflow) 발생 가능
- 반복문은?
- 함수 호출 없음
- 스택 사용 거의 없음
- 메모리 사용이 안정적
| 구분 | 재귀 | 반복문 |
|---|---|---|
| 메모리 | 스택 많이 사용 | 적게 사용 |
| 깊은 호출 | 위험 | 안전 |
| 성능 | 상대적으로 불리 | 유리 |
예상 문제)
A. 재귀는 항상 반복문보다 빠르다
B. 재귀는 스택 메모리를 사용한다
C. 반복문은 스택 오버플로우가 발생한다
D. 재귀는 메모리를 사용하지 않는다
정답: B
9. 배열 길이 구하기 (sizeof)
배열의 전체 크기를 한 요소의 크기로 나누면 배열의 길이(방 개수)를 구할 수 있다
배열 길이 = sizeof(배열 전체) / sizeof(배열[0])
int arr[5] = {1, 2, 3, 4, 5};
int count = sizeof(arr) / sizeof(arr[0]); // 결과 : count = 5
10. 이름(식별자) 명명 규칙
사용할 수 있는 문자 : 영문자 (a ~ z, A ~ Z), 숫자 (0 ~ 9), 밑줄 (_)
- 숫자로 시작할 수 없음
- 공백 포함 불가
- C 키워드 사용 불가
- 대소문자 구분함 (sum != Sum)
C키워드 리스트
-
자료형 : int, char, float, double, void, short, long, signed, unsigned
-
제어문 : if, else switch, case, default for, while, do break, continue, return
-
구조 관련 : struct, union, enum typedef
-
저장 클래스 / 속성 : auto, register static, extern const, volatile
-
기타 : sizeof, goto
11. 함수 포인터
C 언어에서 함수를 매개변수로 전달하려면 함수 포인터를 사용해야 한다.
선언 형식 : [리턴타입] (*변수명) (매개변수 목록);
void print() {
printf("Hello\n");
}
void test(void (*p)()) {
p(); // 전달된 함수 호출
}
int main() {
test(print); // 함수 이름 == 함수 주소
}
- 함수 이름은 함수의 주소다.
test(print); // print()가 아니라 print - 매개변수와 리턴 타입이 반드시 같아야 한다.
void (*p)(); // 리턴값 없음, 매개변수 없음
함수 이름 == 함수 주소?
함수 이름: 코드 영역의 시작 주소를 가리키는 식별자 (변수 X )
=> 배열 & 함수 : 첫 번째 원소 주소/함수 시작 주소로 자동 변환
12. 문자열 리터럴과 상수 영역
문자열 리터럴은 상수 영역에 저장되며, 수정할 수 없다.
기본 선언 char *p = “apple”;
- “apple” → 상수 영역(Read-only memory)
- p → 문자열의 주소만 저장하는 포인터
- 즉, 문자열 자체를 저장한 게 아님.
[안되는 것]
A. 문자열 내용 변경 안됨
p[1] = 'x'; // 안됨
*(p + 1) = 'x'; // 안됨
이유:
"apple"은 상수 영역- 상수 영역의 값은 변경 불가
- 접근은 가능하지만 수정은 불가
B. scanf("%s", p) 안됨
scanf("%s", p);
이유:
p는 주소값만 가지고 있음- 문자열을 실제로 저장할 메모리 공간이 없음
- 입력을 쓸 공간이 없어서 에러
C. scanf("%s", &p) 안됨
scanf("%s", &p);
이유:
&p는char**타입%s는char*를 요구- 타입도 안 맞고 의미도 틀림
[되는 것]
A. 포인터 자체를 바꾸는 것은 가능
p = "adf";
이유:
p는 const가 아님- 다른 문자열의 주소를 가리키도록 변경 가능
B. 올바른 문자열 입력 방법 (**)
char p[20];
scanf("%s", p);
이유:
- 배열은 실제 문자열을 저장할 공간이 있음
| 코드 | 가능 여부 | 이유 |
|---|---|---|
p[1] = 'x' |
:x: | 상수 영역 |
*(p+1)='x' |
:x: | 상수 영역 |
p = "adf" |
:o: | 포인터 변경 |
scanf("%s", p) // p : 포인터 변수 |
:x: | 저장 공간 없음 |
char p[20]; scanf("%s", p); |
:o: | 배열 공간 있음 |
요약 : 문자열 리터럴은 상수 영역에 저장되므로 수정할 수 없고, char 포인터는 문자열을 저장할 공간이 없다.
char *p = "문자열"은 수정 불가다.
13. C 언어의 참(True)과 거짓(False)
핵심 개념 : C 언어에는 boolean 타입이 없다. 양수든 음수든 상관없이 0만 거짓.
14. 포인터 연산과 배열 접근
핵심 개념 : *(p + i)는 p가 가리키는 배열의 i번째 요소에 접근하는 표현이다.
char *p = "apple";
// p → 'a' 'p' 'p' 'l' 'e' '\0'
// 0 1 2 3 4
p[1] ≡ *(p + 1) // 같은 의미
앞 문제랑 연결됨 : 접근은 가능, 수정은 불가능
15. C 문자열 처리 함수 (string.h)
1번. size_t strlen(const char *str);
- 널 문자(‘\0’)를 제외한 문자열 길이 반환
- 문자열 길이만 계산, 변경 없음
2번. char* strcpy(char *dest, const char *src);
- src 문자열을 dest에 복사
- dest는 충분한 공간이 있어야 함
- src는 변경되지 않음 (const)
char a[10];
strcpy(a, "hi");
3번. int strcmp(const char *str1, const char *str2);
- 두 문자열 비교
- 같으면 0 반환
4번. char* strcat(char *str1, const char *str2);
- str2를 str1 뒤에 붙임
- str1은 변경 가능해야 함
- str1에 충분한 공간 필요
함정 :
char *p = "hi";
strcat(p, "!!"); // ❌ (상수 영역)
16. Call by Value vs Call by Reference
C 언어는 기본적으로 call by value이며, call by reference 효과를 내려면 포인터를 사용해야 한다.
1번. Call by Value (값에 의한 전달)
void change(int x) {
x = 10;
}
int main() {
int a = 3;
change(a);
printf("%d", a); // 3
}
- 함수에 값의 복사본이 전달됨
- 함수 안에서 값을 바꿔도 원본은 변경되지 않음
2번. Call by Reference (포인터 사용)
void change(int *p) { // 포인터 변수로 받음
*p = 10; // 값을 의미
}
int main() {
int a = 3;
change(&a); // 주소를 전달
printf("%d", a); // 10
}
- 변수의 주소를 전달
- 포인터로 접근하여 원본 값 직접 변경
- C에는 문법적인 call by reference는 없고, 포인터로 “효과만” 구현한다
17. 구조체 멤버의 종류
구조체의 멤버로는 기본 타입뿐만 아니라 포인터, 배열, 다른 구조체, 함수 포인터까지 모두 사용할 수 있다.
[구조체 멤버로 가능한 것들]
1번. 기본 자료형
struct A {
int x;
char c;
};
2번. 배열
struct B {
int arr[10];
};
3번. 포인터
struct C {
int *p;
};
4번. 다른 구조체 (또는 구조체 포인터)
struct Point {
int x;
int y;
};
struct Rect {
struct Point p; // 구조체 멤버
};
struct Node {
struct Node *next; // 자기 자신을 가리키는 구조체 포인터
};
- 자기 자신 타입의 포인터는 가능 / 자기 자신 타입 자체를 멤버로 두는 것은 불가능
5번. 함수 포인터
struct D {
void (*func)();
};
구조체 안에는 거의 다 넣을 수 있다. 단, 자기 자신은 포인터로만.
18. 3의 배수 판별
어떤 수가 3의 배수인지 확인하려면 3으로 나눈 나머지가 0인지 확인하면 된다.
- (i % 3) == 0
- !(i % 3)
19. 포인터의 의미 (* 연산자)
포인터 변수는 주소값을 저장하는 변수이다.
1번. 포인터 선언문에서의 *
int *p;
- p → int형 변수의 주소를 저장하는 포인터
- 선언문에서의 *는 “p는 포인터다”라는 의미
2번. 실행문(사용할 때)에서의 *
*p = 10;
- *p → p가 가리키는 주소의 값 (그 주소에 담긴 값)
- 실행문에서의 *는 간접 참조(역참조, dereference)
int a = 5;
int *p = &a;
*p = 10;
// 결과 : a = 10
20. 삼항 연산자
조건에 따라 두 값 중 하나를 선택하는 연산자
- 기본 형식 : (조건식) ? 참일 때 값 : 거짓일 때 값
max = (a > b) ? a : b;
printf("%s", (x > 0) ? "양수" : "0 또는 음수");
댓글남기기