Walidacja danych cin C++

Walidacja danych wprowadzanych przez użytkownika za pomocą std::cin w C++ jest niezbędna do zapewnienia, że program otrzymuje i przetwarza poprawne informacje. Do zarządzania błędnymi danymi i czyszczenia bufora wejściowego używane są funkcje cin.clear() oraz cin.ignore().
Czym jest numeric_limits<std::streamsize>::max()?
std::numeric_limits<std::streamsize>::max() to wyrażenie zwracające maksymalną wartość, jaką może przyjąć typ std::streamsize, reprezentujący rozmiar danych strumieniowych. W kontekście cin.ignore(), numeric_limits<std::streamsize>::max() instruuje, aby ignorować wszystkie znaki do napotkania znaku nowej linii, efektywnie czyszcząc bufor z wszelkich pozostałych danych.
Alternatywy bez numeric_limits<std::streamsize>::max()
Zignorowanie określonej liczby znaków:
cin.ignore(10000, '\n');
Ignoruje 10000 znaków lub do napotkania znaku nowej linii, w zależności od tego, co nastąpi wcześniej. Czytanie i ignorowanie każdego znaku aż do nowej linii:
char temp;
while (cin.get(temp) && temp != '\n') {}
Przykłady
Przykład 1: Walidacja liczby całkowitej
Deklaracja funkcji:
#include <iostream>
#include <limits>

void walidujLiczbeCalkowita() {
    int liczba;
    std::cout << "Wprowadź liczbę całkowitą: ";
    while (!(std::cin >> liczba)) {
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        std::cout << "Nieprawidłowe dane. Spróbuj ponownie: ";
    }
    std::cout << "Wprowadzono: " << liczba << std::endl;
}

Przykład 2: Menu wyboru z walidacją

Deklaracje funkcji:
#include <iostream>
#include <limits>

void wyswietlMenu() {
    std::cout << "1. Opcja 1\n";
    std::cout << "2. Opcja 2\n";
    std::cout << "3. Wyjście\n";
    std::cout << "Wybierz opcję: ";
}

void wybierzOpcje() {
    int opcja;
    do {
        wyswietlMenu();
        while (!(std::cin >> opcja) || opcja < 1 || opcja > 3) {
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            std::cout << "Nieprawidłowy wybór. Spróbuj ponownie: ";
        }
        // Obsługa wyboru...
    } while (opcja != 3);
}

Przykład 3: Walidacja wprowadzonego ciągu znaków

Deklaracja funkcji:
#include <iostream>
#include <limits>
#include <string>

void walidujCiagZnakow() {
    std::string dane;
    std::cout << "Wprowadź ciąg znaków: ";
    std::cin >> dane;
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // Czyszczenie bufora
    std::cout << "Wprowadzono: " << dane << std::endl;
}

Przykład 4: Walidacja liczby zmiennoprzecinkowej

Deklaracja funkcji:
#include <iostream>
#include <limits>

void walidujLiczbeZmiennoprzecinkowa() {
    double liczba;
    std::cout << "Wprowadź liczbę zmiennoprzecinkową: ";
    while (!(std::cin >> liczba)) {
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        std::cout << "Nieprawidłowe dane. Spróbuj ponownie: ";
    }
    std::cout << "Wprowadzono: " << liczba << std::endl;
}

Przykład 5: Walidacja liczby z określonego zakresu

Deklaracja funkcji:
#include <iostream>
#include <limits>

void walidujLiczbeZZakresu() {
    int liczba;
    std::cout << "Wprowadź liczbę (1-100): ";
    while (!(std::cin >> liczba) || liczba < 1 || liczba > 100) {
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        std::cout << "Nieprawidłowe dane. Liczba musi być w zakresie 1-100. Spróbuj ponownie: ";
    }
    std::cout << "Wprowadzono: " << liczba << std::endl;
}
Te przykłady ilustrują różne scenariusze, w których walidacja danych wejściowych jest niezbędna, oraz pokazują, jak używać cin.clear() i cin.ignore() do zarządzania błędnymi danymi i przygotowania std::cin do kolejnych operacji wejściowych. Funkcja std::getline(std::cin, str) w C++ służy do wczytywania linii tekstu ze standardowego wejścia (std::cin) do zmiennej typu std::string. Pozwala na odczytanie całej linii – włącznie ze spacjami – aż do napotkania znaku końca linii (Enter), który jest następnie konsumowany (usuwany z bufora wejściowego) ale nie dodawany do stringa str.
Główne zalety std::getline:
  • Wczytywanie linii z przestrzeniami: Inaczej niż std::cin, który domyślnie czyta dane do pierwszej napotkanej spacji lub nowej linii, std::getline pozwala na wczytanie całej linii wraz ze spacjami.
  • Elastyczność: Możliwość łatwego wczytania danych w formacie tekstu, co jest szczególnie przydatne przy pracy z użytkownikiem wprowadzającym dane w formie zdań lub fraz.
  • Zapobieganie błędom wejścia: Skutecznie czyści bufor wejściowy z nowej linii, co może zapobiegać nieoczekiwanemu zachowaniu przy kolejnych odczytach danych.
Przykłady użycia std::getline(std::cin, str):
Wczytanie pojedynczej linii tekstu
#include <iostream>
#include <string>

int main() {
    std::string liniaTekstu;
    std::cout << "Wprowadź tekst: ";
    std::getline(std::cin, liniaTekstu);
    std::cout << "Wprowadzono: " << liniaTekstu << std::endl;
    return 0;
}
Wczytanie kilku linii tekstu
#include <iostream>
#include <string>

int main() {
    std::string linia;
    std::cout << "Wprowadź kilka linii tekstu (wpisz 'koniec' aby zakończyć):" << std::endl;
    
    while (std::getline(std::cin, linia) && linia != "koniec") {
        std::cout << "Wprowadzono: " << linia << std::endl;
    }
    
    std::cout << "Koniec wprowadzania danych." << std::endl;
    return 0;
}
Wczytanie danych z pominięciem pustych linii
#include <iostream>
#include <string>

int main() {
    std::string linia;
    std::cout << "Wprowadź tekst (pusta linia kończy wprowadzanie):" << std::endl;
    
    while (std::getline(std::cin, linia)) {
        if (linia.empty()) break;
        std::cout << "Wprowadzono: " << linia << std::endl;
    }
    
    std::cout << "Koniec wprowadzania danych." << std::endl;
    return 0;
}
Wskazówki
  • Po nieudanej próbie wczytania (np. przy błędnym strumieniu wejściowym), konieczne może być użycie std::cin.clear() przed kolejnym użyciem std::getline, aby wyczyścić stan błędu strumienia.
  • Używając std::getline po wcześniejszym odczycie z std::cin przy użyciu operatora >>, zalecane jest wywołanie std::cin.ignore() przed std::getline, aby usunąć znak nowej linii pozostawiony w buforze przez std::cin.
std::getline(std::cin, str) jest więc niezwykle użyteczną funkcją do wczytywania danych tekstowych w C++, zapewniającą większą kontrolę nad procesem wejścia w porównaniu do standardowego odczytu za pomocą std::cin >>.