Narzędzia użytkownika

Narzędzia witryny


format

Biblioteka Boost Format

Tomasz Piechota G1ISI 2008/04/16 23:44

Biblioteka Boost Format dostarcza klasę do formatowania argumentów zgodnie z dostarczonym łańcuchem formatującym. Podstawowe różnice w stosunku do funkcji printf, to m.in.:

  • deterministyczne zachowanie, nawet w przypadku podania błędnych argumentów (generuje wyjątek lub ignoruje błąd, w zależności od ustawień).
  • naturalne wsparcie typów zdefiniowanych przez użytkownika.
  • podawanie argumentów poprzez kolejne wywołania operatora %.

Podstawowe przykłady

Aby skorzystać z biblioteki Boost Format należy przede wszystkim dołączyć nagłówek boost/format.hpp. Nagłówek ten dostarcza nam do użycia klasę boost::format posiadającą cztery konstruktory:

format(const charT* str);
format(const charT* str, const std::locale & loc);
format(const string_t& s);
format(const string_t& s, const std::locale & loc);

Myślę, że komentarz tutaj jest zbędny. Jako pierwszy argument przekazujemy oczywiście łańcuch formatujący. Oto kilka najprostszych przykładów użycia:

#include <iostream>
#include <boost/format.hpp>
 
// (...)
 
boost::format frm("Abc %1% %2% %3% %4% cbA"); // Tworzymy nowy obiekt
frm % "A";                                    // Przypisujemy pierwszy argument
frm % "B" % "C" % "D";                        // Kolejne argumenty przypisujemy za jednym zamachem
std::cout << frm << std::endl;                // --> "Abc A B C D cbA"

Wszystkie operacje możemy oczywiście wykonać w jednej linii. Liczba argumentów przekazywanych do obiektu jest dowolna, a każdy argument może wystąpić w przekazywanym łańcuchu dowolną ilość razy.

std::cout << boost::format("%1% %2% %1%") % "A" % "B" << std::endl;  // --> "A B A"

Sformatowany string możemy także pobrać do zmiennej:

std::string s = frm.str();         // Odpowiednia liczba argumentów musi być uprzednio przekazana do obiektu
std::string s2 = boost::str(frm); 

Tego samego obiektu format możemy użyć ile razy chcemy, dostarczając nowego zestawu argumentów.

boost::format frm("%% _%1%_ _%2%_ _%3%_ %%");  // Podwójne użycie znaku specjalnego % spowoduje jego wypisanie na ekran
frm % "A" % 2.32 % 10;
std::cout << frm << std::endl;                 // --> "% _A_ _2.32_ _10_ %" 
frm % "Foo" % 2 % "Bar";
std::cout << frm << std::endl;                 // --> "% _Foo_ _2_ _Bar_ %"

Formatowanie argumentów

Na wstępie należałoby zaznaczyć, że argumenty w łańcuchu formatującym mogą być definiowane na trzy różne sposoby:

  1. Odziedziczony po funkcji printf format: %spec
  2. %|spec| z nawiasami użytymi w celu dopuszczenia parametry type-char jako opcjonalny
  3. %N% - prosty, przejrzysty sposób bez żadnego formatowania.

gdzie spec ma następującą składnie:

[ N$ ] [ flags ] [ width ] [ .precision ] type-char

Argumenty ujęte w nawiasy kwadratowe są opcjonalne, a dla typu drugiego (%|spec|) również type-char.

