Die Formatierungsbibliothek in C++20: Details zum Formatstring

Der zweite Teil der Serie zu Formatstrings in C++20 beschäftigt sich mit der Breite, der Genauigkeit und dem Datentyp der Formatspezifikation.

In Pocket speichern vorlesen Druckansicht 12 Kommentare lesen

(Bild: DirtyMono/Shutterstock)

Lesezeit: 3 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis



In meinem letzten Artikel "Die Formatierungsbibliothek in C++20: Der Formatstring" habe ich einige der Formatspezifikationen des Formatstrings vorgestellt. Heute beende ich die Abhandlung dazu.

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++.

Im heutigen Artikel werde ich über die Breite, die Genauigkeit und den Datentyp der Formatspezifikation schreiben. Wenn Du mehr über das Argument id, die Füllzeichen, die Ausrichtung, die Vorzeichen und die alternative Form wissen willst, lies meinen vorherigen Artikel: "Die Formatierungsbibliothek in C++20: Der Formatstring".

Man kann die Breite und die Genauigkeit eines Arguments angeben. Die Breite kann auf Zahlen und die Genauigkeit auf Fließkommazahlen und Zeichenketten angewendet werden. Bei Fließkommazahlen gibt die Genauigkeit die Formatierungsgenauigkeit an; bei Zeichenketten gibt die Genauigkeit an, wie viele Zeichen verwendet werden und wie die Zeichenkette letztendlich abgeschnitten wird. Wenn die Genauigkeit größer ist als die Länge der Zeichenkette, hat dies keine Auswirkungen auf die Zeichenkette.

  • Breite: Man kann entweder eine positive Dezimalzahl oder ein Ersetzungsfeld ({} oder {n}) verwenden. Wenn angegeben, gibt n die Mindestbreite an.
  • Genauigkeit: Man kann einen Punkt (.) gefolgt von einer nicht-negativen Dezimalzahl oder ein Ersetzungsfeld verwenden.

Ein paar Beispiele sollen helfen, die Grundlagen zu verstehen:

// formatWidthPrecision.cpp

#include <format>
#include <iostream>
#include <string>

int main() {

    int i = 123456789;
    double d = 123.456789;

    std::cout << "---" << std::format("{}", i) << "---\n";
    std::cout << "---" << std::format("{:15}", i) 
      << "---\n"; // (w = 15)
    std::cout << "---" << std::format("{:}", i) 
      << "---\n";   // (w = 15)                             // (1)

    std::cout << '\n';

    std::cout << "---" << std::format("{}", d) << "---\n";    
    std::cout << "---" << std::format("{:15}", d)
      << "---\n"; // (w = 15)
    std::cout << "---" << std::format("{:}", d) 
      << "---\n";   // (w = 15)

    std::cout << '\n';

    std::string s= "Only a test";

    std::cout << "---" << std::format("{:10.50}", d) 
      << "---\n"; // (w = 10, p = 50)                        // (2)
    std::cout << "---" << std::format("{:{}.{}}", d, 10, 50)
      << "---\n";  // (w = 10, p = 50)                       // (3)
                                      
    std::cout << "---" << std::format("{:10.5}", d) 
      << "---\n";  // (w = 10, p = 5)
    std::cout << "---" << std::format("{:{}.{}}", d, 10, 5)
      << "---\n";  // (w = 10,  p = 5)

    std::cout << '\n';

    std::cout << "---" << std::format("{:.500}", s) 
      << "---\n";      // (p = 500)                          // (4)
    std::cout << "---" << std::format("{:.{}}", s, 500) 
      << "---\n";  // (p = 500)                              // (5)
    std::cout << "---" << std::format("{:.5}", s) 
      << "---\n";        // (p = 5)

}

Das w-Zeichen im Quellcode steht für die Breite; ebenso das p-Zeichen für die Genauigkeit.

Hier sind ein paar interessante Beobachtungen zu dem Programm: Es werden keine zusätzlichen Leerzeichen hinzugefügt, wenn die Breite mit einem Ersetzungsfeld angegeben wird (1). Wenn eine Genauigkeit verwendet wird, die größer ist, als die Länge des angezeigten double (2 und 3), spiegelt die Länge des angezeigten Wertes die Genauigkeit wider. Diese Beobachtung gilt nicht für eine Zeichenkette (4 und 5).

Zusätzlich lässt sich die Breite und die Genauigkeit parametrisieren.

// formatWidthPrecisionParametrized.cpp

#include <format>
#include <iostream>

int main() {

    std::cout << '\n';

    double doub = 123.456789;

    std::cout << std::format("{:}\n", doub);  // (1)

    std::cout << '\n';

    for (auto precision: {3, 5, 7, 9}) {
       std::cout << std::format("{:.{}}\n", 
                                doub,
                                precision);   // (2)
    }

    std::cout << '\n';

    int width = 10;
    for (auto precision: {3, 5, 7, 9}) {
       std::cout << std::format("{:{}.{}}\n",
                                doub, 
                                width, 
                                precision);   // (3)
    }
    
    std::cout << '\n';

Das Programm formatWidthPrecisionParametrized.cpp stellt das Double doub auf verschiedene Arten dar. (1) wendet die Standardeinstellung an. (2) variiert die Genauigkeit von 3 bis 9. Das letzte Argument des Formatstrings geht in das innere {} des Formatspezifiers {:.{}}. Schließlich wird in (3) die Breite der angezeigten Double-Werte auf 10 gesetzt.

Im Allgemeinen leitet der Compiler den Typ des verwendeten Wertes ab. Aber manchmal möchte man den Datentyp angeben. Dies sind die wichtigsten Datentypen:

Zeichenketten: s

Ganzzahlen:

  • b: Binärformat
  • B: wie b, aber Basispräfix ist 0B
  • d: dezimales Format
  • o: oktales Format
  • x: hexadezimales Format
  • X: wie x, aber das Basispräfix ist 0X

char und wchar_t:

  • b, B, d, o, x, X: wie Ganzzahlen

bool:

  • s: true oder false
  • b, B, d, o, x, X: wie Ganzzahlen

Fließkommazahlen:

  • e: Exponentialformat
  • E: wie e, aber der Exponent wird mit E geschrieben
  • f, F: Festkomma; die Genauigkeit beträgt 6
  • g, G: Genauigkeit 6, aber der Exponent wird mit E geschrieben

Zeiger:

  • p: hexadezimale Schreibweise seiner Adresse

Nur die Datentypen void, const void und std::nullptr_t sind gültig. Wer die Adresse eines beliebigen Zeigers anzeigen will, muss ihn in (const) void* umwandeln.

double d = 123.456789;

std::format("{}", &d);                           // ERROR
std::format("{}", static_cast<void*>(&d));       // okay
std::format("{}", static_cast<const void*>(&d)); // okay
std::format("{}", nullptr);                      // okay

Mit den Datentypen kann man einen int ganz einfach in einem anderen Zahlensystem darstellen.

// formatType.cpp

#include <format>
#include <iostream>

int main() {

    int num{2020};

    std::cout << "default:     " << std::format("{:}", num)
      << '\n';
    std::cout << "decimal:     " << std::format("{:d}", num) 
      << '\n';
    std::cout << "binary:      " << std::format("{:b}", num)
      << '\n';
    std::cout << "octal:       " << std::format("{:o}", num) 
      << '\n';
    std::cout << "hexadecimal: " << std::format("{:x}", num) 
      << '\n';

}

Bisher habe ich die elementaren Datentypen und Strings formatiert. Natürlich lassen sich auch benutzerdefinierte Typen formatieren. Das wird das Thema meines nächsten Artikels sein. (rme)