Strumienie

W tej lekcji zajmiemy się strumieniami. Określenie strumień oznacza powiązanie pomiedzy kilkoma urządzeniami. Strumień służy do przesyłania danych z jednego urządzenia do drugiego. Raz jest to pamięć i dysk, kiedy to zapisujemy plik na dysk. Innym razem pamięć i drukarka podczas drukowania. Takich powiązań może być wiele. O niektórych z nich pogadamy w tej lekcji. Chciałem jeszcze tylko powiedzieć, że strumienie nie sąintegralną częścią samego języka. Bowiem C++ sam w sobie nie posiada instrukcji umożliwiających komunikację z urządzeniach wejściowymi, bądź wyjściowymi. Jeżeli chcesz pobazgrolić trochę po ekranie lub odczytać informacje z klawiatury musisz skorzystać z funkcji bibliotecznych lub samemu je sobie napisać 🙂 Ten drugi sposób jest cięższy w realizacji, ale ma swoje zalety. Funkcje napisane w konkretnym celu działają zazwyczaj lepiej i szybciej. Jednakże nie jest to takie trywialne. Trzeba bowiem znać język maszynowy, czyli assemblera. Jeżeli umiesz się posługiwać assemblerem to na pewno ucieszysz się, że C++ zezwala na umieszczanie wstawek asemblerowych bezpośrednio w kodzie C++! Wystarczy skorzystać ze słowa kluczowego asm i w klamerkach umieścić instrukcje w asm’ie. Dla wtajemniczonych mały przykład:

Taki zapis spotkałem wielokrotnie w literaturze. Jednak niektórzy twierdzą, że on nie zadziała. Tak więc jeśli coś pójdzie źle to spróbuj zapisać tak:

niektóre kompilatory nie akceptują żadnej z tych składni. Wówczas spróbuj napisać w taki sposób:

Jeszcze jedno. Nie wszystkie kompilatory C++ posiadają wbudowany kompilator asemblera. Wtedy trzeba kompilator asm’a umieścić w folderze, w którym mieszczą się takie pomocnicze programiki. Ja korzystam z kompilatora Dev C++, który zawiera kompilator asm’a. Tobie też polecam ten kompilator. Wracając do strumieni. Aby skorzystać ze strumienia należy wykonać kilka rzeczy opisanych poniżej.

1. otworzenie strumienia – zdefiniowanie
2. określenie, przepływu danych – utworzenie powiązania między urządzeniami
3. dokonanie operacji na strumieniu – skorzystanie ze strumienia
4. zamknięcie strumienia – odwołanie

Myslę, że Cię nie przeraziłem powyżdzą wyliczanką 🙂 Wydaje się to bardzo skomplikowane i trudne, ale zaręczem iż tak nie jest. Korzystanie ze strumieni jest łatwe i przyjemne. Dodatkowo istnieją strumienie predefiniowane, czyli definiowane przez kompilator automatycznie i wcale nie musisz uczyć się na pamięć powyższej litani 🙂 Teraz właśnie pogadamy o tych predefiniowanych strumieniach. Warto tutaj jeszcze pamietać, iż strumienie są to obiekty konkretnych klas. Klasy te są zdeklarowane w plikach nagłówkowych iostream.h i fstream.h.

strumień wejściowy – cin

Jest to podstawowy strumień wejściowy C++. Jego działanie polega na odczytaniu ciągu znaków wpisanych z klawiatury. Strumień cin jest obiektem klasy istream. Jej deklaracja znajduje się w pliku iostream.h. Aby wstawić do zmiennej liczbowej wartość z klawiatury należy napisać tak:

Już same strzałki sugerują wykonywaną akcję. Instrukcja: cin >> zmienna; oznacza przelanie zawartości strumienia cin do zmiennej. Pytanie tylko, co się w takim strumieniu znajduje? A no wszystko to, co wstukaliśmy z klawiatury. Po wykonaniu tego fragmentu kodu na ekranie ukarze się migający kursor. Umożliwia on umieszczenie w strumieniu dowolnych danych. Tylko uważaj! Strumień przyjmuje wszystkie znaki. W naszym przykładzie chcemy wstawić zawartość strumienia do zmiennej typu long. Co zatem stanie się gdy wpiszemy litery? No cóż.. raczej jest to błąd. Bynajmniej kompilator nie potrafi na takie sytuacje zareagować. Po prostu jeżeli dane będą nie właściwe to zmienna nie zostanie zmodyfikowana. Należy więc uważać i podawać właściwe dane. Oprócz tego ostatecznym zakończeniem wpisywania znaków z klawiatury jest znak entera, co nie zawsze jest odpowiednie.

