저희가 아주 자주사용하기도 하고 사용하기 쉬운 라이브러리인 ios_base 라이브러리에 대해서 알아보도록 하겠습니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
char c;
std::cout << "문자열을 입력하세요 : ";
std::cin >> s;
std::cout << "문자를 입력하세여 : ";
std::cin >> c;
return 0;
}
위와 같은 코드가 있을 때
위와 같이 입력하면 입력을 받지 않고 바로 끝내버립니다. 마찬가지로
int main()
{
int n;
char c;
printf("정수입력 : ");
scanf_s("%d", &n);
printf("문자 입력 : ");
scanf_s("%c", &c);
return 0;
}
위의 소스코드를 빌드한다음에 실행하고 1을 입력하고 문자를 입력하려는 순간 프로그램이 종료되어 버립니다.
왜 이런것일 까요??
일단 결론은 표준입출력을 다룰 때 "Buffer"라는 개념을 사용하기 때문입니다.
ABCDEFG...이런식으로 입력한다고 했을 때 A를 입력받고 저장을 하고 B를 입력을 받고 저장을 하고... 이런식의 처리는 비효율적일 것입니다.
그래서 ABCDEF..이런식의 데이터를 잠시 어딘가에 보관해두었다가 입력이 끝나면 한번에 처리를 하는 식으로 돌아갑니다.
여기서 잠시 어딘가에 보관 하는 양동이가 "버퍼(Buffer)"입니다. 그렇다면 "입력이 끝났다"라는 것은 어떻게 인지를 할 수 있을 까요? 입력이 끝났다라는 것은 공백문자를 만나는 경우 입력이 끝났다고 볼 수 있습니다.
(공백 문자란? '\n', '\t', ' ', EOF 등등이 있습니다. )
그래서 버퍼에서 일치하는 공백문자를 만나는경우 "아! 입력이 끝났구나!" 라고 인지를 하게됩니다.
그래서 처음코드에서 "Hello World"를 입력받은 뒤 문자를 하나 입력받고 출력하려는데 바로 종료된 이유를 아시겠나요?
Hello를 입력을 받고 world도 동시에 입력을 받고싶은데 Hello와 World사이의 공백문자가 들어가서 "아! 입력이 끝났구나! 끊어야 겠다!라고 인지를 했기 때문에 Hello까지만 s에 저장이 되고 현재 버퍼에는 아래처럼
' 'world가 남아있게된 상태에서 std::cin을 통해 스트림 버퍼에서 하나의 문자열을 가져와 읽는데 하필이면 이게 ' ' 공백문자여서 바로 끊어 버리는 것입니다.
여기서 입력을 받는 경우 주의 할 점이 있습니다.
int main()
{
int n;
cout << "정수를 입력 : ";
cin >> n;
cout << "입력 받은값 : " << n << endl;
return 0;
}
위와같이 아주 평범그자체로 정수를 입력받는 코드가 있을 때 문자를 입력하는 경우 어떻게 될까요?
출력 결과는 0이 나옵니다.
이를 이해하기 위해서는 C++ 입출력 라이브러리의 개념을 아셔야 합니다.
C++ 표준 입출력 라이브러리의 계층구조는 이러합니다.
std::cin은 std::istream클래스의 인스턴스이고 std::istream클래스는 std::streambuf클래스의 인스턴스를 통해 입력 스트림을 관리합니다.
(ios클래스에는 스트림의 상태를 플래그 단위로 관리합니다.)
std::streambuf클래스는 입력 스트립에서 문자를 읽어서 버퍼에 저장하고 std::istream클래스는 버퍼에서 문자를 읽어서 사용자에게 제공합니다.
std::cin은 std::streambuf클래스의 sbumpc()함수를 호출해서 입력 스트립에서 한 문자를 읽습니다.
sbumpc()함수는 입력 스트림에서 한 문자를 읽고 읽은 문자를 버퍼에 저장합니다. 그리고 std::cin은 버퍼에서 문자를 읽어서 n에 저장합니다. 그런데 'a'와 같은 문자는 정수가 아니기 때문에 std::cin은 'a'문자를 n에 저장하지 않고 파싱에 문제가 생겼다고 판단하여
스트림의 상태를 관리하는 ios클래스에서 failbit라는 플래그를 활성화 하여 스트림이 "실패"상태라고 나타냅니다.
그리고 n에 들어가 있을 변수에는 자동으로 0이 할당되어 들어갑니다.
왜 자동?(사실 자동은 아닙니다.. 캐스팅을 거쳐서 할당됩니다)인지는 밑에 글을 읽어주시면 됩니다.
ios클래스는 스트림의 상태를 goodbit, badbit, failbit, eofbit로 관리합니다
- goodbit : 스트림에 입출력 작업이 가능할 때
- badbit : 스트림에 복구 불가능한 오류 발생시
- failbit : 스트림에 복구 가능한 오류 발생시
- eofbit : 입력 작업시에 EOF 도달시
일단 정수를 입력받기로 했는데 문자를 입력하여 n에 0이 할당된 상황을 goodbit나 eofbit상황으로 볼 수는 없을거같습니다.
그러면 badbit의 상황일까요? 위의 문제의 경우 복구 불가능한 문제는 아닙니다.
failbit문제입니다. 위처럼 타입이 맞지 않는 값을 넣어서 오류가 발생하는 경우 failbit가 켜지게 됩니다.
그리고 나서 입력값을 받지 않고 리턴해버립니다.
ios객체에 operator void*() const;라는 함수가 정의 되어있는데 해당 함수는 ios객체를 void*로 변환해주는 함수입니다.
nullptr가 아닌 값을 return하는 조건은 failbit, badbit가 모두 안 켜져있을 경우입니다.
그러면 타입이 맞지 않은 데이터를 입력시 cin객체의 failbit가 켜지게 되고 std::cin >> 이후에 cin이 return이 되는데 return값이 ios객체의 void*이고 이 void*를 int로 캐스팅을 하게되어 0이 n에 들어가게 되는 것이였습니다ㅎㅎ
참고한 자료
'CPP' 카테고리의 다른 글
[C++] constexpr (0) | 2023.08.17 |
---|---|
[C++] 헤더파일의 의미와 Build Process (0) | 2023.08.10 |
[C++] 전달참조 (0) | 2023.07.31 |
[C++] 오른값 참조 (0) | 2023.07.30 |
[C++] Effective C++ 항목 1~27 정리 (0) | 2023.07.03 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!