안녕하세요, katte입니다.
이번 글에서는 cin 객체가 문자를 받아올 때 cin 객체를 리턴하는 것을 이용한 간단한 예제를 살펴보도록 하겠습니다.
이전 글에서 istream의 멤버함수, get이 cin 객체를 리턴한다는 이야기를 했던 적이 있습니다.
또한 이렇게 리턴된 cin 객체가 bool형으로 변환된다는 것까지 말이죠.
자세한 내용은 다음 글을 참고하시길 바랍니다.
https://katteniiki.tistory.com/10
그런데 get 함수뿐만 아니라, istream에서 오버로딩된 >> 연산자도 cin 객체를 리턴합니다.
즉, >> 연산자를 통해 문자를 받아올 때에도 get 함수 때와 마찬가지로 정상적으로 받아왔는지의 여부를 판단할 수 있다는 것입니다.
자, 한번 다음과 같은 상황을 생각해봅시다.
int n;
std::cin >> n;
딱히 더 생각할 것도 없는 두줄짜리 코드입니다만, 만약 우리가 n에 정수가 아닌 단어를 입력하면 어떻게 될까요?
이때 일어나게 될 일을 요약하면 다음과 같습니다.
1. n의 값은 변하지 않는다.
2. 입력 버퍼에 잘못된 입력이 그대로 유지된다.
3. cin 객체에 에러 플래그가 설정된다.
4. cin 멤버함수 호출이 false를 리턴한다.
이러한 상황이 벌어졌을 때, 우리가 다시 정상적으로 숫자를 입력받기 위해 해야 할 일은 다음과 같을 것입니다.
먼저 4번을 보면, cin 객체가 bool형으로 변환되어 false를 리턴한다는 것을 알 수 있습니다.
즉, 우리는 입력을 정상적으로 받았는지에 대한 검사를 시행하기 위해 cin객체를 사용할 수 있습니다.
3번, cin 객체에 에러 플래그가 설정되게 되면 cin 객체는 더 이상 입력을 받지 않게 됩니다.
더 이상 입력을 받지 않아도 괜찮은 상황이라면 상관 없겠지만, 잘못된 입력을 무시하고 다시 한번 정상적인 입력을 받아야 하는 상황이라고 가정해봅시다.
그렇다면 cin 객체에 설정된 에러 플래그를 지워야 할 것입니다.
그리고 그때에 사용하는 함수가 바로 멤버함수 clear입니다. clear 멤버함수를 사용하면 설정되었던 플래그가 지워져 다시 정상적으로 입력을 받을 수 있게 됩니다.
마지막으로 2번, 입력 버퍼에 남아있는 잘못된 입력을 없애야 할 것입니다.
남아있는 이 입력을 없애지 않는다면 백날 clear 함수로 플래그를 지워봤자 여전히 잘못된 입력은 남아있기에, 다시 잘못된 입력을 받아 에러 플래그가 설정되는 무한굴레에 빠지게 되겠죠?
즉, 정리하자면 다음과 같습니다.
1. cin의 에러 플래그 초기화
2. 입력 버퍼에 남아있는 불량 입력 제거
3. 사용자에게 다시 입력할 것을 요구
아니 그런데, clear 함수를 통해 에러 플래그를 초기화 하는 것은 알겠는데, 입력 버퍼에 남아있는 입력은 어떻게 비울까요?
방법은 여러가지가 있습니다.
먼저 첫번째는, 매개변수가 없는 cin.get()을 통해 잘못된 입력을 전부 읽어들이는 방법입니다.
가장 직관적이고 명확한 방법일 것입니다.
두번째로는 C언어를 배울 때 한 번쯤 들어보셨을 fflush 함수를 이용하는 방법이 있습니다.
그러나 fflush가 입력 버퍼를 비우는 역할을 하는 것은 표준으로 정의된 것이 아니며, 잘못된 사용법이라고 하니 입력 버퍼를 비우는 용도로는 사용하지 않는 편이 좋을 것 같습니다.
세번째로는 ignore 멤버함수를 사용하는 방법이 있습니다.
사실 ignore 함수는 이 포스팅을 하기 위해 구글링을 하다가 처음 알게된 함수인데, 문자를 스트림에서 입력받고 버리는 역할을 한다고 합니다.
함수의 원형은 다음과 같습니다.
std::istream &std::istream::ignore(std::streamsize _Count = 1i64, int _Metadelim = -1)
ignore 함수는 매개변수 두개를 갖습니다.
첫번째 매개변수는 버리고자 하는 문자의 수로, streamsize 타입입니다. 입력하지 않는다면 디폴트는 1입니다.
두번째 매개변수는 제한 문자로, 입력한 문자까지 무시하게 됩니다. 입력하지 않는다면 디폴트는 EOF입니다.
그럼 마지막으로 이러한 내용들을 적용해서, 숫자를 받아오다가 문자를 받으면 재입력을 요구하는 프로그램을 만들어봅시다.
저는 입력 버퍼를 비우기 위해 get 함수를 사용하였습니다.
#include <iostream>
int main()
{
std::cout << "평균 점수를 계산하는 프로그램입니다.\n"
<< "학생 수를 입력해주세요\n" << "학생 수: ";
int student_num;
std::cin >> student_num;
int* score = new int[student_num];
for (int i = 0; i < student_num; ++i)
{
std::cout << "학생 " << i + 1 << ": ";
while (!(std::cin >> score[i])) //값을 받아오는데에 실패하면 루프 몸체가 실행
{
std::cin.clear(); //에러 플래그 지우기
while (std::cin.get() == '\n') continue; //잘못된 입력 지우기
//std::cin.ignore(100,'\n');
std::cout << "다시 입력해주세요...\n학생 " << i + 1 << ": ";
}
}
double total = 0.0;
for (int i = 0; i < student_num; ++i) total += score[i];
std::cout << "학생들의 평균 점수는 " << total / student_num << "입니다.";
delete[] score;
}
학생 수를 입력받아 점수의 평균을 계산하는 프로그램입니다.
보이는 바와 같이, while에서 입력을 받아오는 것과 동시에 제대로 입력을 받아왔는지에 대한 조건 검사를 시행하고 있습니다.
만약 입력을 받아오는 것을 실패하여 에러 플래그가 설정된다면, 루프 내에 몸체가 실행될 것입니다.
루프 내에서는 에러 플래그를 지우고, 개행 문자가 나올 때까지 버퍼 내에 잘못된 입력을 지워, 다시 입력을 받을 수 있도록 하고 있습니다.
오늘 글은 여기서 마치도록 하겠습니다. 감사합니다.
* 해당 글은 'C++ 기초 플러스 6판'을 참고하여 작성되었습니다.
'Computer > C++' 카테고리의 다른 글
[C++] cout 관련(1) - write, put 출력 멤버함수 (0) | 2022.11.06 |
---|---|
[C++] 입출력과 스트림, 버퍼 (0) | 2022.10.26 |
[C++] cctype 라이브러리 소개 (0) | 2022.10.23 |
[C++] EOF에 관한 이야기 (0) | 2022.10.22 |
[C++] Range 기반의 for 루프 사용하기 (0) | 2022.10.21 |