strumień wyjściowy – cout

Pewnie już się domyślasz, do czego służy ten strumień. Krótko mówiąc służy do przelania wartości zapisanej w konkretnej zmiennej na ekran. Strumień cout to obiekt klasy ostream. Jej deklaracja umieszczona została w pliku nagłówkowym iostream.h. Składniowo też jest prosto. Wystarczy zapisać tak:

Nie ma tutaj żadnych cudów. Pierwsza linijka do zwykła definicja zmiennej wraz z inicjalizacją. Następnie następuje wypisanie zawartości zmiennej na ekranie. Chyba proste? Tak się prezentują dwa podstawewe strumienie wejścia – wyjścia. Są jeszcze dwa, jednak ich działanie jest mało przydatne. Konkretnie służą do informowania o ewentualnych błędach. Ja nigdy z nich nie korzystałem i jakoś dawałem sobie radę. Dlatego nie będę Ci zawracał głowy zbędnym gadaniem. Zamiast tego przejdźmy do bardziej praktycznych rzeczy, czyli do operacji z dyskiem.

strumienie do pracy z plikami

Tutaj także będziemy rozmawiać o strumienich. Tym razem będą to strumienie stworzone do pracy z plikami. Te strumienie już nie są predefiniowane, przez co bedziesz musiał wykonać nieco więcej pracy przed skorzystaniem ze strumienia. Jednkże nie jest to aż tak skomplikowane i bardzo przypomina zwykłą definicję zmiennej. Aby skorzystać z pliku należy wsześniej określić kilka rzeczy.

1. nazwa pliku
2. tryb w jakim zostanie otwarty plik
3. dostępność pliku dla innych programów

Wszystko to robi się za pomocą jednej funkcji open. Funkcja pobiera trzy argumenty. Pierwszy to nazwa pliku wraz ze ścieżką dostępu. Jeżeli podasz samą nazwę zostanie przeszukany aktualny folder. Argument drugi to tryb pracy. Jest ich kilka i zostały zamieszczone w tabelce poniżej.

tryb pracy opis
ios::in Otwiera plik do czytania.
ios::out Otwiera plik do zapisywania. Jeżeli nie określisz tego inaczej stara część pliku zostanie usunięta!
ios::ate Określa, że po otworzeniu pliku wskaźnik czytania/pisania zostanie ustawiony na końcu pliku. Wówczas wsześniejsza zawartość pliku nie zostanie utracona.
ios::app Sprawia, że wszelkie operacje zapisu będą odbywały się na końcu pliku.
ios::trunc Powoduje utratę starej zawartości pliku
ios::nocreate Zabrania tworzenia newego pliku. Jeżeli plik istniej zostanie otwarty, w przeciwnym wypadku operacja nie powiedzie się.
ios::noreplace Zezwala na utworzenie pliku jedynie w sytucji, gdy jeszcze nie istnieje. W przeciwnym razie operacja nie powiedzie się.
ios::binary Nakazuje otwarcie pliku w trybie binarnym. Domyślnie jest tryb tekstowy. Jeżeli korzystasz z trybu tekstowego, to powinieneś wiedzieć o kilku rzeczach: podczas zapisu do pliku znak '\n’ zostanie zamieniony na: '\r’ i '\n’. W przypadku odczytu następuje sytuacja odwrotna. Każde wystąpienie znaków: '\r’ i '\n’ zostanie zamienione na znak '\n’.

Został jeszcze jeden argument określjący dostępność dla innych programów. W dużym stopniu jest on uzależniony od samego sprzętu, więc skorzystamy z domniemania.
Powyższe tryby można ze sobą łączyć za pomocą sumy bitowej [|]. Aby otworzyć nowy plik z możliwością jednoczesnego zapisu i odczytu w trybie binarnym należy zastowować następującą składnię:

Tak się prezentuje funkcja otwierająca plik. O operacjach zapisu i odczytu powiemy sobie za moment. Pamiętaj tylko, aby przed zakończeniem pracy z plikiem wywołać funkcję zwalniającą strumień. Nosi ona nazwę close i jest wywoływana bez żadnych argumentów.

operacje na plikach

Tutaj którko przedstawie kilka funkcji do pracy ze strumieniami. Najpierw opiszę funkcje do pracy ze strumieniami.

Funkcja get(char &) wyjmuje ze strumienia jeden bajt i umieszcza go w zmiennej znak. Jest to analogiczna sytuacje, jak przy użyciu strumienia cin [cin >> znak;].

