정처기 실기 프로그래밍: C언어
정처기 실기

정처기 실기 프로그래밍: C언어

작성일: 2026년 07월 15일0

정보처리기사 실기 코딩 문제는 "다음 C 코드의 실행 결과(출력)를 쓰시오" 형태로 출제됩니다. 즉 코드가 화면에 무엇을 출력하는지 손으로 추적하는 능력이 핵심입니다. 아래는 실기 빈출 C 문법을 주제별로 정리한 것입니다.

자료형은 변수에 저장할 데이터의 종류를 정하는 것입니다. 실기에서는 자료형별 정확한 바이트 수보다, 자료형에 따라 나눗셈·출력 서식이 어떻게 달라지는지가 중요합니다.

정수형과 실수형

분류자료형크기설명
정수형int4바이트가장 기본. 약 -21억 ~ +21억
정수형char1바이트문자 하나 / 작은 정수(내부는 ASCII 값)
정수형long32~64비트int보다 큰 정수
정수형unsigned int4바이트양수만(0 포함). 약 0 ~ 42억
실수형float4바이트소수점 이하 약 6~7자리
실수형double8바이트소수점 이하 약 15~16자리
int   age = 25;        // 정수
char  grade = 'A';     // 문자 하나 (작은따옴표)
float pi = 3.14f;      // f를 붙이면 float
double d = 3.141592;   // 소수점 리터럴은 기본이 double

정수 리터럴 접미사

코드에 직접 쓴 값을 리터럴이라 하고, 뒤에 붙는 문자가 접미사입니다. 접미사는 값의 자료형을 지정합니다.

int a = 5;             // 기본: int
unsigned int b = 5u;   // u : unsigned int
long c = 5L;           // L : long
unsigned long d = 5UL; // UL: unsigned long

연결 리스트 등 실기 기출에서 구조체 초기화에 5u, 7u처럼 나오는데, 이는 멤버가 unsigned int로 선언되어 타입을 맞춘 것입니다.

char는 사실 정수형

컴퓨터는 문자를 ASCII 코드(숫자)로 저장합니다. 'A'는 내부적으로 65가 저장되므로, char로 작은 정수를 다룰 수 있습니다.

char c = 'A';
printf("%c", c);   // A  (문자로 출력)
printf("%d", c);   // 65 (숫자로 출력)

출력 함수와 서식 지정자

printf는 큰따옴표 안의 내용을 그대로 출력하고, %로 시작하는 서식 지정자 자리에 값이 들어갑니다.

서식의미예시출력
%d정수(10진수)printf("%d", 42);42
%c문자 한 개printf("%c", 'A');A
%s문자열printf("%s", "Hi");Hi
%f실수(기본 6자리)printf("%f", 3.14);3.140000
%x / %X16진수(소/대문자)printf("%x", 255);ff
%o8진수printf("%o", 8);10
%e지수 표기printf("%e", 12345.6789);1.234568e+04

소수점 자릿수 지정 (%.Nf)

%f는 기본이 소수점 6자리입니다. %.Nf로 자릿수를 지정하며, 부족한 자리는 0으로 채웁니다. 실기에 자주 나옵니다.

double pi = 3.141592;
printf("%.0f\n", pi);   // 3
printf("%.2f\n", pi);   // 3.14
printf("%.4f\n", pi);   // 3.1416   (반올림)
printf("%f\n",   pi);   // 3.141592 (기본 6자리)

printf("%.2f", 2795.1); // 2795.10  (부족한 자리 0으로 채움)

같은 값, 다른 진법 출력

값은 하나인데 서식만 바꾸면 진법 표기가 달라집니다. 메모리에 저장된 비트는 동일합니다.

int n = 255;
printf("%d\n", n);   // 255  (10진수)
printf("%x\n", n);   // ff   (16진수 소문자)
printf("%X\n", n);   // FF   (16진수 대문자)
printf("%o\n", n);   // 377  (8진수)

putchar / puts

putchar('A');      // A (문자 한 개)
putchar(65);       // A (ASCII 65 = 'A')
puts("Hello");     // Hello + 자동 줄바꿈

