Wczytywanie całej linii
cin >> text wczytuje jedno słowo: zatrzymuje się na pierwszej spacji. Aby wczytać wszystko, co użytkownik wpisał aż do naciśnięcia Enter, łącznie ze spacjami, użyj std::getline z nagłówka <string>:
#include <iostream>
#include <string>
int main() {
std::string name;
std::cout << "Full name: ";
std::getline(std::cin, name);
std::cout << "Hello, " << name << "!\n";
}
Wpisanie Ada Lovelace wypisuje Hello, Ada Lovelace!. Z std::cin >> name program wypisałby Hello, Ada!, a Lovelace zostałoby w wejściu.
Sygnatura wygląda tak:
std::istream& std::getline(std::istream& input, std::string& text);
std::istream& std::getline(std::istream& input, std::string& text, char delim);
inputto dowolny strumień wejściowy:std::cin, plik otwarty przezstd::ifstreamalbostd::istringstreamzbudowany z napisu.textjest zastępowany (a nie uzupełniany) wczytanymi znakami.delimto znak, który kończy wczytywanie. Domyślnie to'\n'. Separator jest usuwany ze strumienia i nie trafia dotext.- Wartością zwracaną jest sam strumień i to dzięki temu możesz go sprawdzić w warunku.
Ponieważ getline działa tak samo na każdym strumieniu wejściowym, przykłady do uruchomienia na tej stronie czytają z std::istringstream zawierającego stały tekst. Dzięki temu wynik jest przewidywalny i nie trzeba niczego wpisywać. Aby czytać z klawiatury, zastąp zmienną strumienia przez std::cin.
Wynik:
[Ada Lovelace]
[Alan Turing]
Nawiasy kwadratowe pokazują, że znak nowej linii nie należy do żadnego z napisów.
getline po cin >> zwraca pusty napis
To najczęstszy problem z getline. Wczytujesz liczbę przez >>, potem linię przez getline, a getline wydaje się zostać pominięty:
Wynik:
age: 30
city: []
input >> age wczytuje znaki 30 i zatrzymuje się na pierwszym znaku, który nie może być częścią liczby, czyli na znaku nowej linii. Ten znak zostaje w strumieniu. getline czyta potem do najbliższego znaku nowej linii, od razu go znajduje i zwraca pusty napis. Przy std::cin efekt jest taki, że program w ogóle nie czeka na drugą odpowiedź.
Są dwie standardowe poprawki. Obie trafiają między >> a getline.
Poprawka 1: odrzuć resztę linii przez ignore.
Wynik:
age: 30
city: [Paris]
ignore(n, '\n') wyrzuca znaki, dopóki nie usunie znaku nowej linii albo n znaków, zależnie od tego, co nastąpi wcześniej. Przekazanie największej możliwej wartości std::streamsize oznacza "aż do znaku nowej linii, jakkolwiek daleko jest". Usuwa to też wszystko, co użytkownik wpisał po liczbie w tej samej linii, na przykład końcowe spacje. Przy klawiaturze ta linia to std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); i wymaga #include <limits>.
Gołe std::cin.ignore() odrzuca dokładnie jeden znak. Działa, gdy znak nowej linii następuje bezpośrednio po liczbie, i zawodzi, gdy tylko przed nim pojawi się spacja.
Poprawka 2: pomiń początkowe białe znaki przez std::ws.
Wynik:
age: 30
city: [Paris]
std::ws to manipulator, który pochłania wszystkie białe znaki (spacje, tabulatory, znaki nowej linii) w bieżącym miejscu. input >> std::ws zwraca strumień, więc można go od razu przekazać do getline. Pomija też puste linie i początkowe spacje, co jest wygodne przy imionach i miastach, ale błędne, gdy początkowe spacje albo pusta linia są znaczącymi danymi. W takim przypadku użyj ignore.
Wczytywanie do końca danych wejściowych
getline zwraca strumień, a strumień konwertuje się na false, gdy odczyt się nie powiedzie. Gdy nie ma więcej linii, getline zawodzi, więc pętla while wokół niego wczytuje każdą linię i kończy się na końcu:
Wynik:
1: [apples 3]
2: [bread 1]
3: []
4: [milk 2]
read 4 lines
Dwa szczegóły z tego wyniku:
- Pusta linia to nadal udany odczyt. Daje pusty napis, a pętla trwa dalej. Zatrzymuje ją dopiero dotarcie do końca danych.
- Ostatnia linia nie musi kończyć się znakiem nowej linii. Gdyby tekst kończył się na
milk 2bez\n,getlinei tak by ją zwrócił, a dopiero następne wywołanie by się nie powiodło.
Z std::cin ta sama pętla czyta, dopóki wejście się nie skończy. Dzieje się tak, gdy dane są przekierowane z pliku (./program < data.txt) albo gdy użytkownik naciśnie Ctrl+D na macOS i Linuksie lub Ctrl+Z, a potem Enter na Windowsie.
Unikaj wzorca while (!input.eof()) { std::getline(input, line); ... }. eof() staje się prawdziwe dopiero wtedy, gdy odczyt już natrafił na koniec, więc ciało wykonuje się o jeden raz za dużo, z nieudanym odczytem i nieaktualnym line. Zamiast tego umieść wywołanie getline w warunku.
Czytanie pliku linia po linii
Pliki obsługuje ta sama pętla. Otwórz plik przez std::ifstream z <fstream>, sprawdź, czy się otworzył, i czytaj:
Wynik:
1. Ada 92
2. Grace 88
3. Alan 95
if (!file) wyłapuje brakujący plik albo błędną ścieżkę przed pętlą. Bez tego pętla po prostu wczyta zero linii, co wygląda jak pusty plik, a nie jak błąd. Plik zamyka się automatycznie, gdy file wychodzi z zasięgu na końcu main.
Pliki zapisane na Windowsie kończą każdą linię sekwencją \r\n. Gdy taki plik jest czytany na Linuksie lub macOS, getline usuwa \n, ale zostawia \r, więc każda linia kończy się niewidocznym znakiem powrotu karetki, który psuje porównania w rodzaju line == "done". Usuń go po wczytaniu:
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
Własny separator
Trzeci argument zmienia znak, który kończy każdy odczyt. Typowe zastosowanie to dzielenie jednej linii po przecinkach. Najpierw wczytaj linię, a potem podziel ją przez std::istringstream:
Wynik:
fields: 3
[Ada Lovelace]
[36]
[London]
Przecinek zostaje usunięty ze strumienia i nie jest zapisywany, tak samo jak znak nowej linii w formie domyślnej. Zauważ, że przy własnym separatorze znak nowej linii jest zwykłym znakiem. Jeśli wywołasz std::getline(file, field, ',') bezpośrednio na pliku wieloliniowym, ostatnie pole jednej linii i pierwsze pole następnej wrócą sklejone, jak w "London\nGrace Hopper". Wczytywanie po jednej linii i dzielenie każdej z osobna pozwala tego uniknąć. Strona o strumieniach napisowych omawia std::istringstream dokładniej, w tym zamianę pól takich jak "36" na liczby.
std::getline a cin.getline
Istnieje też metoda o podobnej nazwie:
char buffer[20];
std::cin.getline(buffer, 20); // metoda, tablica char o stałym rozmiarze
std::string text;
std::getline(std::cin, text); // wolna funkcja, std::string
cin.getline zapisuje do tablicy char i przechowuje co najwyżej size - 1 znaków plus kończące '\0'. Dłuższa linia wprowadza strumień w stan błędu i każdy późniejszy odczyt jest pomijany, dopóki nie wywołasz std::cin.clear(). std::getline z std::string nie ma limitu długości, którym trzeba by zarządzać. Używaj go, chyba że pracujesz z API, które wymaga tablicy char.
Częste błędy
- Mieszanie
>>igetlinebez usunięcia znaku nowej linii. Dodaj między nimiignore(...)albo użyjstd::ws. Strona o wejściu i wyjściu pokazuje ten sam problem zstd::cini to, jak wyjść z sytuacji, gdy odczyt liczby się nie powiedzie. - Wywoływanie
getline(std::cin, number)zint.getlineczyta tylko dostd::string. Wczytaj linię, a potem skonwertuj ją przezstd::stoialbostd::istringstream. - Pętla oparta na
eof(). Sprawdzaj samo wywołaniegetline:while (std::getline(input, line)). - Zapomniany
#include <string>.std::getlinedla napisów jest zadeklarowany w<string>. Niektóre kompilatory przepuszczają go przez<iostream>, a potem kod nie kompiluje się na innym kompilatorze. - Brak sprawdzenia, czy plik się otworzył. Błędna ścieżka sprawia, że
std::ifstreampo cichu zawodzi, a pętla odczytu wykonuje się zero razy.
Najczęściej zadawane pytania
Co robi getline w C++?
std::getline(stream, text) wczytuje znaki ze strumienia stream do std::string text, aż napotka znak nowej linii, a potem usuwa ten znak ze strumienia, nie zapisując go. W przeciwieństwie do cin >> text zachowuje spacje, więc wczytuje całą linię, na przykład pełne imię i nazwisko albo zdanie.
Dlaczego getline jest pomijany po cin >> w C++?
cin >> number przestaje czytać na znaku nowej linii wpisanym po liczbie i zostawia ten znak w buforze wejściowym. Następny getline od razu go znajduje i zwraca pusty napis. Najpierw go odrzuć przez std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); albo wywołaj std::getline(std::cin >> std::ws, text), aby pominąć początkowe białe znaki.
Jak użyć getline z innym separatorem niż znak nowej linii?
Przekaż separator jako trzeci argument: std::getline(stream, field, ','). Wczytuje on tekst do najbliższego przecinka, zapisuje to, co było przed nim, i odrzuca przecinek. Znaki nowej linii są wtedy traktowane jak zwykłe znaki, więc zwykle łączy się to z std::stringstream zbudowanym z pojedynczej linii.
Jak czytać plik linia po linii w C++?
Otwórz go przez std::ifstream file("data.txt"); i użyj pętli while (std::getline(file, line)) { ... }. getline zwraca strumień, który konwertuje się na false, gdy nie ma już więcej linii, więc pętla kończy się na końcu pliku.
Jaka jest różnica między cin.getline a getline?
std::getline(std::cin, text) to wolna funkcja, która czyta do std::string, a ten rośnie, by zmieścić dowolną linię. std::cin.getline(buffer, size) to metoda, która czyta do tablicy char o stałym rozmiarze i kończy się błędem, jeśli linia jest dłuższa niż size - 1 znaków. Używaj std::getline z std::string, chyba że masz powód, by korzystać z surowych tablic.