C 언어에서의 객체지향 프로그래밍
C 언어는 절차지향 프로그래밍 언어로 잘 알려져 있습니다. 하지만 C++이나 Java 같은 객체지향 언어가 등장하기 전에도 객체지향적인 접근법을 C에서 구현하려는 시도는 꾸준히 이어져 왔습니다. 이번 글에서는 C 언어에서 객체지향 프로그래밍(Object-Oriented Programming, OOP)의 개념을 이해하고 이를 구현하는 방법을 알아보겠습니다.
객체지향 프로그래밍의 기본 개념
객체지향 프로그래밍은 크게 다음 네 가지 특징을 가집니다.
- 캡슐화 (Encapsulation)
- 데이터와 데이터를 처리하는 함수를 하나로 묶어 관리.
- 상속 (Inheritance)
- 기존 클래스(또는 구조체)의 속성과 동작을 재사용하거나 확장.
- 다형성 (Polymorphism)
- 동일한 인터페이스를 통해 다양한 형태의 객체를 조작 가능.
- 추상화 (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;
}
여기서 Dog
는 Animal
을 포함하여 상속을 흉내냈습니다. 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로 작성된 대규모 소프트웨어에서 흔히 사용되며, 객체지향적인 사고방식을 이해하고 활용하는 데 도움을 줍니다.
'c 언어 > c 언어 문법' 카테고리의 다른 글
C와 하드웨어 (0) | 2024.12.15 |
---|---|
C 멀티스레딩 이해하기 (0) | 2024.12.15 |
C 언어의 표준 라이브러리 (0) | 2024.12.14 |
C 언어의 전처리기 (0) | 2024.12.14 |
C 이중 포인터와 함수 포인터, 포인터 배열과 배열 포인터 (0) | 2024.12.14 |