Keluarga find yang kita pelajari pada Chapter std::string dan string_view menemukan teks yang ditulis persis; sementara pertanyaan seperti “apakah ada angka tiga digit di sini” atau “ganti semua tag HTML dengan spasi” membutuhkan pola yang bisa menyebut bentuk, bukan isi. Jawabannya regular expression (regex): bahasa kecil untuk mendeskripsikan pola teks. Header <regex> (sejak C++11) menyediakan kelas polanya (std::regex), hasil kecocokan (std::smatch), dan tiga fungsi pencocokan. Chapter ini membahas tiga fungsi utama itu, pengiterasian semua kecocokan dengan regex_iterator, pilihan grammar lewat syntax option, peringatan performa beserta kasus yang lebih cocok diurai manual, dan tips menulis pola yang di-escape dengan benar.

Tiga Fungsi Utama: regex_match, regex_search, dan regex_replace #

Ketiga fungsi menerima pola dan string target, tetapi menjawab pertanyaan yang berbeda. regex_match hanya berhasil bila pola mencakup seluruh string — cocok untuk validasi format. regex_search berhasil bila sebagian string cocok — cocok untuk menemukan dan membaca potongan pertama. regex_replace mengembalikan string baru berisi seluruh string dengan semua kecocokan diganti teks pengganti. Informasi kecocokan dikembalikan lewat objek std::smatch (alias std::match_results untuk std::string): str() memberi teks yang cocok, position() memberi indeks nol-tempat kemunculannya.

FungsiPertanyaan yang dijawabHasil
regex_matchapakah SELURUH string sesuai pola?bool (versi dengan smatch tersedia)
regex_searchadakah BAGIAN string yang sesuai pola?bool (versi dengan smatch tersedia)
regex_replacebagaimana string baru setelah penggantian?std::string
cpp
#include <iostream>  // std::cout
#include <regex>     // std::regex, regex_match, regex_search, regex_replace
#include <string>    // std::string

int main() {
    std::cout << std::boolalpha;

    std::string teks = "Stok berisi 42 unit, dipesan 7 kali";
    std::regex polaAngka{"\\d+"};   // pola dikompilasi SEKALI di awal
    std::smatch hasil;

    // 1) regex_match: pola harus mencakup SELURUH string
    std::regex polaKode{"[A-Z]{3}-\\d{4}"};
    std::cout << "match CMD-2024 : " << std::regex_match("CMD-2024", polaKode) << "\n";
    std::cout << "match CMD-20240: " << std::regex_match("CMD-20240", polaKode) << "\n";

    // 2) regex_search: cukup SATU bagian string yang cocok
    if (std::regex_search(teks, hasil, polaAngka)) {
        std::cout << "angka pertama  : " << hasil.str() << "\n";
        std::cout << "di posisi      : " << hasil.position() << "\n";
    }

    // 3) regex_replace: SEMUA kecocokan diganti teks pengganti
    std::cout << "setelah replace: " << std::regex_replace(teks, polaAngka, "N") << "\n";
    return 0;
}

Output yang Dihasilkan:

text
match CMD-2024 : true
match CMD-20240: false
angka pertama  : 42
di posisi      : 12
setelah replace: Stok berisi N unit, dipesan N kali

Mari kita bedah:

  1. Baris 9 (std::regex polaAngka{"\\d+"};): pola dikompilasi sekali di awal. Di string literal C++, \\d menjadi \d di dalam pola — dua karakter itu berarti “satu digit” (detail escape dibahas di bagian akhir chapter).
  2. Baris 13-15: regex_match("CMD-2024", polaKode) menghasilkan true karena seluruh string pas dengan [A-Z]{3}-\d{4}; CMD-20240 gagal karena ada satu karakter ekstra yang tidak tercakup pola.
  3. Baris 18-21: regex_search menemukan kecocokan pertama dan mengisinya ke hasil; hasil.str() tercetak 42 dan hasil.position() tercetak 12 — indeks karakter pertama dari 42 di dalam string target.
  4. Baris 24: regex_replace tidak mengubah teks; ia mengembalikan string baru dengan kedua angka (42 dan 7) diganti N.

