Setelah kerangka build berdiri, kita mengisi main.c dengan logika interaktif: menampilkan menu, membaca pilihan operasi, membaca dua angka, dan menanyakan apakah pengguna ingin menghitung lagi. Pada bagian ini kita tidak memanggil kalkulator_hitung — pemanggilan tersebut datang pada Bagian 3. Fokus kita adalah cara membaca input baris secara aman dengan fgets dan strtod, bukan scanf yang sering meninggalkan karakter tersisa di buffer input.

Konsep #

Fungsi scanf dengan format %d atau %lf berhenti membaca pada karakter pertama yang bukan angka, tetapi karakter tersebut tetap tersisa di buffer stdin. Akibatnya, dua panggilan scanf berurutan sering membuat program seolah-olah melewatkan prompt kedua. Sebaliknya, fgets membaca satu baris utuh sampai newline, sehingga buffer stdin selalu bersih setelah pemanggilan.

Untuk mengubah baris teks menjadi angka, kita memakai strtod dari <stdlib.h>. Fungsi ini menerima pointer char **endptr yang akan menunjuk ke karakter pertama yang tidak terurai sebagai angka. Bila endptr sama dengan awal buffer, berarti tidak ada karakter yang berhasil diparse — input tidak valid. Pola inilah yang kita pakai di fungsi baca_angka.

Implementasi #

Fungsi baca_angka menerima label prompt dan pointer keluaran. Bila pengguna mengetik teks non-numerik, fungsi memulangkan 0 tanpa crash:

c
static int baca_angka(const char *label, double *out) {
    char buffer[128];
    printf("%s: ", label);
    if (fgets(buffer, sizeof(buffer), stdin) == 0) return 0;

    char *sisa = 0;
    double nilai = strtod(buffer, &sisa);
    if (sisa == buffer) return 0;   /* tidak ada angka terbaca */
    *out = nilai;
    return 1;
}

Fungsi baca_pilihan membaca satu karakter dan memvalidasi rentangnya:

c
static int baca_pilihan(void) {
    char buffer[128];
    printf("Pilih operasi (1-5): ");
    if (fgets(buffer, sizeof(buffer), stdin) == 0) return 5;
    char *p = buffer;
    while (*p == ' ' || *p == '\t') p++;
    if (*p < '1' || *p > '5') return 0;
    return *p - '0';
}

Loop utama di main memanggil ketiga fungsi tersebut secara berurutan. Bila pilihan tidak dikenali, program menampilkan pesan lalu continue kembali ke awal loop tanpa membaca angka.

Kode Lengkap #

Berkas main.c untuk Bagian 2 (versi ini sudah memanggil kalkulator_hitung sebagai placeholder yang akan diisi di Bagian 3):

c
#include "calculator.h"

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

#define MENU_KELUAR 5

static void tampilkan_menu(void) {
    printf("=== Kalkulator Terminal ===\n");
    printf("1. Tambah\n2. Kurang\n3. Kali\n4. Bagi\n5. Keluar\n");
}

static int baca_angka(const char *label, double *out) {
    char buffer[128];
    printf("%s: ", label);
    if (fgets(buffer, sizeof(buffer), stdin) == 0) return 0;
    char *sisa = 0;
    double nilai = strtod(buffer, &sisa);
    if (sisa == buffer) return 0;
    *out = nilai;
    return 1;
}

static int baca_pilihan(void) {
    char buffer[128];
    printf("Pilih operasi (1-5): ");
    if (fgets(buffer, sizeof(buffer), stdin) == 0) return MENU_KELUAR;
    char *p = buffer;
    while (*p == ' ' || *p == '\t') p++;
    if (*p < '1' || *p > '5') return 0;
    return *p - '0';
}

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, b;
        if (!baca_angka("Angka pertama", &a)) { printf("Input tidak valid.\n"); continue; }
        if (!baca_angka("Angka kedua", &b))   { printf("Input tidak valid.\n"); continue; }

        double hasil = 0.0;
        CalcStatus s = kalkulator_hitung((Operasi)pilihan, a, b, &hasil);
        if (s == CALC_OK) printf("Hasil: %g\n", hasil);
        else              printf("Operasi belum diimplementasikan.\n");

        char yn[16];
        printf("Hitung lagi? (y/t): ");
        if (fgets(yn, sizeof(yn), stdin) == 0) break;
        lanjut = (yn[0] == 'y' || yn[0] == 'Y');
    }
    printf("Terima kasih telah menggunakan Kalkulator Terminal.\n");
    return 0;
}

Pengujian #

Kirim input lewat pipe untuk simulasi pengguna yang memilih keluar:

bash
gcc -std=c11 -Wall -Wextra -o /tmp/p1b2 main.c calculator.c -lm
printf '5\n' | /tmp/p1b2

Output yang Dihasilkan:

text
=== Kalkulator Terminal ===
1. Tambah
2. Kurang
3. Kali
4. Bagi
5. Keluar
Pilih operasi (1-5): Terima kasih telah menggunakan Kalkulator Terminal.

Coba juga input non-numerik untuk membuktikan validasi bekerja:

bash
printf '9\nbudi\n5\n' | /tmp/p1b2

Output yang Dihasilkan:

text
=== Kalkulator Terminal ===
1. Tambah
2. Kurang
3. Kali
4. Bagi
5. Keluar
Pilih operasi (1-5): Pilihan tidak dikenal.
=== Kalkulator Terminal ===
1. Tambah
2. Kurang
3. Kali
4. Bagi
5. Keluar
Pilih operasi (1-5): Terima kasih telah menggunakan Kalkulator Terminal.

Mari kita telaah baris-baris kunci pada baca_angka:

  1. Baris char buffer[128];: buffer statis lokal cukup untuk baris input manusia; bila pengguna mengetik lebih panjang, fgets memotong sisa baris dan menyisakan karakter di stdin. Pada program nyata, kita bisa membersihkan stdin secara eksplisit.
  2. Baris if (fgets(...) == 0) return 0;: fgets memulangkan NULL saat EOF. Cek ini mencegah dereferensi pointer NULL bila pengguna menekan Ctrl-D.
  3. Baris char *sisa = 0; ... strtod(buffer, &sisa);: strtod menulis pointer ke karakter pertama yang tidak terurai. Bila sisa == buffer, tidak ada angka terbaca sama sekali.
  4. Baris *out = nilai;: hanya menulis ke pointer keluaran setelah validasi berhasil. Ini mencegah pemanggil menerima nilai sampah bila input tidak valid.

Bagian 3 akan mengisi kalkulator_hitung dengan empat operasi nyata.

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;
}