[15] Operacje wejścia/wyjścia przy użyciu <iostream> i <cstdio>
(Część C++ FAQ Lite, Copyright © 1991-2002, Marshall Cline, cline@parashift.com)


FAQ - sekcja [15]:


[15.1] Dlaczego powinno się używać <iostream> zamiast tradycyjnego <cstdio>?

<iostream> wspiera kontrolę typów, sprzyja zmniejszeniu liczby popełnianych błędów, oraz umożliwia tworzenie rozszerzeń. Ponadto kod, którego deklaracje znajdują się w tym nagłówku, można dziedziczyć we własnych klasach.

Być może printf() nie jest wcale taki zły, a ze scanf()'em nawet da się żyć pomimo tego, że łatwo popełnić błąd przy jego wywołaniu; jednakże obie funkcje są dość ograniczone w porównaniu do możliwości procedur wejścia/wyjścia w C++. Wejście/wyjście w C++ (przy użyciu operatorów << i >>), w porównaniu do rozwiązania z języka C (printf() i scanf()) posiada następujące zalety:

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.2] Dlaczego mój program wpada w nieskończoną pętlę za każdym razem, gdy użytkownik wpisze niewłaściwy znak?

Załóżmy przykładowo, że masz do czynienia z poniższym kodem, który pobiera liczbę całkowitą ze standardowego wejścia std::cin:

 #include <iostream>
 
 int main()
 {
   std::cout << "Podaj kilka liczb, rozdzielając je spacjami "
         "(wpisz -1 aby zakończyć): ";
   int i = 0;
   while (i != -1) {
     std::cin >> i;        
// ZŁY ZAPIS — Zobacz komentarz niżej
     std::cout << "Wpisano " << i << '\n';
   }
 }

Problem z tym kodem polega na tym, że nie jest on w żaden sposób zabezpieczony przed wpisaniem nieprawidłowego znaku. W szczególności, jeśli użytkownik wpisze coś co nie wygląda na liczbę całkowitą (np. znak 'x'), strumień std::cin wejdzie w "stan błędu" (ang. "failed state") i wszystkie kolejne próby odczytania czegokolwiek z niego będą powodowały natychmiastowy powrót do kodu wywołującego operator >>, bez odbierania jakichkolwiek danych. Krótko mówiąc, program wpadnie w nieskończoną pętlę; jeśli ostatnią wpisaną liczbą byłoby 42, program będzie w kółko wyświetlał Wpisano 42.

Najprostszym sposobem umożliwiającym sprawdzanie prawidłowości wpisanych danych jest przesunięcie instrukcji odczytującej dane do warunku pętli while. Na przykład:

 #include <iostream>
 
 int main()
 {
   std::cout << "Podaj liczbę (-1 = koniec): ";
   int i = 0;
   while (std::cin >> i) {    
// DOBRY ZAPIS
     if (i == -1) break;
     std::cout << "Wpisano " << i << '\n';
   }
 }

Dzięki temu pętla while zakończy działanie z chwilą, gdy strumień dotrze do końca pliku, gdy użytkownik wpisze coś co nie będzie liczbą całkowitą, lub gdy wpisze -1.

(Naturalnie można wyeliminować instrukcję break zmieniając treść instrukcji while z while (std::cin >> i) na while ((std::cin >> i) && (i != -1)); ale tak naprawdę nie o to chodzi w tym faqu; ma on za zadanie wyjaśnienie działania strumieni <iostreams>, a nie udzielanie ogólnych wskazówek odnośnie programowania strukturalnego)

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.3] Jak właściwie działa ta śmieszna instrukcja while (std::cin >> foo) ?

W poprzednim faqu możesz zobaczyć przykład z użyciem "tej śmiesznej instrukcji while (std::cin >> foo)".

