Setelah membahas dasar-dasar C seperti fungsi, kontrol alur, struct, dan modularisasi, kini kita memakai semuanya dalam proyek utuh pertama: kalkulator terminal interaktif. Program ini bukan sekadar latihan aritmetika, melainkan tempat kita berlatih memisahkan logika murni (perhitungan) dari efek samping (input/output) ke dalam dua berkas berbeda. Pemisahan ini bukan kebetalan — pada Bagian 5 kita akan menulis rangkaian test yang membuktikan modul perhitungan bisa diuji tanpa pernah meminta input pengguna.

Program yang akan kita bangun bekerja dalam satu sesi terminal: menampilkan menu lima pilihan, membaca dua angka, menghitung hasil, dan menolak pembagian dengan nol tanpa crash. Pengguna dapat menghitung berkali-kali sampai memilih keluar.

Tampilan Program Akhir #

Sesi pemakaian berikut menunjukkan hasil akhir proyek. Teks yang diketik pengguna tampil di sebelah prompt:

text
=== Kalkulator Terminal ===
1. Tambah
2. Kurang
3. Kali
4. Bagi
5. Keluar
Pilih operasi (1-5): 4
Angka pertama: 10
Angka kedua: 4
Hasil: 10 / 4 = 2.5
Hitung lagi? (y/t): y
=== Kalkulator Terminal ===
1. Tambah
2. Kurang
3. Kali
4. Bagi
5. Keluar
Pilih operasi (1-5): 1
Angka pertama: 2.5
Angka kedua: 1.5
Hasil: 2.5 + 1.5 = 4
Hitung lagi? (y/t): t
Terima kasih telah menggunakan Kalkulator Terminal.

Tampilan sederhana, tetapi di baliknya tersimpan keputusan arsitektur penting: logika hitung dipisah ke modul calculator.c, status error dipulangkan lewat enum CalcStatus, dan I/O tetap berada di main.c agar test runner dapat menguji perhitungan tanpa menyentuh layar.

Tujuan Pembelajaran #

Setelah menyelesaikan proyek ini, kita mampu:

  • menstrukturkan program C menjadi beberapa berkas dengan header .h dan implementasi .c,
  • mendefinisikan enum untuk operasi dan kode status, lalu memakainya sebagai return value,
  • memvalidasi input numerik dengan fgets + strtod alih-alih scanf yang rapuh,
  • menangani pembagian dengan nol dengan return code eksplisit alih-alih crash,
  • menulis test runner sederhana tanpa framework pihak ketiga.

Prasyarat #

Proyek ini memakai materi yang sudah dibahas pada bab-bab sebelumnya:

  • Struktur Program C dan Fungsi main — kerangka program dan titik masuk,
  • Tipe Data Primitif dan Operator Aritmatika — perhitungan double,
  • Kontrol Alur (while, switch) — loop utama dan dispatch operasi,
  • Fungsi dan Header Files — pemisahan antarmuka dan implementasi,
  • Struct dan Enum — tipe bentukan Operasi dan CalcStatus,
  • File I/O Dasar — fgets untuk membaca input baris.

Tahapan Pengerjaan #

Proyek dikerjakan dalam lima bagian berurutan. Setiap bagian menghasilkan program yang dapat di-compile:

  1. Bagian 1: Struktur Program — membuat kerangka main.c, calculator.h, dan Makefile untuk memastikan modular build berjalan.
  2. Bagian 2: Input dan Output — membaca menu, dua angka, dan pertanyaan “hitung lagi” dengan fgets.
  3. Bagian 3: Operator dan Perhitungan — mengimplementasikan empat operasi di calculator.c dengan kode status.
  4. Bagian 4: Validasi Input — menolak input non-numerik dan pembagian dengan nol secara aman.
  5. Bagian 5: Testing dan Build — menulis tests.c dan menyusun Makefile dengan target test.

Mulailah dari Bagian 1 dan ikuti urutannya karena setiap bagian dibangun di atas berkas bagian sebelumnya. Berkas sumber lengkap tersedia pada bagian bawah halaman masing-masing part.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

