Output bawaan std::cout sering kali belum siap dipakai: 3.14159265 tercetak enam digit, angka tidak sejajar di kolom tabel, dan nominal uang kehilangan dua angka di belakang koma. C++ menyediakan dua gaya untuk mengatasi hal ini. Gaya klasik bekerja lewat member function pada stream seperti width(), fill(), dan precision(), sedangkan gaya modern memakai std::format (C++20) dan std::print (C++23) dengan format string yang ringkas seperti "{:.2f}". Pada Chapter ini kita mencoba keduanya, membaca anatomi format spec, dan menetapkan kapan masing-masing jadi pilihan terbaik.

Lebar Kolom, Karakter Pengisi, dan Presisi #

Tiga member function berikut mengatur tampilan pada std::ostream mana pun. width(n) menjamin output minimal n karakter, fill(c) menentukan karakter pengisi ruang kosong, dan precision(n) membatasi jumlah digit untuk floating-point.

cpp
#include <iostream>

int main() {
    std::cout << '[';
    std::cout.width(8);             // lebar kolom minimum 8 karakter
    std::cout << 42 << "]\n";       // rata kanan (bawaan untuk angka)

    std::cout << '[';
    std::cout.width(8);
    std::cout.fill('0');            // ruang kosong diisi karakter '0'
    std::cout << 42 << "]\n";

    std::cout << '[' << 42 << "]\n"; // width() otomatis kembali ke 0

    double pi = 3.14159265;
    std::cout << "Pi lengkap : " << pi << '\n';
    std::cout.precision(3);          // batasi menjadi 3 digit signifikan
    std::cout << "Pi presisi  : " << pi << '\n';

    // Jebakan: fill('0') masih aktif, width() harus di-set ulang
    std::cout << "Total : Rp";
    std::cout.width(10);
    std::cout << 12500 << '\n';

    std::cout.fill(' ');             // kembalikan pengisi ke spasi
    std::cout << "Total : Rp";
    std::cout.width(10);
    std::cout << 12500 << '\n';
    return 0;
}

Output yang Dihasilkan:

text
[      42]
[00000042]
[42]
Pi lengkap : 3.14159
Pi presisi  : 3.14
Total : Rp0000012500
Total : Rp     12500

Mari kita bedah perilaku yang tampak di output:

  1. Baris 5-7 (std::cout.width(8)): angka 42 hanya dua karakter, sehingga enam ruang kosong ditambahkan di depannya — angka secara bawaan rata kanan — dan hasilnya [ 42].
  2. Baris 9-12 (std::cout.fill('0')): lebar yang sama, tetapi ruang kosong kini diisi '0' sehingga menghasilkan [00000042]. Pola ini persis seperti penomoran di struk: 000001 bukan 1.
  3. Baris 14: width() berlaku sekali saja dan otomatis kembali ke 0 setelah satu output, sehingga 42 berikutnya tercetak polos [42].
  4. Baris 16-19 (std::cout.precision(3)): presisi dalam mode bawaan berarti jumlah digit signifikan. 3.14159265 yang bawaannya enam digit (3.14159) dipangkas menjadi 3.14.
  5. Baris 22-24: jebakan yang sering mengejutkan — fill('0') tidak di-reset seperti width(). Pengisi nol masih aktif, sehingga nominal tercetak sebagai Rp0000012500.
  6. Baris 26-28: setelah fill(' ') dikembalikan, baris kedua tampil wajar: Rp 12500.

std::format dan std::print: Cara Modern #

C++20 menghadirkan <format> dengan fungsi std::format, dan C++23 melengkapinya dengan std::print serta std::println dari header <print> — keduanya sudah tersedia di g++ 14.2 dengan flag -std=c++23. Gaya ini meniru keringkasan printf di bahasa C, tetapi type-safe: format string diperiksa compiler, dan jumlah argumen yang salah langsung ditolak pada saat kompilasi.

cpp
#include <format>
#include <print>

