Podrozdziały


16.3 Formatowanie

Opisany wyżej sposób formatowania można zmienić. W szczególności dotyczy to operacji wyjścia, przy których często chcielibyśmy uzyskiwać wyniki w zaplanowanej formie, tak aby zapewnić czytelność lub zgodność z ustalonymi wymaganiami. Służą do tego flagi formatowania i manipulatory.


16.3.1 Flagi formatowania

Sposób działania operatorów we/wy określony jest aktualnym stanem związanej z każdym strumieniem flagi stanu formatowania.

Do manipulowania flagą stanu formatowania danego strumienia służą flagi zdefiniowane w klasie bazowej ios_base — a tym samym w klasie pochodnej ios — jako składowe statyczne. Odwołujemy się do zatem do nich poprzez operator zakresu klasy, np.  ios::left, ios::scientific itd. Flagi te są, jak i cała flaga stanu formatowania, typu ios_base::fmtflags (wiele starszych kompilatorów nie definiuje takiego typu; w takiej sytuacji można zazwyczaj użyć typu long). Zauważmy, że nazwa typu fmtflags jest zadeklarowana w klasie ios_base, więc musimy się do niej odwoływać poprzez nazwę kwalifikowaną ios_base::fmtflags (lub po prostu ios::fmtflags).

Reprezentacja bitowa poszczególnych flag zawiera z reguły jeden ustawiony bit (czyli jedynkę na pewnej pozycji), a pozostałe bity nieustawione. Wynika z tego, że flagi można „ORować” za pomocą alternatywy bitowej, aby uzyskać flagę stanu formatowania o żądanych własnościach.

Zanim podamy przykłady, wyliczmy dostępne flagi formatowania, z których, za pomocą alternatywy bitowej, można zbudować flagę stanu. Flagi boolalpha, showbase, showpoint, showpos, skipws, unitbufuppercase mają też odpowiedniki o odwrotnym działaniu — ich nazwy mają prefix ' no', na przykład noboolalpha, noshowbase itd.

ios::skipws —  zignoruj, przy wczytywaniu, wiodące białe znaki (domyślnie: TAK).

ios::left, ios::right, ios::internal —  wyrównanie przy pisaniu do lewej, prawej albo obustronne, czyli „znak (lub inny prefix, jak na przykład 0x), do lewej, liczba do prawej”. Na przykład, jeśli zapisujemy liczbę -123 w polu o szerokości 8 znaków, to stosując te trzy sposoby wyrównania otrzymalibyśmy

                           |-123    |
                           |    -123|
                           |-    123|
Te trzy flagi razem tworzą pole justowania (wyrównywania) ios::adjustfield. Najwyżej jedna z nich może być ustawiona (patrz dalej). Jeśli żadna nie jest ustawiona, to wyrównanie jest do prawej. Flaga internal, jako cokolwiek dziwaczna, w niektórych implementacjach jest ignorowana.

ios::dec, ios::hex, ios::oct —  podstawa dla wczytywanych/pisanych liczb całkowitych: dziesiętna (domyślnie), szesnastkowa i ósemkowa. Razem tworzą pole podstawy ios::basefield. Najwyżej jedna z nich może być ustawiona; jeśli żadna nie jest ustawiona, to odczyt/zapis jest dziesiętny.

ios::scientific, ios::fixed —  format dla wypisywanych liczb zmiennopozycyjnych. Razem tworzą pole formatu zmiennopozycyjnego ios::floatfield. Najwyżej jedna z nich może być ustawiona. Jeśli żadna nie jest ustawiona, to użyty będzie format ogólny. W notacji naukowej (flaga scientific) wypisywana jest jedna cyfra przed kropką, maksymalnie tyle cyfr po kropce, ile wynosi aktualna precyzja, następnie litera 'e' i wykładnik potęgi dziesięciu, przez którą należy pomnożyć liczbę znajdującą się przed literą 'e'. Jeśli liczba jest ujemna, to przed nią wypisywany jest znak minus. Na przykład 1.123456e2 oznacza 112.3456, natomiast -1.123456e-3 oznacza -0.001123456. Format ustalony (flaga fixed) oznacza wypisywanie maksymalnie tylu cyfr po kropce dziesiętnej, ile wynosi aktualna precyzja. Format ogólny pozostawia implementacji sposób zapisu (naukowy lub normalny) w zależności od wartości liczby tak, żeby zapis z ustaloną precyzją zajmował jak najmniej miejsca.

