C 스타일 문자열
지금까지 숫자나 문자와 같은 단일 데이터를 저장하는 변수와, 동일한 타입의 여러 데이터를 묶어 저장하는 배열에 대해 학습했습니다.
이제는 컴퓨터 프로그램에서 가장 흔히 다루는 데이터 중 하나인 문자열(String)에 대해 알아볼 차례입니다.
C++에는 문자열을 다루는 두 가지 주요 방식이 있습니다.
이 장에서는 C 언어로부터 계승된 방식이자 문자열의 가장 기본적인 형태인 C-스타일 문자열(C-Style String)에 대해 자세히 살펴보겠습니다.
이 방식은 문자 배열의 특별한 형태로, C++ 표준 라이브러리의 std::string과는 다른 방식으로 동작합니다.
C-스타일 문자열이란 무엇인가?
C-스타일 문자열은 널(null) 문자('\0')로 끝나는 문자들의 배열입니다.
여기서 널 문자는 아스키 코드 값 0을 가지는 특수한 문자로, 문자열의 끝을 나타내는 표식(terminator)으로 사용됩니다.
C-스타일 문자열은 C++에서 char 타입의 배열로 표현됩니다.
char배열: 문자 하나하나가char타입으로 배열에 저장됩니다.- 널 종료(
\0): 문자열의 실제 내용 뒤에는 항상 널 문자\0이 자동으로 추가되어 문자열의 끝을 알려줍니다.\0도 하나의 문자이므로, 문자열을 저장할 배열의 크기를 계산할 때는 반드시 널 문자를 위한 공간 1바이트를 추가로 고려해야 합니다. - 문자열 리터럴: 큰따옴표(
" ")로 묶인 문자들은 컴파일러에 의해 자동으로 널 종료된 C-스타일 문자열로 처리됩니다. (예:"Hello"는 실제 메모리에 'H', 'e', 'l', 'l', 'o', '\0'의 6개 문자로 저장됩니다.)
C-스타일 문자열의 선언 및 초기화
C-스타일 문자열은 char 타입의 1차원 배열로 선언하고 초기화합니다.
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 널 문자까지 명시하여 초기화
char city[5] = {'S', 'e', 'o', 'u', '\0'}; // 널 문자를 포함하여 총 5개 문자널 문자를 직접 명시하지 않으면 문자열 함수들이 문자열의 끝을 알 수 없어 오류가 발생할 수 있습니다.
문자열 리터럴로 초기화: 가장 일반적이고 편리한 방법입니다. 컴파일러가 자동으로 널 문자를 추가해 줍니다.
char message[6] = "Hello"; // 컴파일러가 'H', 'e', 'l', 'l', 'o', '\0'로 초기화. 크기는 6
char name[] = "World"; // 배열 크기를 생략하면 "World" + '\0' -> 크기 6으로 자동 결정이때, 문자열 리터럴의 길이(널 문자 포함)가 배열의 크기를 초과하면 컴파일 경고 또는 오류가 발생할 수 있습니다.
char short_array[4] = "LongString"; // 오류/경고: 배열 크기가 문자열 리터럴보다 작음. \0이 들어갈 공간이 부족!초기화하지 않고 선언: 일반 배열과 마찬가지로, 초기화하지 않고 선언만 하면 배열 요소는 쓰레기 값을 가집니다. 이 경우, 문자열을 사용하기 전에 반드시 적절한 문자열을 할당해야 합니다.
char buffer[100]; // 100칸짜리 char 배열 (쓰레기 값)
// buffer = "New String"; // 이렇게 직접 대입하는 것은 불가능! (배열 이름은 상수 포인터처럼 동작)배열 이름 자체는 배열의 시작 주소를 나타내는 상수 포인터처럼 동작하므로, 다른 배열이나 문자열 리터럴을 직접 대입할 수 없습니다.
문자열을 복사하려면 별도의 함수를 사용해야 합니다.
C-스타일 문자열 입출력
std::cout과 std::cin을 사용하여 C-스타일 문자열을 출력하거나 입력받을 수 있습니다.
#include <iostream>
int main() {
char name[20]; // 최대 19글자와 널 문자를 저장할 수 있는 배열
std::cout << "이름을 입력하세요: ";
std::cin >> name; // 공백 전까지만 입력받음
std::cout << "안녕하세요, " << name << "님!" << std::endl;
// 만약 "John Doe"를 입력하면, name에는 "John"만 저장됩니다.
// "Doe"는 입력 버퍼에 남아있게 됩니다.
// 공백을 포함한 문자열 입력 받기 (버퍼 비우기 필요)
std::cin.ignore(); // 이전 입력 버퍼를 비웁니다.
// (std::cin.ignore(제거할_최대_문자수, 제거_종료_문자) 형태로 사용하는 것이 더 안전)
char full_name[50];
std::cout << "성함을 입력하세요 (공백 포함): ";
std::cin.getline(full_name, sizeof(full_name)); // 한 줄 전체를 입력받음
std::cout << "풀 네임: " << full_name << std::endl;
return 0;
}std::cin >> name;: 공백(space), 탭(tab), 줄 바꿈(newline) 문자를 구분자로 사용하여 단어 단위로 입력받습니다. 따라서 Hello World를 입력하면name에는 Hello만 저장됩니다.std::cin.getline(배열이름, 크기);: 한 줄 전체를 입력받을 때 사용합니다. 두 번째 인자는 배열의 최대 크기를 나타내며, 널 문자를 포함하여 저장될 수 있는 최대 문자 수를 지정합니다. 입력 버퍼에 남아있는 이전 개행 문자(\n)를 처리하기 위해std::cin.ignore()를 사용해야 할 때가 많습니다.
C-스타일 문자열 관련 함수
C-스타일 문자열은 일반 배열이므로, 단순히 대입 연산자나 비교 연산자로 조작할 수 없습니다.
문자열의 길이를 구하거나, 복사하거나, 비교하는 등의 작업을 위해서는 <cstring> (C 언어에서는 <string.h>) 헤더에 정의된 표준 라이브러리 함수들을 사용해야 합니다.
| 함수명 | 설명 | 예시 |
|---|---|---|
strlen(str) | 문자열 str의 길이 (널 문자 제외)를 반환. | strlen("Hello")는 5를 반환 |
strcpy(dest, src) | src 문자열을 dest 문자열로 복사. dest는 src를 충분히 담을 수 있어야 함. | strcpy(buffer, "World") |
strncpy(dest, src, n) | src 문자열의 최대 n개 문자를 dest로 복사. 널 종료를 보장하지 않으므로 주의. | strncpy(buffer, "LongString", 5) |
strcat(dest, src) | src 문자열을 dest 문자열의 끝에 이어 붙임 (concatenation). dest는 충분히 커야 함. | strcat(s1, s2) |
strncat(dest, src, n) | src 문자열의 최대 n개 문자를 dest에 이어 붙임. | strncat(s1, s2, 3) |
strcmp(str1, str2) | 두 문자열 str1과 str2를 비교. | 0 반환: 같음 / <0 반환: str1 < str2 / >0 반환: str1 > str2 |
strncmp(str1, str2, n) | 두 문자열의 처음 n개 문자를 비교. | strncmp("apple", "apricot", 2)는 0 |
#include <iostream>
#include <cstring> // C-스타일 문자열 함수를 위해 포함
int main() {
char str1[20] = "Hello";
char str2[20];
char str3[20] = "World";
// 1. 길이 구하기: strlen()
std::cout << "str1의 길이: " << strlen(str1) << std::endl; // 출력: 5
// 2. 문자열 복사: strcpy()
strcpy(str2, str1); // str1의 내용을 str2로 복사
std::cout << "str2: " << str2 << std::endl; // 출력: Hello
// 3. 문자열 이어 붙이기: strcat()
strcat(str1, ", "); // str1 뒤에 ", " 이어 붙임
strcat(str1, str3); // str1 뒤에 str3 이어 붙임
std::cout << "str1 (연결 후): " << str1 << std::endl; // 출력: Hello, World
// 4. 문자열 비교: strcmp()
char s_a[] = "apple";
char s_b[] = "banana";
char s_c[] = "apple";
int cmp1 = strcmp(s_a, s_b); // "apple" vs "banana"
int cmp2 = strcmp(s_a, s_c); // "apple" vs "apple"
std::cout << "s_a vs s_b 결과: " << cmp1 << std::endl; // 음수 (s_a가 s_b보다 사전적으로 앞섬)
std::cout << "s_a vs s_c 결과: " << cmp2 << std::endl; // 0 (s_a와 s_c는 같음)
if (strcmp(s_a, s_b) < 0) {
std::cout << "apple이 banana보다 사전적으로 빠릅니다." << std::endl;
}
return 0;
}⚠️ 주의사항 (버퍼 오버플로우):
strcpy, strcat 함수는 목적지 배열 크기를 검사하지 않습니다.
복사하거나 이어 붙일 문자열이 목적지 배열보다 크면 할당된 메모리 영역을 넘어 다른 메모리를 덮어쓸 수 있고, 이는 버퍼 오버플로우(Buffer Overflow) 같은 심각한 보안 취약점이나 프로그램 충돌로 이어질 수 있습니다.
이 문제를 피하려면 strncpy, strncat처럼 n이 붙은 함수나, 더 현대적인 std::string 사용을 권장합니다.
C-스타일 문자열의 한계와 std::string
C-스타일 문자열은 C++의 기본 문자열 처리 방식이지만 다음과 같은 여러 한계를 가집니다.
- 수동적인 메모리 관리: 널 문자를 직접 관리해야 하고, 버퍼 오버플로우를 방지하기 위해 배열 크기를 항상 신경 써야 합니다.
- 비직관적인 연산: 문자열을 복사하거나 비교할 때 연산자(
=,==)를 사용할 수 없고, 함수(strcpy,strcmp)를 사용해야 합니다. - 고정 크기: 배열로 선언되므로 한번 크기가 결정되면 동적으로 크기를 늘리거나 줄일 수 없습니다.
- 다양한 기능 부족: 문자열 검색, 부분 문자열 추출 등 편리한 기능이 기본적으로 제공되지 않습니다.
이러한 한계점 때문에 C++에서는 C-스타일 문자열보다 훨씬 더 편리하고 안전하며 강력한 std::string 클래스를 표준 라이브러리로 제공합니다.
다음 절에서 std::string에 대해 자세히 다룰 것입니다.
하지만 C-스타일 문자열은 여전히 많은 레거시 코드에서 사용되고, C 언어와의 호환성을 위해 알아두어야 할 중요한 개념입니다.