형식화 된 추출 후 std :: getline ()이 입력을 건너 뛰는 이유는 무엇입니까?


105

사용자에게 이름과 상태를 묻는 다음 코드가 있습니다.

#include <iostream>
#include <string>

int main()
{
    std::string name;
    std::string state;

    if (std::cin >> name && std::getline(std::cin, state))
    {
        std::cout << "Your name is " << name << " and you live in " << state;
    }
}

내가 찾은 것은 이름이 성공적으로 추출되었지만 상태가 아니라는 것입니다. 다음은 입력 및 결과 출력입니다.

Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in "

상태 이름이 출력에서 ​​생략 된 이유는 무엇입니까? 적절한 입력을 제공했지만 코드는이를 무시합니다. 왜 이런 일이 발생합니까?


std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)예상대로 작동해야 한다고 생각 합니다. (아래 답변 외에도).
jww

답변:


122

왜 이런 일이 발생합니까?

이것은 사용자가 직접 제공 한 입력과 거의 관련이 없지만 기본 동작 std::getline()이 나타내는 것과 관련 이 있습니다. 이름 ( std::cin >> name)에 대한 입력을 제공 할 때 다음 문자를 제출했을뿐만 아니라 암시 적 개행이 스트림에 추가되었습니다.

"John\n"

선택 Enter하거나 Return터미널에서 제출할 때 항상 줄 바꿈이 입력에 추가됩니다 . 다음 줄로 이동하기 위해 파일에서도 사용됩니다. 줄 바꿈은 추출 후 name다음 I / O 작업까지 버퍼에 남아 있습니다. 여기서 버려지거나 소비됩니다. 제어 흐름이에 도달 std::getline()하면 개행 문자가 삭제되지만 입력은 즉시 중지됩니다. 이런 일이 발생하는 이유는이 함수의 기본 기능이해야한다고 지시하기 때문입니다 (행을 읽으려고 시도하고 개행을 찾으면 중지).

이 줄 바꿈은 프로그램의 예상되는 기능을 방해하기 때문에 무시해야합니다. 한 가지 옵션은 std::cin.ignore()첫 번째 추출 후 호출 하는 것입니다. 줄 바꿈이 더 이상 방해가되지 않도록 다음 사용 가능한 문자를 버립니다.

std::getline(std::cin.ignore(), state)

자세한 설명 :

이것은 std::getline()당신이 부르는 과부하입니다 .

template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
                                    std::basic_string<charT>& str )

이 함수의 또 다른 오버로드는 유형의 구분 기호를 사용합니다 charT. 구분 문자는 입력 시퀀스 간의 경계를 나타내는 문자입니다. 이 특정 오버로드는 구분 기호가 input.widen('\n')제공되지 않았기 때문에 기본적으로 개행 문자 로 설정합니다.

이제 다음은 std::getline()입력 을 종료 하는 몇 가지 조건입니다 .

  • 스트림이 std::basic_string<charT>보유 할 수 있는 최대 문자 수를 추출한 경우
  • EOF (파일 끝) 문자가 발견 된 경우
  • 구분자가 발견 된 경우

세 번째 조건은 우리가 다루고있는 조건입니다. 에 대한 입력 state은 다음과 같이 표시됩니다.

"John\nNew Hampshire"
     ^
     |
 next_pointer

next_pointer구문 분석 할 다음 문자는 어디에 있습니까 ? 입력 시퀀스의 다음 위치에 저장된 문자가 구분 기호이므로 std::getline()조용히 해당 문자를 버리고 next_pointer사용 가능한 다음 문자로 증가 하고 입력을 중지합니다. 이는 제공 한 나머지 문자가 다음 I / O 작업을 위해 버퍼에 계속 남아 있음을 의미합니다. 행에서로 다른 읽기를 수행 하면 구분 기호 statestd::getline()버리는 마지막 호출로 추출이 올바른 결과를 산출한다는 것을 알 수 있습니다 .