ios::boolalpha —  czy wartości logiczne wypisywać jako 0 i 1 (domyślnie), czy słowami jako true i false. W niektórych implementacjach znacznik ten nie jest zdefiniowany.

ios::showbase —  przy wypisywaniu zawsze pokaż podstawę (wiodące 0 lub 0x w systemie ósemkowym i szesnastkowym).

ios::showpoint —  zawsze wypisz kropkę dziesiętną i końcowe zera w części ułamkowej (domyślnie: NIE).

ios::showpos —  pisz znak '+' przed liczbami dodatnimi (domyślnie: NIE).

ios::uppercase —  litery 'e' w zapisie naukowym i 'x' w szesnastkowym pisz jako duże 'E' i 'X' (domyślnie: NIE).

ios::unitbuf —  opróżnij bufor po każdej operacji zapisu (domyślnie: NIE).

Jak to zaznaczyliśmy w opisie, niektóre flagi tworzą grupy, zwane polami. Chodzi o to, że niektóre ustawienia wykluczają się wzajemnie: nie można na przykład zażądać jednocześnie wypisywania liczb w układzie dziesiętnym i ósemkowym. Tak więc flagi ios::dec, ios::hexios::oct tworzą pole ios::basefield i tylko jedna z nich może być ustawiona. Podobnie flagi ios::left, ios::rightios::internal tworzą pole ios::adjustfield, a flagi ios::scientific, ios::fixed pole ios::floatfield — tu trzecią możliwością jest nieustawienie żadnej flagi, co daje format „ogólny”, który jest formatem domyślnym. Flagi, które wchodzą w skład pól, powinny być ustawiane w specjalny sposób, aby zapewnić, że nigdy nie będą ustawione dwie flagi o wzajemnie wykluczającej się interpretacji. Opiszemy to poniżej.

Jak powiedzieliśmy, flaga stanu formatowania jest związana ze strumieniem, a każdy strumień jest reprezentowany przez obiekt. Zatem do zmiany flagi formatowania będą służyć metody wywoływane na rzecz obiektu reprezentującego strumień (na przykład na rzecz cin lub cout).

Metody te to:

ios::fmtflags flags( ) —  zwraca aktualną flagę stanu formatowania strumienia;

ios::fmtflags flags(ios::fmtflags flg) —  zwraca aktualną flagę stanu formatowania, i ustawia jej nową wartość na flg.

Powyższe metody dotyczą całej flagi stanu formatowania, a nie pojedynczych flag. Jeśli chcemy zdefiniować całą flagę stanu, po to na przykład, aby użyć jej w funkcji flags, możemy ją skonstruować za pomocą „ORowania” pojedynczych flag. Na przykład

      1.        // konstruujemy flage stanu
      2.      ios::fmtflags n = ios::hex | ios::showbase
      3.                                 | ios::uppercase;
      4.        // ustawiamy nowa flage
      5.        // i zapamietujemy stara
      6.      ios::fmtflags o = cout.flags(n);
      7.      // 
      8.      // ... korzystamy z nowych ustawien
      9.      //
     10.      cout.flags(o);  // przywracamy stara
Zauważmy, że w linii 6 ustawiliśmy nową flagę stanu, ale jednocześnie zapamiętaliśmy starą, aby móc ją później przywrócić w linii ostatniej.

Jeśli nie chcemy zmieniać ustawień, a tylko dodać jedną flagę, można postąpić na przykład tak:

         // pobieramy stara flage
       ios::fmtflags stara = cout.flags();
         // tworzymy nowa
       ios::fmtflags nowa  = stara | ios::showpos;
         // ustawiamy nowa
       cout.flags(nowa);
       // ... korzystamy z nowej
       // ... i przywracamy stara
       cout.flags(stara);
Konstruowanie flag w ten sposób jest nieco uciążliwe, istnieją zatem funkcje pozwalające bezpośrednio zmieniać istniejącą flagę stanu bez jej uprzedniego pobierania.

ios::fmtflags setf(ios::fmtflags flg) —  zmienia flagę stanu formatowania „ORując” ją z flagą flg; zwraca flagę sprzed zmiany;

