Mam następujący fragment kodu, który prosi użytkownika o podanie nazwy i stanu:
#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;
}
}
Uważam, że nazwa została pomyślnie wyodrębniona, ale nie stan. Oto dane wejściowe i wynikowe:
Input: "John" "New Hampshire" Output: "Your name is John and you live in "
Dlaczego nazwa stanu została pominięta w danych wyjściowych? Podałem właściwe dane wejściowe, ale kod jakoś je ignoruje. Dlaczego to się dzieje?
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).Odpowiedzi:
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: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ągniestd::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ć.Szczegółowe wyjaśnienie:
To jest przeciążenie tego
std::getline()
, co nazwałeś: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ążenieinput.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:std::basic_string<charT>
można przechowywaćTrzeci warunek to ten, z którym mamy do czynienia. Twój wkład w
state
jest reprezentowany w następujący sposób: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ększynext_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 dostate
, twoje wyodrębnienie da prawidłowy wynik jako ostatnie wywołaniestd::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 manipulatorstd::skipws
1 . Strumienie będą odrzucać wiodące białe znaki ze strumienia, gdy zaczną wykonywać sformatowane dane wejściowe. 2W 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: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]:Ponieważ powyższy warunek jest fałszywy, obiekt wartownika nie odrzuci białych znaków. Powód
noskipws
jest ustawionytrue
przez tę funkcję, ponieważ celemstd::getline()
jest wczytanie surowych, niesformatowanych znaków dostd::basic_string<charT>
obiektu.Rozwiązanie:
Nie ma sposobu, aby powstrzymać to zachowanie
std::getline()
. Musisz samodzielnie odrzucić nową linię przedstd::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:Musisz dołączyć
<limits>
do użyciastd::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: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 odrzucicount
znaków, nie znajdzie separatora (określonego przez drugi argumentdelim
) 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żyjignore()
. W naszym przykładzie musimy jedynie jasnego spacji ponieważ strumień spożywane swoją wejściowych"John"
doname
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ągniedelim
, w zależności od tego, który z nich nastąpi wcześniej. Zwykle używaszstd::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ć.źródło
if (getline(std::cin, name) && getline(std::cin, state))
?std::stoi()
, ale wtedy nie jest tak jasne, że ma to przewagę. Ale wolę po prostu używaćstd::getline()
do wprowadzania zorientowanego liniowo, a następnie zajmować się analizowaniem linii w jakikolwiek sensowny sposób. Myślę, że jest mniej podatny na błędy.std::getline()
jest to, że chcesz przechwycić wszystkie znaki do podanego separatora i wprowadzić je do ciągu, domyślnie jest to nowa linia. Jeśli taX
liczba ciągów to tylko pojedyncze słowa / tokeny, to zadanie to można łatwo wykonać za pomocą>>
. W przeciwnym razie wprowadzisz pierwszą liczbę do liczby całkowitej za pomocą>>
, wywołaszcin.ignore()
następny wiersz, a następnie uruchomisz pętlę, w której używaszgetline()
.Wszystko będzie dobrze, jeśli zmienisz kod początkowy w następujący sposób:
źródło
get()
zużywa następną postać. Jest też to,(std::cin >> name).ignore()
co zasugerowałem wcześniej w mojej odpowiedzi.if (getline(std::cin, name) && getline(std::cin, state))
?Dzieje się tak, ponieważ niejawny znak nowego wiersza, znany również jako znak nowej linii,
\n
jest dołączany do wszystkich danych wejściowych użytkownika z terminala, ponieważ mówi strumieniowi, aby rozpoczął nowy wiersz. Możesz to bezpiecznie uwzględnić, używającstd::getline
podczas sprawdzania wielu wierszy danych wejściowych użytkownika. Domyślne zachowaniestd::getline
odczyta wszystko do znaku nowego wiersza\n
z obiektu strumienia wejściowego włącznie, costd::cin
w tym przypadku ma miejsce.źródło