Wyrażenie (std::cin >> foo) wywołuje odpowiednią wersję operatora >> (np., funkcję operator>> która pobiera "z lewej strony" argument typu std::istream, zaś "z prawej strony", jeśli foo jest typu int, argument typu int&). Zgodnie z przyjętą w <iostream> konwencją, funkcja operator>> zwraca swój "lewy argument", czyli w naszym przypadku strumień std::cin. Następnie kompilator stwierdza, że zwrócony strumień jest typu std::istream i że występuje w kontekście boolowskim [tzn. powinien się dać przedstawić w postaci "prawda" lub "fałsz"; wymaga tego pętla while, która zostanie przerwana dla "fałszu" lub będzie kontynuowana dla "prawdy" - przyp. tłum]; w związku z tym strumień (typ std::istream) zostaje przekształcony do wartości boolowskiej.

Aby przekształcić strumień do wartości boolowskiej, kompilator wstawia wywołanie metody std::istream::operator void*(). Zwraca ona wskaźnik typu void*, który następnie jest konwertowany do wartości boolowskiej (NULL odpowiada false, pozostałe wartości wskaźnika odpowiadają true). Zatem w tym przypadku kompilator generuje wywołanie metody std::cin.operator void*(), dokładnie tak jakbyś jawnie przekształcił(a) strumień pisząc (void*) std::cin.

Operator przekształcenia operator void*() zwraca wskaźnik nie-NULL gdy strumień jest w "stanie prawidłowym" (ang. "good state"), lub wskaźnik NULL gdy strumień jest w "stanie błędu" (ang. "failed state"). Na przykład, jeśli wykonasz zbyt wiele odczytów (i w efekcie już dawno będziesz się znajdować na końcu pliku) lub gdy wartość odczytana ze strumienia nie będzie odpowiadać typowi obiektu foo (np., gdy foo jest typu int a odczytaną daną jest znak 'x'), strumień przejdzie w stan błędu i operator przekształcenia zwróci NULL.

Przyczyną dla której operator>> nie zwraca po prostu wartości bool (lub void*) celem poinformowania o wyniku operacji, jest możliwość zastosowania "kaskadowej" składni:

   std::cin >> foo >> bar;

Łączność operatora >> jest lewostronna, stąd powyższy zapis można przedstawić tak:

   (std::cin >> foo) >> bar;

Innymi słowy, gdyby operator>> zastąpić zwykłą funkcją o nazwie, powiedzmy, readFrom(), powyższe wyrażenie możnaby zapisać w ten sposób:

   readFrom( readFrom(std::cin, foo), bar);

Jak zwykle, obliczanie wyrażenia rozpoczynamy od najbardziej wewnętrznego elementu. Z powodu lewostronnej łączności operatora >>, najbardziej wewnętrzne jest wyrażenie z lewej strony: std::cin >> foo. To wyrażenie zwraca strumień std::cin (ściślej, zwraca ono referencję do swojego lewostronnego argumentu), który zostaje umieszczony w następnym wyrażeniu [tym z "bar", w efekcie czego powstaje wyrażenie std::cin >> bar - przyp. tłum.]. To wyrażenie również zwróci (referencję do) std::cin, ale ta druga referencja zostanie zignorowana, gdyż jest ona wynikiem najbardziej zewnętrznego wyrażenia w instrukcji.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.4] Dlaczego mój program próbuje odczytać dane za końcem pliku?

Ponieważ znacznik końca pliku (ang. "eof state") jest ustawiany dopiero przy próbie odczytania danych za końcem pliku. Oznacza to, że odczytanie ostatniego bajtu w pliku nie spowoduje jeszcze ustawienia znacznika "eof". Przykładowo, załóżmy że standardowe wejście jest połączone z klawiaturą — w tym przypadku biblioteka C++ nie może nawet teoretycznie przewidzieć, czy znak, który użytkownik wpisał przed chwilą, jest ostatnim znakiem czy też może zostaną jeszcze wpisane kolejne.

Poniższy przykładowy kod będzie próbował odczytać jeden bajt tuż za końcem pliku - zmienna i osiągnie wartość o 1 większą, niż wynosi długość pliku liczona w bajtach:

 int i = 0;
 while (! std::cin.eof()) {   
// ŹLE!
   std::cin >> x;
   ++i;
   
// Zrób coś z x ...
 }