ios::fmtflags setf(ios::fmtflags flg, ios::fmtflags pole) zmienia flagę stanu formatowania „ORując” ją z flagą flg pochodzącą z pola pole; usuwa ustawienie innych flag pochodzących z tego samego pola; zwraca flagę stanu sprzed zmiany. W ten sposób należy ustawiać flagi dotyczące opcji które należą do pewnych pól; w ten sposób unikniemy sytuacji, w której ustawione są opcje wzajemnie się wykluczające. Na przykład

             cout.setf(ios::scientific, ios::floatfield);

ios::fmtflags unsetf(ios::fmtflags flg) —  zeruje flagę flg we fladze stanu formatowania „ANDując” ją z flagą będącą negacją bitową flagi flg.

Użycie tych metod można prześledzić na poniższym przykładzie


P121: flags.cpp     Flagi formatowania

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  typedef ios_base::fmtflags FFLAG;
      5.  
      6.  int main() {
      7.      int    m =  49;
      8.      double x = 21.73;
      9.  
     10.      cout << "1. m = " << m << ", x = " << x << endl;
     11.  
     12.      FFLAG  newf = ios::hex | ios::showbase
     13.                             | ios::showpoint;
     14.      FFLAG  oldf = cout.flags(newf);
     15.      cout << "2. m = " << m << ", x = " << x << endl;
     16.  
     17.      cout.setf(ios::scientific, ios::floatfield);
     18.      cout.unsetf(ios::showbase);
     19.      cout << "3. m = " << m << ", x = " << x << endl;
     20.  
     21.      cout.setf(ios::fixed, ios::floatfield);
     22.      cout.setf(ios::showbase | ios::uppercase);
     23.      cout << "4. m = " << m << ", x = " << x << endl;
     24.  
     25.      cout.flags(oldf);
     26.      cout << "5. m = " << m << ", x = " << x << endl;
     27.  }

którego wydruk to
    1. m = 49, x = 21.73
    2. m = 0x31, x = 21.7300
    3. m = 31, x = 2.173000e+01
    4. m = 0X31, x = 21.730000
    5. m = 49, x = 21.73
Zwróćmy uwagę, że domyślna precyzja wynosi 6, ale oznacza to co innego w zależności od sposobu formatowania: przy formatowaniu naukowym i „fixed” oznacza liczbę cyfr po kropce (jak w liniach 3 i 4 wydruku), natomiast przy formatowaniu „ogólnym” oznacza liczbę wszystkich cyfr znaczących (przed kropką i po kropce, jak w linii 2). Dodatkowo, jeśli nie jest ustawiona flaga ios::showpos, to opuszczane są końcowe zera (linia 1 i 5). W linii 4 programu zdefiniowaliśmy za pomocą typedef alias dla nazwy typu ios_base::fmtflags. W ten sposób nie musimy powtarzać tej przydługiej nazwy w dalszej części programu.

W klasie ios są też zdefiniowane metody pozwalające na określenie szerokości pola, w jakim ma być wypisana liczba lub napis, oraz na określenie precyzji wyprowadzanych liczb zmiennopozycyjnych (są to też metody klasy, a więc muszą być wywoływane zawsze na rzecz konkretnego strumienia, na przykład na rzecz obiektu cout lub cin):

streamsize width( ) —  zwraca aktualne ustawienie szerokości pola. Wartość zerowa oznacza „tyle ile trzeba, ale nie więcej”. Typ streamsize to alias pewnego typu całościowego.

streamsize width(streamsize szer) —  ustala szerokość pola wydruku na szer znaków; zwraca ustawienie szerokości pola sprzed zmiany. W ten sposób określana jest minimalna szerokość pola: jeśli wyprowadzana dana zajmuje więcej znaków, to nie zostanie „obcięta”, a odpowiednie pole wydruku zostanie zwiększone (tak jakby szerokość pola była ustawiona na domyślną wartość 0).

streamsize precision( ) —  zwraca aktualne ustawienie precyzji wyprowadzanych liczb zmiennopozycyjnych — patrz komentarz do programu flags.cpp.

streamsize precision(streamsize prec) — ustala precyzję na prec; zwraca ustawienie precyzji sprzed zmiany.

char fill( ) —  zwraca aktualny znak używany do wypełniania wyprowadzanego napisu, jeśli szerokość pola przeznaczona na ten napis jest większa niż ilość znaków w napisie (domyślnie jest to znak odstępu).

char fill(char znak) —  ustala znak używany do wypełniania wyprowadzanego napisu; zwraca znak poprzednio używany do tego celu.

Należy jednak pamiętać, że