Znaczenie poszczególnych parametrów

  • N - numer argumentu
  • flags - jedna lub więcej spośród flag:
    • '-' - wyrównuje wynikowy łańcuch do lewej strony (ma sens przy jednoczesnym użyciu parametru width
    • '=' - centruje wynikowy łańcuch (sens w przypadku jak wyżej)
    • '_' - wyrównuje wynikowy łańcuch do prawej strony
    • '+' - włącza wyświetlanie znaku także liczb dodatnich
    • '#' - włącza wyświetlanie bazy liczby (np. 0x dla heksadecymalnych) oraz przecinek oddzielający część dziesiętną
    • '0' - wypełnia puste miejsca zerami
    • ' ' - poprzedza wynikowy łańcuch dodatkową spacją, jeśli nie jest wyświetlany minus ani plus.
  • width - wymuszenie minimalnej szerokości
  • precision - ilość cyfr po przecinku dla np trybu naukowego (type-char = e) lub ilość cyfr w ogóle dla np trybu general (type-char = g)
  • type-char - typ formatowania (jest to tylko sugestia typu, jeśli formatowanie nie powiedzie się, zostanie użyty inny domyślny typ):
    • p lub x - heksadecymalny
    • o - ósemkowy
    • e - naukowy
    • f - zmiennoprzecinkowy
    • g - ogólny (domyślny)
    • X, E or G - tak samo jak w przypadku x, e i g, ale wyświetla duże litery
    • d, i or u - dziesiętny
    • s or S - łańcuch znaków
    • c or C - jeden znak

Przykłady formatowania przy użyciu łańcucha znaków

Przykłady użycia poszczególnych formatowań pokazane są w poniższym kodzie:

// Sposób pierwszy według formatu %spec
std::cout << boost::format("_%1$d_ _%1$o_ _%1$#o_") % 10 << std::endl;                // --> "_10_ _12_ _012_"
std::cout << boost::format("_%1$d_ _%1$x_ _%1$#X_") % 47 << std::endl;                // --> "_47_ _2f_ _0X2F_"
std::cout << boost::format("_%1$5.2f_ _%1$=5.0f_ _%1$05.0f_") % 48.237 << std::endl;  // --> "_48.24_ _  48 _ _00048_"
 
// Sposób drugi według formatu %|spec|
std::cout << boost::format("_%|1$-6|_ _%|2$+-7.2f|_") % "foo" % 23.199 << std::endl;  // --> "_foo   _ _+23.20 _"
std::cout << boost::format("_%|6.2e|_ _%|11.3e|_") % 30.2312 % 312.3 << std::endl;    // --> "_3.02e+001_ _ 3.123e+002_"
 
// Trzeci sposób według formatu %N%
std::cout << boost::format("%1% %2% %1% %2%") % "o_O" % "O_o" << std::endl;           // --> "o_O O_o o_O O_o"

W jednym łańcuchu możemy korzystać z różnych sposóbów, natomiast błędem jest użycie zarówno argumentów numerowanych, jak i nienumerowanych.

std::cout << boost::format("%d %|s|") % 10 % "foo" << std::endl;        // OK
std::cout << boost::format("%1$s %2%") % "foo" % "bar" << std::endl;    // OK
std::cout << boost::format("%1$d %s") % 10 % "bar" << std::endl;        // BŁĄD!

Ciekawym przykładem może być również technika wymuszająca wcięcie (z wypełnieniem lub bez) niezależnie od długości poprzednich argumentów:

// w obu przypadkach liczba 199 zaczyna się po 15 znakach
std::cout << boost::format("%s %|15t|%d%") % "Abcdefgh" % 199 << std::endl;  // --> "Abcdefgh       199"
 
// z wypełnieniem (duże T + wypełniacz)
std::cout << boost::format("%s %|15T-|%d%") % "Abcde" % 199 << std::endl;    // --> "Abcde ---------199"

Formatowanie poprzez manipulatory - metoda group()

Na wygląd formatowania poszczególnych parametrów możemy wpływać także mając już zdefiniowany łańcuch formatujący (utworzony obiekt klasy boost::format). Tak zdefiniowane formatowanie ma wyższy priorytet od tego wpisanego w konstruktorze. Manipulatory dostępne są po dołączeniu biblioteki iomanip.

#include <iomanip>
 
// (...)
 
// Pierwsze wystapienie argumentu pierwszego również ma szerokość 5 (priorytet manipulatora większy)
std::cout << boost::format("_%1$3d_ _%1%_") % boost::io::group(showpos, std::setw(5), 101);  // --> "_ +101_ _ +101_"
 
// Manipulatory nie resetują wszystkich, tylko nadpisują istniejące flagi
std::cout << boost::format("_%1$-3d_ _%1%_") % boost::io::group(showpos, std::setw(5), 101); // --> "_+101 _ _ +101_"
, setfill('x')
 
 
// Inny przykład
std::cout << boost::format("_%1%_") % boost::io::group(hex, std::setw(5), 99);  // --> "_   63_"
std::cout << boost::format("_%|=10|_") % boost::io::group(setfill('-'), 101);   // --> "_----101---_"

Funkcja boost::io::group może przyjąc wiele manipulatorów, jednak ostatnim argumentem musi być wartość przekazywanego parametru.

Wyjątki i ich konfiguracja

Boost Format wprowadza pewną liczbę zasad korzystania z objektów boost::format:

  • Łańcuch znaków musi przestrzegać składni opisanej powyżej
  • Użytkownik musi dostarczyć dokładnie taką liczbę argumentów, jakie zadeklarował w łańcuchu, przed pobraniem sformatowanego wyjścia
  • Używając metod modify_item oraz bind_arg, indeksy elementów i argumentów muszą być właściwe

Jeśli klasa format wykryje, że jedna z powyższych zasad nie jest spełniona, odpowiedni wyjątek zostanie rzucony. Użytkownik jednak ma możliwość wpłynięcia na zachowanie obiektu, deklarując które błędy mają skutkować wyjątkiem, a które mają zostać przemilczane (wyjście zostanie wygenerowane z pustą wartością danego argumentu).

W celu ustawienia bitów błędów wykorzystuje się metody:

unsigned char exceptions(unsigned char newexcept);  // ustaw i zwróć
unsigned char exceptions() const;                   // tylko zwróć

Dostępne są następujące bity błędów:

  • boost::io::bad_format_string_bit - reakcja na błędny format łańcucha formatującego
  • boost::io::too_few_args_bit - za mało argumentów
  • boost::io::too_many_args_bit - za dużo argumentów
  • boost::io::out_of_range_bit - nieprawidłowy index dostarczony podczas wywołania np metody modify_item
  • boost::io::all_error_bits - reakcja na wszystkie błędy
  • boost::io::no_error_bits - brak reakcji na jakikolwiek błąd

Przykładowy kod prezentujący działane metod exceptions i bitów błędów:

boost::format f("_%1%_ _%2%_");
f % 10;
try
{
    std::cout << f << std::endl;
}
catch (boost::io::too_few_args &e)
{
    std::cout << "Za malo argumentow" << std::endl;
}
f.exceptions(~boost::io::too_few_args_bit);
try
{
    std::cout << f << std::endl;
}
catch (boost::io::too_few_args &e)
{
    std::cout << e.what() << std::endl;
}
 
// --> "Za malo argumentow"
// --> "_10_ __"

Przykład użycia

Poniższy program wyświetla listę produktów w postaci sformatowanej tabeli

#include <iostream>
#include <boost/format.hpp>
#include <string>
#include <list>
 
using namespace std;
using namespace boost;
 
class Produkt
{
public:
    Produkt(string n, int i, float c, float z) : nazwa(n), ilosc(i), cena(c), zmiana(z) { }
    string nazwa;
    int ilosc;
    float cena;
    float zmiana;
};
 
int main(int argc, char *argv[])
{
    // Lista obiektów
    list<Produkt> p;
    p.push_back(Produkt("Widelec", 4, 4.99f, 1.021f));
    p.push_back(Produkt("Łopatka", 32, 2.99f, -10.2292f));
    p.push_back(Produkt("Packa", 19, 19.39f, 19.1133f));
 
 
    // pozioma linia
    format line("%|55T-|");
 
    // nazwa:  %|1$-.20s| - wyrownanie do lewej, maksimum 20 znakow
    // ilość:  %2% - bez dodatkowego formatowania
    // cena:   %|3$.2f| - liczba zmiennoprzecinkowa, do dwóch miejsc po przecinku
    // zmiana: %|4$+10.4f| - wymuszenie wyświetlania znaku +, szerokosc 10, do 4 miejsc po przecinku, wyrównanie do prawej
    format row("|%|1$-20.20s|%|21t||%2%%|32t||%|3$.2f|%|43t||%|4$+10.4f|%|54t||");
 
    cout << line << endl;
    cout << row % "Nazwa produktu" % "Ilosc" % "Cena" % "Zmiana (%)" << endl;
    cout << line << endl;
 
    for (list<Produkt>::const_iterator it = p.begin(); it != p.end(); it++)
        cout << row % it->nazwa % it->ilosc % it->cena % it->zmiana << endl;
 
    cout << line << endl;
 
    return 0;
}
format.txt · ostatnio zmienione: 2008/04/16 23:50 przez piechot