Dlaczego to się dzieje?
Ma to niewiele wspólnego z danymi wejściowymi, które sam podałeś, ale raczej z domyślnymi std::getline()
pokazami zachowania . Kiedy podałeś swoje dane wejściowe dla name ( std::cin >> name
), nie tylko przesłałeś następujące znaki, ale także niejawny znak nowej linii został dołączony do strumienia:
"John\n"
Nowa linia jest zawsze dodawana do twoich danych wejściowych, gdy wybierasz Enterlub Returnprzesyłasz z terminala. Jest również używany w plikach do przechodzenia do następnego wiersza. Nowa linia pozostaje w buforze po wyodrębnieniu name
do następnej operacji we / wy, w której jest odrzucana lub zużyta. Kiedy przepływ kontroli osiągnie std::getline()
, nowa linia zostanie odrzucona, ale dane wejściowe natychmiast się zatrzymają. Powodem tego jest to, że domyślna funkcjonalność tej funkcji narzuca, że powinna (próbuje odczytać linię i zatrzymuje się, gdy znajdzie nową linię).
Ponieważ ten wiodący znak nowej linii ogranicza oczekiwaną funkcjonalność twojego programu, wynika z tego, że musi on zostać w jakiś sposób pominięty i zignorowany. Jedną z opcji jest wezwanie std::cin.ignore()
po pierwszej ekstrakcji. Odrzuci następny dostępny znak, dzięki czemu nowa linia nie będzie już przeszkadzać.
std::getline(std::cin.ignore(), state)
Szczegółowe wyjaśnienie:
To jest przeciążenie tego std::getline()
, co nazwałeś:
template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
std::basic_string<charT>& str )
Inne przeciążenie tej funkcji przyjmuje ogranicznik typu charT
. Znak ogranicznika to znak, który reprezentuje granicę między sekwencjami danych wejściowych. To szczególne przeciążenie input.widen('\n')
domyślnie ustawia ogranicznik na znak nowego wiersza, ponieważ nie został on podany.
Oto kilka warunków, które powodują std::getline()
zakończenie wejścia:
- Jeśli strumień wyodrębnił maksymalną liczbę znaków, które
std::basic_string<charT>
można przechowywać
- Jeśli znaleziono znak końca pliku (EOF)
- Jeśli separator został znaleziony
Trzeci warunek to ten, z którym mamy do czynienia. Twój wkład w state
jest reprezentowany w następujący sposób:
"John\nNew Hampshire"
^
|
next_pointer
gdzie next_pointer
jest następny znak do przeanalizowania. Ponieważ znak przechowywany na następnej pozycji w sekwencji wejściowej jest ogranicznikiem, std::getline()
po cichu odrzuci ten znak, zwiększy next_pointer
do następnego dostępnego znaku i zatrzyma wprowadzanie. Oznacza to, że reszta znaków, które podałeś, nadal pozostaje w buforze do następnej operacji we / wy. Zauważysz, że jeśli wykonasz kolejny odczyt z wiersza do state
, twoje wyodrębnienie da prawidłowy wynik jako ostatnie wywołanie std::getline()
odrzucenia separatora.
Być może zauważyłeś, że zwykle nie napotykasz tego problemu podczas wyodrębniania za pomocą sformatowanego operatora wejściowego ( operator>>()
). Dzieje się tak, ponieważ strumienie wejściowe używają białych znaków jako separatorów danych wejściowych i mają domyślnie włączony manipulator std::skipws
1 . Strumienie będą odrzucać wiodące białe znaki ze strumienia, gdy zaczną wykonywać sformatowane dane wejściowe. 2
W przeciwieństwie do sformatowanych operatorów wejściowych, std::getline()
jest to niesformatowana funkcja wejściowa. Wszystkie niesformatowane funkcje wejściowe mają nieco wspólny kod:
typename std::basic_istream<charT>::sentry ok(istream_object, true);
Powyższy obiekt to wartownik, którego instancja jest tworzona we wszystkich sformatowanych / niesformatowanych funkcjach we / wy w standardowej implementacji C ++. Obiekty Sentry są używane do przygotowania strumienia do wejścia / wyjścia i określenia, czy jest w stanie awarii. Przekonasz się tylko, że w niesformatowanych funkcjach wejściowych drugim argumentem konstruktora wartownika jest true
. Ten argument oznacza, że początkowe białe znaki nie zostaną odrzucone z początku sekwencji wejściowej. Oto odpowiedni cytat ze standardu [§27.7.2.1.3 / 2]:
explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);
[...] Jeśli noskipws
wynosi zero i is.flags() & ios_base::skipws
jest niezerowe, funkcja wyodrębnia i odrzuca każdy znak, o ile następny dostępny znak wejściowy c
jest znakiem odstępu. […]
Ponieważ powyższy warunek jest fałszywy, obiekt wartownika nie odrzuci białych znaków. Powód noskipws
jest ustawiony true
przez tę funkcję, ponieważ celem std::getline()
jest wczytanie surowych, niesformatowanych znaków do std::basic_string<charT>
obiektu.
Rozwiązanie:
Nie ma sposobu, aby powstrzymać to zachowanie std::getline()
. Musisz samodzielnie odrzucić nową linię przed std::getline()
uruchomieniem (ale zrób to po sformatowanym rozpakowaniu). Można to zrobić za pomocą, ignore()
aby odrzucić resztę danych wejściowych, dopóki nie osiągniemy nowego nowego wiersza:
if (std::cin >> name &&
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
std::getline(std::cin, state))
{ ... }
Musisz dołączyć <limits>
do użycia std::numeric_limits
. std::basic_istream<...>::ignore()
jest funkcją, która odrzuca określoną liczbę znaków, dopóki nie znajdzie separatora lub nie osiągnie końca strumienia ( ignore()
również odrzuca ogranicznik, jeśli go znajdzie). max()
Zwraca największą ilość znaków, że strumień może zaakceptować.
Innym sposobem na odrzucenie białych znaków jest użycie std::ws
funkcji, która jest manipulatorem zaprojektowanym do wyodrębniania i odrzucania wiodących białych znaków z początku strumienia wejściowego:
if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }
Co za różnica?
Różnica polega na tym, że ignore(std::streamsize count = 1, int_type delim = Traits::eof())
3 bezkrytycznie odrzuca znaki, dopóki nie odrzuci count
znaków, nie znajdzie separatora (określonego przez drugi argument delim
) lub nie dotrze do końca strumienia. std::ws
służy tylko do usuwania białych znaków z początku strumienia.
Jeśli mieszasz formatowane wejście z niesformatowanym wejściem i musisz odrzucić pozostałe białe znaki, użyj std::ws
. W przeciwnym razie, jeśli chcesz usunąć nieprawidłowe dane wejściowe, niezależnie od tego, co to jest, użyj ignore()
. W naszym przykładzie musimy jedynie jasnego spacji ponieważ strumień spożywane swoją wejściowych "John"
do name
zmiennej. Został tylko znak nowej linii.
1: std::skipws
to manipulator, który mówi strumieniowi wejściowemu, aby odrzucał wiodące białe znaki podczas wykonywania sformatowanych danych wejściowych. Można to wyłączyć za pomocą std::noskipws
manipulatora.
2: Strumienie wejściowe domyślnie uznają pewne znaki za białe spacje, takie jak spacja, znak nowego wiersza, wysuw strony, powrót karetki itp.
3: To jest podpis std::basic_istream<...>::ignore()
. Możesz wywołać go bez argumentów, aby odrzucić pojedynczy znak ze strumienia, jeden argument, aby odrzucić określoną liczbę znaków lub dwa argumenty, aby odrzucić count
znaki lub do momentu, gdy osiągnie delim
, w zależności od tego, który z nich nastąpi wcześniej. Zwykle używasz std::numeric_limits<std::streamsize>::max()
jako wartości, count
jeśli nie wiesz, ile znaków znajduje się przed ogranicznikiem, ale i tak chcesz je odrzucić.
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
powinno również działać zgodnie z oczekiwaniami. (Oprócz odpowiedzi poniżej).