Funkcja (metoda) width(int szerokosc) ma też zastosowanie do strumieni wejściowych. Jest, co prawda, ignorowana przy wczytywaniu liczb, ale za to jest bardzo przydatna przy wczytywaniu napisów: przy wczytywaniu do tablicy znakowej określa maksymalną liczbę wczytanych znaków, wliczając w to znak ' \0', który zostanie dodany zawsze. Zatem, po
       char napis[10];
       cin.width(sizeof(napis));
       cin >> napis;
wczytanych zostanie z klawiatury maksymalnie 9 znaków i dostawiony znak ' \0' — w ten sposób mamy gwarancję, że nawet jeśli użytkownik podał napis liczący więcej niż 9 liter, to nie będzie „mazania po pamięci” i  napis będzie zawierać prawidłowy, zakończony znakiem ' \0', napis. Należy tylko pamiętać, że niewczytane znaki pozostają wtedy w buforze wejściowym i będą pierwszymi, które zostaną wczytane przy następnej operacji wejścia na tym strumieniu (o tym, jak się ich pozbyć, powiemy za chwilę).


16.3.2 Manipulatory

Wygodniejsze od flag formatowania są manipulatory. Są to funkcje zdefiniowane w klasie ios i wywoływane poprzez podanie ich nazw jako elementów wstawianych do lub wyjmowanych ze strumienia. Ich działanie może, ale nie musi, polegać na modyfikacji flagi stanu formatowania strumienia.

Jak się przekonamy, nie muszą to być właściwie funkcje: mogą to też być obiekty funkcyjne, o których powiemy w rozdziale o obiektach funkcyjnych .

Manipulatory mogą mieć argumenty, ale nie muszą.


16.3.2.1 Manipulatory bezargumentowe

Manipulatory bezargumentowe wstawia się do strumienia nie podając nawiasów. Mają one nazwy takie jak flagi dyskutowane już w w rozdziale o flagach .

hex, oct, dec — ustawiają podstawę wyprowadzanych liczb całkowitych, podobnie jak robią to funkcje setf(ios::hex,ios::basefield) itd. Zmiana jest trwała: aby przywrócić poprzednie ustawienia, trzeba do strumienia wstawić odpowiedni manipulator lub na rzecz strumienia wywołać jedną z metod zmieniających flagę stanu formatownia.

left, right, internal — ustawiają sposób wyrównywania (justowania) wyprowadzanych danych, podobnie jak robią to poznane już przez nas wcześniej funkcje setf(ios::left,ios::adjustfield) itd.

fixed, scientific — ustawiają formatowanie wyprowadzanych liczb zmiennopozycyjnych, podobnie jak robią to funkcje setf(ios::fixed,ios::floatfield) itd.

showbase, noshowbase — podobne do wywołania setf(ios::showbase) lub setf(ios::noshowbase).

showpoint, noshowpoint — podobne do wywołania setf(ios::showpoint) lub setf(ios::noshowpoint).

flush —  powoduje opróżnienie strumienia wyjściowego.

endl —  —  powoduje wysłanie do strumienia wyjściowego znaku końca linii i opróżnienie bufora związanego z tym strumieniem, czyli natychmiastowe wyprowadzenie znaków z bufora do miejsca przeznaczenia (na ekran, do pliku itd).

ends —  — powoduje wysłanie do strumienia wyjściowego znaku końca napisu, czyli znaku ' \0'.

Przykładowo, program


P122: manb.cpp     Manipulatory bezargumentowe

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  int main() {
      5.      int a = 0xdf, b = 0771, c = 123;
      6.  
      7.      cout << "dec (default): "
      8.           << dec << a << " " << b << " " << c << endl;
      9.  
     10.      cout << "hex bez showbase: "
     11.           << hex << a << " " << b << " " << c << endl;
     12.  
     13.      cout.setf(ios::showbase);
     14.  
     15.      cout << "hex z showbase:   "
     16.           << a << " " << b << " " << c << endl;
     17.  
     18.      cout << "oct z showbase:   "
     19.           << oct << a << " " << b << " " << c << endl;
     20.  
     21.      cout.unsetf(ios::showbase);
     22.  
     23.      cout << "oct bez showbase: "
     24.           << a << " " << b << " " << c << endl;
     25.  }

wypisuje na ekranie
    dec (default): 223 505 123
    hex bez showbase: df 1f9 7b
    hex z showbase:   0xdf 0x1f9 0x7b
    oct z showbase:   0337 0771 0173
    oct bez showbase: 337 771 173
