switch dan case

Perilaku fall-through, default, dan pertimbangan switch dibanding rantai if.

Daftar isi

Pengujian satu nilai terhadap sejumlah kemungkinan tetap dapat ditulis dengan switch, yang melompat langsung ke label case yang cocok tanpa menguji kondisi satu per satu. Topik ini membahas struktur switch dan case, perilaku fall-through ketika break tidak ditulis, peran label default, serta pertimbangan memilih switch dibanding rantai if. Seluruh contoh berupa program utuh yang dikompilasi dengan g++ pada standar C++17 beserta keluaran aslinya.

Struktur switch dan case#

Pernyataan switch menguji satu ekspresi bertipe bilangan bulat, seperti int atau char, kemudian melompat ke label case yang nilainya sama dengan hasil ekspresi itu. Nilai label wajib berupa konstanta yang diketahui saat kompilasi, sehingga variabel biasa tidak dapat menjadi label. Eksekusi dimulai dari label yang cocok dan berlanjut sampai break menghentikan switch atau akhir blok tercapai. Apabila tidak ada label yang cocok dan label default ditulis, eksekusi berpindah ke label tersebut. Bentuk ini menggantikan rantai panjang else if yang semuanya membandingkan variabel yang sama dengan nilai tetap.

Program berikut menerjemahkan nomor hari menjadi namanya. break pada akhir setiap case menjaga agar case berikutnya tidak ikut terjalankan, sedangkan default menangani nomor di luar 1 sampai 7. Susunan label yang berurutan seperti ini mudah dibaca karena setiap nilai tetap memiliki jalurnya sendiri.

cpp nama-hari.cpp
#include <iostream>

int main() {
    int nomorHari = 3;

    switch (nomorHari) {
    case 1:
        std::cout << "Senin\n";
        break;
    case 2:
        std::cout << "Selasa\n";
        break;
    case 3:
        std::cout << "Rabu\n";
        break;
    case 4:
        std::cout << "Kamis\n";
        break;
    case 5:
        std::cout << "Jumat\n";
        break;
    case 6:
        std::cout << "Sabtu\n";
        break;
    case 7:
        std::cout << "Minggu\n";
        break;
    default:
        std::cout << "Nomor hari tidak valid\n";
        break;
    }
    return 0;
}
text Keluaran
Rabu

Nilai nomorHari bernilai 3, sehingga eksekusi melompat langsung ke label case 3: dan mencetak Rabu sebelum break menghentikannya. Pembandingan yang dilakukan switch berupa kesamaan, bukan rentang, sehingga kebutuhan seperti menguji nomor di atas 5 dinyatakan lewat default atau di luar switch. Karakter juga dapat diuji dengan cara yang sama karena char termasuk tipe bilangan bulat.

Perilaku fall-through#

Tanpa break, eksekusi tidak berhenti di akhir suatu case, melainkan meneruskan pernyataan pada case berikutnya. Perilaku ini disebut fall-through dan kebanyakan merupakan kekeliruan, tetapi dapat dimanfaatkan dengan sengaja ketika beberapa nilai membutuhkan penanganan yang sama. Bentuk yang paling aman adalah label yang berjejer tanpa pernyataan di antaranya, karena tidak ada baris yang terlewat secara keliru. Pola label berjejer ini lazim untuk pengelompokan nilai, sebagaimana perhitungan jumlah hari pada setiap bulan.

Program berikut mengelompokkan bulan dengan 31 hari dan 30 hari, sedangkan Februari ditangani tersendiri. Tujuh label jatuh ke pernyataan yang sama, sehingga tidak ada duplikasi pernyataan cetak di antara kelompoknya.

cpp hari-dalam-bulan.cpp
#include <iostream>

int main() {
    int bulan = 7;

    switch (bulan) {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
        std::cout << "31 hari\n";
        break;
    case 4:
    case 6:
    case 9:
    case 11:
        std::cout << "30 hari\n";
        break;
    case 2:
        std::cout << "28 atau 29 hari\n";
        break;
    default:
        std::cout << "Bulan tidak valid\n";
        break;
    }
    return 0;
}
text Keluaran
31 hari

Bentuk fall-through lain sengaja menjalankan sebagian pernyataan sebelum jatuh ke label berikutnya. C++17 menyediakan atribut [[fallthrough]] yang menandai maksud tersebut secara eksplisit sekaligus mencegah munculnya peringatan compiler. Program berikut menyusun rangkaian kerja kebersihan, yaitu tingkat tertinggi mengerjakan semua langkah, sedangkan tingkat yang lebih rendah mengerjakan sebagiannya.

