0. 목표
- C 문법 핵심 개념 전체 흐름 이해
- 시험에 자주 나오는 유형 10개 이상 익히기
- 코드 해석 문제 정확도 70% 이상 만들기
- 출력 결과 예측 문제 실수 줄이기
- 기출 또는 모의 문제 최소 2회독
1. C언어 기본 문법 구조
1-1. 변수와 자료형
1) 변수란?
- 값을 저장하기 위한 메모리 공간의 이름
- 데이터를 저장하고 필요할 때 꺼내서 사용
2) 기본 자료형
| 자료형 | 설명 | 크기 |
|---|
int | 정수형 | 4byte |
float | 실수형 (단정도) | 4byte |
double | 실수형 (배정도) | 8byte |
char | 문자형 | 1byte |
1
2
3
4
| int num = 10;
float f = 3.14;
double d = 3.141592;
char c = 'A';
|
자료형의 크기는 컴퓨터 메모리에서 차지하는 공간이 각각 다르다. 크기가 클수록 더 정밀한 값 표현이 가능하다.
3) 형 변환
- 자동 형 변환
- 작은 타입 → 큰 타입으로 자동 변환
1
2
3
| int a = 3;
double b = 2.5;
double result = a + b; // 결과: 5.5 (int → double 변환)
|
- 강제 형 변환
- 개발자가 직접 타입을 변환
1
2
| double a = 3.7;
int b = (int)a; // 강제 형 변환 (b = 3)
|
1-2. 연산자
1) 산술 연산자
1
2
3
4
5
6
7
| int a = 10, b = 3;
printf("%d\n", a + b); // 13
printf("%d\n", a - b); // 7
printf("%d\n", a * b); // 30
printf("%d\n", a / b); // 3 (정수 나눗셈)
printf("%d\n", a % b); // 1
|
2) 대입 연산자
| 연산자 | 의미 |
|---|
+= | 더해서 대입 |
-= | 빼서 대입 |
*= | 곱해서 대입 |
/= | 나눠서 대입 |
1
2
3
4
5
6
| int a = 10;
printf("%d\n", a += 5); // a = a + 5 → 15
printf("%d\n", a -= 5); // a = a - 5 → 10
printf("%d\n", a *= 5); // a = a * 5 → 50
printf("%d\n", a /= 5); // a = a / 5 → 10
|
3) 비교 연산자
- 두 값을 비교하여 참(1) / 거짓(0) 반환
| 연산자 | 의미 |
|---|
<, > | 크기 비교 |
<=, >= | 이상/이하 |
== | 같다 |
!= | 다르다 |
1
2
3
4
5
| int a = 5, b = 10;
printf("%d\n", a < b); // 1 (true)
printf("%d\n", a == b); // 0 (false)
printf("%d\n", a != b); // 1 (true)
|
4) 증감 연산자
| 형태 | 의미 |
|---|
++i | 선증가 (먼저 증가, 후 사용) |
i++ | 후증가 (먼저 사용, 후 증가) |
--i | 선감소 (먼저 감소, 후 사용) |
i-- | 후감소 (먼저 사용, 후 감소) |
1
2
3
4
| int i = 5;
printf("%d\n", ++i); // 6 (증가 후 출력)
printf("%d\n", i++); // 6 (출력 후 증가 → i는 7됨)
|
5) 논리 연산자
| 연산자 | 의미 |
|---|
&& | AND (둘 다 참) |
|| | OR (하나라도 참) |
! | NOT (반대) |
1
2
3
4
5
| int a = 1, b = 0;
printf("%d\n", a && b); // 0
printf("%d\n", a || b); // 1
printf("%d\n", !a); // 0
|
6) 비트 연산자
| 연산자 | 의미 |
|---|
& | AND |
\| | OR |
^ | XOR |
~ | NOT |
<< | 왼쪽 시프트 |
>> | 오른쪽 시프트 |
1
2
3
4
5
6
7
8
9
| int a = 5; // 0101
int b = 3; // 0011
printf("%d\n", a & b); // 1 (0001)
printf("%d\n", a | b); // 7 (0111)
printf("%d\n", a ^ b); // 6 (0110)
printf("%d\n", ~a); // -6 (1111...1010)
printf("%d\n", a << 1); // 10 (1010)
printf("%d\n", a >> 1); // 2 (0010)
|
1-3. 삼항 연산자 & 연산자 우선순위
1) 삼항 연산자 (조건 연산자)
1
2
3
4
| // 기본 구조 - 조건 ? 참값 : 거짓값;
int a = 10;
int result = (a > 5) ? 1 : 0; // 조건이 참 → 1
|
삼항 연산자는 if-else문을 한 줄로 표현이 가능하다. 또한 반드시 결과값을 반환한다.
2) 연산자 우선순위
- 연산자는 실행되는 순서(우선순위)가 있음
- 괄호가 없으면 우선순위에 따라 계산됨
| 우선순위 | 연산자 |
|---|
| 1 | (), [], -> |
| 2 | ++, --, !, (type) |
| 3 | *, /, % |
| 4 | +, - |
| 5 | <, <=, >, >= |
| 6 | ==, != |
| 7 | && |
| 8 | || |
| 9 | ? : |
| 10 | =, +=, -= |
산술 → 비교 → 논리 → 삼항 → 대입 순서
예시 1)
1
2
3
| int a = 5;
int result = a + 3 > 7 ? 1 : 0; // 결과: 1
|
예시 2)
1
2
3
| int a = 5;
int result = a + (3 > 7 ? 1 : 0); // 결과: 5
|
1-4. 변수 선언과 초기화
1) 지역변수 vs 전역변수
지역변수란?
- 함수 내부에서 선언
- 함수가 끝나면 소멸
- 초기값 없음 → 쓰레기값(garbage value)
1
2
3
4
| void func() {
int a;
printf("%d\n", a); // 쓰레기값 출력
}
|
전역변수란?
- 함수 밖에서 선언
- 프로그램 종료까지 유지
- 자동으로 0으로 초기화
1
2
3
4
5
| int a; // 전역변수 → 자동 초기화 (0)
int main() {
printf("%d\n", a); // 0
}
|
| 구분 | 선언 위치 | 초기값 | 생명 주기 |
|---|
| 지역변수 | 함수 내부 | 쓰레기값 | 함수 종료 시 소멸 |
| 전역변수 | 함수 외부 | 0 | 프로그램 종료까지 유지 |
2) static 변수
1
2
3
4
5
6
7
8
9
10
11
| void func() {
static int a = 0;
a++;
printf("%d\n", a);
}
int main() {
func(); // 1
func(); // 2
func(); // 3
}
|
2. 제어문
2-1. 조건문
1) if / else if / else
1
2
3
4
5
6
7
8
9
10
11
| int a = 10;
if (a > 10) {
printf("크다");
} else if (a == 10) {
printf("같다");
} else {
printf("작다");
}
// 결과: 같다
|
2) switch ~ case
- 특정 값에 따라 분기 처리
break를 만나면 switch 탈출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| int num = 2;
switch(num) {
case 1:
printf("One");
break;
case 2:
printf("Two");
break;
default:
printf("Other");
}
// 결과: Two
|
3) break가 없는 경우
break가 없으면 아래 case를 모두 실행
1
2
3
4
5
6
7
8
9
10
11
12
| int num = 2;
switch(num) {
case 1:
printf("A");
case 2:
printf("B");
case 3:
printf("C");
}
// 결과: BC
|
2-2. 반복문
1) for 문
기본 구조
1
2
3
| for (초기식; 조건식; 증감식) {
실행문;
}
|
예시)
1
2
3
4
5
| for (int i = 1; i <= 5; i++) {
printf("%d ", i);
}
// 출력: 1 2 3 4 5
|
2) while 문
1
2
3
4
5
6
7
8
| int i = 1;
while (i <= 5) {
printf("%d ", i);
i++;
}
// 출력: 1 2 3 4 5
|
3) do ~ while 문
1
2
3
4
5
6
7
| int i = 6;
do {
printf("%d ", i);
} while (i <= 5);
// 출력: 6 (조건이 거짓이어도 1번 실행됨)
|
4) 중첩 반목문
1
2
3
4
5
6
7
| for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 2; j++) {
printf("(%d,%d) ", i, j);
}
}
// 출력: (1,1) (1,2) (2,1) (2,2) (3,1) (3,2)
|
2-3. 분기문
1) break
- 반복문을 즉시 종료
- 가장 가까운 반복문 1개만 종료
1
2
3
4
5
6
7
8
| for (int i = 1; i <= 5; i++) {
if (i == 3) {
break;
}
printf("%d ", i);
}
// 출력: 1 2
|
1
2
3
4
5
6
7
8
| for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (j == 2) break;
printf("(%d,%d) ", i, j);
}
}
/// 출력: (1,1) (2,1) (3,1)
|
2) continue
1
2
3
4
5
6
7
8
| for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue;
}
printf("%d ", i);
}
// 출력: 1 2 4 5
|
1
2
3
4
5
6
7
8
| for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (j == 2) continue;
printf("(%d,%d) ", i, j);
}
}
// 출력: (1,1) (1,3) (2,1) (2,3) (3,1) (3,3)
|
3) break vs continue 차이
| 구분 | 동작 |
|---|
break | 반복문 완전히 종료 |
continue | 현재 반복만 건너뜀 |
3. 배열과 문자열
3-1. 1차원 배열
1) 배열이란?
- 같은 자료형의 데이터를 연속된 메모리 공간에 저장
- 인덱스(index)로 접근
2) 배열 선언 및 초기화
1
2
| int arr[5]; // 선언
int arr2[5] = {1,2,3,4,5}; // 초기화
|
3) 인덱스 접근
1
2
3
4
| int arr[5] = {1,2,3,4,5};
printf("%d\n", arr[0]); // 1
printf("%d\n", arr[4]); // 5
|
3-2. 2차원 배열
1) 2차원 배열이란?
- 배열 안에 배열이 있는 구조 (행과 열)
- 표(테이블) 형태로 데이터 저장
2) 2차원 배열 선언 및 초기화
1
2
3
4
5
| int arr[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
|
3) 인덱스 접근
1
2
| printf("%d\n", arr[0][0]); // 1
printf("%d\n", arr[1][2]); // 6
|
4) 이중 for문 순회
1
2
3
4
5
6
7
8
9
10
11
| for (int i = 0; i < 3; i++) { // 행
for (int j = 0; j < 3; j++) { // 열
printf("%d ", arr[i][j]);
}
printf("\n");
}
// 출력:
// 1 2 3
// 4 5 6
// 7 8 9
|
3-3. 문자 배열과 문자 포인터 ⭐
1) 문자 배열
- 문자열을 배열 형태로 저장
- 마지막에 자동으로
/0 (널 문자) 추가
1
2
3
| char str[] = "hello";
// 실제 저장: ["h", "e", "l", "l", "o", \0]
|
2) 문자 포인터
1
2
3
4
5
| 메모리 구조
K O R E A \0
↑
p
|
3) 배열 vs 포인터 차이
4) 문자열 관련 함수
| 함수 | 기능 |
|---|
strlen() | 문자열 길이 |
strcpy() | 문자열 복사 |
strcmp() | 문자열 비교 |
strcat() | 문자열 연결 |
1
2
3
4
| char a[10] = "hi";
char b[10] = "hello";
strcpy(a, b); // a = "hello"
|
4. 함수
4-1. 함수 정의와 호출
1) 함수란?
- 특정 작업을 수행하는 코드 묶음
- 재사용 가능 → 코드 중복 감소
2) 함수 구조
1
2
3
4
| 반환형 함수명(매개변수) {
실행문;
return 반환값;
}
|
1
2
3
| int add(int a, int b) {
return a + b;
}
|
3) 함수 호출
1
2
3
4
5
6
7
8
9
10
11
| #include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(3, 5);
printf("%d\n", result); // 8
return 0;
}
|
4) 함수 원형 선언
- 함수 사용 전에 미리 선언
- 컴파일러에게 “이 함수 나중에 정의될거야”라고 알려주는 역할
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| #include <stdio.h>
// 함수 원형 선언
int add(int a, int b);
int main() {
printf("%d\n", add(2, 3));
return 0;
}
// 함수 정의
int add(int a, int b) {
return a + b;
}
|
왜 필요할까?
- 함수 정의보다 먼저 호출할 때 필수
- 없으면 컴파일 오류 발생 가능
4-2. 재귀 함수 ⭐
1) 재귀 함수란?
- 함수가 자기 자신을 다시 호출하는 구조
- 반복문을 함수 호출로 표현한 것
2) 재귀 함수 필수 요소
- 종료 조건
- 자기 자신 호출
→ 둘 중 하나라도 없으면 무한 호출 발생
3) 팩토리얼
1
2
3
4
| int f(int n) {
if (n <= 1) return 1; // 종료 조건
else return n * f(n - 1); // 재귀 호출
}
|
1
2
3
4
5
6
7
8
9
| f(7)
= 7 * f(6)
= 7 * 6 * f(5)
= 7 * 6 * 5 * f(4)
= 7 * 6 * 5 * 4 * f(3)
= 7 * 6 * 5 * 4 * 3 * f(2)
= 7 * 6 * 5 * 4 * 3 * 2 * f(1)
= 7 * 6 * 5 * 4 * 3 * 2 * 1
= 5040
|
4) 피보나치 수열
1
2
3
4
| int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
|
1
2
3
4
5
| fib(5)
= fib(4) + fib(3)
= (fib(3)+fib(2)) + (fib(2)+fib(1))
...
= 5
|
4-3. 전역변수 공유 함정 ⭐
1) 개념
- 전역 변수는 모든 함수에서 공유되는 하나의 메모리
- 주소를 받환하면 같은 주소를 계속 가리킴
2) 기출 예제 - 입력값이 홍길동/김철수/박영희 순일 때 출력값은?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| #include <stdio.h>
char n[30]; // 전역 변수 (하나의 메모리 공간)
char* test() {
gets(n);
return n; // 항상 같은 주소 반환
}
int main() {
char* test1 = test();
char* test2 = test();
char* test3 = test();
printf("%s\n", test1);
printf("%s\n", test2);
printf("%s\n", test3);
}
|
입력 과정:
- 첫 입력 → “홍길동” → n에 저장
- 두 번째 입력 → “김철수” → 덮어쓰기
- 세 번째 입력 → “박영희” → 덮어쓰기
최종 출력:
5. 포인터 ⭐
5-1. 포인터 기본
1) 포인터란?
| 연산자 | 의미 |
|---|
& | 주소 연산자 (주소 얻기) |
* | 역참조 연산자 (값 접근) |
2) 포인터 선언 및 사용
1
2
| int a = 10;
int *p = &a; // p는 a의 주소 저장
|
3) 값 접근
1
2
| printf("%d\n", p); // 주소 출력
printf("%d\n", *p); // 10 (실제 값)
|
4) 포인터로 값 변경
1
2
3
4
5
6
| int a = 10;
int *p = &a;
*p = 20;
printf("%d\n", a); // 20
|
→ 포인터를 통해 원본 값 변경 가능
5) 기출 예제 - *p+4 vs *(p+4)
1
2
3
4
5
| int arr[5] = {1,2,3,4,5};
int *p = arr;
printf("%d\n", *p + 4); // 5
printf("%d\n", *(p + 4)); // 5
|
| 표현 | 의미 |
|---|
*p + 4 | 값에 4 더함 |
*(p + 4) | 주소 이동 후 값 |
5-2. 포인터와 배열
1) 배열과 포인터 관계
1
2
| int arr[3] = {10, 20, 30};
int *p = arr; // arr == &arr[0]
|
arr[i] == *(arr + i)p[i] == *(p + i)
예제)
1
2
3
4
| int arr[3] = {10, 20, 30};
printf("%d\n", arr[1]); // 20
printf("%d\n", *(arr + 1)); // 20
|
2) 포인터로 배열 순회
1
2
3
4
5
6
7
8
| int arr[3] = {10, 20, 30};
int *p = arr;
for (int i = 0; i < 3; i++) {
printf("%d ", *(p + i));
}
// 결과: 10 20 30
|
3) 기출 예시 - 문자열 뒤집기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #include <stdio.h>
#include <string.h>
int main() {
char str[] = "ABCDE";
char *start = str;
char *end = str + strlen(str) - 1;
while (start < end) {
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
printf("%s\n", str); // EDCBA
return 0;
}
|
실행 흐름:
1
2
3
| A B C D E
↑ ↑
start end
|
start++ → 앞에서 이동end-- → 뒤에서 이동- 중앙에서 만날 때 종료
5-3. 포인터와 함수
1) Call by Value (값 전달)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| #include <stdio.h>
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
swap(x, y);
printf("%d %d\n", x, y); // 10 20 (변경 안됨)
return 0;
}
|
왜 안바뀔까?
a, b는 복사된 값- 원본
x, y와는 다른 변수
2) Call by Reference (주소 전달)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| #include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
swap(&x, &y);
printf("%d %d\n", x, y); // 20 10
return 0;
}
|
동작 원리:
&x는 x의 주소 전달*a는 해당 주소의 실제 값 접근
실행 흐름:
1
2
3
4
5
6
7
| x = 10, y = 20
swap(&x, &y)
→ *a = 10, *b = 20
→ 값 교환
→ x = 20, y = 10
|
3) Call by Value vs Call by Reference 차이
| 구분 | 특징 |
|---|
| Call by Value | 값 복사 → 원본 유지 |
| Call by Reference | 주소 전달 → 원본 변경 |
6. 구조체 (struct) ⭐
6-1. 구조체 정의와 사용
1) 구조체란?
- 서로 다른 자료형을 하나로 묶은 사용자 정의 자료형
1
2
3
4
5
6
7
| // 문자열 + 정수 + 실수를 하나로 묶음
struct Student {
char name[20];
int age;
float grade;
};
|
2) 구조체 변수 선언 및 초기화
1
| struct Student s1 = {"홍길동", 20, 4.5};
|
3) 구조체 포인터
1
2
| struct Student s1 = {"홍길동", 20, 4.5};
struct Student *p = &s1;
|
4) 멤버 접근 (일반 변수 vs 포인터)
1
2
3
| printf("%s\n", s1.name); // 홍길동
printf("%d\n", s1.age); // 20
printf("%.1f\n", s1.grade); // 4.5
|
1
2
3
| printf("%s\n", p->name);
printf("%d\n", p->age);
printf("%.1f\n", p->grade);
|
6-2. 동적 할당
1) 동적 할당이란?
- 실행 중에 메모리를 할당하는 것
malloc() 사용
2) 구조체 동적 할당
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| #include <stdio.h>
#include <stdlib.h>
struct Data {
int num;
int *numPtr;
};
int main() {
struct Data *d2 = (struct Data*)malloc(sizeof(struct Data));
int num = 10;
d2->num = 20;
d2->numPtr = #
printf("%d\n", d2->num); // 20
printf("%d\n", *(d2->numPtr)); // 10
free(d2); // 메모리 해제
return 0;
}
|
malloc() → 메모리 할당free() → 메모리 해제 (반드시 해야함. 안하면 메모리 누수 발생)
7. 스택(Stack) 구현 코드 추적 ⭐
7-1. 스택 동작 원리
1) 스택(Stack)이란?
- LIFO 구조 (Last In First Out)
- 나중에 들어간 데이터가 먼저 나옴
2) 기본 연산
| 연산 | 의미 |
|---|
push (into) | 데이터 삽입 |
pop (take) | 데이터 꺼내기 |
isEmpty | 비어있는지 확인 |
isFull | 가득 찼는지 확인 |
3) 예시
1
2
3
4
5
6
7
8
9
10
| into(5); into(2); // [5, 2]
take() // 2 출력 → [5]
into(4); into(1); // [5, 4, 1]
printf("%d", take()); // 1 출력 → [5, 4]
into(3); // [5, 4, 3]
printf("%d", take()); // 3 출력 → [5, 4]
printf("%d", take()); // 4 출력 → [5]
into(6); // [5, 6]
printf("%d", take()); // 6 출력 → [5]
printf("%d", take()); // 5 출력 → []
|
출력 순서: 2 → 1 → 3 → 4 → 6 → 5
8. 알고리즘 (정렬 / 완전수 / 진수 변환) ⭐
8-1. 선택 정렬 빈칸 채우기
1) 개념
- 배열에서 가장 작은(또는 큰) 값 선택 → 앞쪽으로 교환
- 반복하면서 정렬 완성
2) 코드 예제
1
2
3
4
5
6
7
8
9
| for (int i = 0; i < n-1; i++) {
for (int j = i+1; j < n; j++) {
if (E[i] > E[j]) { // 오름차순
int temp = E[i];
E[i] = E[j];
E[j] = temp;
}
}
}
|
| 조건 | 의미 |
|---|
E[i] > E[j] | 오름차순 |
E[i] < E[j] | 내림차순 |
8-2. 완전수 탐색
1) 개념
- 자기 자신을 제외한 약수의 합 = 자기 자신
- 예
- 6 = 1 + 2 + 3
- 28 = 1 + 2 + 4 + 7 + 14
2) 코드 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| #include <stdio.h>
int main() {
int sum = 0;
for (int i = 2; i <= 100; i++) {
int divSum = 0;
for (int j = 1; j < i; j++) {
if (i % j == 0) {
divSum += j;
}
}
if (divSum == i) {
sum += i;
}
}
printf("%d\n", sum); // 34
}
|
8-3. 진수 변환 빈칸 채우기
1) 개념
2) 코드 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| #include <stdio.h>
int main() {
int n = 10;
int arr[10];
int i = 0;
while (n > 0) {
arr[i++] = (a);
(b);
}
for (int j = i - 1; j >= 0; j--) {
printf("%d", arr[j]);
}
return 0;
}
|
시험 출제: