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

c++ input iostream istream c++-faq


사용자에게 이름과 상태를 묻는 프롬프트가 표시되는 다음 코드가 있습니다.

#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 "

출력에서 상태 이름이 생략 된 이유는 무엇입니까? 적절한 입력을 받았지만 코드는 그것을 무시합니다. 왜 이런 일이 발생합니까?





Answer 1 0x499602D2


왜 이런 일이 발생합니까?

이것은 사용자가 제공 한 입력과 거의 관련이 없지만 기본 동작 std::getline() 이 표시합니다. 이름 ( std::cin >> name )에 대한 입력을 제공 한 경우 다음 문자를 제출했을뿐 아니라 내재 된 개행도 스트림에 추가되었습니다.

"John\n"

선택하면 항상 줄 바꿈이 입력에 추가됩니다.EnterorReturn터미널에서 제출할 때. 파일에서 다음 줄로 이동하는 데에도 사용됩니다. 줄 바꾸기는 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 조작을 위해 여전히 버퍼에 남아 있음을 의미합니다. line에서 state 로 다른 읽기를 수행 하면 std::getline() 대한 마지막 호출 이 구분 기호를 버렸을 때 추출하면 올바른 결과가 산출 됩니다.


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

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

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

위는 표준 C ++ 구현에서 모든 형식화 / 형식화되지 않은 I / O 함수에서 인스턴스화 된 센트리 객체입니다. 센트리 객체는 I / O 용 스트림을 준비하고 스트림이 페일 상태인지 여부를 결정하는 데 사용됩니다. 당신은 단지에서 찾을 수 있습니다 포맷되지 않은 입력 기능, 보초 생성자에 두 번째 인수가 true . 이 인수는 선행 공백이 입력 시퀀스의 시작 부분에서 삭제 되지 않음을 의미합니다 . 다음은 표준 [§27.7.2.1.3 / 2]의 관련 인용문입니다.

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

[...] noskipws 가 0이고 is.flags() & ios_base::skipws 가 0이 아닌 경우, 함수는 사용 가능한 다음 입력 문자 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))
{ ... }

std::numeric_limits 를 사용하려면 <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 를 버리는 인수 1 개 또는 카운트 문자 를 버리는 인수 2 개 또는 delim 에 도달 할 때까지 ( 둘 중 먼저 오는 문자 중 하나)를 호출 할 수 있습니다. 구분 기호 앞에 몇 문자가 있는지 모르지만 어쨌든 버리고 싶을 경우 일반적으로 std::numeric_limits<std::streamsize>::max()count 값으로 사용 합니다.




Answer 2 Boris


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

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



Answer 3 Justin Randall


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

#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"