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:
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:
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:
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:
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_demoOutput yang Dihasilkan:
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:
- Baris
for (int i = 0; i < list->jumlah; i++): loop linier mencari indeks dengan ID yang cocok. Sama dengancontact_ubah. - Baris
if (list->data[i].id == id): bandingkan ID integer. Bila cocok, masuk blok penghapusan. - Baris
for (int j = i; j < list->jumlah - 1; j++): loop dalam yang menggeser elemen. Dimulai darii(posisi hapus), berakhir sebelumjumlah - 1agar tidak membacadata[jumlah]yang out-of-bounds. - Baris
list->data[j] = list->data[j + 1];: assignment struct. C mendukung copy struct langsung dengan=— tidak perlumemcpy. Seluruh field (id, nama, telp, email) tersalin sekaligus. - Baris
list->jumlah--;: turunkan counter setelah pergeseran selesai. Slotdata[jumlah_lama - 1]masih berisi data lama, tetapi tidak diakses karenajumlahsudah turun. - Baris
return CONTACT_OK;: keluar lebih awal setelah penghapusan berhasil. Tanpa early return, loop akan terus mencari — boros. - 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/* 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;
}