Natomiast poniższy kod działa zgodnie z oczekiwaniami:

 int i = 0;
 while (std::cin >> x) {      
// DOBRZE!
   ++i;
   
// Zrób coś z x ...
 }

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.5] Dlaczego mój program pomija instrukcję odczytu po zakończeniu pierwszej iteracji? UPDATED!

[Recently Wersja polska - poprawiono przypis tłumacza (in 9/02). Click here to go to the next FAQ in the "chain" of recent changes.]

Ponieważ po odczytaniu liczby, wszystkie znaki wpisane po niej, a nie będące cyframi, pozostają w buforze wejściowym.

Jeśli Twój kod wygląda mniej więcej tak:

 char name[1000];
 int age;
 
 for (;;) {
   std::cout << "Imię: ";
   std::cin >> name;
   std::cout << "Wiek: ";
   std::cin >> age;
 }

To tak naprawdę potrzebujesz czegoś takiego:

 char name[1000];
 int age;
 
 for (;;) {
   std::cout << "Imię: ";
   std::cin >> name;
   std::cout << "Wiek: ";
   std::cin >> age;
   std::cin.ignore(INT_MAX, '\n');
 }

[Przypis tłumacza - Ostatnia instrukcja w pętli pomija wszystkie znaki w buforze wejściowym aż do napotkania znaku końca linii '\n' - ten znak również jest ignorowany. Normalnie pierwszy parametr metody "std::cin.ignore" oznacza maksymalną liczbę znaków do pominięcia - jeżeli jednak parametr ten będzie równy maksymalnej liczbie jaką można zapisać w typie int, czyli INT_MAX (lub "std::numeric_limits<int>::max()") to wtedy nie jest wprowadzane żadne ograniczenie co do liczby znaków i metoda po prostu pomija wszystkie znaki aż do napotkania takiego samego jak podano w drugim parametrze. Symbol INT_MAX zdefiniowany jest w nagłówkach <climits> i <limits.h> (wystarczy dołączyć jeden z nich).

Dzięki tej instrukcji unika się sytuacji, w której nieprawidłowa część danych z pola "Wiek:" jest przepisywana do pola "Imię:". Jeśli ktoś, na przykład, w polu "Wiek:" wpisze "20 i pół" to sekwencja " i pół" zostanie pominięta i nie trafi ona do pola "Imię:" w następnej iteracji]

Ponadto możesz oczywiście zmienić instrukcję for (;; na while (std::cin) [aby np. sprawdzać, czy podano wiek w nieprawidłowej formie - przyp. tłum.], ale nie należy tego mylić z pomijaniem zbędnych znaków na końcu pętli przy użyciu instrukcji: std::cin.ignore(...);.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.6] W jaki sposób powinno się oznaczać koniec linii: przy użyciu "std::endl" czy '\n'? NEW!

[Recently created thanks to Moshe Shimoni (in 9/02). Click here to go to the next FAQ in the "chain" of recent changes.]

"std::endl" wysyła znak końca wiersza '\n' a następnie opróżnia bufor wyjściowy; oznacza to, że "std::endl" jest bardziej kosztowny pod względem wydajności. Oczywiście, jeżeli chcesz aby bufor był opróżniany dla każdej linijki, powinieneś (powinnaś) stosować "std::endl"; ale jeśli nie zależy Ci na opróżnianiu bufora przy każdej linijce, możesz przyśpieszyć swój kod stosując znaki '\n'.

Ten kod po prostu wysyła znak końca linii '\n':

 void f()
 {
   std::cout << 
...jakiś tekst... << '\n';
 }

Ten kod wysyła znak '\n', a następnie opróżnia bufor:

 void g()
 {
   std::cout << 
...jakiś tekst... << std::endl;
 }

Ten kod po prostu opróżnia bufor:

 void h()
 {
   std::cout << 
...jakiś tekst... << std::flush;
 }

Uwaga: każdy z powyższych trzech przykładów wymaga nagłówka #include <iostream>

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.7] W jaki sposób mogę umożliwić wyświetlanie/drukowanie/wysyłanie/zapisywanie mojej klasy Fred?