puts는 문자열 출력 후 자동 줄바꿈을 하고, printf\n을 직접 넣어야 합니다.

입력 함수 (scanf)와 & 의 이유

scanf는 입력값을 변수에 직접 저장해야 하므로, 변수의 주소(&)를 전달합니다. 값을 그대로 넘기면 복사본만 전달되어 원본이 바뀌지 않습니다.

int a;
scanf("%d", &a);      // a의 주소를 전달
double y;
scanf("%lf", &y);     // double은 %lf
char name[20];
scanf("%s", name);    // 배열 이름 자체가 주소라 & 불필요
함수입력 단위& 사용공백 처리
scanf다양한 형식필요(문자열 제외)%s는 공백 전까지만
getchar문자 한 개불필요(반환값 사용)공백도 입력 가능
gets문자열(한 줄)불필요(배열 이름=주소)공백 포함 입력

scanf("%s", name)으로 "Hello World"를 입력하면 공백 전 "Hello"만 저장됩니다. 공백 포함 입력은 gets를 씁니다.

sizeof

sizeof는 자료형·변수가 차지하는 크기를 바이트로 돌려주는 연산자입니다. 실기에서는 배열 길이 관용구가 핵심입니다.

int arr[] = {10, 20, 30, 40, 50};
int len = sizeof(arr) / sizeof(arr[0]);  // 5 (원소 개수)

포인터에 sizeof를 쓰면 가리키는 대상이 아니라 주소를 담는 공간의 크기가 나옵니다. 배열을 함수 인자로 넘기면 포인터로 변환되어, 함수 안에서는 sizeof로 길이를 구할 수 없습니다.

형변환 (Casting)

(자료형)으로 명시적 변환을 합니다. doubleint는 반올림이 아니라 소수점 절단(0 방향)입니다.

int n = (int)3.7;   // 3 (4 아님)
int m = (int)-3.7;  // -3

실기 단골 함정: 정수끼리 나누면 정수 나눗셈이라 소수점이 버려집니다. 대입 좌변이 double이어도 우변이 먼저 계산됩니다.

int a = 7, b = 3;
double r1 = a / b;          // 2.0  (정수 나눗셈 2가 먼저 계산됨)
double r2 = (double)a / b;  // 2.333333 (한쪽을 double로 → 실수 나눗셈)

산술 연산자

/(나눗셈)와 %(나머지)가 핵심입니다. 정수끼리 나누면 몫만 남습니다.

int d = 5 / 3;   // 1 (소수점 버림)
int e = 5 % 3;   // 2 (나머지)

나머지 연산 활용: 짝수/홀수 판별(n % 2), 자릿수 추출(n % 10), 순환((i+1) % 3 → 0,1,2,0,...).

증감 연산자 (전위 / 후위)

++a는 먼저 증가 후 값 사용, a++는 먼저 값 사용 후 증가입니다. 단독으로 쓰면(예: for 증감식) 차이가 없지만, 값을 대입·사용할 때 결과가 달라집니다.

int i = 5;
int a = ++i;   // i를 먼저 증가(6) → a = 6, i = 6
int j = 5;
int b = j++;   // j를 먼저 사용 → b = 5, 그 뒤 j = 6
int x = 10;
printf("%d\n", ++x);   // 11 (먼저 증가 후 출력)
printf("%d\n", x);     // 11
printf("%d\n", x++);   // 11 (먼저 출력 후 증가)
printf("%d\n", x);     // 12

비교 연산자

C에서 참은 1(또는 0이 아닌 값), 거짓은 0입니다.

int a = 5, b = 3;
printf("%d", a == b);   // 0
printf("%d", a != b);   // 1
printf("%d", a > b);    // 1

기출 단골 패턴: 문자열 끝('\0') 찾기. while (str[a] != '\0') ++a; 실행 후 a는 문자열 길이가 됩니다.

논리 연산자와 단축 평가

&&(AND), ||(OR), !(NOT). 왼쪽만으로 결과가 확정되면 오른쪽을 평가하지 않는 단축 평가를 합니다.

printf("%d", !0);   // 1
printf("%d", !5);   // 0 (0이 아닌 값은 참 → 반대는 거짓)

