Menghapus elemen dari array statis memerlukan strategi khusus karena array tidak dapat di-“cil” secara langsung. Pada bagian ini kita mengimplementasikan contact_hapus dengan teknik pergeseran: elemen setelah posisi yang dihapus digeser ke kiri satu langkah, lalu jumlah dikurangi. Teknik ini menjaga elemen-elemen selalu berdekatan tanpa “lubang” di tengah array.

Konsep #

Bayangkan array [A, B, C, D, E] dengan jumlah = 5. Bila kita hapus C (indeks 2), kita ingin hasilnya [A, B, D, E, ?] dengan jumlah = 4. Slot kelima (?) masih ada di memori tetapi tidak dianggap valid karena jumlah sudah 4. Implementasi: untuk setiap j mulai dari indeks yang dihapus sampai jumlah - 2, salin data[j+1] ke data[j]. Setelah loop, kurangi jumlah.

Kompleksitas operasi ini O(n) karena kita perlu menggeser rata-rata setengah elemen. Untuk daftar kecil ini tidak masalah. Alternatif lain: tukar dengan elemen terakhir lalu kurangi jumlah (O(1)), tetapi ini mengubah urutan kontak — tidak cocok bila urutan tampilan harus stabil.

Setelah penghapusan, ID kontak tidak digunakan ulang. id_berikutnya tetap naik. Ini menjaga referensi ID tetap unik secara historis — bila suatu hari kita menambah log audit, ID yang dihapus tetap dapat dirujuk.

Implementasi #

Fungsi contact_hapus:

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

Loop dalam for (int j = i; j < list->jumlah - 1; j++) menggeser elemen. Batas jumlah - 1 penting: kita hanya menggeser sampai elemen kedua-terakhir, karena elemen terakhir akan disalin ke posisi jumlah - 2.

Kode Lengkap #

Fungsi contact_hapus final:

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

Pemanggil dari main.c:

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

Pengujian #

Demo hapus dan verifikasi pergeseran:

bash
cat > /tmp/p3b4_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]");
    contact_tambah(&list, "Andi", "0813", "[email protected]");

    printf("Sebelum hapus: jumlah=%d\n", list.jumlah);
    ContactStatus s = contact_hapus(&list, 2);   /* hapus Siti */
    printf("Hapus id=2 -> status %d, jumlah=%d\n", s, list.jumlah);
    printf("Elemen 0: %s (id=%d)\n", list.data[0].nama, list.data[0].id);
    printf("Elemen 1: %s (id=%d)\n", list.data[1].nama, list.data[1].id);

    /* Hapus ID yang tidak ada */
    s = contact_hapus(&list, 999);
    printf("Hapus 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/p3b4_demo /tmp/p3b4_demo.c /home/z/my-project/content/c/proyek-c/proyek-03-manajemen-kontak/contact.c && /tmp/p3b4_demo

Output yang Dihasilkan:

text
Sebelum hapus: jumlah=3
Hapus id=2 -> status 0, jumlah=2
Elemen 0: Budi (id=1)
Elemen 1: Andi (id=3)

Baris kedua dari bawah membuktikan pergeseran: setelah Siti (id=2) dihapus, Andi (semula di indeks 2) berpindah ke indeks 1, dan jumlah turun dari 3 menjadi 2. ID Andi tetap 3 — tidak diubah menjadi 2 — karena ID adalah identitas permanen.

Mari kita telaah baris-baris kunci pada contact_hapus:

  1. Baris for (int i = 0; i < list->jumlah; i++): loop linier mencari indeks dengan ID yang cocok. Sama dengan contact_ubah.
  2. Baris if (list->data[i].id == id): bandingkan ID integer. Bila cocok, masuk blok penghapusan.
  3. Baris for (int j = i; j < list->jumlah - 1; j++): loop dalam yang menggeser elemen. Dimulai dari i (posisi hapus), berakhir sebelum jumlah - 1 agar tidak membaca data[jumlah] yang out-of-bounds.
  4. Baris list->data[j] = list->data[j + 1];: assignment struct. C mendukung copy struct langsung dengan = — tidak perlu memcpy. Seluruh field (id, nama, telp, email) tersalin sekaligus.
  5. Baris list->jumlah--;: turunkan counter setelah pergeseran selesai. Slot data[jumlah_lama - 1] masih berisi data lama, tetapi tidak diakses karena jumlah sudah turun.
  6. Baris return CONTACT_OK;: keluar lebih awal setelah penghapusan berhasil. Tanpa early return, loop akan terus mencari — boros.
  7. Baris return CONTACT_ERR_TIDAK_DITEMUKAN;: bila loop selesai tanpa menemukan ID, pulangkan kode error.

Bagian 5 akan menambahkan penyimpanan ke file teks.

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