Możesz przeciążyć operator wysyłania <<, pisząc jego nową wersję zaprzyjaźnioną z klasą Fred.

 #include <iostream>
 
 class Fred {
 public:
   friend std::ostream& operator<< (std::ostream& o, const Fred& fred);
   
// ...
 private:
   int i_;    
// Tak dla przykładu
 };
 
 std::ostream& operator<< (std::ostream& o, const Fred& fred)
 {
   return o << fred.i_;
 }
 
 int main()
 {
   Fred f;
   std::cout << "Mój obiekt klasy Fred: " << f << "\n";
 }

Nową wersję operatora zdefiniowaliśmy w zwykłej funkcji (w naszym przypadku zaprzyjaźnionej z klasą Fred), ponieważ obiekt Fred jest prawostronnym argumentem operatora <<. Gdyby obiekty Fred miały być umieszczane po lewej stronie operatora << (to znaczy tak: myFred << std::cout, a nie tak: std::cout << myFred), moglibyśmy zdefiniować nową wersję operatora w postaci metody.

Zwróć uwagę, że operator<< zwraca strumień. Dzięki temu operacje wysyłania mogą być łączone kaskadowo.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.8] Ale czy zamiast "zaprzyjaźnionej funkcji" nie powinno się raczej definiować metod w stylu printOn()?

Nie.

Tego rodzaju twierdzenia, że zawsze powinno się używać metod w stylu printOn() zamiast "zaprzyjaźnionych funkcji", przeważnie wynikają z niesłusznego przekonania, że "przyjaciele klas" naruszają enkapsulację i że wogóle nie powinno się ich stosować. Są to naiwne i złe przesądy: "przyjaciele klas", prawidłowo zastosowani, w rzeczywistości mogą nawet polepszyć enkapsulację.

Dla pełnej jasności tematu przedstawię o co chodzi we wspomnianej "metodzie w stylu printOn()". Pomysł polega na zdefiniowaniu wewnątrz klasy metody (na ogół nazywanej właśnie printOn()), która dokonywałaby właściwego wysyłania danych, a następnie zdefiniowaniu w zasięgu globalnym funkcji operator<< która wywoływałaby metodę printOn(). W złych rozwiązaniach, metoda printOn() jest publiczna, stąd operator<< nie musi być "przyjacielem klasy" — może być zwykłą, globalną funkcją, nie będącą ani "przyjacielem" ani metodą klasy. Przykładowy kod mógłby wyglądać następująco:

 #include <iostream>
 
 class Fred {
 public:
   void printOn(std::ostream& o) const;
   
// ...
 };
 
 
// operator<< nie musi być deklarowany jako "przyjaciel"
 
// [NIE zalecane!]
 std::ostream& operator<< (std::ostream& o, const Fred& fred);
 
 
// Dane wysyłane są wewnątrz metody printOn() [NIE zalecane!]
 void Fred::printOn(std::ostream& o) const
 {
   
// ...
 }
 
 
// operator<< po prostu wywołuje printOn() [NIE zalecane!]
 std::ostream& operator<< (std::ostream& o, const Fred& fred)
 {
   fred.printOn(o);
   return o;
 }