int a = 0;
if (a != 0 && 10 / a > 1) { }  // 왼쪽이 거짓 → 오른쪽(10/a) 평가 안 함, 에러 없음

삼항 연산자

조건식 ? 참일때값 : 거짓일때값. if-else를 한 줄로 줄입니다. 0만 거짓, 음수를 포함한 나머지는 모두 참입니다.

int score = 85;
int result = (score >= 60) ? 1 : 0;  // 1
int a = -1 ? 10 : 20;                 // 10 (-1은 참)

중첩 삼항은 오른쪽에서 왼쪽으로 결합합니다. a ? b : c ? d : ea ? b : (c ? d : e)로 해석됩니다.

연산자 우선순위 (C)

숫자가 작을수록 먼저 계산됩니다.

순위연산자결합 방향
1() [] -> .왼→오
2! ~ ++ --(전위) *(역참조) &(주소)오→왼
3* / %왼→오
4+ -왼→오
5<< >> (시프트)왼→오
6< <= > >= (크기 비교)왼→오
7== != (같음 비교)왼→오
8& (비트 AND)왼→오
9^ (비트 XOR)왼→오
10| (비트 OR)왼→오
11&& (논리 AND)왼→오
12|| (논리 OR)왼→오
13?: (삼항)오→왼
14= += -= (대입)오→왼

실기 최다 함정: 비교 > 비트

비교 연산자(==, !=, <, >)가 비트 연산자(&, ^, |)보다 우선순위가 높습니다. 그리고 비트 연산끼리는 & > ^ > | 순입니다.

int a = 3 & 2 == 2;   // 3 & (2 == 2) → 3 & 1 → 1  (괄호 없으면 == 먼저!)
int b = (3 & 2) == 2; // 2 == 2 → 1

int z = 2 & 2 >> 1;   // 2 & (2 >> 1) → 2 & 1 → 0  (시프트가 & 보다 먼저)

복합식 단계별 풀이

int x = 7, y = 4, z;
z = y % 3 < 3 ? 2 : 1;                // 1단계: (4%3=1)<3 → 참 → z=2
z = z & z >> 1;                        // 2단계: z & (z>>1) = 2 & 1 = 0
z = x > 5 && z <= 3 ? z * x : z / x;   // 3단계: (1 && 1) → z*x = 0*7 = 0
printf("%d", z);                       // 출력: 0

풀이 순서: 괄호 → 산술(% * /) → 비교(< > ==) → 비트(& ^ |) → 논리(&& ||) → 삼항(?:) → 대입(=).

비트 연산 문제는 10진수·16진수를 2진수로 바꿔 자리별로 계산한 뒤 다시 10진수로 되돌리는 과정을 손으로 해야 합니다.

