Menu
Coddy logo textTech

다형적 순회

Coddy C 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 42번째.

여러분은 인터페이스를 생성하고 이를 서로 다른 구체적인 함수로 구현하는 방법을 배웠습니다. 진정한 이점은 서로 다른 객체들의 컬렉션을 일관되게 다룰 수 있을 때 나타납니다. 즉, 어떤 구체적인 동작이 연결되어 있는지와 상관없이, 이들을 반복하며 각 객체에 대해 동일한 함수 포인터를 호출할 수 있습니다.

각각 고유한 execute 함수가 할당된 Action 구조체 배열이 있다고 상상해 보세요:

typedef void (*ExecuteFunc)(void);

typedef struct {
    const char* name;
    ExecuteFunc execute;
} Action;

void jump(void) { printf("Jumping!\n"); }
void run(void)  { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }

이러한 액션들의 배열을 생성하고, 각각을 서로 다른 함수에 연결한 다음, 간단한 for 루프를 사용하여 이들을 반복할 수 있습니다:

int main() {
    Action actions[3] = {
        { "Jump", jump },
        { "Run", run },
        { "Rest", rest }
    };
    
    for (int i = 0; i < 3; i++) {
        printf("%s: ", actions[i].name);
        actions[i].execute();
    }
    return 0;
}

루프는 각 작업이 내부적으로 무엇을 하는지 알지 못하며 신경 쓰지도 않습니다. 단순히 각 요소에 대해 execute()를 호출할 뿐입니다.

이것이 실제로 작동하는 다형성입니다. 동일한 코드가 완전히 다른 동작을 가진 객체들을 처리합니다. 새로운 액션 타입을 추가할 때 반복 로직을 변경할 필요가 없으며, 배열에 요소를 하나 더 추가하기만 하면 됩니다.

challenge icon

챌린지

쉬움

공통 인터페이스를 공유하지만 서로 다른 동작을 실행하는 객체 컬렉션을 처리하는 다형성 반복(polymorphic iteration)을 보여주는 Task 관리 시스템을 구축해 보겠습니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • task.h: 여기에 태스크 인터페이스를 정의합니다. 매개변수가 없고 반환값이 없는 TaskFunc라는 함수 포인터 타입을 생성하세요. 그런 다음 name(const char*)과 TaskFunc 타입의 run 함수 포인터를 포함하는 Task 구조체를 정의하세요.
  • task.c: 서로 다른 유형의 작업을 나타내는 세 가지 태스크 함수를 구현합니다:
    • backup_taskBacking up data...를 출력합니다.
    • cleanup_taskCleaning up files...를 출력합니다.
    • report_taskGenerating report...를 출력합니다.
  • main.c: 여기서 모든 것을 하나로 합칩니다. 각각 다른 태스크 함수에 연결된 Task 구조체 배열을 생성하세요. 그런 다음 배열을 반복하며 각 태스크를 다형성 있게 실행합니다. 각 태스크에 대해 이름을 출력한 다음 run 함수를 호출하세요.

프로그램은 단일 입력을 받습니다: 실행할 태스크의 개수 (1, 2, 또는 3).

Backup이라는 이름의 백업 태스크, Cleanup이라는 이름의 정리 태스크, Report라는 이름의 보고 태스크 순서로 정확히 세 개의 태스크를 포함하는 배열을 생성하세요. 입력받은 개수에 따라 배열의 시작부터 해당 개수만큼의 태스크만 반복 실행합니다.

반복문의 각 태스크에 대해, 태스크의 이름 뒤에 콜론과 공백을 출력한 다음, 해당 태스크의 run 함수를 호출하세요.

입력이 2일 때의 출력 예시:

Backup: Backing up data...
Cleanup: Cleaning up files...

입력이 3일 때의 출력 예시:

Backup: Backing up data...
Cleanup: Cleaning up files...
Report: Generating report...

핵심은 반복문이 각 태스크가 무엇을 하는지 알 필요가 없다는 것입니다. 단순히 이름을 출력하고 각 요소의 run()을 호출할 뿐입니다. 동일한 반복 코드가 모든 태스크 유형을 균일하게 처리합니다. 헤더 파일에 include guard를 사용하는 것을 잊지 마세요.

치트 시트

공통 인터페이스를 공유하는 객체 컬렉션을 반복하면서, 각 요소의 구체적인 구현에 관계없이 동일한 함수 포인터를 호출할 수 있습니다. 이것이 바로 다형성입니다. 즉, 서로 다른 객체를 균일하게 처리하는 것입니다.

함수 포인터 타입과 이를 사용하는 구조체를 정의합니다:

typedef void (*ExecuteFunc)(void);

typedef struct {
    const char* name;
    ExecuteFunc execute;
} Action;

시그니처와 일치하는 구체적인 함수들을 생성합니다:

void jump(void) { printf("Jumping!\n"); }
void run(void)  { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }

각각 다른 함수에 연결된 구조체 배열을 빌드한 다음, 이를 반복합니다:

Action actions[3] = {
    { "Jump", jump },
    { "Run", run },
    { "Rest", rest }
};

for (int i = 0; i < 3; i++) {
    printf("%s: ", actions[i].name);
    actions[i].execute();
}

루프는 각 작업이 내부적으로 무엇을 하는지 알 필요가 없습니다. 단순히 각 요소의 함수 포인터를 호출할 뿐입니다. 새로운 타입을 추가해도 반복 로직을 변경할 필요가 없습니다.

직접 해보기

#include <stdio.h>
#include "task.h"

// task.c에 구현된 태스크 함수들을 선언합니다
void backup_task(void);
void cleanup_task(void);
void report_task(void);

int main() {
    int count;
    scanf("%d", &count);
    
    // TODO: 다음 순서대로 3개의 Task 구조체 배열을 생성하세요:
    // 1. backup_task를 사용하는 "Backup"이라는 이름의 백업 태스크
    // 2. cleanup_task를 사용하는 "Cleanup"이라는 이름의 정리 태스크
    // 3. report_task를 사용하는 "Report"이라는 이름의 보고 태스크
    
    // TODO: 배열에서 'count'개의 태스크를 반복합니다
    // 각 태스크에 대해 "<name>: "을 출력한 다음 해당 run 함수를 호출합니다
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Object Oriented Programming의 모든 레슨