sobota, 26 kwietnia 2014

Operacje wejścia i wyjścia w C++

Wyświetlanie wartości w innym systemie liczbowym:
#include <iostream>

int main() {
    int number;

    std::cout << "Podaj liczbe calkowita: ";

    std::cin >> number;

    std::cout << "Osemkowo: " << std::oct << number << std::endl;

    std::cout << "Szesnastkowo: " << std::hex << number << std::endl;

    std::cout << "Dziesietnie: " << std::dec << number << std::endl;
}

Dostosowywanie szerokości pól:
#include <iostream>

int main() {
    std::cout.width(20); // Następny łańcuch danych wyświetlony na standardowym wyjściu zostanie umieszczony w polu o szerokości 20 znaków i dosunięty do prawej.

    std::cout << "String.";

    std::cout << "String.\n"; // Domyślnie (dosunięte do lewej krawędzi).
}

Ustawianie sposobu wyświetlania liczb:
#include <iostream>

int main() {
    float number_float = 20.1000;

    std::cout << "Domyslnie: " << number_float << std::endl; // Wyświetla "20.1".

    std::cout.precision(2);

    std::cout << "Po ustawieniu precyzji na wartosc 2 (domyslnie precyzja jest ustawiona na 6): " << number_float << std::endl; // Wyświetla "20".

    std::cout.setf(std::ios_base::showpoint);

    std::cout.precision(6);

    std::cout << "Po ustawieniu flagi \"showpoint\" i prezycji na domyslna wartosc: " << number_float << std::endl; // Wyświetla "20.1000".

    std::cout.unsetf(std::ios_base::showpoint);

    std::cout << "Po usunieciu flagi \"showpoint\" (precyzja nadal na taka wartosc jak poprzednio): " << number_float << std::endl; // Wyświetla "20.1".
}

Plik nagłówkowy iomanip:
#include <iostream>
#include <iomanip>

int main() {
    float number_float = 20.1001;

    std::cout << std::setw(15); // Umieść dane wysłane na standardowe wyjście w polu o szerokości 15 znaków i wyrównaj do prawej.

    std::cout << number_float << std::endl;

    // Aktualnie powyższe wywołanie funkcji setw już nie obowiązuje.

    std::cout << std::setprecision(6); // Precyzja na 6.

    std::cout << number_float << std::endl; // Wyświetla "20.1001".

    std::cout << std::setw(15);

    std::cout << std::setfill('.'); // Wypełnia pustą przestrzeń aktualnego pola określonymi znakami.

    std::cout << number_float << std::endl;
}

Podstawowa instrukcją służącą w C++ do wprowadzania danych jest cin, np.:
cin >> zmienna;
Tak jak C++ traktuje dane wynikowe jako strumień znakowy tak samo strumień wejściowy jest strumieniem znakowym wpływającym do programu. Obiekt cin potrafi konwertować dane wejściowe będące łańcuchem znaków z klawiatury na postać odpowiednią dla zmiennej. Nie można z klawiatury wprowadzić znaku końca łańcucha więc obiekt cin musi mieć inną metodę znajdowania jego końca. Uznaje się, że łańcuch kończy się wraz z napotkaniem białego znaku. W związku z tym cin odczytuje tylko jedno słowo. Po jego odczytaniu automatycznie dodawany jest znak końca łańcucha jeżeli wczytywanie następuje do tablicy znakowej. Obiekt cin może być również wywoływany w postaci "cin.get(zmienna_char);", która wczytuje następny znak nawet jeżeli jest to spacja i przypisuje go zmiennej.

Przykład tego jak widoczne są dane wejściowe przy zastosowaniu cin:
#include <iostream>
#include <string>

int main() {
    std::string input;
    int integer;
    char sign;

    std::cout << "Podaj dane (tablica char, int, char oddzielone spacjami): ";

    std::cin >> input >> integer >> sign;

    // Jeżeli podamy dane wejściowe tak jak nas prosi program to oczywiście jest to poprawne ale jeżeli trzecia dana będzie stringiem to zostanie przeczytany tylko jeden znak, a reszta zostanie w buforze.

    std::cin.clear(); // Ta funkcja zeruje stan strumienia.

    std::cout << "Podaj liczby: ";

    while (std::cin >> integer) {
        std::cout << "Podales: " << integer << std::endl;

        // Jeżeli jednak zamiast liczby całkowitej podamy np. "123Z" to program wczyta do zmiennej wartość 123 i zakończy działanie.
    }
}

Jednoznakowe funkcje wejścia:
#include <iostream>

int main() {
    char character;

    std::cout << "Podaj dane wejsciowe (funkcja get):\n";

    std::cin.get(character); // Ze względu na zastosowanie funkcji get program będzie wyświetlał zarówno znaki białe jak i drukowalne.

    while (character != '\n') {
        std::cout << "Podales: " << character << std::endl;

        std::cout << "Podaj dane wejsciowe (funkcja get):\n";

        std::cin.get(character);
    }

    std::cout << std::endl;

    std::cout << "Podaj dane wejsciowe (obiekt cin) i zakoncz znakiem \"q\":\n";

    std::cin >> character; // Użycie tej instrukcji spowoduje, iż program pominie białe znaki, a wykonywanie poniższej pętli nie zostanie nigdy przerwane jeżeli warunkiem jest przypisanie do zmiennej białego znaku.

    while (character != 'q') {
        std::cout << "Podales: " << character << std::endl;

        std::cout << "Podaj dane wejsciowe (obiekt cin):\n";

        std::cin >> character;
    }

    // Poniższy kod pokazuje można łączyć wywołanie funkcji get i obiektu cin:
    // char character_one, character_two, character_three;
    //
    // std::cin.get(character_one).cin.get(character_two) >> character_three;
}

