c 언어/c 언어 문법

C 언어에서의 객체지향 프로그래밍

임베디드 친구 2024. 12. 15. 15:30
728x90
반응형

C 언어에서의 객체지향 프로그래밍

C 언어는 절차지향 프로그래밍 언어로 잘 알려져 있습니다. 하지만 C++이나 Java 같은 객체지향 언어가 등장하기 전에도 객체지향적인 접근법을 C에서 구현하려는 시도는 꾸준히 이어져 왔습니다. 이번 글에서는 C 언어에서 객체지향 프로그래밍(Object-Oriented Programming, OOP)의 개념을 이해하고 이를 구현하는 방법을 알아보겠습니다.

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

객체지향 프로그래밍은 크게 다음 네 가지 특징을 가집니다.

  1. 캡슐화 (Encapsulation)
    • 데이터와 데이터를 처리하는 함수를 하나로 묶어 관리.
  2. 상속 (Inheritance)
    • 기존 클래스(또는 구조체)의 속성과 동작을 재사용하거나 확장.
  3. 다형성 (Polymorphism)
    • 동일한 인터페이스를 통해 다양한 형태의 객체를 조작 가능.
  4. 추상화 (Abstraction)
    • 필요한 정보만 노출하고 복잡한 구현은 숨김.

C는 이러한 특징을 언어적으로 지원하지 않지만, 함수 포인터와 구조체를 이용해 객체지향적인 설계를 흉내낼 수 있습니다.

C에서의 객체지향 구현

1. 캡슐화

C에서 구조체와 함수를 사용해 데이터를 캡슐화할 수 있습니다. 다음은 간단한 예제입니다.

#include <stdio.h>
#include <string.h>

// 캡슐화된 구조체 정의
typedef struct {
    char name[50];
    int age;
    void (*print)(struct Person*);
} Person;

// 출력 함수
void printPerson(Person *p) {
    printf("Name: %s, Age: %d\n", p->name, p->age);
}

int main() {
    // 객체 생성 및 초기화
    Person person;
    strcpy(person.name, "John Doe");
    person.age = 30;
    person.print = printPerson;

    // 객체의 메서드 호출
    person.print(&person);

    return 0;
}

위 코드에서 Person 구조체는 데이터를 포함하고 있으며, print라는 함수 포인터를 통해 메서드를 구현했습니다. 이는 캡슐화의 기초적인 형태를 보여줍니다.

2. 상속

C 언어는 상속을 지원하지 않지만, 구조체를 포함하는 방식으로 상속을 흉내낼 수 있습니다.

#include <stdio.h>
#include <string.h>

// 부모 클래스 역할의 구조체
typedef struct {
    char name[50];
    void (*speak)(void);
} Animal;

// 자식 클래스 역할의 구조체
typedef struct {
    Animal base;  // Animal 구조체 포함
    int legs;
} Dog;

// 메서드 정의
void dogSpeak(void) {
    printf("Woof! Woof!\n");
}

int main() {
    // 객체 생성 및 초기화
    Dog dog;
    strcpy(dog.base.name, "Buddy");
    dog.base.speak = dogSpeak;
    dog.legs = 4;

    // Animal의 메서드 호출
    printf("Animal Name: %s\n", dog.base.name);
    dog.base.speak();

    return 0;
}

여기서 DogAnimal을 포함하여 상속을 흉내냈습니다. Dog 구조체는 Animal의 속성과 동작을 재사용하며, 필요한 경우 확장할 수 있습니다.

3. 다형성

C에서 다형성은 함수 포인터와 공용체(Union)를 활용하여 구현할 수 있습니다. 다음은 간단한 예제입니다.

#include <stdio.h>
#include <string.h>

// 동작 인터페이스 정의
typedef struct {
    void (*draw)(void);
} Shape;

// 사각형
typedef struct {
    Shape base;
    int width, height;
} Rectangle;

// 원
typedef struct {
    Shape base;
    int radius;
} Circle;

// 메서드 정의
void drawRectangle(void) {
    printf("Drawing a rectangle\n");
}

void drawCircle(void) {
    printf("Drawing a circle\n");
}

int main() {
    // Rectangle 객체 생성
    Rectangle rect;
    rect.base.draw = drawRectangle;
    rect.width = 10;
    rect.height = 20;

    // Circle 객체 생성
    Circle circ;
    circ.base.draw = drawCircle;
    circ.radius = 5;

    // 다형성으로 객체 조작
    Shape *shapes[] = { (Shape*)&rect, (Shape*)&circ };

    for (int i = 0; i < 2; ++i) {
        shapes[i]->draw();
    }

    return 0;
}

위 코드에서 Shape 인터페이스는 draw라는 함수 포인터를 정의합니다. 이를 통해 다양한 형태의 객체(Rectangle, Circle)를 다형적으로 처리할 수 있습니다.

4. 추상화

추상화는 인터페이스 역할을 하는 구조체와 함수를 통해 구현됩니다. 위의 다형성 예제는 추상화를 포함한다고 볼 수 있습니다. 구체적인 구현 세부 사항을 숨기고 인터페이스를 통해서만 객체를 다룰 수 있기 때문입니다.

객체지향적인 설계로 얻을 수 있는 이점

  • 유지보수성: 코드의 재사용성과 확장성이 높아짐.
  • 가독성: 논리적인 구조를 통해 코드를 쉽게 이해할 수 있음.
  • 모듈화: 각 기능을 독립적으로 설계할 수 있음.

결론

C 언어에서 객체지향 프로그래밍은 기본적으로 지원되지 않지만, 구조체, 함수 포인터, 그리고 디자인 패턴을 활용하여 구현할 수 있습니다. 이러한 접근법은 C로 작성된 대규모 소프트웨어에서 흔히 사용되며, 객체지향적인 사고방식을 이해하고 활용하는 데 도움을 줍니다.

728x90
반응형