Niektórzy niesłusznie zakładają, że takie rozwiązanie obniża koszty związane z utrzymywaniem kodu, "ponieważ pozwala ono uniknąć stosowania zaprzyjaźnionej funkcji". Jest to niesłuszne założenie, gdyż:

  1. Rozwiązanie z metodą-wywoływaną-przez-globalną-funkcję nie przynosi żadnych korzyści co do kosztów utrzymania kodu. Powiedzmy, że wysłanie danych zajmuje N linii kodu. W przypadku "zaprzyjaźnionej funkcji", te N linii kodu będzie miało bezpośredni dostęp do prywatnych/chronionych składowych klasy, co oznacza, że jeśli ktoś wprowadzi jakieś zmiany do tych prywatnych/chronionych składowych, konieczne będzie sprawdzenie i, prawdopodobnie, zmodyfikowanie tych N linii kodu, co zwiększa koszty utrzymania. Jednakże zastosowanie metody printOn() niczego nie zmieni: wciąż będziemy mieli N linii kodu, które będą dysponowały bezpośrednim dostępem do prywatnych/chronionych składowych klasy. Stąd przemieszczenie kodu z "funkcji-przyjaciela" do metody klasy wcale nie zmniejszyło kosztów utrzymania. Nie ma żadnej redukcji kosztów. Żadnych korzyści. (Sytuacja w zasadzie się nawet pogorszyła, gdyż teraz mamy więcej linii kodu do utrzymania - w związku z dodaniem nowej funkcji, której wcześniej nie było).
  2. Rozwiązanie z metodą-wywoływaną-przez-globalną-funkcję utrudnia stosowanie klasy, zwłaszcza dla programistów, którzy nie uczestniczyli w jej projektowaniu. W rozwiązaniu tym występuje publiczna metoda, której programiści nie powinni wywoływać bezpośrednio. Gdy programista będzie przeglądał spis publicznych metod klasy, stwierdzi, że tą samą operację [wysyłanie danych do strumienia - przyp. tłum.] można wykonać na dwa sposoby. W dokumentacji klasy może być napisane "'A' robi dokładnie to samo co 'B', ale nie używaj 'A' tylko 'B'". Natomiast przeciętny programista stwierdzi "Hęęę? Po co było deklarować tę metodę jako publiczną, skoro i tak nie wolno mi jej wywoływać?" W zasadzie metoda printOn() jest deklarowana jako publiczna tylko po to, aby nie zaprzyjaźniać funkcji operator<< z klasą - dla programisty który po prostu chce użyć tej klasy, reakcja na takie podejście może się mieścić gdzieś pomiędzy zdziwieniem a niezrozumieniem.

Podsumowując: rozwiązanie z metodą-wywoływaną-przez-globalną-funkcję wiąże się z pewnymi kosztami, ale nie przynosi żadnych korzyści. Dlatego jest to generalnie zły pomysł.

Notka: W sytuacji gdy metoda printOn() jest zadeklarowana jako chroniona lub prywatna, druga uwaga przestaje obowiązywać. Istnieją przypadki, gdy omówione rozwiązanie okazuje się być rozsądnym wyjściem, np. aby dodać obsługę wysyłania obiektów do strumienia dla całej hierarchii klas. Ponadto zwróć uwagę, że gdy metoda printOn() jest niepubliczna, konieczne jest zadeklarowanie funkcji operator<< jako "przyjaciela klasy".

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.9] W jaki sposób mogę umożliwić pobieranie ze strumienia obiektów mojej klasy Fred?

Możesz przeciążyć operator pobierania >>, pisząc jego nową wersję zaprzyjaźnioną z klasą Fred. Jest to rozwiązanie analogiczne jak w przypadku operatora wysyłania, z tym że parametr operatora nie jest zadeklarowany jako stały: deklaruje się go "Fred&" zamiast "const Fred&".

 #include <iostream>
 
 class Fred {
 public:
   friend std::istream& operator>> (std::istream& i, Fred& fred);
   
// ...
 private:
   int i_;    
// Tak dla przykładu
 };
 
 std::istream& operator>> (std::istream& i, Fred& fred)
 {
   return i >> fred.i_;
 }
 
 int main()
 {
   Fred f;
   std::cout << "Podaj obiekt klasy Fred: ";
   std::cin >> f;
   
// ...
 }

Zauważ, że operator>> zwraca strumień. Dzięki temu operacje pobierania mogą być łączone kaskadowo oraz/lub używane jako warunek w instrukcjach while i if.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.10] W jaki sposób mogę dodać obsługę drukowania/wyświetlania/wysyłania dla całej hierarchii klas?