calculator.c

Unduh berkas
c
/* calculator.c -- implementasi logika perhitungan kalkulator terminal. */

#include "calculator.h"

/* Header ini diperlukan untuk strcmp di kalkulator_label. */
#include <string.h>

CalcStatus kalkulator_hitung(Operasi op, double a, double b, double *hasil) {
    /* Pastikan pointer hasil tidak NULL sebelum menulis. */
    if (hasil == 0) {
        return CALC_ERR_OP_TIDAK_DIKETAHUI;
    }

    switch (op) {
        case OP_TAMBAH:
            *hasil = a + b;
            return CALC_OK;
        case OP_KURANG:
            *hasil = a - b;
            return CALC_OK;
        case OP_KALI:
            *hasil = a * b;
            return CALC_OK;
        case OP_BAGI:
            if (b == 0.0) {
                return CALC_ERR_BAGI_NOL;
            }
            *hasil = a / b;
            return CALC_OK;
        default:
            return CALC_ERR_OP_TIDAK_DIKETAHUI;
    }
}

const char *kalkulator_label(Operasi op) {
    switch (op) {
        case OP_TAMBAH: return "Tambah";
        case OP_KURANG: return "Kurang";
        case OP_KALI:   return "Kali";
        case OP_BAGI:   return "Bagi";
        default:        return "Tidak Diketahui";
    }
}

const char *kalkulator_simbol(Operasi op) {
    switch (op) {
        case OP_TAMBAH: return "+";
        case OP_KURANG: return "-";
        case OP_KALI:   return "*";
        case OP_BAGI:   return "/";
        default:        return "?";
    }
}

main.c

Unduh berkas
c
/* main.c -- titik masuk kalkulator terminal.
 *
 * Memisahkan I/O (di main.c) dari logika perhitungan (di calculator.c)
 * supaya calculator.c dapat diuji tanpa membaca input pengguna.
 */

#include "calculator.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Konstanta menu untuk pilihan keluar. */
#define MENU_KELUAR 5

/* Menampilkan daftar menu utama ke stdout. */
static void tampilkan_menu(void) {
    printf("=== Kalkulator Terminal ===\n");
    printf("1. Tambah\n");
    printf("2. Kurang\n");
    printf("3. Kali\n");
    printf("4. Bagi\n");
    printf("5. Keluar\n");
}

/* Membaca satu baris input dan memparse bilangan double.
 *
 * Mengembalikan 1 bila berhasil, 0 bila input tidak terurai sebagai angka.
 */
static int baca_angka(const char *label, double *out) {
    char buffer[128];

    printf("%s: ", label);
    if (fgets(buffer, sizeof(buffer), stdin) == 0) {
        return 0;
    }

    /* fgets menyimpan newline; kita andalkan strtod untuk berhenti di sana. */
    char *sisa = 0;
    double nilai = strtod(buffer, &sisa);

    /* Bila seluruh isi buffer adalah whitespace atau kosong, gagal. */
    if (sisa == buffer) {
        return 0;
    }
    *out = nilai;
    return 1;
}

/* Membaca pilihan menu 1-5 dari input. */
static int baca_pilihan(void) {
    char buffer[128];

    printf("Pilih operasi (1-5): ");
    if (fgets(buffer, sizeof(buffer), stdin) == 0) {
        return MENU_KELUAR;
    }

    /* Ambil karakter pertama yang bukan whitespace. */
    char *p = buffer;
    while (*p == ' ' || *p == '\t') {
        p++;
    }

    if (*p < '1' || *p > '5') {
        return 0;
    }
    return *p - '0';
}

/* Menanyakan apakah pengguna ingin menghitung lagi. */
static int tanya_lagi(void) {
    char buffer[16];

    printf("Hitung lagi? (y/t): ");
    if (fgets(buffer, sizeof(buffer), stdin) == 0) {
        return 0;
    }
    return buffer[0] == 'y' || buffer[0] == 'Y';
}