Podobnie, jak poprzednio funkcja get(char *, int, int) służy do pobrania informacji ze strumienia. Poprzednio był to jeden bajt. Tym razem możemy dodatkowo określić ilość pobranych bajtów. Pierwszy argument to wskaźnik do tablicy znakowej, w której będą umieszczane pobierane bajty. Argument drugi to ilość wczytanych bajtów. Trzeci argument jest domniemany i określa, po którym znaku należy zakończyć pobieranie danych. W domyśle jest to znak końca wiersza. Znak kończący nie jest wczytywany.

Działanie tej funkcje jest bardzo zbliżone do poprzedniej. Różnica polega na tym, że znak ograniczający również zostanie pobrany ze strumienia.

Kolejna przydatna funkcja. Służy do odczytu ze strumienia dowolnej ilości bajtów. Odczytuje dane w postaci binarnej. Pierwszy argument to oczywiście adres bufora, do którego powędruja wczytywane dane. Kolejny to ilość wczytywanych bajtów.

Następna dość ciekawa funkcja. Jej działanie skupia się na przeskoczeniu kilku bajtów. Jest to przydatne w sytuacjach, gdy chcemy pominąć pewne dane. Argument pierwszy to ilość pomijanych bajtów. Drugi argument to znak ograniczający. W domyśle jest to znak końca pliku, czyli EOF.

Informuje, ile bajtów zostało wczytanych podczas ostatniej operacji odczytu ze strumienia.

Ta funkcja służy do sprawdzenia, co też znajduje się w strumieniu. Sama funkcja nie pobiera bajtu, a jedynie informuje o jego wartości.

Funkcja służy do zwrócenia do strumienia znaku, który został już pobrany wcześniej.

Ta funkcja wstawia do strumienia jeden bajt.

Funkcja wstawia do strumienia, na którym pracuje określoną ilość znaków zawartych w tablicy. Pierwszy argument to właśnie ta tablica źródłowa, a drugi do ilość wstawianych bajtów.

Funkcja tellg() informuje, gdzie aktualnie znajduje się wskaźnik czytania. Ten tajemniczy typ streampos zo zwykły typ long.

Jak nie trudno się domyślić, funkcja tellp() zwraca pozycję wskaźnika pisania.

Ta funkcja służy do określenia nowego położenia wskaźnika czytania. Argument pierwszy to typ streampos, czyli po prostu long, zaś drugi to typ wyliczeniowy enum. Za jego pomocą określamy położenie wskaźnika w strumieniu. Jeśli chcemy przesunąć wskaźnik czytania względem bieżącej pozycji to podajemy w drugim argumencie ios::cur. Jeżeli chcesz ustawić pozycję wskaźnika zaczynając od początku strumienia należy napisać: ios::beg, jako argument drugi. Natomiast jeśli chciałbyś przeskoczyć do określonego bajtu numerując od końca strumienia trzeba podać: ios::end w drugim argumencie.

Jej działanie również polega na pozycjonowaniu wskaźnika. Tym razem jest to wskaźnik pisania. Obowiązują tutaj takie same zasady, jak w funkcji poprzedniej.

Bardzo przydatna funkcja informująca, czy ostatnia operacja na strumieniu powiodła się. W przypadku sukcesu funkcja zwraca wartość niezerową. W razie porażki rezultatem jest zero.

Również funkcja informacyjna. Tym razem zwrócona zostanie wartość zerowa w sytuacji, gdy został przekroczony zakres strumienia. Przeważnie stosowana jest do skontrolowania długości pliku.

Rezultatem tej funkcji jest wartość niezerowa, w przypadku gdy wystąpił jakiś błąd podczas pracy ze strumieniem.

Ta funkcja również zwraca wartość niezerową, jeżeli zostanie napotkany błąd ze strumieniem. Dotyczy poważniejszych błędów.
Na tym zakończę ten rozdział. Oczywiście funkcji składowych do operacji za strumieniami jest więcej. Ja przedstawiłem jedynie część z nich. Tak naprawdę sam korzystam z nich sporadycznie! Wolę funkcje, jakie oferuje mi Windows. O tych funkcjach na razie nie mówiłem. Postaram się za jakiś czas dodać kilka lekcji opisujących tworzenie programów pod Win. Wtedy nadmienię także o funkcjach zapisu / odczytu z dysku. Póki co pobaw się trochę tym, co jest 🙂

Autor: Kesay

[Artykuł pochodzi ze strony guidecpp.prv.pl, autor wyraził zgodę na publikację]