Sebelum menulis logika perhitungan, kita perlu menyiapkan kerangka proyek yang siap di-compile. Pada bagian ini kita membuat tiga berkas: calculator.h sebagai antarmuka publik, calculator.c sebagai tempat implementasi, dan main.c sebagai titik masuk program. Kita juga menulis Makefile agar perintah make merangkai ketiga berkas tersebut menjadi satu executable. Tujuan bagian ini bukan menghitung apa pun — fungsi perhitungan masih kosong — melainkan memastikan struktur modular bekerja bersih tanpa peringatan compiler.
Konsep #
Program C yang lebih dari sekadar latihan biasanya dipecah menjadi beberapa translation unit. Setiap unit terdiri dari pasangan header (.h) yang berisi deklarasi dan implementasi (.c) yang berisi definisi. Header berperan sebagai kontrak: ia memberi tahu pemanggil fungsi apa yang ada, tipe apa yang dipulangkan, dan parameter apa yang diterima — tanpa membocorkan rincian cara kerja internal. Pemisahan ini punya dua keuntungan praktis: kompilasi lebih cepat karena hanya berkas yang berubah yang perlu di-compile ulang, dan testing lebih mudah karena kita dapat menguji fungsi-fungsi di calculator.c tanpa membawa kode I/O dari main.c.
Dalam proyek kalkulator ini, kita menetapkan dua enum sebagai kontrak bersama: Operasi untuk memilih jenis perhitungan, dan CalcStatus untuk melaporkan hasil dari fungsi kalkulator_hitung. Kedua enum ini didefinisikan di calculator.h sehingga main.c dan tests.c dapat memakainya dengan cara yang sama persis.
Implementasi #
Kita mulai dari berkas header calculator.h. Berkas ini dikelilingi include guard agar aman bila di-include lebih dari sekali dalam satu unit kompilasi.
#ifndef CALCULATOR_H
#define CALCULATOR_H
typedef enum {
OP_TAMBAH = 1,
OP_KURANG = 2,
OP_KALI = 3,
OP_BAGI = 4
} Operasi;
typedef enum {
CALC_OK = 0,
CALC_ERR_BAGI_NOL = 1,
CALC_ERR_OP_TIDAK_DIKETAHUI = 2
} CalcStatus;
CalcStatus kalkulator_hitung(Operasi op, double a, double b, double *hasil);
#endif /* CALCULATOR_H */Implementasi di calculator.c sementara ini hanya berisi stub yang selalu memulangkan CALC_ERR_OP_TIDAK_DIKETAHUI. Stub memungkinkan kita menguji build sebelum logika nyata ditulis pada Bagian 3.
#include "calculator.h"
CalcStatus kalkulator_hitung(Operasi op, double a, double b, double *hasil) {
(void)op; (void)a; (void)b; (void)hasil;
return CALC_ERR_OP_TIDAK_DIKETAHUI;
}Sementara itu main.c hanya mencetak pesan pembuka agar kita tahu build berhasil:
#include <stdio.h>
int main(void) {
printf("Kalkulator Terminal - kerangka aktif.\n");
return 0;
}Kode Lengkap #
Berkas calculator.h lengkap untuk bagian ini:
#ifndef CALCULATOR_H
#define CALCULATOR_H
typedef enum {
OP_TAMBAH = 1,
OP_KURANG = 2,
OP_KALI = 3,
OP_BAGI = 4
} Operasi;
typedef enum {
CALC_OK = 0,
CALC_ERR_BAGI_NOL = 1,
CALC_ERR_OP_TIDAK_DIKETAHUI = 2
} CalcStatus;
CalcStatus kalkulator_hitung(Operasi op, double a, double b, double *hasil);
#endif /* CALCULATOR_H */Berkas Makefile:
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -g
TARGET = program
$(TARGET): main.c calculator.c calculator.h
$(CC) $(CFLAGS) -o $(TARGET) main.c calculator.c -lm
clean:
rm -f $(TARGET)
.PHONY: cleanPengujian #
Kompilasi dengan make lalu jalankan binary:
make
./programOutput yang Dihasilkan:
Kalkulator Terminal - kerangka aktif.Verifikasi bahwa tidak ada peringatan dari compiler — flag -Wall -Wextra akan menandai bila ada deklarasi fungsi tanpa prototipe atau variabel yang tidak terpakai. Mari kita telaah baris-baris kunci pada calculator.h:
- Baris 1-2 (
#ifndef CALCULATOR_H): include guard mencegah deklarasi ganda bilacalculator.hdi-include dari dua berkas.cberbeda dalam satu translation unit. - Baris 4-9 (
typedef enum { ... } Operasi): memberi nama tipeOperasike enum anonim sehingga kita dapat mendeklarasikan variabelOperasi op;alih-alihenum { ... } op;. - Baris 11-15 (
CalcStatus): enum status berbeda dariOperasiagar pemanggil tidak tertukar antara “apa operasinya” dan “apa hasil eksekusinya”. - Baris 17 (
CalcStatus kalkulator_hitung(...)): prototipe fungsi —double *hasiladalah parameter keluaran (out parameter) sehingga fungsi dapat memulangkan status sekaligus menulis hasil perhitungan melalui pointer.
Pada Bagian 2 kita akan mengganti stub di main.c dengan menu utama yang membaca input pengguna.
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;
}