Możesz napisać nową wersję operatora <<, zaprzyjaźnioną z klasą podstawową hierarchii. Wersja ta wywoływałaby chronioną, wirtualną metodę, która z kolei dokonywałaby właściwego wysłania obiektu:

 class Base { // Klasa podstawowa
 public:
   friend std::ostream& operator<< (std::ostream& o, const Base& b);
   
// ...
 protected:
   virtual void printOn(std::ostream& o) const;
 };
 
 
// Wersja operatora << zaprzyjaźniona z klasą podstawową
 inline std::ostream& operator<< (std::ostream& o, const Base& b)
 {
   b.printOn(o);
   return o;
 }
 
 
// Kolejna klasa w hierarchii
 class Derived : public Base {
 protected:
   virtual void printOn(std::ostream& o) const;
 };

W rezultacie operator<< zachowuje się tak, jakby był dynamicznie wiązany z odpowiednią klasą, pomimo tego, że jest to zwykła, zaprzyjaźniona funkcja. Technika ta nazywana jest Virtual Friend Function Idiom.

Zwróć uwagę, że klasy potomne przeciążają metodę printOn(std::ostream&) const. W szczególności, nie dostarczają one własnych wersji operatora <<.

Naturalnie, gdyby Base była abstrakcyjną klasą podstawową, metoda Base::printOn(std::ostream&) const mogłaby zostać zadeklarowana jako metoda abstrakcyjna przy użyciu zapisu "= 0".

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.11] W jaki sposób mogę otworzyć strumienie std::cin i std::cout w trybie binarnym pod DOSem i/lub OS/2?

Zależy to od implementacji. Sprawdź w dokumentacji Twojego kompilatora.

Na przykład, załóżmy że chcesz dysponować binarnym wejściem/wyjściem przy użyciu strumieni std::cin i std::cout. Następnie załóżmy, że Twój system operacyjny (np. DOS lub OS/2) tłumaczy w strumieniu wejściowym sekwencje "\r\n" na "\n", zaś w strumieniu wyjściowym i strumieniu błędów, znaki "\n" na sekwencje "\r\n".

Niestety, nie istnieje żadne standardowe rozwiązanie pozwalająca na otwarcie strumieni std::cin, std::cout i/lub std::cerr w trybie binarnym. Zamknięcie strumieni i próba ich ponownego otwarcia w trybie binarnym może doprowadzić do nieprzewidzianych i niepożądanych skutków.

W systemach, w których istnieje różnica między strumieniem binarnym a strumieniem tekstowym, mogą istnieć rozwiązania pozwalające na zmianę trybu pracy strumienia - ale żeby coś się o nich dowiedzieć, będziesz musiał(a) poszukać informacji na ten temat w dokumentacji systemu.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.12] W jaki sposób mogę sprawdzić "czy klawisz" lub "który klawisz" został wciśnięty zanim użytkownik nacisnął ENTER?

Nie jest to standardową właściwością języka C++ — C++ nie wymaga nawet, aby Twój system zawierał klawiaturę! Innymi słowy, każdy system operacyjny i producent stosują inne rozwiązania.

Proszę poszukać szczegółów dotyczących danej implementacji w dokumentacji dołączonej do Twojego kompilatora.

(Swoją drogą, w przypadku procesu pracującego pod kontrolą systemu UNIX na ogół wystarczą dwa kroki: najpierw trzeba ustawić terminal na tryb jednoznakowy [ang. single-character mode], a następnie wywołać funkcję select() lub poll() aby sprawdzić, czy klawisz został naciśnięty. Ewentualnie, możesz spróbować zastosować kod znajdujący się pod tym odsyłaczem.)

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.13] W jaki sposób można wyłączyć automatyczne wyświetlanie znaków wpisanych przez użytkownika [ang. echo]?

Nie jest to standardową właściwością języka C++ — C++ nie wymaga nawet, aby Twój system zawierał klawiaturę lub monitor. Oznacza to, że każdy system operacyjny oraz producent stosują inne rozwiązania.

Proszę poszukać szczegółów dotyczących danej implementacji w dokumentacji dołączonej do Twojego kompilatora.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.14] W jaki sposób mogę zmienić położenie kursora na ekranie?