Mengiterasi Semua Kecocokan dengan regex_iterator #

regex_search berhenti pada kecocokan pertama; untuk mendapatkan semua kecocokan, standar menyediakan std::regex_iterator (untuk std::string: alias std::sregex_iterator). Iterator ini dibangun dari pasangan iterator string plus pola, lalu setiap ++ melangkah ke kecocokan berikutnya; *it memberikan objek smatch yang berisi teks dan posisinya. Iterator akhir ditandai oleh sregex_iterator yang dibangun tanpa argumen — idiom yang sama seperti end() pada container. Bila kebutuhan kita hanya mengambil potongan tertentu dari tiap kecocokan (atau potongan antara kecocokan), std::regex_token_iterator adalah varian yang menyaring sub-ekspresi; untuk chapter ini regex_iterator saja sudah cukup.

cpp
#include <iostream>  // std::cout
#include <regex>     // std::regex, std::sregex_iterator, std::smatch
#include <string>    // std::string

int main() {
    std::string log = "hari1: 120 langkah, hari2: 85 langkah, hari3: 232 langkah";
    std::regex polaAngka{"\\d+"};

    // Iterator akhir: sregex_iterator yang dibangun tanpa argumen
    std::sregex_iterator akhir;

    int jumlah = 0;
    for (std::sregex_iterator it{log.begin(), log.end(), polaAngka};
         it != akhir; ++it) {
        std::smatch cocok = *it;  // satu kecocokan lengkap dengan posisinya
        ++jumlah;
        std::cout << "posisi " << cocok.position()
                  << " : " << cocok.str() << "\n";
    }
    std::cout << "jumlah kecocokan: " << jumlah << "\n";
    return 0;
}

Output yang Dihasilkan:

text
posisi 4 : 1
posisi 7 : 120
posisi 24 : 2
posisi 27 : 85
posisi 43 : 3
posisi 46 : 232
jumlah kecocokan: 6

Mari kita bedah:

  1. Baris 10 (std::sregex_iterator akhir;): iterator akhir tanpa isi; perbandingan it != akhir menjadi syarat berhenti loop.
  2. Baris 13-14: iterator awal dibangun dari log.begin(), log.end(), dan pola; konstruktor itu langsung mencari kecocokan pertama, dan loop berjalan selama masih ada.
  3. Baris 15: *it mengembalikan smatch — bukan cuma teks, tetapi juga posisi, prefix, dan suffix relatif terhadap string target.
  4. Baris 17-18: tiap kecocokan dicetak bersama position()-nya; keenam angka pada log (“1”, “120”, “2”, “85”, “3”, “232”) terangkat lengkap dengan indeksnya.
  5. Baris 20: penghitung sederhana menjawab “ada berapa kecocokan” — enam angka.

Syntax Option: Grammar ECMAScript dan Variasinya #

Konstruktor std::regex menerima argumen kedua bertipe syntax_option_type — bitmask di namespace std::regex_constants (nilai yang sama juga tersedia sebagai anggota std::regex). Opsi terbagi dua kelompok. Kelompok grammar memilih bahasa polanya: ECMAScript, basic, extended, awk, grep, egrep; dari keenamnya paling banyak satu saja boleh dipilih, dan bila tidak ada yang disebut, ECMAScript dipakai sebagai bawaan. Kelompok variasi melengkapi grammar mana pun: icase (mengabaikan huruf besar/kecil), nosubs (grup tidak disimpan), optimize (pencocokan lebih cepat, konstruksi lebih lambat), collate (rentang karakter seperti [a-b] peka locale), dan multiline (C++17: ^/$ mencocokkan tiap baris). Salah satu fitur khas grammar ECMAScript adalah kuantor non-greedy: *? dan +? berhenti pada peluang pertama, sedangkan bentuk serakahnya (*, +) menelan sebanyak mungkin.