형식이 지정된 입력 연산자 ( operator>>())를 사용하여 추출 할 때 일반적으로이 문제가 발생하지 않는다는 것을 알 수 있습니다 . 이는 입력 스트림이 공백을 입력 구분 기호로 사용 하고 기본적으로 std::skipws1 개의 조작자가 설정되어 있기 때문 입니다. 스트림은 형식화 된 입력을 수행하기 시작할 때 스트림에서 선행 공백을 삭제합니다. 2

포맷 된 입력 연산자 달리 std::getline()이다 형식화 입력 기능. 그리고 모든 형식화되지 않은 입력 함수에는 다음과 같은 코드가 공통적으로 있습니다.

typename std::basic_istream<charT>::sentry ok(istream_object, true);

위는 표준 C ++ 구현에서 모든 형식화 / 형식화되지 않은 I / O 함수에서 인스턴스화되는 센트리 개체입니다. Sentry 개체는 I / O 용 스트림을 준비하고 실패 상태인지 여부를 결정하는 데 사용됩니다. 형식이 지정 되지 않은 입력 함수 에서만 센트리 생성자의 두 번째 인수가 true. 이 인수 는 입력 시퀀스의 시작 부분에서 선행 공백이 삭제 되지 않음을 의미합니다 . 다음은 표준 [§27.7.2.1.3 / 2]의 관련 인용문입니다.

 explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);

[...] noskipws가 0이고 0 is.flags() & ios_base::skipws이 아닌 경우 다음 사용 가능한 입력 문자 c가 공백 문자 인 한 함수는 각 문자를 추출하여 버립니다 . [...]

위의 조건이 거짓이므로 센트리 객체는 공백을 버리지 않습니다. 이 함수 noskipwstrue의해 설정 되는 이유 는 요점이 std::getline()형식화되지 않은 원시 문자를 std::basic_string<charT>객체 로 읽어 들이기 때문 입니다.


해결책:

의이 동작을 중지 할 방법이 없습니다 std::getline(). 해야 할 일은 std::getline()실행 하기 전에 새 줄을 직접 버리는 것입니다 (그러나 형식화 된 추출 후에 수행하십시오 ). 이것은 ignore()새로운 줄에 도달 할 때까지 나머지 입력을 버리기 위해 를 사용하여 수행 할 수 있습니다 .

if (std::cin >> name &&
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
    std::getline(std::cin, state))
{ ... }

<limits>을 사용 하려면 포함 해야합니다 std::numeric_limits. std::basic_istream<...>::ignore()구분 기호를 찾거나 스트림의 끝에 도달 할 때까지 지정된 양의 문자를 버리는 함수입니다 (찾으면 구분 ignore()기호도 버립니다). 이 max()함수는 스트림이 허용 할 수있는 최대 문자 수를 반환합니다.

공백을 버리는 또 다른 방법 std::ws은 입력 스트림의 시작 부분에서 선행 공백을 추출하고 삭제하도록 설계된 조작자 인 함수 를 사용하는 것입니다.

if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }

차이점이 뭐야?

차이점은 ignore(std::streamsize count = 1, int_type delim = Traits::eof())3 은 문자를 버리거나 count구분 기호 (두 번째 인수로 지정됨 delim)를 찾거나 스트림의 끝에 도달 할 때까지 무차별 적으로 문자를 버린다는 것 입니다. std::ws스트림 시작 부분에서 공백 문자를 버릴 때만 사용됩니다.

형식이 지정된 입력을 형식이 지정되지 않은 입력과 혼합하고 나머지 공백을 버려야하는 경우 std::ws. 그렇지 않으면 입력 내용에 관계없이 유효하지 않은 입력을 지워야하는 경우 ignore(). 이 예에서는 스트림 "John"name변수 에 대한 입력을 소비했기 때문에 공백 만 지울 필요가 있습니다. 남은 것은 개행 문자뿐이었습니다.