Łańcuchowe funkcje wejścia:
#include <iostream>

const int limit = 256;

int main() {
    char array[limit];
    char character;

    std::cout << "Wprowadz lancuch znakow (funkcja get): ";

    std::cin.get(array, limit, '\n'); // Ta funkcja pozostawia znak separatora w kolejce wejściowej.

    std::cout << "Oto Twoje dane: " << array << std::endl;

    std::cin.get(character);

    std::cout << "Nastepny znak wejsciowy to: " << character << std::endl;

    std::cout << "Wprowadz lancuch znakow (funkcja getline): ";

    std::cin.getline(array, limit, '\n'); // Ta funkcja nie pozostawia znaku separatora w kolejce wejściowej.

    std::cout << "Oto Twoje dane: " << array << std::endl;

    std::cin.get(character);

    std::cout << "Nastepny znak wejsciowy to: " << character << std::endl;
}

Funkcja getline (klasa istream) wczytuje całe wiersze uznając za koniec danych znak nowego wiersza, a na końcu tablicy dodaje znak końca łańcucha, np.:
cin.getline(tablica, ilość_znaków);

Kolejną funkcją wczytującą całe wiersze jest get (również klasa istream), np.:
cin.get(tablica, ilość_znaków);
Różnicą w stosunku do getline jest to, iż zamiast wczytać i odrzucić znak końca wiersza, zostawia go w kolejce wejściowej. W związku z tym kolejne wywołanie w postaci "cin.get(tablica, ilość_znaków);" sprawi, że zostanie wczytany pusty łańcuch. Rozwiązaniem jest tutaj wywołanie w postaci "cin.get();" poprzedzające chęć pobrania danych z klawiatury, które "zje" znak nowego wiersza. Bezargumentowa wersja get zwraca następny znak pozyskany z wejścia programu, który można przypisać zmiennej (zwrca kod znaku, który można przypisać do typu int). Rozwiązanie problemu może mieć również postać "cin.get(tablica, ilość_znaków).get();" po czym następuje kolejne wywołanie get z argumentami.

Różnicą między getline a get jest to, że get pozwala na zachowanie większej ostrożności. Przykładowo jeżeli użyliśmy get do wczytania tablicy to skąd wiadomo, iż odczytany został cały wiersz czy też przerwano go z powodu przepełnienia tablicy. Jeżeli kolejnym znakiem wejściowym jest znak nowego wiersza to wczytano cały wiersz, a w przeciwnym wypadku nadal mamy dane do odczytania. getline jest prostsza w użyciu jednak w starszych implementacjach może w ogóle nie występować.

Jednym z problemów jakie towarzyszą wywołaniom powyższych funkcji jest to, że w funkcje getline i get pozostawiają niewczytane znaki w kolejce wejściowej jeżeli łańcuch wejściowy jest dłuższy od miejsca na niego przeznaczonego. Poza tym jeżeli get odczyta pusty wiersz to ustawia bit błędu (co nie tyczy się getline), a dalsze wczytywanie danych jest wstrzymane aż do wywołania "cin.clear();". Problem pojawia się też kiedy wczytujemy liczbę za pomocą obiektu cin, a w kolejce wejściowej pozostaje nam znak nowego wiersza. Wyjściem z sytuacji jest oczywiście puste wywołanie "cin.get()" tudzież "(cin >> zmienna).get()".

Wejście i wyjście plikowe:
#include <iostream>
#include <fstream> // Dla klas ofstream (zapis) i ifstream (odczyt).
#include <string>
#include <exception>

int main() {
    std::string filename;

    std::cout << "Podaj sciezke i nazwe nowego pliku: ";

    std::cin >> filename;

    std::ofstream out(filename.c_str(), std::ios_base::out|std::ios_base::app); // Odczyt i dołączanie na końcu pliku.

    // std::ofstream out(filename.c_str()); // W tej postaci wywołania wszystkie dane, które są w podanym pliku zostaną uprzednio wykasowane.

    // Można również powyższa instrukcję zastąpić poniższymi:
    // std::ofstream out;
    //
    // out.open(filename.c_str());

    out << "Pierwsza linijka.\n";

    out << "Druga linijka.\n";

    out.close();

    std::ifstream in(filename.c_str());

    std::cout << "Oto zawartosc pliku:\n";

    char character;

    try {
        while (in.get(character))
            std::cout << character;
    }
    catch (std::ios::failure const & exception) {
        std::cout << "Nie udalo sie przeczytac z pliku poniewaz: " << exception.what() << std::endl;
    }

    // Do sprawdzania możliwości otwarcia pliku można również użyć wywołania "in.is_open()", które zwraca false jeżeli nie udało się otworzyć do odczytu.

    in.close();
}

Stałe trybu otwarcia pliku:
  • "ios_base::in": odczyt,
  • "ios_base::out": zapis,
  • "ios_base::ate": ustaw się na końcu,
  • "ios_base::app": dołącz na koniec pliku,
  • "ios_base::trunc": usuń zawartość pliku,
  • "ios_base::binary": plik binarny.

Źródło: Prata S., Język C++. Szkoła programowania, Wydanie VI, Helion SA, 2012