cpp
#include <iostream>  // std::cout
#include <regex>     // std::regex, std::regex_constants
#include <string>    // std::string

int main() {
    std::cout << std::boolalpha;

    // icase: pencocokan mengabaikan huruf besar/kecil
    std::regex pola{"Gagal", std::regex_constants::icase};
    std::cout << "search GAGAL : "
              << std::regex_search("proses GAGAL total", pola) << "\n";

    // Grammar bawaan ECMAScript mendukung kuantor non-greedy (?)
    std::string teks = "<b>tebal</b><i>miring</i>";
    std::smatch m;

    std::regex serakah{"(<.+>)"};   // .+  : serakah, telan sebanyak mungkin
    std::regex malas{"(<.+?>)"};    // .+? : malas, berhenti di peluang pertama
    std::regex_search(teks, m, serakah);
    std::cout << "greedy : " << m[1] << "\n";
    std::regex_search(teks, m, malas);
    std::cout << "lazy   : " << m[1] << "\n";
    return 0;
}

Output yang Dihasilkan:

text
search GAGAL : true
greedy : <b>tebal</b><i>miring</i>
lazy   : <b>

Mari kita bedah:

  1. Baris 9 (std::regex pola{"Gagal", std::regex_constants::icase};): icase ditambahkan di atas grammar bawaan; bentuk std::regex::icase setara karena nilainya diduplikasi di std::regex.
  2. Baris 10-11: pola "Gagal" dengan icase menemukan GAGAL di dalam string target — pencocokan mengabaikan perbedaan huruf besar/kecil.
  3. Baris 17-18: dua pola berbeda hanya pada kuantornya: .+ serakah versus .+? malas (fitur ECMAScript).
  4. Baris 19-22: versi serakah menelan dari < pertama sampai > terakhir sehingga satu grup menampung seluruh baris; versi malas berhenti di > pertama dan menangkap <b> saja.

Kapan Parsing Manual Lebih Tepat #

std::regex nyaman, tetapi bukan alat gratis. Konstruksi std::regex menguraikan dan menyusun pola menjadi mesin pencocokan — pekerjaan yang relatif mahal — sementara pencocokannya sendiri dijalankan mesin serbaguna yang jauh lebih lambat daripada kode yang menyerang format itu secara langsung. Konsekuensinya dua aturan praktis: bangun objek std::regex sekali di luar loop (bukan di dalam setiap panggilan fungsi), dan pertimbangkan parsing manual untuk pekerjaan sederhana yang berulang banyak kali — memecah baris pada satu karakter pemisah, memeriksa awalan atau akhiran, atau menguraikan format tetap. Kemampuan yang dibutuhkan — find, substr, dan npos — sudah kita kuasai pada Chapter std::string dan string_view.

cpp
#include <iostream>  // std::cout
#include <string>    // std::string, find, substr, npos
#include <vector>    // std::vector

// Memecah baris CSV sederhana TANPA regex: find + substr saja
std::vector<std::string> pecah(const std::string& baris, char pemisah) {
    std::vector<std::string> hasil;
    std::size_t mulai = 0;
    while (true) {
        std::size_t koma = baris.find(pemisah, mulai);
        // npos berarti tidak ada lagi pemisah: substr sampai akhir baris
        hasil.push_back(baris.substr(mulai, koma - mulai));
        if (koma == std::string::npos) break;
        mulai = koma + 1;
    }
    return hasil;
}

int main() {
    std::string baris = "Budi,Siti,Dewi";
    for (const std::string& nama : pecah(baris, ',')) {
        std::cout << nama << "\n";
    }
    return 0;
}

Output yang Dihasilkan:

text
Budi
Siti
Dewi

