문자열 암호화 및 복호화 구현
포인터를 활용하여 문자열을 순회하고 각 문자의 ASCII 값을 조작함으로써 간단한 시저 암호(Caesar Cipher) 방식의 암호화와 복호화를 구현할 수 있다. 이 방식은 알파벳 범위를 벗어나는 경우를 처리하기 위해 모듈러 연산 또는 조건문을 통해 순환 구조를 유지해야 한다.
#include <stdio.h>
#include <string.h>
#define BUFFER_SIZE 100
void encrypt_text(char *src);
void decrypt_text(char *src);
int main() {
char buffer[BUFFER_SIZE];
printf("문자열 입력: ");
fgets(buffer, BUFFER_SIZE, stdin);
buffer[strcspn(buffer, "\n")] = '\0'; // 개행 문자 제거
encrypt_text(buffer);
printf("암호화된 텍스트: %s\n", buffer);
decrypt_text(buffer);
printf("복호화된 텍스트: %s\n", buffer);
return 0;
}
void encrypt_text(char *src) {
for (; *src != '\0'; src++) {
if (*src >= 'a' && *src <= 'z') {
*src = (*src - 'a' + 1) % 26 + 'a';
} else if (*src >= 'A' && *src <= 'Z') {
*src = (*src - 'A' + 1) % 26 + 'A';
}
}
}
void decrypt_text(char *src) {
for (; *src != '\0'; src++) {
if (*src >= 'a' && *src <= 'z') {
*src = (*src - 'a' - 1 + 26) % 26 + 'a';
} else if (*src >= 'A' && *src <= 'Z') {
*src = (*src - 'A' - 1 + 26) % 26 + 'A';
}
}
}배열에서 최댓값의 메모리 주소 탐색
배열의 이름은 첫 번째 요소의 주소를 가리키는 포인터 상수이다. 배열 내에서 특정 조건을 만족하는 요소의 주소를 찾기 위해 포인터 연산을 사용할 수 있으며, 이를 통해 해당 요소에 직접 접근하거나 수정할 수 있다. 아래 예제는 배열 내 최댓값을 가진 요소의 주소를 반환하는 함수를 보여준다.
#include <stdio.h>
#define ARRAY_LEN 5
void input_data(int arr[], int n);
void print_data(int arr[], int n);
int* find_max_address(int arr[], int n);
int main() {
int data[ARRAY_LEN];
int *max_ptr;
printf("%d개의 정수를 입력하세요:\n", ARRAY_LEN);
input_data(data, ARRAY_LEN);
printf("입력된 데이터: ");
print_data(data, ARRAY_LEN);
max_ptr = find_max_address(data, ARRAY_LEN);
printf("최댓값: %d, 주소: %p\n", *max_ptr, max_ptr);
return 0;
}
void input_data(int arr[], int n) {
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
}
void print_data(int arr[], int n) {
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int* find_max_address(int arr[], int n) {
int *max_pos = arr;
for (int i = 1; i < n; i++) {
if (arr[i] > *max_pos) {
max_pos = &arr[i];
}
}
return max_pos;
}sizeof 연산자와 strlen 함수의 차이점
문자열을 다룰 때 sizeof와 strlen은 자주 혼동되지만 그 목적이 다르다. sizeof는 변수나 자료형이 차지하는 전체 메모리 크기(바이트 단위)를 반환하며, 문자열 배열의 경우 널(null) 종단 문자를 포함한 크기를 계산한다. 반면, strlen은 널 문자 이전까지의 실제 문자열 길이를 반환한다.
#include <stdio.h>
#include <string.h>
#define MAX_LEN 80
int main() {
char str_arr[] = "Pointer arithmetic";
char *str_ptr = "Pointer arithmetic";
// 배열 기반 문자열
printf("배열 sizeof: %zu\n", sizeof(str_arr)); // 널 문자 포함 크기
printf("배열 strlen: %zu\n", strlen(str_arr)); // 실제 문자열 길이
// 포인터 기반 문자열
printf("포인터 sizeof: %zu\n", sizeof(str_ptr)); // 포인터 변수 자체의 크기 (시스템에 따라 4 또는 8)
printf("포인터 strlen: %zu\n", strlen(str_ptr)); // 가리키는 문자열의 길이
return 0;
}
또한, 배열 이름은 상수 포인터이므로 직접 다른 주소를 대입할 수 없지만, 포인터 변수는 값을 변경하여 다른 메모리 영역을 가리키도록 할 수 있다. 두 문자열의 내용을 교환(swap)할 때, 배열은 strcpy를 통해 내용을 복사해야 하지만, 포인터는 주소값만 교환하면 되므로 훨씬 효율적이다.
2차원 배열과 포인터의 관계
2차원 배열은 메모리에 연속적으로 할당되며, 이에 접근하는 포인터의 유형에 따라 해석 방식이 달라진다. int (*ptr)[4]와 같이 선언된 배열 포인터는 4개의 정수를 원소로 갖는 1차원 배열을 가리킨다. 반면, int *ptr[4]는 정수를 가리키는 포인터들의 배열이다. 전자는 행 단위 접근에, 후자는 불연속적인 데이터 집합을 다루는 데 사용될 수 있다.
#include <stdio.h>
int main() {
int matrix[2][4] = {{10, 20, 30, 40}, {50, 60, 70, 80}};
int *elem_ptr;
int (*row_ptr)[4];
// 1. 인덱스를 이용한 접근
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
// 2. 요소 포인터를 이용한 접근
for (elem_ptr = &matrix[0][0]; elem_ptr < &matrix[0][0] + 8; elem_ptr++) {
printf("%d ", *elem_ptr);
}
printf("\n");
// 3. 행 포인터를 이용한 접근
for (row_ptr = matrix; row_ptr < matrix + 2; row_ptr++) {
for (int k = 0; k < 4; k++) {
printf("%d ", (*row_ptr)[k]);
}
printf("\n");
}
return 0;
}문자열 치환 및 잘라내기
문자열 내의 특정 문자를 다른 문자로 교체하거나 특정 지점에서 문자열을 종료하는 것은 포인터 연산의 기초적인 응용이다. 널 종단 문자('\0')를 적절히 배치함으로써 문자열의 끝을 조작할 수 있다.
#include <stdio.h>
#define MAX_TEXT 100
void replace_character(char *text, char target, char replacement);
void truncate_at_char(char *text, char delimiter);
int main() {
char content[MAX_TEXT] = "Data structures and algorithms";
replace_character(content, 'a', '@');
printf("치환 결과: %s\n", content);
truncate_at_char(content, '@');
printf("잘라내기 결과: %s\n", content);
return 0;
}
void replace_character(char *text, char target, char replacement) {
while (*text) {
if (*text == target) {
*text = replacement;
}
text++;
}
}
void truncate_at_char(char *text, char delimiter) {
while (*text) {
if (*text == delimiter) {
*text = '\0';
break;
}
text++;
}
}포인터 배열을 활용한 문자열 정렬
여러 개의 문자열을 정렬할 때, 문자열 자체를 복사하여 이동시키는 것은 비효율적이다. 대신 문자열의 주소를 저장하는 포인터 배열을 사용하여 포인터의 가리키는 대상만 교환하면 메모리 이동 비용을 최소화할 수 있다. 선택 정렬(Selection Sort)이나 버블 정렬(Bubble Sort) 알고리즘을 포인터 배열에 적용할 수 있다.
#include <stdio.h>
#include <string.h>
void sort_string_pointers(char *list[], int count);
int main() {
char *courses[] = {
"Operating Systems",
"Data Structures",
"Computer Networks",
"Algorithms"
};
int n = sizeof(courses) / sizeof(courses[0]);
sort_string_pointers(courses, n);
printf("정렬된 과목 목록:\n");
for (int i = 0; i < n; i++) {
printf("%s\n", courses[i]);
}
return 0;
}
void sort_string_pointers(char *list[], int count) {
int i, j, min_idx;
char *temp;
for (i = 0; i < count - 1; i++) {
min_idx = i;
for (j = i + 1; j < count; j++) {
if (strcmp(list[j], list[min_idx]) < 0) {
min_idx = j;
}
}
// 포인터 교환
if (min_idx != i) {
temp = list[i];
list[i] = list[min_idx];
list[min_idx] = temp;
}
}
}