2진수 변환

  • 10진수 → 2진수: 2로 계속 나누고 나머지를 아래에서 위로(역순) 읽습니다.
  • 2진수 → 10진수: 1인 자리의 자릿값(오른쪽부터 1, 2, 4, 8, 16 ...)을 모두 더합니다.
  • 2진수 11018 + 4 + 0 + 1 = 13

    외워둘 값: 4비트 최댓값 1111 = 15, 8비트 최댓값 1111 1111 = 255.

    16진수

    0 ~ 9와 A(10) ~ F(15)를 씁니다. 코드에서는 0x 접두사를 붙입니다. 16진수 1자리 = 2진수 4비트입니다.

    int a = 0xA5;  // 10*16 + 5 = 165
    int b = 0xFF;  // 255
    16진수계산10진수
    0xA010*16 + 0160
    0xA510*16 + 5165
    0xDB13*16 + 11219
    0xFF15*16 + 15255

    ASCII 코드

    문자ASCII비고
    '\0'0널 문자(문자열 끝)
    '0' ~ '9'48 ~ 57숫자 문자
    'A' ~ 'Z'65 ~ 90대문자
    'a' ~ 'z'97 ~ 122소문자

    문자는 숫자이므로 연산이 가능합니다. 대소문자 차이는 32입니다.

    int diff = 'E' - 'A';   // 69 - 65 = 4
    char lower = 'A' + 32;  // 'a'
    int num = '7' - '0';    // 55 - 48 = 7 (문자 숫자 → 실제 정수)

    비트 연산자

    연산자이름규칙예시
    &AND둘 다 1이면 15 & 3 = 1
    |OR하나라도 1이면 15 | 3 = 7
    ^XOR서로 다르면 15 ^ 3 = 6
    ~NOT비트 반전~5 = -6 (int)
    <<왼쪽 시프트1칸당 x25 << 1 = 10
    >>오른쪽 시프트1칸당 /25 >> 1 = 2
      0101  (5)          0101  (5)          0101  (5)
    & 0011  (3)        | 0011  (3)        ^ 0011  (3)
    ------             ------             ------
      0001  (1)          0111  (7)          0110  (6)

    ~5는 int(부호 있는 32비트)에서 -6이지만, unsigned char에서 ~5는 8비트가 반전되어 250(1111 1010)입니다.

    16진수 + 비트 연산 종합

    int result = 0xDB & 0xA5;   // 129
    
    // 1) 0xDB = 219 = 1101 1011,  0xA5 = 165 = 1010 0101
    //   1101 1011
    // & 1010 0101
    //   ---------
    //   1000 0001 = 128 + 1 = 129

    시프트는 곱셈·나눗셈과 같습니다. a << n = a * 2^n, a >> n = a / 2^n(소수점 버림). 시프트는 비트 AND/OR보다 우선순위가 높습니다.

    if / else if

    여러 조건을 위에서부터 순서대로 검사하고, 처음 참인 블록만 실행합니다.

    int score = 75;
    if (score >= 90)      printf("A");
    else if (score >= 80) printf("B");
    else if (score >= 70) printf("C");   // 75 >= 70 참 → 실행
    else                  printf("F");
    // 출력: C

    75는 >= 70도 참이지만 위의 조건을 먼저 검사해 거짓이면 건너뛰고, 처음 참인 곳에서 멈춥니다. 범위가 좁은 조건을 위에 써야 합니다.

    switch-case와 fall-through

    switch는 값이 일치하는 case로 진입한 뒤 break를 만날 때까지 아래 case들을 연속 실행합니다. 이것이 fall-through입니다.

    int a = 2;
    switch (a) {
        case 1: printf("A");
        case 2: printf("B");   // 여기 진입
        case 3: printf("C");   // break 없어서 계속
        default: printf("D");
    }
    // 출력: BCD

    break가 모두 있으면 출력은 B 하나뿐입니다. default는 어느 위치에 있어도 동작하며, default에도 break가 없으면 fall-through됩니다. 값이 없는 case를 연속으로 쓰면(case 10: case 9:) 여러 값을 묶을 수 있습니다.

    복합 대입 + fall-through 기출 패턴

    int i = 3, k = 1;
    switch (i) {
        case 0:
        case 1:
        case 2:
        case 3: k = 0;      // 진입, k=0 (초기값 1은 대입으로 사라짐)
        case 4: k += 3;     // k=3
        case 5: k -= 10;    // k=-7
        default: k--;       // k=-8
    }
    // 출력: -8

    case 3에서 k = 0은 대입(=)이라 초기값 1이 사라지고 0부터 시작하는 점이 함정입니다.

    for / while / do-while

    for (int i = 0; i < 3; i++) printf("%d ", i);  // 0 1 2
    
    int i = 0;
    while (i < 3) { printf("%d ", i); i++; }        // 0 1 2
    
    int j = 5;
    do { printf("%d ", j); j++; } while (j < 3);    // 5 (조건 거짓이어도 최소 1회 실행)

    for문은 초기식·조건식·증감식을 각각 생략할 수 있으나 세미콜론은 유지합니다. 기출 단골: int i = 3; for (; i < 5; i++) ..., while (str[a] != '\0') ++a;.

    break / continue

    for (int i = 0; i < 5; i++) {
        if (i == 2) continue;   // 2만 건너뜀
        printf("%d ", i);
    }
    // 0 1 3 4

    break는 루프를 완전히 종료, continue는 현재 반복만 건너뛰고 다음 반복을 계속합니다.

    중첩 반복문

    바깥 루프 1회마다 안쪽 루프가 처음부터 끝까지 돕니다.

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) printf("(%d,%d) ", i, j);
        printf("\n");
    }
    // (0,0) (0,1) (0,2)
    // (1,0) (1,1) (1,2)

    1차원 배열

    같은 자료형을 나란히 저장합니다. 인덱스는 0부터 시작합니다.

    int scores[3] = {85, 90, 78};
    printf("%d", scores[0]);   // 85
    scores[2] += 10;           // scores[2] = 88
    int arr[] = {10, 20, 30, 40};  // 크기 생략 시 4로 자동 결정

    크기 3인 배열의 유효 인덱스는 0, 1, 2입니다. scores[3]은 범위를 벗어나 쓰레기 값이 나오거나 비정상 종료될 수 있습니다.

    배열 이름은 주소다

    C에서 배열 이름은 첫 번째 요소의 주소입니다. 즉 a&a[0]은 같고, a[i]*(a + i)와 완전히 동일합니다.

    int a[3] = {10, 20, 30};
    int *ptr = a;      // = &a[0], 그래서 & 없이 대입
    *a;                // = a[0] = 10

    2차원 배열

    행과 열로 이루어진 표이지만, 메모리에는 행 순서대로 한 줄로 연속 저장됩니다.

    int arr[2][3] = {1, 2, 3, 4, 5, 6};   // 0행부터 채움
    // {{1,2,3}, {4,5,6}} 과 동일
    printf("%d", arr[0][0]);  // 1
    printf("%d", arr[1][2]);  // 6

    행 이름(arr[0], arr[1])은 각 행의 시작 주소입니다. 실기에서는 2차원 배열을 이중 for문(바깥=행, 안쪽=열)으로 순회하는 문제가 자주 나옵니다.

    문자 배열 = 문자열

    C에는 문자열 타입이 없고, char 배열이 문자열입니다. 끝에는 널 문자 '\0'(ASCII 0)이 자동으로 붙습니다.

    char str[] = "HELLO";   // 실제 저장: 'H' 'E' 'L' 'L' 'O' '\0'

    "HELLO"의 길이는 5이지만 배열 크기는 널 문자 포함 6입니다. printf, strlen 등은 널 문자를 만날 때까지 처리합니다.

    문자열 길이 직접 세기 (기출 단골)

    char str[] = "REPUBLICOFKOREA";
    int a = 0;
    while (str[a] != '\0') ++a;   // 널 문자 전까지 세기
    // 루프 종료 후 a = 15 (문자열 길이)

    이렇게 구한 길이로 특정 위치 문자를 묻습니다. str[a - 2]str[13] = 'E'입니다.

    문자열 복사 (strcpy 직접 구현)

    실기에서는 표준 strcpy 대신 포인터로 직접 구현한 복사 함수가 자주 나옵니다.

    void fn(char *d, const char *s) {
        while (*s) {   // s가 가리키는 값이 '\0'이 아닌 동안
            *d = *s;   // 문자 복사
            d++;       // 대상 포인터 이동
            s++;       // 원본 포인터 이동
        }
        *d = '\0';     // 마지막에 널 문자
    }

    const char *s는 원본을 읽기만 하고 수정하지 않는다는 표시입니다.

    포인터는 실기 배점이 가장 큰 영역입니다. 시험장에서는 요소마다 임의 주소를 1씩 증가시켜(예: 100, 101, 102) 포인터의 움직임을 손으로 추적하는 것이 정석입니다.

    주소(&)와 역참조(*)

    포인터는 다른 변수의 주소를 저장하는 변수입니다. &는 주소를 가져오고(참조), *는 주소가 가리키는 값에 접근합니다(역참조).

    int num = 10;
    int *p = &num;      // p에 num의 주소 저장
    
    printf("%d", *p);   // 10 (p가 가리키는 값)
    *p = 20;            // p가 가리키는 곳의 값을 변경
    printf("%d", num);  // 20 (원본이 바뀜)

    선언할 때의 *(int p)는 "포인터 선언" 표시이고, `p`처럼 쓰면 "역참조"입니다.

    선언 함정: 콤마

    *는 자료형이 아니라 각 변수 이름에 붙습니다.

    char *p1, *p2;   // 둘 다 char 포인터
    char* p1, p2;    // p1만 포인터, p2는 char 변수 (함정!)

    기출: struct node* h = NULL, *n;hn 모두 구조체 포인터입니다.

    포인터 산술

    포인터에 정수를 더하면 가리키는 자료형 크기만큼 이동합니다. 시험에서는 바이트 수를 몰라도 되고, p + np[n]의 주소라는 것만 알면 됩니다.

    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;
    *p;        // 10  ( = p[0] = arr[0])
    *(p + 1);  // 20  ( = p[1])
    *(p + 2);  // 30

    a[i]*(a + i)는 완전히 같습니다. arr[2] = *(arr + 2) = *(2 + arr).

    괄호 위치 함정: (p + n) vs p + n

    *(역참조)가 +보다 우선순위가 높습니다.

    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;
    *(p + 3);   // 40  (3칸 이동한 위치의 값 = arr[3])
    *p + 3;     // 13  (p가 가리키는 값 10에 3을 더함)

    포인터 증감: p++ vs (p)++

    int arr[] = {10, 20, 30};
    int *p = arr;
    int a = *p++;    // *(p++): 값 10을 읽고 p를 arr[1]로 이동 → a=10
    int b = (*p)++;  // p가 가리키는 값(20)을 b에, 그 뒤 arr[1]을 21로 증가 → b=20, arr[1]=21

    *p++는 포인터를 이동시키고, (*p)++는 가리키는 값을 증가시킵니다.

    문자열 포인터 (char *)

    문자열 리터럴 "KOREA"는 그 자체가 시작 주소입니다.

    char *p = "KOREA";       // p는 'K'의 주소
    printf("%s", p);         // KOREA (주소부터 '\0'까지)
    printf("%s", p + 3);     // EA    (인덱스 3부터)
    printf("%c", *p);        // K     (한 문자)
    printf("%c", *(p + 3));  // E

    %s는 주소를 받아 널 문자까지 출력하고, %c는 역참조한 문자 값 하나를 출력합니다. 문자열 리터럴에 바로 +도 가능합니다: printf("%s", "HELLO" + 1);ELLO.

    포인터 배열

    포인터를 요소로 갖는 배열입니다. 2차원 배열 행 주소 저장에 자주 쓰입니다.

    int arr[3][3] = {1,2,3,4,5,6,7,8,9};
    int *parr[2] = {arr[1], arr[2]};   // parr[0]=arr[1], parr[1]=arr[2]
    
    parr[0][0];   // arr[1][0] = 4
    parr[1][1];   // arr[2][1] = 8
    **parr;       // *(parr[0]) = arr[1][0] = 4

    빠른 풀이: parr[i]를 저장된 행으로 치환합니다. parr[1][1]arr[2][1] = 8.

    이중 포인터

    포인터의 주소를 저장하는 포인터입니다.

    int num = 10;
    int *ptr = &num;      // 단일 포인터
    int **pptr = &ptr;    // 이중 포인터
    
    *pptr;    // ptr (포인터)
    **pptr;   // 10  (최종 값)

    함수 정의 / 호출 / 프로토타입

    C 프로그램은 항상 main에서 시작합니다. 컴파일러가 위에서 아래로 읽으므로, 함수 정의가 호출보다 아래에 있으면 프로토타입(본문 없이 선언 후 세미콜론)이 필요합니다.

    #include <stdio.h>
    int add(int a, int b);   // 프로토타입 (정의가 아래에 있을 때 필요)
    
    int main() {
        printf("%d", add(3, 5));  // 8
        return 0;
    }
    int add(int a, int b) { return a + b; }

    정의가 main보다 위에 있으면 프로토타입은 필요 없습니다. #include <stdio.h>printf 등의 프로토타입을 가져오는 것입니다.

    Call by Value (값 복사)

    기본은 값을 복사해서 전달하므로 함수 안에서 매개변수를 바꿔도 원본은 그대로입니다.

    void swap(int a, int b) { int t = a; a = b; b = t; }
    
    int main() {
        int a = 11, b = 19;
        swap(a, b);
        printf("%d %d", a, b);   // 11 19 (교환 안 됨!)
    }

    swap의 a, b와 main의 a, b는 이름만 같은 별개의 변수입니다.

    Call by Reference (주소 전달)

    주소를 넘기면 원본을 직접 수정할 수 있습니다.

    void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
    
    int main() {
        int a = 11, b = 19;
        swap(&a, &b);
        printf("%d %d", a, b);   // 19 11 (교환됨!)
    }

    배열 전달

    배열 이름 자체가 주소이므로 & 없이 넘겨도 원본이 수정됩니다.

    void change(int arr[]) { arr[0] = 100; }
    int main() {
        int arr[] = {1, 2, 3};
        change(arr);
        printf("%d", arr[0]);   // 100
    }

    재귀 함수

    자기 자신을 호출하는 함수입니다. 반드시 멈추는 기저 조건이 있어야 합니다.

    int fn(int n) {
        if (n <= 1) return 1;   // 기저 조건
        return n * fn(n - 1);   // 재귀 호출
    }
    // fn(5) = 5*4*3*2*1 = 120

    풀이법: 기저 조건에 도달할 때까지 호출을 펼친 뒤, 가장 안쪽부터 거꾸로 값을 채웁니다.

    fn(1)=1 → fn(2)=2*1=2 → fn(3)=3*2=6 → fn(4)=4*6=24 → fn(5)=5*24=120

    재귀 호출이 두 번 이상이면 각 호출값을 모두 계산합니다. 기저 조건의 반환값(return 1 인지 return n인지)을 꼼꼼히 확인해야 합니다.

    int gamja(int n) {
        if (n <= 1) return n;                 // n <= 1이면 n 반환 (1이 아님!)
        return gamja(n - 1) + gamja(n - 3);
    }
    // gamja(7) = 2

    정의와 멤버 접근

    여러 자료형을 하나로 묶는 사용자 정의 자료형입니다. 구조체 변수는 점(.), 구조체 포인터는 화살표(->)로 멤버에 접근합니다.

    struct Student {
        int id;
        char name[20];
        float score;
    };
    
    struct Student s1 = {1001, "김철수", 85.5};
    printf("%d", s1.id);      // 1001
    printf("%s", s1.name);    // 김철수
    
    struct Student *p = &s1;
    printf("%d", p->id);      // 1001

    p->id(*p).id와 동일합니다. 중괄호 초기화 값은 멤버 선언 순서대로 들어갑니다. 문자 배열 멤버는 s1.name = "..."가 안 되고 strcpy를 써야 합니다.

    구조체 배열과 포인터

    struct Student students[3] = {
        {1001, "김철수", 85.5},
        {1002, "이영희", 90.0},
        {1003, "박민수", 78.5}
    };
    
    struct Student *p = &students[1];
    printf("%s", p->name);        // 이영희
    printf("%s", (p + 1)->name);  // 박민수 (다음 구조체 요소)
    printf("%d", (p - 1)->id);    // 1001

    바깥 중괄호를 생략한 초기화도 기출로 나옵니다. 컴파일러가 멤버 순서대로 채우므로 어디서 잘리는지 파악하는 게 핵심입니다.

    -> 체인 (연결 리스트 패턴)

    struct node { int n1; struct node *n2; };
    struct node a = {10, 0}, b = {20, 0}, c = {30, 0};
    struct node *head = &a;
    a.n2 = &b;  b.n2 = &c;
    
    head->n2->n1;   // (head->n2)->n1 = (&b)->n1 = b.n1 = 20

    ->는 왼쪽부터 결합하고, 포인터 멤버를 만날 때만 씁니다.

    중첩 구조체

    구조체 멤버가 또 다른 구조체이면, 포인터는 처음 한 번만 ->이고 이후는 .으로 이어갑니다.

    struct Date { int year, month, day; };
    struct Student { int id; struct Date birth; };
    
    struct Student s = {1001, {2025, 3, 19}};
    struct Student *p = &s;
    printf("%d/%d/%d", p->birth.year, p->birth.month, p->birth.day);  // 2025/3/19

    p->birth는 구조체(값)를 돌려주므로 그 다음은 .year입니다. p->birth->year는 컴파일 에러입니다.

    typedef

    struct 키워드를 매번 안 써도 되게 별칭을 붙입니다. 기출에서 typedef struct 패턴이 매우 자주 나옵니다.

    typedef struct {
        int acc;
        double bal;
    } Acc;
    
    Acc ba;             // struct 없이 사용

    자기 참조 구조체는 정의가 끝나기 전에는 별칭을 못 쓰므로 내부에서 struct 태그를 붙여야 합니다.

    typedef struct Node {
        int data;
        struct Node *next;   // 자기 참조 시 struct Node 필수
    } Node;

    포인터 타입 별칭 함정: typedef int* IntPtr; IntPtr p1, p2;는 둘 다 포인터지만, int *p1, p2;는 p2가 일반 int입니다.

    ctype.h는 문자 판별·변환 함수를 제공합니다. #include <ctype.h>가 필요합니다. 판별 함수는 참이면 0이 아닌 값, 거짓이면 0을 반환합니다(반드시 1은 아님).

    함수설명
    isupper(c)대문자인지isupper('A') → 참
    islower(c)소문자인지islower('t') → 참
    isdigit(c)숫자 문자인지isdigit('8') → 참
    isalpha(c)알파벳인지isalpha('A') → 참
    toupper(c)소문자→대문자toupper('a') → 'A'
    tolower(c)대문자→소문자tolower('A') → 'a'
    #include <ctype.h>
    char c = 'A';
    if (isupper(c)) printf("대문자입니다");   // 출력됨

    함정: isdigit는 문자 '0'~'9'만 참입니다. isdigit(5)는 거짓이고, 문자 '5'(ASCII 53)를 넣어야 참입니다.

    string.h의 문자열 함수도 함께 알아두면 좋습니다: strlen(길이), strcpy(복사). 실기에서는 이 함수들을 포인터로 직접 구현한 코드(5번 항목 참고)가 더 자주 출제됩니다.

    실기는 "출력 결과 쓰기"가 핵심이므로, 아래 함정들을 특히 조심해야 합니다.

    코드 읽는 순서

    C는 컴파일러가 위에서 아래로 한 번 읽습니다(1-pass). 그래서 main을 맨 아래에 두고, 호출되는 함수는 그 위에 정의하거나 프로토타입을 씁니다. 코드 추적은 main부터 시작해서 함수 호출을 만나면 그 정의로 이동, 인자를 매개변수에 대입하고, 반환값을 호출 자리에 넣어 이어 읽습니다.

    자주 틀리는 함정 체크리스트

  • 정수 나눗셈: int / int는 소수점 버림. 10 / 3 = 3, 7 / 2 = 3. double이 필요하면 한쪽을 (double)로 변환.
  • 전위/후위 증감: a = i++(먼저 대입) vs a = ++i(먼저 증가). 단독 사용은 차이 없음.
  • 연산자 우선순위: 비교(== !=)가 비트(& ^ |)보다 먼저. 3 & 2 == 2 = 3 & 1 = 1(C 기준).
  • switch break 유무: break 없으면 아래 case로 fall-through. default 위치는 무관.
  • for/증감식 위치: 증감식은 반복 본문 실행 후에 실행됨.
  • do-while: 조건이 거짓이어도 최소 1회 실행.
  • 문자 vs 숫자: '0'(48)과 숫자 0은 다름. '\0'(0)과 문자 '0'(48)도 다름.
  • %.2f 자릿수 채움: 2795.1%.2f로 출력하면 2795.10.
  • 포인터 괄호: *(p + n)(n칸 뒤 값) vs *p + n(값에 n 더함).
  • Call by Value: 값 전달 swap은 교환 안 됨. 포인터/배열 전달만 원본 변경.
  • 배열 인덱스: 0부터. 크기 N 배열의 유효 인덱스는 0 ~ N-1.
  • 재귀 기저 조건: return 1인지 return n인지 반환값을 정확히 확인.
  • 구조체 접근: 포인터는 ->, 값은 .. p->birth.year(중첩은 첫 단계만 ->).