Variabel tunggal seperti int umur atau float ipk hanya bisa menyimpan satu nilai dengan satu tipe. Padahal banyak entitas di dunia nyata tersusun dari beberapa atribut bertipe berbeda yang secara logis saling terkait: seorang mahasiswa punya nama (string), umur (integer), dan IPK (floating point). Jika kita memakai variabel-variabel lepas seperti char nama1[50]; int umur1; float ipk1; untuk setiap mahasiswa, kode cepat berantakan dan sulit dipindahkan antar fungsi. struct hadir untuk menggabungkan beberapa tipe data berbeda menjadi satu unit bernama, sehingga kita bisa menangani satu mahasiswa sebagai satu kesatuan, mengirimkannya ke fungsi, atau menyimpannya dalam array. Pada chapter ini kita akan membangun pengertian struct dari contoh konkret struct Mahasiswa.
Apa Itu struct #
struct (kependekan dari structure) adalah tipe data agregat yang menggabungkan beberapa variabel—disebut member atau field—menjadi satu unit. Setiap member bisa memiliki tipe yang berbeda. Saat kita mendeklarasikan sebuah struct, compiler menghitung total ukuran dengan menjumlahkan ukuran setiap member (ditambah padding yang akan kita bahas pada chapter khusus). Setelah definisi struct ada, nama struct tersebut menjadi tipe baru yang bisa dipakai untuk mendeklarasikan variabel, mengirim parameter ke fungsi, atau menyusun array.
Mekanisme kerja struct di balik layar cukup sederhana: compiler mencatat tata letak (layout) member-member di dalam tipe struct, dan ketika kita menulis mhs.umur, compiler menerjemahkan ekspresi tersebut menjadi akses ke alamat memori &mhs + offset_umur. Karena setiap member punya offset tetap yang diketahui saat kompilasi, akses member semudah dan secepat akses variabel biasa.
Visualisasi Konsep #
Diagram berikut memperlihatkan bagaimana tiga member struct Mahasiswa (array char, int, float) disusun berdampingan dalam satu blok memori:
Pada diagram di atas, tiga member struct Mahasiswa disusun berurutan dalam satu blok memori sebesar 60 byte. Member nama menempati offset 0 hingga 49, member umur menempati offset 50 hingga 53, dan member ipk menempati offset 54 hingga 57 (dengan 2 byte padding di akhir untuk alignment). Saat kita menulis mhs.umur, compiler menerjemahkan ekspresi ini menjadi akses ke alamat &mhs + 50—offset setiap member sudah diketahui saat kompilasi sehingga akses member sama cepatnya dengan akses variabel biasa. Inilah mengapa struct menjadi unit efisien: satu nama (mhs) cukup untuk mengakses seluruh atribut lewat operator titik (.).
Mendefinisikan dan Menggunakan struct Pertama #
Kita mulai dengan contoh klasik: struct Mahasiswa yang memiliki tiga member bertipe berbeda—char array, int, dan float.
#include <stdio.h>
#include <string.h>
/* Definisi struct Mahasiswa */
struct Mahasiswa {
char nama[50];
int umur;
float ipk;
};
int main(void) {
/* Deklarasi dan inisialisasi struct Mahasiswa */
struct Mahasiswa mhs1;
strcpy(mhs1.nama, "Budi Santoso");
mhs1.umur = 21;
mhs1.ipk = 3.75f;
printf("Data Mahasiswa:\n");
printf(" Nama : %s\n", mhs1.nama);
printf(" Umur : %d tahun\n", mhs1.umur);
printf(" IPK : %.2f\n", mhs1.ipk);
printf(" Ukuran struct: %zu byte\n", sizeof(struct Mahasiswa));
return 0;
}Output yang Dihasilkan:
Data Mahasiswa:
Nama : Budi Santoso
Umur : 21 tahun
IPK : 3.75
Ukuran struct: 60 byteMari kita telaah baris-baris kode di atas:
- Baris 5-9 (
struct Mahasiswa { ... };): definisi tipestruct Mahasiswa. Tiga member dideklarasikan di dalam kurung kurawal: arraynamaberukuran 50 byte,int umur(umumnya 4 byte), danfloat ipk(4 byte). Tanda titik koma setelah kurung kurawal tutup wajib ada—banyak pemula melupakannya. - Baris 13 (
struct Mahasiswa mhs1;): mendeklarasikan variabelmhs1dengan tipestruct Mahasiswa. Memori dialokasikan sebesar 60 byte (atau sedikit lebih bila ada padding; pada platform ini totalnya pas 60). - Baris 15 (
strcpy(mhs1.nama, "Budi Santoso");): mengisi membernama. Karenanamaadalah arraychar, kita tidak bisa menulismhs1.nama = "Budi Santoso";(array tidak bisa di-assign), sehingga kita pakaistrcpy. - Baris 16-17 (
mhs1.umur = 21; mhs1.ipk = 3.75f;): assignment langsung untuk memberintdanfloat. Suffixfmembuat literal3.75bertipefloat, bukandouble. - Baris 19-22 (
printf(...)): mengakses setiap member lewat operator titik (.) dan mencetaknya dengan format specifier yang sesuai (%suntuk string,%duntukint,%.2funtukfloatdua desimal). - Baris 23 (
sizeof(struct Mahasiswa)): operatorsizeofmengembalikan ukuran tipe dalam byte. Hasilnya 60 byte (50 + 4 + 4 + kemungkinan padding; pada platform ini tidak ada padding).
struct sebagai Tipe yang Dapat Dipass ke Fungsi #
Karena struct adalah tipe penuh, kita bisa mengirimkannya sebagai parameter fungsi dan mengembalikannya sebagai nilai balik. Hal ini membuat kode lebih rapi dibanding mengoper banyak parameter lepas.
#include <stdio.h>
/* Definisi struct untuk titik koordinat 2D */
struct Titik {
int x;
int y;
};
/* Fungsi yang menerima struct sebagai parameter */
void tampilkan_titik(struct Titik p) {
printf("Titik (%d, %d)\n", p.x, p.y);
}
int main(void) {
/* Inisialisasi struct saat deklarasi */
struct Titik asal = {0, 0};
struct Titik tujuan = {3, 4};
tampilkan_titik(asal);
tampilkan_titik(tujuan);
/* Mengubah nilai member */
tujuan.x = 10;
tujuan.y = 20;
tampilkan_titik(tujuan);
return 0;
}Output yang Dihasilkan:
Titik (0, 0)
Titik (3, 4)
Titik (10, 20)Pada contoh di atas, struct Titik asal = {0, 0}; memanfaatkan aggregate initialization: nilai di dalam kurung kurawal dipetakan ke member sesuai urutan deklarasi, sehingga asal.x = 0 dan asal.y = 0. Ketika tampilkan_titik(asal) dipanggil, seluruh isi struct disalin ke parameter p (pass-by-value). Perubahan pada tujuan.x dan tujuan.y di main tidak mempengaruhi asal, karena setiap variabel struct memiliki memori sendiri.