반응형
Understanding Generic Programming / 제네릭 프로그래밍 이해
제네릭 프로그래밍이란?
- 정의: 데이터 타입에 관계없이 동작하는 코드를 작성하는 기법.
- 목적:
- 코드 재사용성: 다양한 데이터 타입에 대해 동일한 코드를 사용.
- 유연성: 타입에 구애받지 않고 동작.
- 중복 제거: 동일한 기능을 여러 타입으로 구현할 필요 없음.
C 언어에서 제네릭 프로그래밍
C 언어는 템플릿(C++에서 제공)을 지원하지 않으므로, 매크로와 void * 포인터를 활용하여 제네릭 프로그래밍을 구현합니다.
사용 사례
- 데이터 구조(스택, 큐, 링크드 리스트 등)에서 다양한 데이터 타입 처리.
- 정렬, 검색 알고리즘에서 데이터 타입 독립적 구현.
13.2. Macros and Inline Functions / 매크로와 인라인 함수
매크로를 사용한 제네릭 프로그래밍
매크로 예제: MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int x = 10, y = 20;
printf("Max: %d\n", MAX(x, y)); // 출력: 20
double a = 5.5, b = 3.3;
printf("Max: %.2f\n", MAX(a, b)); // 출력: 5.5
return 0;
}
매크로의 장점
- 다양한 데이터 타입에서 동작.
- 간단한 함수 구현에 유리.
매크로의 단점
- 디버깅 어려움: 매크로는 텍스트 치환이므로, 디버깅 과정에서 원래 코드를 확인하기 어려움.
- 부작용 가능:
printf("%d\n", MAX(x++, y)); // x가 두 번 증가할 수 있음
인라인 함수를 사용한 대체
인라인 함수는 매크로의 단점을 해결하며, 컴파일러가 함수 호출 대신 코드를 삽입하도록 요청합니다.
인라인 함수 예제
inline int max(int a, int b) {
return (a > b) ? a : b;
}
int main() {
int x = 10, y = 20;
printf("Max: %d\n", max(x, y)); // 출력: 20
return 0;
}
장점
- 타입 안전성 제공.
- 디버깅 편리.
- 부작용 방지.
단점
- 함수 크기가 클 경우 코드 팽창 가능.
13.3. Using void * Pointers / void * 포인터 사용
void *는 타입이 지정되지 않은 포인터로, 제네릭 데이터를 처리하는 데 유용합니다.
1. void *를 사용한 배열 검색
#include <stdio.h>
#include <string.h>
int compare_int(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
void *find(void *array, size_t n, size_t size, void *target, int (*cmp)(const void *, const void *)) {
for (size_t i = 0; i < n; i++) {
void *element = (char *)array + i * size;
if (cmp(element, target) == 0) {
return element;
}
}
return NULL;
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int target = 3;
int *result = (int *)find(arr, 5, sizeof(int), &target, compare_int);
if (result) {
printf("Found: %d\n", *result);
} else {
printf("Not found\n");
}
return 0;
}
코드 설명
- void *로 배열 요소의 타입과 관계없이 검색 가능.
- 비교 함수(cmp)로 데이터 타입별 비교 방식을 전달.
2. void *를 사용한 링크드 리스트
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
void *data;
struct Node *next;
} Node;
Node *createNode(void *data) {
Node *node = (Node *)malloc(sizeof(Node));
node->data = data;
node->next = NULL;
return node;
}
void printList(Node *head, void (*print)(void *)) {
Node *current = head;
while (current) {
print(current->data);
current = current->next;
}
}
void printInt(void *data) {
printf("%d -> ", *(int *)data);
}
int main() {
int a = 10, b = 20, c = 30;
Node *head = createNode(&a);
head->next = createNode(&b);
head->next->next = createNode(&c);
printList(head, printInt);
printf("NULL\n");
return 0;
}
코드 설명
- 링크드 리스트의 데이터는 void *로 저장되어 타입 독립적으로 사용 가능.
- print 함수 포인터를 활용해 데이터를 출력.
13.4. Writing Type-Independent Code / 타입에 독립적인 코드 작성
1. 제네릭 동적 배열 생성
#include <stdio.h>
#include <stdlib.h>
#define CREATE_ARRAY(type, size) (type *)malloc(sizeof(type) * (size))
int main() {
int *arr = CREATE_ARRAY(int, 5);
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
printf("%d ", arr[i]);
}
free(arr);
return 0;
}
설명
- CREATE_ARRAY 매크로로 동적 배열을 생성하며, 데이터 타입과 크기를 동적으로 지정 가능.
2. 제네릭 정렬: qsort
C 표준 라이브러리의 qsort 함수는 타입 독립적인 정렬을 제공합니다.
예제
#include <stdio.h>
#include <stdlib.h>
int compare_int(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
int main() {
int arr[] = {5, 3, 2, 4, 1};
size_t n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), compare_int);
for (size_t i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
설명
- qsort는 배열 크기, 요소 크기, 비교 함수 포인터를 받아 제네릭 정렬을 수행.
- 데이터를 정렬할 때 데이터 타입에 구애받지 않음.
요약
- 매크로:
- 텍스트 치환을 통해 간단한 제네릭 코드를 작성.
- 인라인 함수:
- 타입 안전성과 디버깅 편의성을 제공하며, 매크로를 대체.
- void * 포인터:
- 타입 독립적인 데이터 처리 및 함수 설계.
- 타입 독립적 데이터 구조:
- 동적 배열, 링크드 리스트, 정렬 등에서 활용.
- C 표준 라이브러리 활용:
반응형
'Computer Science > C 언어' 카테고리의 다른 글
[C언어 19] Advanced Uses of Pointers 포인터의 고급 활용 (1) | 2024.12.27 |
---|---|
[C언어 18] File Input/Output (파일 입출력) (0) | 2024.12.25 |
[C #17] 구조체(Structs), 공용체(Unions), 열거형(Enums) (0) | 2024.12.14 |
[C #16] 문자열 입력 기본 함수와 상황별 선택 (3) | 2024.12.14 |
[C #15] 문자열 (Strings) (0) | 2024.12.13 |