Nie jest to standardową właściwością języka C++ — C++ nie wymaga nawet, aby Twój system zawierał monitor. Oznacza to, że każdy system operacyjny oraz producent stosują inne rozwiązania.

Proszę poszukać szczegółów dotyczących danej implementacji w dokumentacji dołączonej do Twojego kompilatora.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.15] W jaki sposób mogę "wymazać" zawartość ekranu? Czy w C++ jest coś w rodzaju clrscr()? NEW!

[Recently created (in 9/02). Click here to go to the next FAQ in the "chain" of recent changes.]

Nie jest to standardową właściwością języka C++ — C++ nie wymaga nawet, aby Twój system zawierał monitor. Oznacza to, że każdy system operacyjny oraz producent stosują inne rozwiązania.

Proszę poszukać szczegółów dotyczących danej implementacji w dokumentacji dołączonej do Twojego kompilatora.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.16] W jaki sposób mogę zmienić kolor wyświetlanego tekstu? NEW!

[Recently created (in 9/02). Click here to go to the next FAQ in the "chain" of recent changes.]

Nie jest to standardową właściwością języka C++ — C++ nie wymaga nawet, aby Twój system zawierał monitor. Oznacza to, że każdy system operacyjny oraz producent stosują inne rozwiązania.

Proszę poszukać szczegółów dotyczących danej implementacji w dokumentacji dołączonej do Twojego kompilatora.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.17] Dlaczego nie mogę otworzyć pliku znajdującego się w innym katalogu, jak na przykład "..\test.dat"?

Ponieważ "\t" oznacza "znak tabulacji".

Zamiast znaków backslash "\" powinieneś (powinnaś) używać znaków slash "/" (forward slash), nawet jeżeli pracujesz na systemie operacyjnym używającym backslashy (DOS, Windows, OS/2, etc.). Na przykład:

 #include <iostream>
 #include <fstream>
 
 int main()
 {
   #if 1
     std::ifstream file("../test.dat");  
// DOBRZE!
   #else
     std::ifstream file("..\test.dat");  
// ŹLE!
   #endif
 
   
// ...
 }

Zapamiętaj: backslash "\" używany jest w stałych napisowych do zapisywania znaków specjalnych: "\n" oznacza koniec wiersza, "\b" cofnięcie kursora o jedną pozycję i zmazanie znaku (backspace), "\t" poziomą tabulację, "\a" sygnał dźwiękowy, "\v" pionową tabulację itd. Dlatego nazwa "\version\next\alpha\beta\test.dat" interpretowana jest jako ciąg bardzo śmiesznych znaczków. Zamiast tego, bezpieczniej jest stosować nazwy plików w postaci "/version/next/alpha/beta/test.dat", nawet w przypadku systemów, w których znakiem oddzielającym nazwy kolejnych katalogów jest backslash "\". Jest to możliwe, gdyż procedury biblioteczne pracujące na tych systemach obsługują zarówno ścieżki ze znakami "/" jak i "\".

Oczywiście, możesz również zapisać nazwę pliku w postaci "\\version\\next\\alpha\\beta\\test.dat", [sekwencja "\\" zastępuje jeden znak "\" - przyp. tłum.] ale to może Cię kiedyś skrzywdzić (zawsze istnieje nie-zerowe prawdopodobieństwo, że w jakimś miejscu zapomnisz wpisać jednego znaku "\"; to dosyć subtelny błąd, którego wielu ludzi początkowo nie zauważa), poza tym nie pomoże Ci to w żadnym stopniu (stosowanie znaków "\\" nie ma żadnej przewagi nad stosowaniem znaków "/"). Ponadto, rozwiązanie ze slashami "/" jest bardziej przenośne, gdyż działa ono na wszystkich odmianach systemu Unix, Plan 9, Inferno, wszystkich wersjach Windows, OS/2, itd., natomiast rozwiązanie z backslashami "\\" działa tylko na nielicznych systemach z tej listy. Zatem stosując backslashe "\\" ponosisz jedynie koszty, nie otrzymując nic w zamian: zamiast nich stosuj znaki slash "/".

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.18] Jak mogę przekonwertować pewną wartość (na przykład liczbę) na typ std::string?

