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:
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:
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):
#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:
gcc -std=c11 -Wall -Wextra -o /tmp/p1b2 main.c calculator.c -lm
printf '5\n' | /tmp/p1b2Output yang Dihasilkan:
=== 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:
printf '9\nbudi\n5\n' | /tmp/p1b2Output yang Dihasilkan:
=== 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:
- Baris
char buffer[128];: buffer statis lokal cukup untuk baris input manusia; bila pengguna mengetik lebih panjang,fgetsmemotong sisa baris dan menyisakan karakter di stdin. Pada program nyata, kita bisa membersihkan stdin secara eksplisit. - Baris
if (fgets(...) == 0) return 0;:fgetsmemulangkanNULLsaat EOF. Cek ini mencegah dereferensi pointer NULL bila pengguna menekan Ctrl-D. - Baris
char *sisa = 0; ... strtod(buffer, &sisa);:strtodmenulis pointer ke karakter pertama yang tidak terurai. Bilasisa == buffer, tidak ada angka terbaca sama sekali. - 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/* 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/* 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/* 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;
}