cpp tingkat-kebersihan.cpp
#include <iostream>

int main() {
    int tingkat = 2;

    switch (tingkat) {
    case 3:
        std::cout << "mengepel lantai\n";
        [[fallthrough]];
    case 2:
        std::cout << "menyapu lantai\n";
        [[fallthrough]];
    case 1:
        std::cout << "membuang sampah\n";
        break;
    default:
        std::cout << "tingkat tidak dikenal\n";
        break;
    }
    return 0;
}
text Keluaran
menyapu lantai
membuang sampah

Tingkat 2 menghasilkan dua baris karena eksekusi dimulai pada case 2:, mencetak penyapuan lantai, kemudian jatuh ke case 1: tanpa break. Atribut [[fallthrough]] tidak mengubah alur program sama sekali, melainkan mendokumentasikan bahwa jatuhnya memang disengaja. Pembaca kode berikutnya pun tidak perlu menduga adanya break yang terlupakan.

default dan pemilihan switch atau if#

Label default menangani nilai yang tidak cocok dengan label case mana pun, sehingga kasus yang tidak terduga tetap mendapat perlakuan yang jelas. Letaknya bebas di dalam blok switch, tetapi posisi terakhir disertai break merupakan kebiasaan yang paling mudah dibaca. Tanpa default, nilai di luar semua label hanya membuat switch tidak melakukan apa pun, dan hal itu mungkin memang dimaksudkan atau justru kelalaian.

Perbandingan ciri kedua pendekatan diringkas pada tabel berikut. Baris terakhirnya menjadi alasan utama memilih switch ketika nilainya banyak dan tetap.

AspekswitchRantai if-else
Tipe nilai yang diujiBilangan bulat dan tipe turunannyaEkspresi bool apa pun
Bentuk labelKonstanta yang diketahui saat kompilasiEkspresi bebas, termasuk rentang
Cara memilih jalurLompatan langsung ke label yang cocokDiuji berurutan dari atas ke bawah
Penulisan banyak nilai tetapRingkas pada satu blokMemanjang pada setiap tambahan nilai

Sebagai kaidah praktis, switch dipilih ketika satu nilai dibandingkan dengan sejumlah kemungkinan tetap, seperti nomor bulan atau pilihan menu. Rantai if-else dipilih ketika kondisinya berupa rentang, menggabungkan beberapa variabel, atau bertipe selain bilangan bulat seperti std::string. Perbedaan kinerja antara keduanya umumnya tidak berarti bagi program kecil, sehingga keterbacaan menjadi penentu utama pilihannya.

Kesalahan umum#

Tiga kekeliruan berikut paling sering muncul pada pemakaian switch. Semuanya dapat dicegah dengan kebiasaan penulisan yang tetap.

  1. break yang terlupakan pada akhir case. Eksekusi jatuh ke case berikutnya tanpa pesan error, dan hasilnya berupa logika yang salah secara diam-diam. Tulis break pada akhir setiap case, atau letakkan atribut [[fallthrough]] apabila jatuhnya memang disengaja.
  2. Deklarasi variabel di dalam case tanpa tanda kurung kurawal. Compiler menolak programnya karena terdapat jalur lompatan yang dapat melewati inisialisasi variabel tersebut. Bungkus isi case dengan { } atau pindahkan deklarasinya ke sebelum switch.
  3. switch terhadap std::string. Tipe string bukan bilangan bulat, sehingga programnya tidak dapat dikompilasi. Gunakan rantai if-else atau tabel pemetaan seperti std::map untuk kasus tersebut.

Ketiga pola di atas berbagi satu sifat, yaitu muncul tanpa peringatan saat penulisan berlangsung cepat. Sebagian besar kesalahan tersebut tertangkap dengan membaca ulang setiap case dan memeriksa satu hal sederhana, yaitu apakah jalur keluarnya jelas.

Ringkasan#

  • switch melompat ke label case bernilai sama, dengan label yang wajib berupa konstanta bertipe bilangan bulat.
  • Tanpa break, eksekusi jatuh ke case berikutnya, label yang berjejer menjadi pola pengelompokan yang sah, dan [[fallthrough]] menandai jatuh yang disengaja.
  • default menangani nilai di luar semua label, sedangkan rantai if-else lebih cocok untuk rentang dan kondisi campuran.

Referensi#