C 언어로 OOP 스타일의 Stack 구현하기
최근 C 언어에서 객체지향 프로그래밍(OOP)을 구현하려고 시도하면서, C++에서 많이 사용되는 벡터(Vector)를 구현하기 전에 먼저 스택(Stack)을 구현해보기로 했습니다. 자료구조를 공부하면서 Stack의 push 연산부터 구현해보기로 했고, 이를 OOP 스타일로 어떻게 구현할 수 있는지 살펴보았습니다.
이번에 구현한 스택은 C 언어에서 객체지향 프로그래밍의 개념을 적용하여, 스택에 데이터를 push, pop, clear, delete하는 메서드를 갖는 구조체를 작성하는 방식입니다.
프로젝트 링크
이 프로젝트는 GitHub - Object-Oriented-C-Language에서 확인할 수 있습니다. 이곳의 algorithm.h와 algorithm.c 파일을 참고하여 스택을 구현했습니다.
algorithm.h (헤더 파일)
#include "main.h"
#ifndef __ALGORITHM_H
#define __ALGORITHM_H
#pragma pack(push, 1)
typedef struct STACK {
void** data; // 스택에 저장될 데이터
size_t top; // 스택의 탑(가장 최근에 저장된 데이터)
size_t size; // 스택이 차지하는 메모리 크기
size_t byteSize; // 각 데이터 항목의 크기
void (*push)(struct STACK*, void*); // push 함수 포인터
void* (*pop)(struct STACK*); // pop 함수 포인터
void (*clear)(struct STACK*); // clear 함수 포인터
void (*delete)(struct STACK*); // delete 함수 포인터
} Stack;
#pragma pack(pop)
Stack new_stack(size_t type); // 새로운 스택 생성 함수
#define new_Stack(type) new_stack(sizeof(type)) // 매크로를 사용하여 타입에 맞는 스택 생성
#endif
이 헤더 파일에서는 Stack 구조체를 정의하고, 스택을 조작하는 메서드들을 함수 포인터로 연결했습니다. 각 메서드는 스택에 데이터를 추가하거나 제거하고, 스택을 초기화하거나 삭제하는 기능을 합니다. 또한, new_stack() 함수는 새로운 스택을 생성하는 함수로, 각 데이터 항목의 크기를 인자로 받습니다.
algorithm.c (구현 파일)
#include "algorithm.h"
// stackPush: 스택에 데이터를 추가하는 함수
// stack: 스택 객체, data: 스택에 넣을 데이터
static void stackPush(Stack* stack, void* data) {
// 스택에 저장된 데이터의 크기를 확장하기 위해 realloc 호출
// 현재 스택의 크기보다 하나 더 큰 크기로 재할당
stack->data = (void**)realloc(stack->data, (stack->top + 1) * sizeof(void*));
// 새 공간에 데이터를 할당하고, 해당 위치에 data 복사
*(stack->data + stack->top) = malloc(stack->byteSize); // 새 데이터를 위한 메모리 할당
memcpy(*(stack->data + stack->top), data, stack->byteSize); // 실제 데이터 복사
// 스택의 top을 증가시키고, 스택의 크기를 다시 계산
stack->top++;
stack->size = stack->top * sizeof(void*);
}
// stackPop: 스택에서 데이터를 꺼내는 함수
// stack: 스택 객체
static void* stackPop(Stack* stack) {
// 스택의 맨 위에서 데이터를 제거하고, 그 데이터를 반환
void* removedData = *(stack->data + stack->top - 1);
// top을 하나 줄이고, 스택 크기 재계산
stack->top--;
stack->size = stack->top * sizeof(void*);
// 스택 메모리를 재할당하여 크기 줄이기
stack->data = (void**)realloc(stack->data, stack->size);
// 꺼낸 데이터 반환
return removedData;
}
// stackClear: 스택에 저장된 모든 데이터를 해제하고, 스택을 비우는 함수
// stack: 스택 객체
static void stackClear(Stack* stack) {
// 스택에 있는 각 데이터를 해제
for (size_t i = 0; i < stack->top; i++) {
free(*(stack->data + i)); // 각 데이터 메모리 해제
}
// 스택 정보를 초기화
stack->top = 0; // 스택의 top을 0으로 초기화
stack->size = 0; // 스택의 크기도 0으로 초기화
free(stack->data); // 스택 데이터 메모리 해제
stack->data = NULL; // 데이터 포인터 초기화
}
// stackDelete: 스택을 삭제하는 함수
// stack: 스택 객체
static void stackDelete(Stack* stack) {
// 스택을 비운 후
stackClear(stack);
// 스택 관련 정보를 초기화
stack->byteSize = 0; // 각 데이터 항목의 크기 초기화
stack->data = NULL; // 데이터 포인터 초기화
}
// new_stack: 새로운 스택을 생성하는 함수
// type: 스택에 저장될 데이터 타입의 크기
Stack new_stack(size_t type) {
Stack stack;
stack.data = NULL; // 초기 데이터 포인터는 NULL
stack.push = stackPush; // push 함수 포인터 연결
stack.pop = stackPop; // pop 함수 포인터 연결
stack.clear = stackClear; // clear 함수 포인터 연결
stack.delete = stackDelete; // delete 함수 포인터 연결
stack.top = 0; // 초기 top 값은 0
stack.size = 0; // 초기 크기는 0
stack.byteSize = type; // 데이터 항목 크기 설정 (type)
return stack; // 초기화된 스택 객체 반환
}
각 함수 설명:
- stackPush:
- 스택에 데이터를 추가하는 함수입니다.
- realloc을 사용해 기존의 data 배열을 확장하고, 새 데이터를 할당하여 복사합니다.
- stackPop:
- 스택에서 데이터를 제거하는 함수입니다.
- top을 감소시키고, 스택의 크기를 다시 계산하여 realloc을 사용해 메모리를 조정합니다.
- stackClear:
- 스택에 저장된 모든 데이터를 해제하고 스택을 초기화합니다.
- stackDelete:
- stackClear를 호출하여 스택을 비운 후, 스택 구조체 내의 추가적인 메모리 해제를 수행합니다.
- new_stack:
- 새로운 스택을 생성하는 함수입니다. 타입 크기를 인자로 받아 스택 구조체를 초기화하고 반환합니다.
매크로 new_Stack:
매크로 new_Stack(type)은 new_stack(sizeof(type))을 호출하는 방식으로, 스택을 쉽게 생성할 수 있게 도와줍니다. 예를 들어, new_Stack(int)를 사용하면 int 타입에 맞는 스택을 생성할 수 있습니다.
예시 사용법:
int main() {
Stack intStack = new_Stack(int); // int 타입의 스택 생성
int value = 10;
intStack.push(&intStack, &value); // 값 10을 스택에 push
int* poppedValue = (int*)intStack.pop(&intStack); // 값을 pop하고 출력
printf("Popped value: %d\n", *poppedValue); // 출력: Popped value: 10
intStack.delete(&intStack); // 스택 삭제
return 0;
}
결론
C 언어에서 객체지향적인 방식으로 스택을 구현하면, 코드의 재사용성과 유지보수성이 높아지며, 다양한 데이터 타입을 처리할 수 있는 유연성을 갖게 됩니다. void* 포인터를 활용해 어떤 데이터 타입도 저장할 수 있도록 구현한 것이 큰 특징입니다. 이를 통해 C 언어에서도 객체지향 프로그래밍의 핵심 개념을 적용할 수 있다는 점에서 유용한 학습이 되었습니다.
'C' 카테고리의 다른 글
C) 개발 환경 설정: 컴파일러 설치 및 설정 (1) | 2024.11.22 |
---|---|
C) C 언어 소개 (0) | 2024.11.22 |
C) C언어로 회원가입/로그인 시스템 구현하기! (1) | 2023.03.19 |
C) C언어로 자바 입출력 따라하기! (4) | 2022.09.18 |
C) 여러 C 파일 한번에 컴파일 하는법! (0) | 2022.09.14 |