W linii 15 nie specyfikujemy podstawy, bo została ona ustawiona na hex w linii 11. Dopiero gdy chcemy zmienić podstawę na ósemkową, wstawiamy manipulator oct w linii 19.


Stosunkowo łatwo jest zdefiniować dodatkowe, własne manipulatory bezargumentowe. Manipulator taki tworzymy jako funkcję z dokładnie jednym parametrem, który musi być typu referencja do strumienia. Wartością zwracaną musi być ta sama referencja; na przykład

       ostream& moj_manip(ostream& strum) {
           // ...
           return strum;
       }
Aby użyć tak zdefiniowanego manipulatora, wstawiamy do strumienia samą jego nazwę, bez żadnego argumentu i bez nawiasów. Nasza funkcja zostanie wywołana; argument zostanie dodany automatycznie — będzie nim referencja do strumienia, do którego manipulator został wstawiony. Ponieważ referencja do tego samego strumienia jest też wartością zwracaną, więc manipulatory można wstawiać do strumienia kaskadowo, tak jak inne elementy.

Rozpatrzmy przykład:


P123: wman.cpp     Własne manipulatory

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  ostream& naukowy(ostream&);
      5.  ostream& normalny(ostream&);
      6.  ostream& przec(ostream&);
      7.  
      8.  int main() {
      9.      double x = 123.456;
     10.      cout << naukowy  << x << przec
     11.           << normalny << x << endl;
     12.  }
     13.  
     14.  ostream& naukowy(ostream& str) {
     15.      str.setf(ios::showpos | ios::showpoint);
     16.      str.setf(ios::scientific, ios::floatfield);
     17.      str.precision(12);
     18.      return str;
     19.  }
     20.  
     21.  ostream& normalny(ostream& str) {
     22.      str.flags((ios::fmtflags)0);
     23.      return str;
     24.  }
     25.  
     26.  ostream& przec(ostream& str) {
     27.      return str << ", ";
     28.  }

który drukuje
    +1.234560000000e+02, 123.456
Pierwszy manipulator, naukowy, zdefiniowany w liniach 14-19, znanymi nam już metodami zmienia flagę stanu formatowania strumienia. Drugi, normalny, przywraca fladze formatowania wartość domyślną, którą jest układ bitów złożony z samych zer (rzutowanie tego zera na typ ios::fmtflags w linii 22 jest konieczne). Wreszcie trzeci manipulator, przec, nie zmienia żadnych flag, tylko po prostu wpisuje do strumienia znaki przecinka i odstępu.

We wszystkich przypadkach parametr funkcji definiującej manipulator ma typ ostream&. Daje nam to dużą elastyczność: nigdzie tu nie jest powiedziane, że tym strumieniem będzie cout. Może to być dowolny strumień wyjściowy reprezentowany obiektem klasy ostream lub obiektem klasy dziedziczącej z  ostream, na przykład strumień wyjściowy związany z plikiem (czyli obiekt klasy ofstream).


16.3.2.2 Manipulatory z argumentami

Istnieją też manipulatory argumentowe. Stosuje się je analogicznie jak manipulatory bezargumentowe, ale wymagają one argumentów, które, jak dla zwykłych funkcji, podaje się w nawiasach. Implementowane są jako obiekty funkcyjne (patrz rozdział o obiektach funkcyjnych ).

Aby używać predefiniowanych manipulatorów argumentowych, należy dołączyć plik nagłówkowy iomanip.

Predefiniowane manipulatory argumentowe pozwalają wykonać zadania realizowane przez metody już wcześniej opisywane, ale bez wywoływania tych metod bezpośrednio, a poprzez wstawianie manipulatorów do strumienia. Różnica jest taka, że zwracają, jak wszystkie manipulatory, referencję do strumienia do którego zostały wstawione:

setw(int szer) —  ustawia szerokość pola dla najbliższej operacji na strumieniu. Działa jak opisana wcześniej metoda width (tyle, że nie zwraca liczby, ale referencję do strumienia, jak wszystkie manipulatory). Przypomnijmy, że w ten sposób określana jest minimalna szerokość pola: jeśli dana zajmuje więcej znaków, to odpowiednie pole zostanie zwiększone. Domyślną wartością szerokości pola jest zero, czyli każda wypisywana dana zajmie tyle znaków, ile jest potrzebne, ale nie więcej;