int main() {
    double harga = 1234.5;
    int stok = 7;

    // std::print mencetak langsung ke stdout (C++23)
    std::print("Harga : Rp{:.2f}\n", harga);     // 2 angka di belakang koma
    std::print("Stok  : {:>10}\n", stok);        // rata kanan, lebar 10
    std::print("Stok  : {:<10}|\n", stok);       // rata kiri, lebar 10
    std::print("Kode  : {:05d}\n", stok);        // isi nol, lebar 5
    std::print("Ilmiah: {:.2e}\n", 299792458.0); // notasi ilmiah

    // std::format mengembalikan string agar bisa dipakai ulang
    std::string kolom = std::format("|{:^9}|{:^9}|", "Kopi", "Teh");
    std::print("{}\n", kolom);

    std::println("Jumlah item: {}", stok);       // println menambah '\n'
    return 0;
}

Output yang Dihasilkan:

text
Harga : Rp1234.50
Stok  :          7
Stok  : 7         |
Kode  : 00007
Ilmiah: 3.00e+08
|  Kopi   |   Teh   |
Jumlah item: 7

Bedah barisnya:

  1. Baris 7 ({:.2f}): type f memaksa notasi fixed dengan 2 digit di belakang koma; 1234.5 tampil sebagai 1234.50.
  2. Baris 8-9 ({:>10} dan {:<10}): simbol > meratakan kanan, < meratakan kiri, keduanya dalam lebar 10 — perhatikan tanda | di baris kiri untuk melihat sisa ruangnya.
  3. Baris 10 ({:05d}): 0 di depan lebar berarti pengisi nol; type d menegaskan desimal, hasilnya 00007.
  4. Baris 11 ({:.2e}): type e menghasilkan notasi ilmiah dengan dua digit desimal; 299792458.0 menjadi 3.00e+08.
  5. Baris 14-15 (std::format): fungsi ini tidak mencetak apa pun, melainkan mengembalikan std::string — berguna saat hasil format perlu disimpan ke variabel, dikirim ke file, atau diubah lagi. Pengatur {:^9} meratakan tengah; sisa pembagian ganjil diberikan ke sisi kanan (Kopi mendapat 2 spasi kiri, 3 kanan).
  6. Baris 18 (std::println): identik dengan std::print, tetapi otomatis menambahkan newline di akhir sehingga kita tidak menulis \n manual.

Memahami Mini-Format-Spec #

Semua kekuatan gaya modern tersimpan dalam satu pola di dalam kurung kurawal: {[[fill]align][sign][#][0][width][.precision][type]}. Kita tidak perlu menghafal semuanya — tabel berikut memetakan bagian yang paling sering dipakai:

SpesifikasiArtiContoh hasil
{:.2f}fixed, 2 digit desimal1234.50
{:>10}rata kanan, lebar 107
{:<10}rata kiri, lebar 107
{:^9}rata tengah, lebar 9Kopi
{:05d}lebar 5, pengisi nol00007
{:.2e}notasi ilmiah, 2 digit3.00e+08
{:x} / {:b}heksadesimal / binerff / 10101111

Kolom fill dan align ditulis berpasangan: untuk mengisi dengan . dan meratakan tengah, kita menulis {:^9.2f} bergaya {:*^9} — karakter pertama menjadi pengisi, karakter kedua arah rata. Detail lengkapnya terdokumentasi pada halaman format spec cppreference di bagian referensi.

Klasik atau Modern? #

Untuk kode baru, std::format dan std::print adalah pilihan utama: lebih ringkas, lebih aman, dan keinginan format terbaca sekali jelas di dalam string. Member function klasik tetap penting untuk dua alasan — kode lama masih memakainya di mana-mana, dan beberapa pengaturan stream (seperti flag yang dijelaskan pada Chapter berikutnya) hanya tersedia lewat jalur tersebut.

AspekGaya klasik (cout.width)Gaya modern (std::print)
Usulan standarSejak C++ awalC++20 / C++23
Keterbacaan formatTersebar di banyak pemanggilanTerkumpul dalam satu string
Keamanan tipeDiperiksa saat runtimeDiperiksa saat kompilasi
Hasil bisa disimpanTidak langsungstd::format mengembalikan string

Gaya klasik sebenarnya masih menyimpan satu keunggulan yang belum kita sentuh: manipulator seperti std::setw dan std::fixed yang disisipkan langsung di rantai <<. Chapter berikutnya membahasnya satu per satu — dan membandingkan std::endl dengan karakter '\n' biasa.

Referensi #