- C언어에서 하던 것처럼 문자열을 입력받아보자.
C언어를 하다 보면, 어떤 문자열을 선언한 다음 scanf 함수를 사용하여 일일히 값을 집어넣은 경험이 있을 것이다. C++에서도 문자열을 선언하고, 키보드를 통해 값을 입력받은 후 문자열에 저장할 수 있다.
#include <iostream>
int main() {
char name[11];
std::cout << "이름을 입력: ";
std::cin >> name;
std::cout << "이름은 " << name << "입니다" << std::endl;
return 0;
}
각 칸이 character type인, 총 11칸을 가지는 문자열을 선언하였고 그 문자열의 변수명인 name인 변수를 선언하였다.
11칸의 문자열인 만큼, 영어로는 10글자, 한글은 5글자를 받아올 수 있겠다. (영어는 한 글자당 1바이트, 한글은 2바이트다.)
이름을 Grace라고 입력했다고 가정하자. 그렇다면 std::cin을 통해 초기화된 name의 변수의 문자열은 아래와 같이 저장될 것이다.
11칸을 선언했는데, 5글자밖에 저장을 안 했다고 걱정하지 않아도 된다. 저장되지 않은 공간은 \0 (null값) 으로 알아서 초기화될 것이다.
- 문자열의 마지막 문자는 왜 \0이어야 하는 것일까?
위 질문을 통해 char type를 가진 배열이 '문자열'이냐, '단순 문자 배열' 이냐를 답할 수 있다.
char name1[6] = {'G', 'r', 'a', 'c', 'e', '\0'};
char name2[5] = {'G', 'r', 'a', 'c', 'e'};
std::cout은 \0 (null)을 만나야지 문자열이 끝났다고 인식한다. 일종의 마침표라고 생각하기 때문에, cout을 통해 문자열을 출력하고 싶다면 \0을 무조건 넣어야 한다. 만약 \0을 넣지 않고 cout을 사용했다면.. name2배열의 모든 값을 출력하고도 뒤에 저장된 쓰레기값까지 읽어올 것이다.
하지만 \0을 포함하는 것처럼 세세한 것까지 신경쓰기에는 머리가 아프니... 대부분은 std::string을 사용한다. string은 문자열처럼 미리 길이를 선언한 후에 입력받을 필요도 없고, 문자열의 마지막 원소에 \0도 자동으로 넣어주기 때문에 편리하다. 또한 pointer로 참조도 쉽기 때문에, string을 사용하지 않을 수 없다!
- 만약 선언한 문자열 길이보다 더 긴 값을 입력한다면?
프로그램은 비정상 종료하게 될 것이다.
이 경우처럼 선언한 문자열보다 더 긴 문자열을 저장하려고 하면 '버퍼 오버플로우'가 발생할 수 있다.
버퍼 오버플로우란, 프로그램이 변수에게 할당한 메모리보다 더 큰 크기의 정보를 저장하려 할 때 발생하는 일종의 오류이다. 언뜻 보기에는 별 것 아닌 것처럼 느껴지곤 한다. 배열에서 삐죽 튀어나와서 정보를 저장한다고 뭐가 위험해질까?
하지만 text영역 바로 뒤에, 사전에 정의되어 있었던 다른 변수가 저장되어 있었다면?
애꿎은 다른 변수만 피를 보는 상황이 나오기 때문에, 버퍼 오버플로우가 나기 이전에 컴파일 오류를 발생시키는 것이다. 그렇기 때문에 문자열을 입력받는 상황이라면, 마지막 원소를 \0으로 설정하는것을 잊지 말자!
- 문자열 길이를 정의하지 않고, 일단 입력받고 생각하면 안되려나?
그런 편한 기능을 가진 클래스가 바로 string 클래스이다. 아래 예제코드를 확인하면서 string클래스를 알아가보자.
#include <iostream>
#include <string>
int main() {
const int size = 3;
std::string todo[size] = { "장보기", "빨래 하기" };
std::cout << "할 일을 입력하세요: ";
std::getline(std::cin, todo[2]);
for (int i = 0; i < size; i++) {
std::cout << "오늘 할 일" << i+1 << ": " << todo[i] << std::endl;
};
return 0;
}
편하게 생각하면, string도 int, char처럼 하나의 변수의 type이다. 그렇기 때문에 string배열도 만들 수 있다!
그렇다면 평소에 C언어에서 사용한 것 처럼 todo[3] = {...}처럼 정의하는 것과 뭐가 다르냐고 물을 수 있지만, 여러가지 편의성이 담겨 있다.
- 메모리를 저절로 조절해준다
앞에서 알아본 것처럼 C-스트링은 사용자가 직접 문자열의 크기를 지정하여 (\0의 자리까지 계산하여) 제한된 입력을 받아와야 했지만, string클래스는 사전에 문자열의 크기를 지정하지 않아도 된다. 추가로 문자열을 받은 이후 \0까지 추가해준다. - 동적할당 vs 정적할당
C-스트링은 정적 할당이다. 컴파일 시작부터 Stack memory에 각인되어 프로그램 종료시까지 삭제가 불가하다. 결과적으로 배열의 크기가 바뀌어야 할 상황에서 큰 걸림돌이 될 수 있다.
반면 string클래스는 동적 할당이다. 그렇기 때문에 자유자재로 배열의 크기를 조절할 수 있고, 더 유동적인 접근이 가능하다. - string클래스는 배열의 성격도 갖는다.
C-스트링은 전통적인 '배열'의 성격을 갖기 때문에 포인터를 쓰기에도, 인덱스(todo[n] 처럼 []안에 숫자로 값 접근)를 쓰기에도 편리하다. 하지만 string도 그 성격을 갖고 있다.
위 코드처럼 2차원 배열을 만들 수 있고, todo[0][1]처럼 2차원 배열 포인터도 사용이 가능하다!
- 참고 _ getline()의 매개변수
getline()함수는 여러 군데에서 많이 쓰인다. 그렇기 때문에 매개변수를 안다면 언젠가 요긴히 써먹을 것이다.
std::getline(std::istream& is, std::string& str, char delim);
- is ; 입력 스트림
입력 스트림이란, 데이터를 외부(키보드, 다른 파일 etc)에서 읽어오는 경로라고 생각하면 된다.
std::cin이면 키보드에서, std::ifstream이면 다른 파일에서 읽어오는 경로다~ 라고 알리는 것이 된다.
우리는 키보드에서 데이터를 읽어올 것이기 때문에 std::cin을 사용한다. - str ; 문자열 변수
입력 스트림으로부터 받아온 데이터를 저장할 변수명이 되겠다. 이 때 문자열의 각 원소의 type은 string이 되어야 한다. C-스트링도 안 된다! - delim : 구분자
보통 사용자는 Enter으로 입력을 마친다. 하지만 Enter이 아니라 쉼표로 입력을 마친다거나, \n을 포함한 문자열을 받아야 할 때 요긴하게 사용된다.
'C++' 카테고리의 다른 글
C++ / cin을 활용하여 키 입력받기 (0) | 2025.03.24 |
---|