C에서 객체지향 프로그래밍을 구현하는 방법: 캡슐화, 상속, 다형성 설명

1. 소개

C 언어는 그 역사적 배경과 저수준 제어 능력 때문에 많은 프로그래머에게 사랑받고 있습니다. 하지만 C는 객체지향 언어가 아닙니다. 다시 말해, Java나 C++와 달리 C 자체는 클래스, 상속, 캡슐화와 같은 객체지향 기능을 기본적으로 지원하지 않습니다. 그럼에도 불구하고 C에서 객체지향 프로그래밍(OOP) 개념을 흉내 내어 어느 정도의 OOP 기능을 구현할 수 있습니다. 이 글에서는 캡슐화, 상속, 다형성이라는 핵심 개념에 초점을 맞춰 C에서 객체지향 프로그래밍을 구현하는 방법을 설명합니다.

2. 객체지향 프로그래밍의 기본 개념

객체지향 프로그래밍(OOP)은 데이터와 그 데이터를 조작하는 메서드를 하나의 단위로 관리하는 것을 목표로 합니다. 이러한 접근 방식은 프로그램 구조를 명확히 하고 재사용성과 유지보수성을 향상시킵니다. OOP의 주요 개념은 캡슐화, 상속, 다형성입니다. C는 이러한 기능을 직접 제공하지 않지만, 약간의 창의성을 발휘하면 비슷한 방식으로 구현할 수 있습니다.

2.1 캡슐화

캡슐화는 데이터와 그 연산(메서드)을 하나의 단위로 묶고 외부에서의 접근을 제어하는 것을 의미합니다. C에서는 struct를 사용해 데이터를 그룹화할 수 있습니다. struct는 여러 데이터를 하나의 구조로 결합한다는 점에서 클래스와 유사한 역할을 합니다.

typedef struct {
    int age;
    char name[50];
} Person;

이 구조체에서 Person 데이터 타입은 나이와 이름 정보를 모두 캡슐화합니다. 이를 통해 Person 인스턴스를 생성하고 조작할 수 있습니다.

2.2 상속

C에는 기본적인 상속 기능이 없으므로 클래스처럼 부모‑자식 관계를 만들 수 없습니다. 하지만 하나의 struct를 다른 struct의 멤버로 포함시킴으로써 상속과 유사한 메커니즘을 구현할 수 있습니다.

typedef struct {
    int age;
} Parent;

typedef struct {
    Parent parent;
    int studentID;
} Child;

이 코드에서 Child 구조체는 Parent 구조체를 포함하고 있어 일종의 의사 상속(pseudo‑inheritance) 형태를 나타냅니다.

2.3 다형성

다형성은 동일한 연산이 객체 타입에 따라 다르게 동작할 수 있는 능력을 의미합니다. C에서는 함수 포인터를 활용해 이를 구현할 수 있습니다. 함수 포인터는 함수의 주소를 저장하는 변수로, 실행 시점에 서로 다른 함수를 동적으로 호출할 수 있게 해줍니다.

typedef int (*OperationFunc)(int, int);

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

OperationFunc op = add;  // Set to add function
printf("%d", op(3, 4));  // Output: 7
op = multiply;            // Switch to multiply function
printf("%d", op(3, 4));   // Output: 12

위와 같이 동일한 함수 포인터를 사용해 다양한 연산을 수행할 수 있습니다.

侍エンジニア塾

3. C에서 클래스 구현 방법

C에 객체지향 프로그래밍을 도입하려면 클래스 개념을 시뮬레이션해야 합니다. 이는 struct와 함수 포인터를 결합해 클래스와 유사한 구조를 만드는 방식으로 이루어집니다.

3.1 구조체를 클래스처럼 사용하기

C에서 클래스를 구현하려면 데이터를 묶고 메서드를 함께 관리하는 struct를 사용합니다. 메서드는 함수로 정의하고, 해당 함수에 대한 포인터를 struct 내부에 저장해 관리합니다.

typedef struct {
    int age;
    void (*setAge)(struct Person*, int);
    int (*getAge)(struct Person*);
} Person;

void setAge(struct Person* p, int age) {
    p->age = age;
}

int getAge(struct Person* p) {
    return p->age;
}

Person person = {0, setAge, getAge};
person.setAge(&person, 25);
printf("Age: %d", person.getAge(&person));  // Output: 25

이 예시에서 Person 구조체는 setAgegetAge 메서드를 가지고 있어 클래스와 같은 동작을 구현합니다.

4. 메서드 구현

OOP의 핵심 기능인 “메서드”를 C에서 재현하려면 함수 포인터를 사용합니다. 이를 통해 메서드를 struct 멤버로 정의할 수 있습니다.

typedef struct {
    int age;
    void (*setAge)(struct Person*, int);
} Person;

void setAge(struct Person* p, int age) {
    p->age = age;
}

5. 요약 및 적용

C에서 객체 지향 기능을 구현할 수는 있지만, 언어 자체가 OOP를 기본적으로 지원하지 않는다는 점을 기억하세요. 클래스와 상속과 같은 개념을 도입하려면 구조체, 함수 포인터, 메모리 관리를 충분히 활용하는 창의성이 필요합니다.

侍エンジニア塾