Istnieją na to dwa proste sposoby: możesz skorzystać z funkcji zawartych w <cstdio> lub w bibliotece <iostream>. Generalnie, powinno się raczej stosować bibliotekę <iostream>.

Biblioteka <iostream> pozwala Ci przekonwertować niemal wszystkie typy danych na std::string przy użyciu poniższej składni (w tym przykładzie konwertowany jest typ double, ale możesz tu umieścić dowolny typ, który może być wysyłany do strumienia przy użyciu operatora <<):

 #include <iostream>
 #include <sstream>
 #include <string>
 
 std::string convertToString(double x)
 {
   std::ostringstream o;
   if (o << x)
     return o.str();
   
// tu powinno się znajdować coś do obsługi błędów...
   return "conversion error";
 }

Obiekt o klasy std::ostringstream udostępnia takie same mechanizmy służące do formatowania tekstu jak strumień std::cout. Możesz używać manipulatorów oraz flag formatujących aby wpływać na kształt końcowego ciągu znaków, dokładnie tak jak możesz to robić w przypadku std::cout.

W powyższym przykładzie wstawiamy wartość zmiennej x do o przy użyciu przeciążonego operatora wysyłania <<. Powoduje to wywołanie mechanizmów formatujących zaszytych w bibliotece iostream, które konwertują wartość zmiennej x do typu std::string. Instrukcja if sprawdza, czy konwersja przebiegła pomyślnie — dla wbudowanych/prostych typów powinna się ona zawsze powieść, ale w dobrym stylu jest sprawdzanie tego na wszelki wypadek przy użyciu instrukcji if.

Wyrażenie o.str() zwraca napis typu std::string, który zawiera wszystko, co zostało wcześniej umieszczone w strumieniu o; w tym przypadku wartość zmiennej x przedstawioną w formie tekstowej.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[15.19] Jak mogę przekonwertować napis typu std::string na liczbę?

Istnieją na to dwa proste sposoby: możesz użyć funkcji zadeklarowanych w nagłówku <cstdio> lub znajdujących się w bibliotece <iostream>. Generalnie, powinno się raczej korzystać z biblioteki <iostream>.

Biblioteka <iostream> umożliwia przekonwertowanie napisu typu std::string na niemal dowolny inny typ przy użyciu składni przedstawionej w poniższym przykładzie (w którym napis konwertowany jest na typ double, ale możesz tu umieścić cokolwiek co można odczytać przy użyciu operatora >>):

 #include <iostream>
 #include <sstream>
 #include <string>
 
 double convertFromString(const std::string& s)
 {
   std::istringstream i(s);
   double x;
   if (i >> x)
     return x;
   
// tu powinna się znajdować jakaś obsługa błędów...
   return 0.0;
 }

Obiekt i klasy std::istringstream udostępnia takie same mechanizmy służące do formatowania tekstu jak strumień std::cin. Możesz używać manipulatorów oraz flag formatujących dokładnie tak samo, jak w przypadku strumienia std::cin.

W powyższym przykładzie, inicjujemy strumień i klasy std::istringstream przy użyciu napisu s typu std::string (przykładowo, s może zawierać napis "123.456"), a następnie pobieramy liczbę ze strumienia i do zmiennej x przy użyciu przeciążonego operatora pobierania >>. W trakcie tej operacji zostają wywołane mechanizmy formatowania, które konwertują największą możliwą część napisu do typu zmiennej x.

Instrukcja if sprawdza, czy konwersja powiodła się czy też nie. Przykładowo, jeśli napis zawierałby znaki, które nie pasowałyby do typu zmiennej x, warunek w instrukcji if nie zostałby spełniony.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


E-Mail E-mail the author
C++ FAQ LiteSpis treściSkorowidzO autorze©Pobierz swoją własną kopię ]
Ostatnia aktualizacja Jun 17, 2002
Wersja polska: 0.1i Jul 12, 2004