Dengan kemampuan menambah dan menampilkan, kita perlu cara menemukan kembali kontak tertentu untuk diubah. Pada bagian ini kita mengimplementasikan dua fungsi: contact_cari yang mencari kontak berdasarkan nama (pencarian linier), dan contact_ubah yang mengubah field kontak berdasarkan ID. Pengubahan bersifat parsial — pengguna dapat mengosongkan field tertentu agar tidak diubah, sambil tetap mengubah field lain.

Konsep #

Pencarian linier adalah algoritma paling sederhana untuk mencari di array tak terurut: bandingkan setiap elemen secara berurutan sampai ketemu atau habis. Fungsi contact_cari memakai strcmp untuk membandingkan field nama dengan query. strcmp memulangkan 0 bila dua string identik (case-sensitive). Bila ditemukan, kita pulangkan indeks; bila tidak, pulangkan -1 sebagai penanda “tidak ditemukan”. Konvensi -1 lazim di C (mis. strchr).

Fungsi contact_ubah menerima ID dan tiga field baru. Salah satu field dapat berupa NULL yang berarti “tidak diubah”. Implementasi memeriksa tiap parameter: bila bukan NULL, salin ke field kontak; bila NULL, lewati. Pola ini berguna untuk menu “ubah nama saja” atau “ubah telepon saja” tanpa memaksa pengguna mengetik ulang seluruh data.

Pencarian ID dilakukan dengan loop linier juga. Untuk daftar kecil (<= 100 kontak), performa O(n) tidak masalah. Untuk skala besar, kita akan memakai hash table atau index terpisah — konsep yang dibahas di Proyek 06.

Implementasi #

Fungsi contact_cari:

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

Fungsi contact_ubah:

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

Kode Lengkap #

Dua fungsi tersebut sudah merupakan implementasi final. Berikut pemanggilan dari main.c:

c
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_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);
    }
}

Pengujian #

Demo cari dan ubah:

bash
cat > /tmp/p3b3_demo.c << 'EOF'
#include "contact.h"
#include <stdio.h>
#include <string.h>
int main(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");
    printf("Cari 'Siti' -> indeks %d\n", idx);

    /* Ubah nama Budi menjadi Budi Santoso, biarkan telp/email */
    ContactStatus s = contact_ubah(&list, 1, "Budi Santoso", 0, 0);
    printf("Ubah status = %d (0 = OK)\n", s);
    printf("Nama: %s, Telp: %s (harus tetap 0811)\n",
           list.data[0].nama, list.data[0].telp);
    return 0;
}
EOF
gcc -std=c11 -Wall -Wextra -I/home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/ -o /tmp/p3b3_demo /tmp/p3b3_demo.c /home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/contact.c && /tmp/p3b3_demo

Output yang Dihasilkan:

text
Cari 'Siti' -> indeks 1
Ubah status = 0 (0 = OK)
Nama: Budi Santoso, Telp: 0811 (harus tetap 0811)

Uji pencarian nama yang tidak ada:

bash
cat > /tmp/p3b3_notfound.c << 'EOF'
#include "contact.h"
#include <stdio.h>
int main(void) {
    ContactList list;
    contact_list_init(&list);
    contact_tambah(&list, "Budi", "0811", "[email protected]");
    int idx = contact_cari(&list, "Tidak Ada");
    printf("Cari 'Tidak Ada' -> %d (harus -1)\n", idx);
    ContactStatus s = contact_ubah(&list, 999, "X", 0, 0);
    printf("Ubah id=999 -> status %d (2 = TIDAK_DITEMUKAN)\n", s);
    return 0;
}
EOF
gcc -std=c11 -Wall -Wextra -I/home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/ -o /tmp/p3b3_notfound /tmp/p3b3_notfound.c /home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/contact.c && /tmp/p3b3_notfound

Output yang Dihasilkan:

text
Cari 'Tidak Ada' -> -1 (harus -1)
Ubah id=999 -> status 2 (2 = TIDAK_DITEMUKAN)

Mari kita telaah baris-baris kunci pada contact_ubah:

  1. Baris for (int i = 0; i < list->jumlah; i++): loop linier untuk mencari indeks dengan ID yang cocok. O(n) untuk setiap pemanggilan.
  2. Baris if (list->data[i].id == id): pembandingan ID integer — aman, presisi, dan tidak ambigu seperti string.
  3. Baris if (nama_baru && !contact_valid_nama(nama_baru)) return ...;: dua tingkat cek. Bila nama_baru NULL, kita lewati validasi (tidak mengubah nama). Bila tidak NULL, validasi dulu.
  4. Baris if (nama_baru) { strncpy(...); ...[MAKS-1] = '\0'; }: hanya menulis field yang tidak NULL. Pola ini memungkinkan pengubahan parsial tanpa memaksa pengguna mengetik ulang seluruh data.
  5. Baris return CONTACT_OK;: setelah berhasil menulis, langsung keluar. Tidak perlu break lalu return di akhir fungsi.
  6. Baris return CONTACT_ERR_TIDAK_DITEMUKAN; di akhir: bila loop selesai tanpa menemukan ID, pulangkan kode error ini. Ini adalah fallback default.

Bagian 4 akan menambahkan operasi penghapusan kontak.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

contact.c

Unduh berkas
c
/* 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
c
/* 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
c
/* 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
c
/* 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;
}