int main(void) {
    int lanjut = 1;

    while (lanjut) {
        tampilkan_menu();
        int pilihan = baca_pilihan();

        if (pilihan == MENU_KELUAR) {
            break;
        }
        if (pilihan < 1 || pilihan > 4) {
            printf("Pilihan tidak dikenal.\n");
            continue;
        }

        double a = 0.0;
        double b = 0.0;
        if (!baca_angka("Angka pertama", &a)) {
            printf("Input angka pertama tidak valid.\n");
            continue;
        }
        if (!baca_angka("Angka kedua", &b)) {
            printf("Input angka kedua tidak valid.\n");
            continue;
        }

        Operasi op = (Operasi)pilihan;
        double hasil = 0.0;
        CalcStatus status = kalkulator_hitung(op, a, b, &hasil);

        if (status == CALC_OK) {
            printf("Hasil: %g %s %g = %g\n",
                   a, kalkulator_simbol(op), b, hasil);
        } else if (status == CALC_ERR_BAGI_NOL) {
            printf("Error: tidak bisa membagi dengan nol.\n");
        } else {
            printf("Error: operasi tidak dikenal.\n");
        }

        lanjut = tanya_lagi();
    }

    printf("Terima kasih telah menggunakan Kalkulator Terminal.\n");
    return 0;
}

tests.c

Unduh berkas
c
/* tests.c -- rangkaian test sederhana untuk modul calculator.
 *
 * Tiap test memanggil kalkulator_hitung lalu membandingkan hasil dengan
 * nilai yang diharapkan. Jika ada test yang gagal, program keluar dengan
 * kode bukan nol.
 */

#include "calculator.h"

#include <stdio.h>
#include <math.h>
#include <string.h>

static int test_lulus = 0;
static int test_gagal = 0;

/* Pembanding aman untuk bilangan double. */
static int hampir_sama(double a, double b) {
    return fabs(a - b) < 1e-9;
}

/* Macro untuk mencatat dan melaporkan hasil test. */
#define PERIKSA(kondisi, nama)                                  \
    do {                                                        \
        if (kondisi) {                                          \
            test_lulus++;                                       \
            printf("[LULUS] %s\n", nama);                       \
        } else {                                                \
            test_gagal++;                                       \
            printf("[GAGAL] %s\n", nama);                       \
        }                                                       \
    } while (0)

static void test_tambah(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_TAMBAH, 5.0, 3.0, &hasil);
    PERIKSA(s == CALC_OK && hampir_sama(hasil, 8.0), "Tambah 5 + 3 = 8");
}

static void test_kurang(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_KURANG, 10.0, 4.0, &hasil);
    PERIKSA(s == CALC_OK && hampir_sama(hasil, 6.0), "Kurang 10 - 4 = 6");
}

static void test_kali(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_KALI, 2.5, 4.0, &hasil);
    PERIKSA(s == CALC_OK && hampir_sama(hasil, 10.0), "Kali 2.5 * 4 = 10");
}

static void test_bagi(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_BAGI, 10.0, 4.0, &hasil);
    PERIKSA(s == CALC_OK && hampir_sama(hasil, 2.5), "Bagi 10 / 4 = 2.5");
}

static void test_bagi_nol(void) {
    double hasil = 0.0;
    CalcStatus s = kalkulator_hitung(OP_BAGI, 5.0, 0.0, &hasil);
    PERIKSA(s == CALC_ERR_BAGI_NOL, "Bagi dengan nol menolak");
}

static void test_label_dan_simbol(void) {
    PERIKSA(strcmp(kalkulator_label(OP_TAMBAH), "Tambah") == 0,
           "Label OP_TAMBAH = \"Tambah\"");
    PERIKSA(strcmp(kalkulator_simbol(OP_BAGI), "/") == 0,
           "Simbol OP_BAGI = \"/\"");
}

int main(void) {
    test_tambah();
    test_kurang();
    test_kali();
    test_bagi();
    test_bagi_nol();
    test_label_dan_simbol();

    printf("\nRingkasan: %d lulus, %d gagal\n", test_lulus, test_gagal);
    return test_gagal == 0 ? 0 : 1;
}