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차원 배열로 선언하고 초기화합니다.
1. 배열 크기를 명시하고 초기화
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 널 문자까지 명시하여 초기화
char city[5] = {'S', 'e', 'o', 'u', '\0'}; // 널 문자를 포함하여 총 5개 문자
널 문자를 직접 명시하지 않으면 문자열 함수들이 문자열의 끝을 알 수 없어 오류가 발생할 수 있습니다.
2. 문자열 리터럴로 초기화: 가장 일반적이고 편리한 방법입니다. 컴파일러가 자동으로 널 문자를 추가해 줍니다.
char message[6] = "Hello"; // 컴파일러가 'H', 'e', 'l', 'l', 'o', '\0'로 초기화. 크기는 6
char name[] = "World"; // 배열 크기를 생략하면 "World" + '\0' -> 크기 6으로 자동 결정
이때, 문자열 리터럴의 길이(널 문자 포함)가 배열의 크기를 초과하면 컴파일 경고 또는 오류가 발생할 수 있습니다.
char short_array[4] = "LongString"; // 오류/경고: 배열 크기가 문자열 리터럴보다 작음. \0이 들어갈 공간이 부족!
3. 초기화하지 않고 선언: 일반 배열과 마찬가지로, 초기화하지 않고 선언만 하면 배열 요소는 쓰레기 값을 가집니다. 이 경우, 문자열을 사용하기 전에 반드시 적절한 문자열을 할당해야 합니다.
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 언어와의 호환성을 위해 알아두어야 할 중요한 개념입니다.