Dengan model data siap, kita mulai mengisi logika CRUD. Pada bagian ini kita mengimplementasikan dua fungsi: contact_tambah untuk menambahkan kontak baru ke daftar, dan contact_tampilkan untuk mencetak seluruh kontak dalam format tabel. Fokus khusus adalah penggunaan strncpy dengan batasan panjang untuk mencegah buffer overflow — sebuah kehati-hatian yang wajib di setiap kali kita menulis ke array karakter fixed-size.
Konsep #
Fungsi contact_tambah menerima tiga string (nama, telp, email) dan menempatkannya ke Contact baru di akhir array data. Setiap kali dipanggil, fungsi memeriksa tiga kondisi: apakah list valid, apakah nama tidak kosong, dan apakah daftar masih punya slot. Hanya setelah ketiga cek lolos, kontak baru ditulis dan jumlah ditambah. ID diambil dari id_berikutnya lalu counter dinaikkan.
Penggunaan strncpy(c->nama, nama, CONTACT_NAMA_MAKS - 1) membatasi salinan ke 49 karakter. strncpy tidak menjamin null terminator bila sumber lebih panjang dari batas, sehingga kita paksa c->nama[CONTACT_NAMA_MAKS - 1] = '\0'; setelahnya. Pola dua langkah ini wajib untuk mencegah string tidak ter-terminasi yang dapat menyebabkan pembacaan berlebih.
Fungsi contact_tampilkan memakai fprintf dengan format lebar tetap %-20s untuk membuat tabel rapi. Tanda minus di depan angka berarti left-align (rata kiri); tanpa minus, kolom akan rata kanan. Header tabel dicetak lebih dulu, lalu iterasi menampilkan setiap kontak.
Implementasi #
Fungsi contact_tambah:
ContactStatus contact_tambah(ContactList *list,
const char *nama,
const char *telp,
const char *email) {
if (list == 0) return CONTACT_ERR_INPUT_INVALID;
if (!contact_valid_nama(nama)) return CONTACT_ERR_INPUT_INVALID;
if (list->jumlah >= CONTACT_MAKS) return CONTACT_ERR_PENUH;
Contact *c = &list->data[list->jumlah];
c->id = list->id_berikutnya++;
strncpy(c->nama, nama, CONTACT_NAMA_MAKS - 1);
c->nama[CONTACT_NAMA_MAKS - 1] = '\0';
strncpy(c->telp, telp ? telp : "", CONTACT_TELP_MAKS - 1);
c->telp[CONTACT_TELP_MAKS - 1] = '\0';
strncpy(c->email, email ? email : "", CONTACT_EMAIL_MAKS - 1);
c->email[CONTACT_EMAIL_MAKS - 1] = '\0';
list->jumlah++;
return CONTACT_OK;
}Fungsi contact_tampilkan:
void contact_tampilkan(const ContactList *list, FILE *out) {
if (list == 0 || out == 0) return;
fprintf(out, "%-4s %-20s %-15s %-25s\n",
"ID", "Nama", "Telp", "Email");
for (int i = 0; i < list->jumlah; i++) {
const Contact *c = &list->data[i];
fprintf(out, "%-4d %-20s %-15s %-25s\n",
c->id, c->nama, c->telp, c->email);
}
fprintf(out, "Total: %d kontak\n", list->jumlah);
}Fungsi validasi nama:
int contact_valid_nama(const char *nama) {
if (nama == 0 || nama[0] == '\0') return 0;
if (strlen(nama) >= CONTACT_NAMA_MAKS) return 0;
return 1;
}Kode Lengkap #
Berkas contact.c untuk Bagian 2 (hanya fungsi yang relevan; fungsi lain masih stub):
#include "contact.h"
#include <string.h>
void contact_list_init(ContactList *list) {
if (list == 0) return;
list->jumlah = 0;
list->id_berikutnya = 1;
}
int contact_valid_nama(const char *nama) {
if (nama == 0 || nama[0] == '\0') return 0;
if (strlen(nama) >= CONTACT_NAMA_MAKS) return 0;
return 1;
}
ContactStatus contact_tambah(ContactList *list,
const char *nama,
const char *telp,
const char *email) {
if (list == 0) return CONTACT_ERR_INPUT_INVALID;
if (!contact_valid_nama(nama)) return CONTACT_ERR_INPUT_INVALID;
if (list->jumlah >= CONTACT_MAKS) return CONTACT_ERR_PENUH;
Contact *c = &list->data[list->jumlah];
c->id = list->id_berikutnya++;
strncpy(c->nama, nama, CONTACT_NAMA_MAKS - 1);
c->nama[CONTACT_NAMA_MAKS - 1] = '\0';
strncpy(c->telp, telp ? telp : "", CONTACT_TELP_MAKS - 1);
c->telp[CONTACT_TELP_MAKS - 1] = '\0';
strncpy(c->email, email ? email : "", CONTACT_EMAIL_MAKS - 1);
c->email[CONTACT_EMAIL_MAKS - 1] = '\0';
list->jumlah++;
return CONTACT_OK;
}
void contact_tampilkan(const ContactList *list, FILE *out) {
if (list == 0 || out == 0) return;
fprintf(out, "%-4s %-20s %-15s %-25s\n",
"ID", "Nama", "Telp", "Email");
for (int i = 0; i < list->jumlah; i++) {
const Contact *c = &list->data[i];
fprintf(out, "%-4d %-20s %-15s %-25s\n",
c->id, c->nama, c->telp, c->email);
}
fprintf(out, "Total: %d kontak\n", list->jumlah);
}Pengujian #
Verifikasi tambah dan tampilkan dengan demo cepat:
cat > /tmp/p3b2_demo.c << 'EOF'
#include "contact.h"
#include <stdio.h>
int main(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi Santoso", "0812345678", "[email protected]");
contact_tambah(&list, "Siti Rahma", "0898765432", "[email protected]");
contact_tampilkan(&list, stdout);
return 0;
}
EOF
gcc -std=c11 -Wall -Wextra -I/home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/ -o /tmp/p3b2_demo /tmp/p3b2_demo.c /home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/contact.c && /tmp/p3b2_demoOutput yang Dihasilkan:
ID Nama Telp Email
1 Budi Santoso 0812345678 [email protected]
2 Siti Rahma 0898765432 [email protected]
Total: 2 kontakUji juga penolakan nama kosong:
cat > /tmp/p3b2_invalid.c << 'EOF'
#include "contact.h"
#include <stdio.h>
int main(void) {
ContactList list;
contact_list_init(&list);
ContactStatus s = contact_tambah(&list, "", "0811", "[email protected]");
printf("status = %d (3 = INPUT_INVALID), jumlah = %d\n", s, list.jumlah);
return 0;
}
EOF
gcc -std=c11 -Wall -Wextra -I/home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/ -o /tmp/p3b2_invalid /tmp/p3b2_invalid.c /home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/contact.c && /tmp/p3b2_invalidOutput yang Dihasilkan:
status = 3 (3 = INPUT_INVALID), jumlah = 0Mari kita telaah baris-baris kunci pada contact_tambah:
- Baris
if (list == 0) return CONTACT_ERR_INPUT_INVALID;: pointer NULL check lebih dulu. Tanpa cek ini, dereferensilist->jumlahakan crash. - Baris
if (!contact_valid_nama(nama)) return CONTACT_ERR_INPUT_INVALID;: nama wajib diisi. Telp dan email boleh kosong (string kosong""). - Baris
if (list->jumlah >= CONTACT_MAKS) return CONTACT_ERR_PENUH;: cek kapasitas sebelum menulis. Setelah return, jumlah tetap tidak naik. - Baris
c->id = list->id_berikutnya++;: post-increment — kita pakai nilai dulu, lalu naikkan. ID kontak pertama adalah 1 (sesuaicontact_list_init), bukan 0. - Baris
strncpy(c->nama, nama, CONTACT_NAMA_MAKS - 1);: salin maksimal 49 karakter.MAKS - 1menyisakan satu slot untuk null terminator. - Baris
c->nama[CONTACT_NAMA_MAKS - 1] = '\0';: jamin terminator ada.strncpytidak menulis terminator bila sumber lebih panjang dari batas. - Baris
strncpy(c->telp, telp ? telp : "", ...);: ekspresi ternary memberi default string kosong bilatelpNULL. Ini membuattelpopsional tanpa crash. - Baris
list->jumlah++;: baru di akhir, setelah semua field ditulis. Bila ada error di tengah, jumlah tidak naik — menjaga konsistensi array.
Bagian 3 akan menambahkan pencarian dan pengubahan kontak.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contact.c
Unduh berkas/* contact.c -- implementasi model dan operasi kontak. */
#include "contact.h"
#include <string.h>
void contact_list_init(ContactList *list) {
if (list == 0) return;
list->jumlah = 0;
list->id_berikutnya = 1;
}
int contact_valid_nama(const char *nama) {
if (nama == 0 || nama[0] == '\0') return 0;
/* Cek panjang maksimum. */
if (strlen(nama) >= CONTACT_NAMA_MAKS) return 0;
return 1;
}
ContactStatus contact_tambah(ContactList *list,
const char *nama,
const char *telp,
const char *email) {
if (list == 0) return CONTACT_ERR_INPUT_INVALID;
if (!contact_valid_nama(nama)) return CONTACT_ERR_INPUT_INVALID;
if (list->jumlah >= CONTACT_MAKS) return CONTACT_ERR_PENUH;
Contact *c = &list->data[list->jumlah];
c->id = list->id_berikutnya++;
strncpy(c->nama, nama, CONTACT_NAMA_MAKS - 1);
c->nama[CONTACT_NAMA_MAKS - 1] = '\0';
strncpy(c->telp, telp ? telp : "", CONTACT_TELP_MAKS - 1);
c->telp[CONTACT_TELP_MAKS - 1] = '\0';
strncpy(c->email, email ? email : "", CONTACT_EMAIL_MAKS - 1);
c->email[CONTACT_EMAIL_MAKS - 1] = '\0';
list->jumlah++;
return CONTACT_OK;
}
void contact_tampilkan(const ContactList *list, FILE *out) {
if (list == 0 || out == 0) return;
fprintf(out, "%-4s %-20s %-15s %-25s\n",
"ID", "Nama", "Telp", "Email");
for (int i = 0; i < list->jumlah; i++) {
const Contact *c = &list->data[i];
fprintf(out, "%-4d %-20s %-15s %-25s\n",
c->id, c->nama, c->telp, c->email);
}
fprintf(out, "Total: %d kontak\n", list->jumlah);
}
int contact_cari(const ContactList *list, const char *nama) {
if (list == 0 || nama == 0) return -1;
for (int i = 0; i < list->jumlah; i++) {
if (strcmp(list->data[i].nama, nama) == 0) {
return i;
}
}
return -1;
}
ContactStatus contact_ubah(ContactList *list, int id,
const char *nama_baru,
const char *telp_baru,
const char *email_baru) {
if (list == 0) return CONTACT_ERR_INPUT_INVALID;
for (int i = 0; i < list->jumlah; i++) {
if (list->data[i].id == id) {
if (nama_baru && !contact_valid_nama(nama_baru)) {
return CONTACT_ERR_INPUT_INVALID;
}
if (nama_baru) {
strncpy(list->data[i].nama, nama_baru, CONTACT_NAMA_MAKS - 1);
list->data[i].nama[CONTACT_NAMA_MAKS - 1] = '\0';
}
if (telp_baru) {
strncpy(list->data[i].telp, telp_baru, CONTACT_TELP_MAKS - 1);
list->data[i].telp[CONTACT_TELP_MAKS - 1] = '\0';
}
if (email_baru) {
strncpy(list->data[i].email, email_baru, CONTACT_EMAIL_MAKS - 1);
list->data[i].email[CONTACT_EMAIL_MAKS - 1] = '\0';
}
return CONTACT_OK;
}
}
return CONTACT_ERR_TIDAK_DITEMUKAN;
}
ContactStatus contact_hapus(ContactList *list, int id) {
if (list == 0) return CONTACT_ERR_INPUT_INVALID;
for (int i = 0; i < list->jumlah; i++) {
if (list->data[i].id == id) {
/* Geser elemen setelahnya ke depan. */
for (int j = i; j < list->jumlah - 1; j++) {
list->data[j] = list->data[j + 1];
}
list->jumlah--;
return CONTACT_OK;
}
}
return CONTACT_ERR_TIDAK_DITEMUKAN;
}
main.c
Unduh berkas/* main.c -- titik masuk aplikasi manajemen kontak. */
#include "contact.h"
#include "storage.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DB_PATH "kontak.txt"
/* Membaca satu baris dari stdin, memotong newline. */
static void baca_baris(const char *prompt, char *buf, size_t ukuran) {
printf("%s", prompt);
if (fgets(buf, (int)ukuran, stdin) == 0) {
buf[0] = '\0';
return;
}
size_t n = strlen(buf);
while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) {
buf[--n] = '\0';
}
}
static int baca_int(const char *prompt, int *out) {
char buf[32];
baca_baris(prompt, buf, sizeof(buf));
char *sisa = 0;
long v = strtol(buf, &sisa, 10);
if (sisa == buf) return 0;
*out = (int)v;
return 1;
}
static void menu_tambah(ContactList *list) {
char nama[CONTACT_NAMA_MAKS];
char telp[CONTACT_TELP_MAKS];
char email[CONTACT_EMAIL_MAKS];
baca_baris("Nama : ", nama, sizeof(nama));
baca_baris("Telp : ", telp, sizeof(telp));
baca_baris("Email : ", email, sizeof(email));
ContactStatus s = contact_tambah(list, nama, telp, email);
switch (s) {
case CONTACT_OK:
printf("Kontak ditambahkan (id=%d).\n", list->data[list->jumlah - 1].id);
break;
case CONTACT_ERR_PENUH:
printf("Daftar penuh.\n"); break;
case CONTACT_ERR_INPUT_INVALID:
printf("Input tidak valid (nama wajib diisi).\n"); break;
default:
printf("Error tidak dikenal.\n"); break;
}
}
static void menu_ubah(ContactList *list) {
int id;
if (!baca_int("ID kontak yang diubah: ", &id)) {
printf("ID tidak valid.\n"); return;
}
char nama[CONTACT_NAMA_MAKS];
char telp[CONTACT_TELP_MAKS];
char email[CONTACT_EMAIL_MAKS];
baca_baris("Nama baru (kosongkan jika tidak diubah) : ", nama, sizeof(nama));
baca_baris("Telp baru (kosongkan jika tidak diubah) : ", telp, sizeof(telp));
baca_baris("Email baru (kosongkan jika tidak diubah): ", email, sizeof(email));
ContactStatus s = contact_ubah(list, id,
nama[0] ? nama : 0,
telp[0] ? telp : 0,
email[0] ? email : 0);
if (s == CONTACT_OK) printf("Kontak diubah.\n");
else if (s == CONTACT_ERR_TIDAK_DITEMUKAN) printf("Kontak tidak ditemukan.\n");
else printf("Input tidak valid.\n");
}
static void menu_hapus(ContactList *list) {
int id;
if (!baca_int("ID kontak yang dihapus: ", &id)) {
printf("ID tidak valid.\n"); return;
}
ContactStatus s = contact_hapus(list, id);
if (s == CONTACT_OK) printf("Kontak dihapus.\n");
else if (s == CONTACT_ERR_TIDAK_DITEMUKAN) printf("Kontak tidak ditemukan.\n");
}
static void menu_cari(const ContactList *list) {
char nama[CONTACT_NAMA_MAKS];
baca_baris("Nama yang dicari: ", nama, sizeof(nama));
int idx = contact_cari(list, nama);
if (idx < 0) {
printf("Kontak dengan nama '%s' tidak ditemukan.\n", nama);
} else {
const Contact *c = &list->data[idx];
printf("Ditemukan: [%d] %s | %s | %s\n",
c->id, c->nama, c->telp, c->email);
}
}
int main(void) {
ContactList list;
contact_list_init(&list);
StorageStatus sm = storage_muat(&list, DB_PATH);
if (sm == STORAGE_OK) {
printf("Memuat %d kontak dari %s.\n", list.jumlah, DB_PATH);
} else if (sm == STORAGE_ERR_BUKA_FILE) {
printf("Belum ada file %s. Memulai dengan daftar kosong.\n", DB_PATH);
} else {
printf("Peringatan: format file rusak (kode %d).\n", sm);
}
int lanjut = 1;
while (lanjut) {
printf("\n=== Manajemen Kontak ===\n");
printf("1. Tambah\n2. Tampilkan\n3. Cari\n4. Ubah\n5. Hapus\n6. Keluar\n");
int pilihan = 0;
if (!baca_int("Pilih: ", &pilihan)) { printf("Pilihan tidak valid.\n"); continue; }
switch (pilihan) {
case 1: menu_tambah(&list); break;
case 2: contact_tampilkan(&list, stdout); break;
case 3: menu_cari(&list); break;
case 4: menu_ubah(&list); break;
case 5: menu_hapus(&list); break;
case 6: lanjut = 0; break;
default: printf("Pilihan tidak dikenal.\n"); break;
}
}
StorageStatus ss = storage_simpan(&list, DB_PATH);
if (ss == STORAGE_OK) {
printf("Menyimpan %d kontak ke %s.\n", list.jumlah, DB_PATH);
} else {
printf("Gagal menyimpan (kode %d).\n", ss);
}
printf("Terima kasih.\n");
return 0;
}
storage.c
Unduh berkas/* storage.c -- persistensi kontak ke file teks. */
#include "storage.h"
#include <stdio.h>
#include <string.h>
/* Memotong newline di akhir string (kalau ada). */
static void potong_newline(char *s) {
size_t n = strlen(s);
while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) {
s[--n] = '\0';
}
}
StorageStatus storage_simpan(const ContactList *list, const char *path) {
if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
FILE *f = fopen(path, "w");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
fprintf(f, "%d\n", list->jumlah);
for (int i = 0; i < list->jumlah; i++) {
const Contact *c = &list->data[i];
fprintf(f, "%d|%s|%s|%s\n",
c->id, c->nama, c->telp, c->email);
}
if (fclose(f) != 0) return STORAGE_ERR_TULIS;
return STORAGE_OK;
}
StorageStatus storage_muat(ContactList *list, const char *path) {
if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
FILE *f = fopen(path, "r");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
int jumlah = 0;
if (fscanf(f, "%d\n", &jumlah) != 1) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
if (jumlah < 0 || jumlah > CONTACT_MAKS) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
char baris[256];
int id_terbesar = list->id_berikutnya - 1;
for (int i = 0; i < jumlah; i++) {
if (fgets(baris, sizeof(baris), f) == 0) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
potong_newline(baris);
int id;
char nama[CONTACT_NAMA_MAKS];
char telp[CONTACT_TELP_MAKS];
char email[CONTACT_EMAIL_MAKS];
/* sscanf dengan %[^\|] untuk membaca sampai '|'. */
int parsed = sscanf(baris, "%d|%49[^|]|%19[^|]|%59[^\n]",
&id, nama, telp, email);
if (parsed < 4) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
if (list->jumlah >= CONTACT_MAKS) break;
Contact *c = &list->data[list->jumlah];
c->id = id;
strncpy(c->nama, nama, CONTACT_NAMA_MAKS - 1);
c->nama[CONTACT_NAMA_MAKS - 1] = '\0';
strncpy(c->telp, telp, CONTACT_TELP_MAKS - 1);
c->telp[CONTACT_TELP_MAKS - 1] = '\0';
strncpy(c->email, email, CONTACT_EMAIL_MAKS - 1);
c->email[CONTACT_EMAIL_MAKS - 1] = '\0';
list->jumlah++;
if (id > id_terbesar) id_terbesar = id;
}
list->id_berikutnya = id_terbesar + 1;
fclose(f);
return STORAGE_OK;
}
tests.c
Unduh berkas/* tests.c -- rangkaian test untuk modul contact dan storage. */
#include "contact.h"
#include "storage.h"
#include <stdio.h>
#include <string.h>
static int lulus = 0;
static int gagal = 0;
#define PERIKSA(kondisi, nama) \
do { \
if (kondisi) { lulus++; printf("[LULUS] %s\n", nama); } \
else { gagal++; printf("[GAGAL] %s\n", nama); } \
} while (0)
static void test_tambah_dan_tampilkan(void) {
ContactList list;
contact_list_init(&list);
ContactStatus s1 = contact_tambah(&list, "Budi", "0811", "[email protected]");
ContactStatus s2 = contact_tambah(&list, "Siti", "0812", "[email protected]");
PERIKSA(s1 == CONTACT_OK && s2 == CONTACT_OK, "Tambah 2 kontak berhasil");
PERIKSA(list.jumlah == 2, "Jumlah = 2 setelah tambah");
PERIKSA(list.data[0].id == 1 && list.data[1].id == 2,
"ID auto-increment 1, 2");
}
static void test_tambah_nama_kosong(void) {
ContactList list;
contact_list_init(&list);
ContactStatus s = contact_tambah(&list, "", "0811", "[email protected]");
PERIKSA(s == CONTACT_ERR_INPUT_INVALID, "Nama kosong ditolak");
PERIKSA(list.jumlah == 0, "Tidak ada kontak ditambahkan");
}
static void test_cari(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi", "0811", "[email protected]");
contact_tambah(&list, "Siti", "0812", "[email protected]");
int idx = contact_cari(&list, "Siti");
PERIKSA(idx == 1, "Cari 'Siti' -> indeks 1");
PERIKSA(contact_cari(&list, "Tidak Ada") == -1, "Cari nama tidak ada -> -1");
}
static void test_ubah(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi", "0811", "[email protected]");
ContactStatus s = contact_ubah(&list, 1, "Budi Santoso", 0, 0);
PERIKSA(s == CONTACT_OK, "Ubah kontak id=1 berhasil");
PERIKSA(strcmp(list.data[0].nama, "Budi Santoso") == 0,
"Nama berubah menjadi 'Budi Santoso'");
PERIKSA(strcmp(list.data[0].telp, "0811") == 0,
"Telp tetap '0811' (tidak diubah)");
}
static void test_hapus(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi", "0811", "[email protected]");
contact_tambah(&list, "Siti", "0812", "[email protected]");
contact_tambah(&list, "Andi", "0813", "[email protected]");
ContactStatus s = contact_hapus(&list, 2);
PERIKSA(s == CONTACT_OK, "Hapus id=2 berhasil");
PERIKSA(list.jumlah == 2, "Jumlah = 2 setelah hapus");
PERIKSA(strcmp(list.data[1].nama, "Andi") == 0,
"Setelah hapus, elemen 1 = 'Andi'");
PERIKSA(contact_hapus(&list, 999) == CONTACT_ERR_TIDAK_DITEMUKAN,
"Hapus id tidak ada -> error");
}
static void test_penyimpanan(void) {
ContactList list;
contact_list_init(&list);
contact_tambah(&list, "Budi", "0811", "[email protected]");
contact_tambah(&list, "Siti", "0812", "[email protected]");
StorageStatus ss = storage_simpan(&list, "/tmp/test_kontak.txt");
PERIKSA(ss == STORAGE_OK, "Simpan ke /tmp/test_kontak.txt");
ContactList dimuat;
contact_list_init(&dimuat);
ss = storage_muat(&dimuat, "/tmp/test_kontak.txt");
PERIKSA(ss == STORAGE_OK, "Muat dari /tmp/test_kontak.txt");
PERIKSA(dimuat.jumlah == 2, "Jumlah dimuat = 2");
PERIKSA(strcmp(dimuat.data[0].nama, "Budi") == 0,
"Kontak pertama dimuat: 'Budi'");
PERIKSA(dimuat.id_berikutnya == 3,
"id_berikutnya = 3 setelah muat");
}
int main(void) {
test_tambah_dan_tampilkan();
test_tambah_nama_kosong();
test_cari();
test_ubah();
test_hapus();
test_penyimpanan();
printf("\nRingkasan: %d lulus, %d gagal\n", lulus, gagal);
return gagal == 0 ? 0 : 1;
}