setfill(int znak) —  ustawia znak, którym będą wypełniane puste miejsca jeśli szerokość pola wydruku jest większa niż liczba wyprowadzanych znaków (domyślnie jest to znak odstępu). Odpowiada metodzie fill.

setprecision(int prec) —  ustawia precyzję jak metoda precision.

setiosflags(ios::fmtflags flag) —  modyfikuje flagę formatowania tak jak jednoargumentowa metoda setf, czyli „ORuje” flag z flagą stanu formatowania.

resetiosflags(ios::fmtflags flag) —  odpowiednik metody unsetf.

setbase(int baza) —  zmienia podstawę używaną przy wyprowadzaniu liczb całkowitych.

Różne metody formatowania wyników zademonstrowane są w poniższym programie:


P124: primatr.cpp     Formatowanie

      1.  #include <iostream>
      2.  #include <iomanip>
      3.  using namespace std;
      4.  
      5.  void printMatrix(ostream&,double**,int,int,const char*);
      6.  
      7.  int main() {
      8.      const int DIM = 5;
      9.      double t[][DIM] = { {    1,  3,    5,       23, 16.42},
     10.                          {4.567,  4,    6,  234.345,    98},
     11.                          {  585, 34,    1,       67,  31.2},
     12.                          {    1,  0,    1, 2345.967, 123.2},
     13.                          {  1.2, 10, 34.1,    5.900,   0.2}
     14.                        };
     15.      double* tab[DIM];
     16.      for (int i = 0; i < 5; i++) tab[i] = t[i];
     17.  
     18.      char  name[5];
     19.      int  prec = 3;
     20.  
     21.      cout << "Name: ";
     22.      cin  >> setw(5) >> name;
     23.  
     24.      printMatrix(cout, tab, DIM, prec, name);
     25.  }
     26.  
     27.  void printMatrix(ostream& strm, double* tab[], int size,
     28.                               int prec, const char* name) {
     29.      ios::fmtflags old =
     30.          strm.setf(ios::fixed, ios::floatfield);
     31.  
     32.      strm << setiosflags(strm.flags() | ios::showpoint)
     33.           << setprecision(prec) << "\nMatrix: " << name
     34.           << "\n\n";
     35.  
     36.      for (int i = 0; i < size; i++) {
     37.          strm << "ROW " << setfill('0') << setw(2)
     38.               << (i+1)  <<  ":" << setfill(' ');
     39.          for (int j = 0; j < size; j++)
     40.              strm << setw(9) << tab[i][j];
     41.          strm << endl;
     42.      }
     43.      strm << endl << setiosflags(old);
     44.  }

W programie zdefiniowana jest funkcja wyprowadzająca macierz w czytelnej formie (macierz jest przekazywana jako tablica wskaźników do wierszy). Zauważmy, że funkcja ta pobiera poprzez argument strumień, na który mają być wyprowadzane wyniki. W ten sposób ta sama funkcja będzie mogła być użyta do zapisania macierzy na przykład do pliku.

Zauważmy również sposób pobierania od użytkownika pewnej nazwy w linii 22. Ponieważ tablica przewidziana na nazwę ma tylko pięć znaków, używamy tu funkcji setw: w ten sposób, nawet jeśli użytkownik poda za długą nazwę, zostanie ona obcięta (do czterech znaków + NUL), ale nie nastąpi przepełnienie tablicy:

  Name: zanzibar

  Matrix: zanz

  ROW 01:    1.000    3.000    5.000   23.000   16.420
  ROW 02:    4.567    4.000    6.000  234.345   98.000
  ROW 03:  585.000   34.000    1.000   67.000   31.200
  ROW 04:    1.000    0.000    1.000 2345.967  123.200
  ROW 05:    1.200   10.000   34.100    5.900    0.200

Inną ważną kwestią jest pisanie funkcji tak, aby nie powodowały skutków ubocznych. Dlatego dbamy o to, aby po zakończeniu wykonywania zadania funkcja przywróciła flagę formatowania używanego strumienia do stanu sprzed wywołania (linie 29 i 43).

Podobnie jak dla manipulatorów bezargumentowych, programista ma możliwość zdefiniowania dodatkowych manipulatorów argumentowych. Jak to zrobić, powiemy przy okazji omawiania obiektów funkcyjnych (rozdział o obiektach funkcyjnych ).

T.R. Werner, 25 lutego 2017; 22:31