Podstawy programowania 13

Rozszerzanie możliwości strumieni

Przekazywanie do strumienia własnych typów

Standardowo język C++ ma wbudowane przekazywanie do strumieni typów wbudowanych i niektórych typów bibliotecznych (np. std::string). Biblioteka IOstreams pozwala także na stosunkowo łatwe rozszerzanie strumieni tak, aby obsługiwały typy definiowane przez programistę. Przypuśćmy, że chcemy „nauczyć” strumienie wczytywać i wypisywać punkty z płaszczyzny w notacji (`x, y). W tym celu musimy stworzyć dwie funkcje. Jedną, która wypiszą dane obiektu w pożądany sposób i druga, która wczyta dane do obiektu. Funkcje te będą wywoływane nie normalnie, ale przez operatory << i >>. Kod wygląda następująco:

#include <iostream>

using namespace std;

struct Point {
    int x;
    int y;
};

ostream &operator<<(ostream &os, const Point &p) {
    os << "(" << p.x << ", " << p.y << ")";
    return os;
}

istream &operator>>(istream &is, Point &p) {
    is.ignore(1);
    is >> p.x;
    is.ignore(2);
    is >> p.y;
    is.ignore(1);
    return is;
}

int main() {
    Point p;
    cin >> p;
    cout << p << endl;
    return 0;
}

Kolorowanie wyjścia tekstowego

Prawie od samego początku korzystania ze strumieni mieliśmy styczność z std::endl. W istocie jest funkcja (a nawet szablon funkcji) zdefiniowany w pliku nagłówkowym ostream następująco (w używanej przeze mnie bibliotece):

template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>&
                    endl(basic_ostream<_CharT, _Traits>& __os) {
    return flush(__os.put(__os.widen('\n')));
}

Ta wersja jest na oko bardzo skomplikowana, ale pozbywając się na razie niepotrzebnych nam szablonów dostajemy sygnaturę:

ostream& endl(ostream& __os);

To nam w zupełności wystarczy. Funkcje o takiej sygnaturze nazywamy manipulatorami strumieni. Można je bez większych problemów wrzucić w ciąg wywołań operatorów przekazania do strumienia i zmieniają one stan strumienia (z reguły dla wszystkich następnych przekazanych obiektów).

Można to wykorzystać do łatwego zmieniania kolorów linuksowej konsoli poprzez użycie kodów ANSI:

#include <iostream>

using namespace std;

#ifdef _WIN32
#include <windows.h>
#endif

ostream &red(ostream& os) {
#ifdef __gnu_linux__
    return os << "\033[31m";
#elif defined _WIN32
    os.flush();
    HANDLE console;
    console = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(console, 4);
    return os;
#endif
}

ostream &reset(ostream& os) {
#ifdef __gnu_linux__
    return os << "\033[0m";
#elif defined _WIN32
    os.flush();
    HANDLE console;
    console = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(console, 7);
    return os;
#endif
}


int main() {
    cout << "plain " << red << "red" << reset << " plain\n";
    return 0;
}

Aby kolory zmieniały się także pod MS Windows, należy tylko inaczej zdefiniować odpowiednie funkcje. Najlepiej użyć do tego kompilacji warunkowej – wtedy nasz program po przekompilowaniu będzie obsługiwał kolorowe wyjście zarówno pod Linuksem jak i pod Windowsem.