Mari kita bedah:

  1. Baris 6-8 (pecah): fungsi menerima satu baris dan satu karakter pemisah; hasilnya std::vector<std::string> — tanpa regex dan tanpa alokasi pola.
  2. Baris 10-12: find mencari pemisah berikutnya dari posisi mulai; substr(mulai, koma - mulai) memotong potongan sebelum pemisah itu.
  3. Baris 13-14: bila find mengembalikan npos (pemisah tidak ada lagi), potongan terakhir diambil lalu loop berhenti; selain itu pencarian dilanjutkan setelah pemisah.
  4. Baris 20-22: pemanggil menguraikan "Budi,Siti,Dewi" menjadi tiga nama — tugas yang jika ditulis regex akan membayar biaya mesin pola untuk pola yang sebenarnya sudah pasti bentuknya.

Tips Escape String #

Dua lapis bahasa bertemu di sini: string literal C++ dan bahasa regex. Karakter yang istimewa di regex — . ^ $ | ( ) [ ] { } * + ? \ — harus diawali \ agar dibaca sebagai karakter biasa; dan karena \ juga istimewa di string literal C++, kita menulisnya ganda: "\\." di kode menjadi \. di pola. Kebalikannya juga perlu diwaspadai: menulis "\d" (satu backslash) membuat compiler membaca \d sebagai escape string yang tidak dikenal — GCC melaporkannya sebagai warning unknown escape sequence, dan hasilnya bukan pola yang kita maksud. Kebiasaan yang sehat: pola selalu ditulis dengan backslash ganda untuk kelas karakter (\\d, \\w, \\s) dan tanda escape-nya. Bila pola datang dari input pengguna saat runtime (misalnya kata yang dicari), tidak ada fungsi standar yang meng-escape-nya — kita perlu menambahkan \ sebelum tiap karakter khusus secara manual, atau membatasi karakter yang diterima.

cpp
#include <iostream>  // std::cout
#include <regex>     // std::regex, regex_search
#include <string>    // std::string

int main() {
    std::cout << std::boolalpha;

    // Tanpa escape: titik (.) cocok dengan karakter APA PUN
    std::regex longgar{"foto.png"};
    std::cout << "fotoXpng vs longgar: "
              << std::regex_search("fotoXpng", longgar) << "\n";

    // Dengan escape: pola \\. hanya cocok dengan titik sungguhan
    std::regex ketat{"foto\\.png"};
    std::cout << "foto.png vs ketat  : "
              << std::regex_search("foto.png", ketat) << "\n";
    std::cout << "fotoXpng vs ketat  : "
              << std::regex_search("fotoXpng", ketat) << "\n";

    // Pola nama file gambar: kelas karakter, kuantor, dan anchor $
    std::regex gambar{"\\w+\\.(png|jpg)$"};
    std::cout << "libur.jpg vs gambar: "
              << std::regex_search("libur.jpg", gambar) << "\n";
    std::cout << "lapor.txt vs gambar: "
              << std::regex_search("lapor.txt", gambar) << "\n";
    return 0;
}

Output yang Dihasilkan:

text
fotoXpng vs longgar: true
foto.png vs ketat  : true
fotoXpng vs ketat  : false
libur.jpg vs gambar: true
lapor.txt vs gambar: false

Mari kita bedah:

  1. Baris 9 (std::regex longgar{"foto.png"};): titik tanpa escape berarti “karakter apa pun”, sehingga fotoXpng pun cocok — sering bukan niat kita.
  2. Baris 13-14: pola "foto\\.png" berisi \. yang membaca titik secara harfiah; foto.png cocok dan fotoXpng ditolak.
  3. Baris 21 ("\\w+\\.(png|jpg)$"): gabungan kelas karakter \w+, titik harfiah, grup alternatif (png|jpg), dan anchor $ yang menuntut kecocokan di ujung string.
  4. Baris 22-25: libur.jpg lolos sedangkan lapor.txt ditolak — ekstensi lain tidak tercakup pola.

Pola teks kini tercakup: dari kecocokan tunggal sampai penggantian massal. Chapter berikutnya membahas std::bitset, himpunan bit berukuran tetap yang menjadi rumah flag dan operasi bitwise pada utilitas standar.

Rujukan resmi: cppreference — Regular expressions library , cppreference — regex_iterator , cppreference — syntax_option_type .