Post

[정보처리기사 실기] 1. C언어

[정보처리기사 실기] 1. C언어

0. 목표


  • C 문법 핵심 개념 전체 흐름 이해
  • 시험에 자주 나오는 유형 10개 이상 익히기
  • 코드 해석 문제 정확도 70% 이상 만들기
  • 출력 결과 예측 문제 실수 줄이기
  • 기출 또는 모의 문제 최소 2회독

1. C언어 기본 문법 구조


1-1. 변수와 자료형


1) 변수란?

  • 값을 저장하기 위한 메모리 공간의 이름
  • 데이터를 저장하고 필요할 때 꺼내서 사용
1
int a = 10;  // 변수 = a

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. 자동 형 변환
    • 작은 타입 → 큰 타입으로 자동 변환
      1
      2
      3
      
       int a = 3;
       double b = 2.5;  
       double result = a + b;  // 결과: 5.5 (int → double 변환)
      
  2. 강제 형 변환
    • 개발자가 직접 타입을 변환
      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) 증감 연산자

  • 값을 1 증가 또는 감소
형태의미
++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번만
  • 함수가 끝나도 값이 유지됨
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번 실행 후 조건 검사
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) 인덱스 접근

  • 배열은 0부터 시작
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) 인덱스 접근

  • arr[행][열]
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
char* p = "KOREA";
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) 재귀 함수 필수 요소

  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);
}

입력 과정:

  1. 첫 입력 → “홍길동” → n에 저장
  2. 두 번째 입력 → “김철수” → 덮어쓰기
  3. 세 번째 입력 → “박영희” → 덮어쓰기

최종 출력:

1
2
3
박영희
박영희
박영희

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 = &num;

    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로 나눈 나머지를 계속 저장
  • 역순으로 출력

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;
}

시험 출제:

빈칸정답
(a)n % 2
(b)n /= 2
This post is licensed under CC BY 4.0 by the author.