Die Formatierungsbibliothek in C++20: Der Formatstring

Der Beitrag setzt die Serie zu Formatstrings in C++20 fort und taucht tiefer in die Formatspezifikation ein.​

In Pocket speichern vorlesen Druckansicht 8 Kommentare lesen

(Bild: MyImages - Micha/Shutterstock)

Lesezeit: 3 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

In meinem letzten Artikel habe ich "Softwareentwicklung: Die Formatierungsbibliothek in C++20" vorgestellt. Heute werde ich tiefer in die Formatspezifikation des Formatstrings eintauchen.

Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

Anfangen möchte ich mit einer kurzen Zusammenfassung des Formatstrings.

  • Syntax: std::format(FormatString, Args)

Der Formatstring FormatString besteht aus

  • Gewöhnlichen Zeichen (außer { und })
  • Escape-Sequenzen {{ und }}, die durch { und } ersetzt werden
  • Ersetzungsfelder

Ein Ersatzfeld hat das Format { }.

Man kann eine Argument-ID und einen Doppelpunkt innerhalb des Ersetzungsfeldes verwenden, gefolgt von einer Formatangabe. Beide Komponenten sind optional.

Dank der Argument-ID lassen sich bestimmte Argumente neu anordnen oder ansprechen. Mit der Argument-ID lässt sich der Index der Argumente in Args angeben. Die IDs beginnen mit 0. Wenn keine Argument-ID angegeben ist, werden die Felder in der gleichen Reihenfolge ausgefüllt, in der die Argumente angegeben werden. Entweder müssen alle Ersetzungsfelder eine Argument-ID verwenden oder keine.

// formatArgumentID.cpp

#include <format>
#include <iostream>
#include <string>
 
int main() {
    
    std::cout << '\n';

    std::cout << std::format("{} {}: {}!\n", 
                             "Hello", "World", 2020); // (1)

    std::cout << std::format("{1} {0}: {2}!\n",
                             "World", "Hello", 2020); // (2)

    std::cout << std::format("{0} {0} {1}: {2}!\n",
                             "Hello", "World", 2020); // (3)

    std::cout << std::format("{0}: {2}!\n", 
                             "Hello", "World", 2020); // (4)
    
    std::cout << '\n';
   
}

(1) zeigt die Argumente in der angegebenen Reihenfolge an. Im Gegensatz dazu werden in (2) das erste und das zweite Argument neu angeordnet, in (3) wird das erste Argument zweimal angezeigt und in (4) wird das zweite Argument ignoriert.

Der Vollständigkeit halber ist hier die Ausgabe des Programms:

Die Anwendung des Arguments id mit der Formatspezifikation macht die Formatierung von Text in C++20 sehr leistungsfähig.

Ich werde nicht die formale Formatspezifikation für Datentypen, String-Typen und Chrono-Typen vorstellen. Für Datentypen und std::string finden sich die vollständigen Details hier: standard format specification. Dementsprechend findet man die Details für Chrono-Typen hier: chrono format specification.

Stattdessen stelle ich eine pragmatische Beschreibung des Formatstrings für Datentypen, String-Typen und Chrono-Typen vor.

fill_align(opt) sign(opt) #(opt) 0(opt) width(opt) precision(opt) L(opt) type(opt) 

Alle Teile sind optional (opt). In den folgenden Abschnitten gehe ich auf die Features dieser Formatspezifikation genauer ein.

Das Füllzeichen ist optional (jedes Zeichen außer { oder }) und wird von einer Ausrichtungsangabe gefolgt. Um das Füllzeichen zu verwenden, muss man die Ausrichtung angeben.

  • Füllzeichen: Standardmäßig wird das Leerzeichen verwendet
  • Ausrichtung:
    • <: links (Standard für Werte, die keine Zahlen sind)
    • >: rechts (Standard für Zahlen)
    • ^: zentriert
// formatFillAlign.cpp

#include <format>
#include <iostream>
 
int main() {

    std::cout << '\n';
    
    int num = 2020;

    std::cout << std::format("{:6}", num) << '\n'; 
    std::cout << std::format("{:6}", 'x') << '\n';   
    std::cout << std::format("{:*<6}", 'x') << '\n';
    std::cout << std::format("{:*>6}", 'x') << '\n';
    std::cout << std::format("{:*^6}", 'x') << '\n';
    std::cout << std::format("{:6d}", num) << '\n';
    std::cout << std::format("{:6}", true) << '\n';

    std::cout << '\n';
   
}

Die Standardausrichtung hängt von den verwendeten Datentypen ab. Im Gegensatz zum iostream-Operator werden boolesche Werte standardmäßig als true oder false dargestellt.

Vorzeichen, # und 0 sind nur gültig, wenn ein Ganzzahl- oder Fließkommatyp verwendet wird.

Das Vorzeichen kann die folgenden Werte haben:

  • +: Das Vorzeichen wird für Null und positive Zahlen verwendet.
  • -: Das Vorzeichen wird nur für negative Zahlen verwendet (Standard).
  • Leerzeichen: Ein führendes Leerzeichen wird für nicht negative Zahlen verwendet und ein Minuszeichen für negative Zahlen.
// formatSign.cpp

#include <format>
#include <iostream>
 
int main() {

    std::cout << '\n';

    std::cout << std::format("{0:},{0:+},{0:-},{0: }", 0) << '\n';
    std::cout << std::format("{0:},{0:+},{0:-},{0: }", -0) << '\n';
    std::cout << std::format("{0:},{0:+},{0:-},{0: }", 1) << '\n';
    std::cout << std::format("{0:},{0:+},{0:-},{0: }", -1) << '\n';

    std::cout << '\n';
   
}

Das # bewirkt die alternative Form:

  • Bei ganzzahligen Datentypen wird das Präfix 0b, 0 oder 0x für binäre, oktale oder hexadezimale dargestellte Typen verwendet.
  • Für Datentypen mit Fließkommazahlen wird immer ein Dezimalpunkt verwendet.
  • 0: Auffüllen mit führenden Nullen
// formatAlternate.cpp

#include <format>
#include <iostream>
 
int main() {

    std::cout << '\n';

    std::cout << std::format("{:#015}", 0x78) << '\n';
    std::cout << std::format("{:#015b}", 0x78) << '\n';
    std::cout << std::format("{:#015x}", 0x78) << '\n';

    std::cout << '\n';

    std::cout << std::format("{:g}", 120.0) << '\n';
    std::cout << std::format("{:#g}", 120.0) << '\n';

    std::cout << '\n';
   
}

Mit dem Formatbezeichner kann man die Genauigkeit, die Breite und den Datentyp des Wertes angeben. Darüber werde ich in meinem nächsten Artikel schreiben. (rme)