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:
=== 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
.hdan implementasi.c, - mendefinisikan enum untuk operasi dan kode status, lalu memakainya sebagai return value,
- memvalidasi input numerik dengan
fgets+strtodalih-alihscanfyang 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
OperasidanCalcStatus, - File I/O Dasar —
fgetsuntuk membaca input baris.
Tahapan Pengerjaan #
Proyek dikerjakan dalam lima bagian berurutan. Setiap bagian menghasilkan program yang dapat di-compile:
- Bagian 1: Struktur Program — membuat kerangka
main.c,calculator.h, danMakefileuntuk memastikan modular build berjalan. - Bagian 2: Input dan Output — membaca menu, dua angka, dan pertanyaan “hitung lagi” dengan
fgets. - Bagian 3: Operator dan Perhitungan — mengimplementasikan empat operasi di
calculator.cdengan kode status. - Bagian 4: Validasi Input — menolak input non-numerik dan pembagian dengan nol secara aman.
- Bagian 5: Testing dan Build — menulis
tests.cdan menyusunMakefiledengan targettest.
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.
Susun kerangka kalkulator: header, fungsi main, dan Makefile.
Buka → # Bagian 2: Input dan OutputBaca menu, dua angka, dan pertanyaan ulang dengan fgets.
Buka → # Bagian 3: Operator dan PerhitunganImplementasi empat operasi aritmetika di calculator.c.
Buka → # Bagian 4: Validasi InputTolak input non-numerik dan tangani pembagian nol secara aman.
Buka → # Bagian 5: Testing dan BuildTulis tests.c, susun Makefile, dan jalankan test runner.
Buka → # Buka →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;
}