1 : std::skipws형식화 된 입력을 수행 할 때 선행 공백을 버리도록 입력 스트림에 지시하는 조작기입니다. std::noskipws조작기 로 끌 수 있습니다 .

2 : 입력 스트림은 공백 문자, 개행 문자, 용지 공급, 캐리지 리턴 등과 같은 특정 문자를 기본적으로 공백으로 간주합니다.

3 : 이것은의 서명입니다 std::basic_istream<...>::ignore(). 0 인수로 호출하여 스트림에서 단일 문자를 버리고, 하나의 인수를 사용하여 특정 양의 문자를 버리거나, 두 개의 인수를 사용하여 count문자 를 버리거나에 도달 할 때까지 delim둘 중 하나가 먼저 올 수 있습니다. 일반적으로 구분 기호 앞에 몇 개의 문자가 있는지 모르는 경우 std::numeric_limits<std::streamsize>::max()값 으로 사용 count하지만 어쨌든 버리고 싶을 때 사용합니다.


1
왜 간단하지 if (getline(std::cin, name) && getline(std::cin, state))않습니까?
Fred Larson

@FredLarson 좋은 지적입니다. 첫 번째 추출이 정수이거나 문자열이 아닌 경우 작동하지 않지만.
0x499602D2

물론 여기에서는 그렇지 않으며 두 가지 다른 방식으로 동일한 작업을 수행 할 필요가 없습니다. 정수의 경우 줄을 문자열로 가져온 다음를 사용할 수 std::stoi()있지만 이점이 있다는 것이 명확하지 않습니다. 그러나 나는 std::getline()라인 지향 입력 에만 사용 하고 어떤 방식 으로든 라인을 구문 분석하는 것을 선호합니다 . 오류가 덜 발생한다고 생각합니다.
Fred Larson

@FredLarson 동의합니다. 시간이 있으면 추가 할 수 있습니다.
0x499602D2

1
@Albin 사용하려는 이유 std::getline()는 주어진 구분 기호까지 모든 문자를 캡처하여 문자열에 입력하려는 경우 (기본적으로 줄 바꿈)입니다. 이러한 X문자열 수가 단일 단어 / 토큰 일 경우이 작업은 >>. 그렇지 않으면 첫 번째 숫자를로 정수에 입력하고 다음 줄에서 >>호출 cin.ignore()한 다음를 사용하는 루프를 실행합니다 getline().
0x499602D2

11

다음과 같은 방법으로 초기 코드를 변경하면 모든 것이 정상입니다.

if ((cin >> name).get() && std::getline(cin, state))

3
감사합니다. get()다음 문자를 소비 하기 때문에 이것은 또한 작동 합니다. (std::cin >> name).ignore()내 대답에서 앞서 제안한 것도 있습니다 .
0x499602D2 2014 년

"..work because get () ..."네, 맞습니다. 세부 사항없이 답변 해주셔서 죄송합니다.
Boris

4
왜 간단하지 if (getline(std::cin, name) && getline(std::cin, state))않습니까?
Fred Larson

0

이는 줄 바꿈 문자라고도하는 암시 적 줄 바꿈 \n이 스트림에 새 줄을 시작하도록 지시 할 때 터미널의 모든 사용자 입력에 추가 되기 때문에 발생 합니다. std::getline여러 줄의 사용자 입력을 확인할 때 를 사용하여 안전하게 설명 할 수 있습니다 . 의 기본 동작은 이 경우에 있는 입력 스트림 개체 std::getline의 개행 문자 \n를 포함하여 모든 것을 읽습니다 std::cin.

#include <iostream>
#include <string>

int main()
{
    std::string name;
    std::string state;

    if (std::getline(std::cin, name) && std::getline(std::cin, state))
    {
        std::cout << "Your name is " << name << " and you live in " << state;
    }
    return 0;
}
Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in New Hampshire"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.