Sebuah struct tidak hanya bisa berisi tipe primitif seperti int atau char[], tetapi juga bisa berisi struct lain sebagai member. Susunan seperti ini disebut nested struct dan sangat umum dipakai untuk memodelkan entitas dunia nyata yang tersusun secara hierarkis: mahasiswa punya alamat, alamat punya kota dan kode pos; rapat punya waktu mulai dan waktu selesai; pesanan punya pelanggan dan pelanggan punya kontak. Daripada menyatukan semua atribut dalam satu struct datar, kita bisa memecah entitas sub-domain menjadi tipe terpisah lalu menyusunnya. Pada chapter ini kita akan melihat bagaimana nested struct dideklarasikan, diakses, dan dipassing ke fungsi.
Mendeklarasikan struct Bersarang #
Pola dasar nested struct sederhana: kita definisikan tipe struct dalam (misalnya struct Alamat) terlebih dahulu, lalu dipakai sebagai tipe member di struct luar (misalnya struct Mahasiswa). Urutan definisi penting—tipe dalam harus terdefinisi sebelum dipakai sebagai member. Sebagai alternatif, kita bisa memakai forward declaration (struct Alamat;), tetapi member dengan tipe yang belum lengkap hanya bisa berupa pointer, bukan instance langsung.
Mekanisme akses: ketika kita menulis mhs.alamat.jalan, compiler menghitung alamat dengan menjumlahkan offset alamat di dalam struct Mahasiswa, lalu offset jalan di dalam struct Alamat. Hasilnya satu alamat absolut—komputasi sama cepatnya dengan akses member biasa.
#include <stdio.h>
#include <string.h>
/* Definisi struct Alamat */
struct Alamat {
char jalan[60];
char kota[30];
int kode_pos;
};
/* struct Mahasiswa memiliki member bertipe struct Alamat */
struct Mahasiswa {
char nama[50];
int umur;
struct Alamat alamat; /* member bertipe struct */
};
int main(void) {
struct Mahasiswa mhs = {
"Budi Santoso",
21,
{"Jl. Merdeka No. 10", "Bandung", 40123}
};
/* Akses member top-level pakai titik */
printf("Nama : %s\n", mhs.nama);
printf("Umur : %d\n", mhs.umur);
/* Akses member di dalam nested struct: titik berlapis */
printf("Jalan : %s\n", mhs.alamat.jalan);
printf("Kota : %s\n", mhs.alamat.kota);
printf("Pos : %d\n", mhs.alamat.kode_pos);
printf("Ukuran struct Mahasiswa: %zu byte\n", sizeof(struct Mahasiswa));
return 0;
}Output yang Dihasilkan:
Nama : Budi Santoso
Umur : 21
Jalan : Jl. Merdeka No. 10
Kota : Bandung
Pos : 40123
Ukuran struct Mahasiswa: 152 byteMari kita telaah baris-baris kode di atas:
- Baris 5-9 (
struct Alamat { ... };): definisi tipestruct Alamatdengan tiga member:jalan,kota,kode_pos. Tipe ini berdiri sendiri—bisa juga dipakai di luar konteksstruct Mahasiswajika dibutuhkan. - Baris 12-16 (
struct Mahasiswa { ... };): tipestruct Mahasiswamemuat memberalamatbertipestruct Alamat. Memberalamatbukan pointer, melainkan instance penuh, sehingga memori untukjalan,kota, dankode_posdialokasikan langsung di dalamstruct Mahasiswa. - Baris 19-23 (
struct Mahasiswa mhs = {...}): inisialisasi aggregate berlapis. Kurung kurawal{...}luar mengisi memberMahasiswasecara berurutan:nama,umur, lalualamat. Untuk memberalamat, kurung kurawal dalam{...}mengisi memberAlamatsecara berurutan:jalan,kota,kode_pos. - Baris 28-30 (
mhs.alamat.jalandst.): akses member bersarang menggunakan operator titik berlapis.mhs.alamatmenghasilkan instancestruct Alamat, lalu.jalanmengakses memberjalandari instance tersebut. - Baris 32 (
sizeof(struct Mahasiswa)): ukuran total adalah 152 byte—jumlah dari semua member beserta padding. Membernama(50) +umur(4) + padding (6) +alamat(60+30+4+padding = 96) menghasilkan angka tersebut.
Nested struct dengan typedef dan Pointer #
Pola nested struct sering dipadukan dengan typedef agar penulisan lebih ringkas, dan dipadukan dengan pointer agar fungsi bisa menerima data tanpa menyalin seluruh isi struct.
#include <stdio.h>
typedef struct {
int jam;
int menit;
int detik;
} Waktu;
typedef struct {
Waktu mulai;
Waktu selesai;
char label[20];
} Sesi;
/* Fungsi yang memproses nested struct */
int durasi_menit(const Sesi *s) {
int mulai_total = s->mulai.jam * 60 + s->mulai.menit;
int selesai_total = s->selesai.jam * 60 + s->selesai.menit;
return selesai_total - mulai_total;
}
int main(void) {
Sesi rapat = {
{9, 30, 0}, /* mulai */
{11, 15, 0}, /* selesai */
"Sprint Planning"
};
/* Akses berlapis: titik untuk member bertipe struct */
printf("Rapat : %s\n", rapat.label);
printf("Mulai : %02d:%02d:%02d\n",
rapat.mulai.jam, rapat.mulai.menit, rapat.mulai.detik);
printf("Selesai : %02d:%02d:%02d\n",
rapat.selesai.jam, rapat.selesai.menit, rapat.selesai.detik);
printf("Durasi : %d menit\n", durasi_menit(&rapat));
return 0;
}Output yang Dihasilkan:
Rapat : Sprint Planning
Mulai : 09:30:00
Selesai : 11:15:00
Durasi : 105 menitPada contoh di atas, Sesi punya dua member bertipe Waktu (mulai dan selesai). Di dalam fungsi durasi_menit, parameter const Sesi *s adalah pointer. Akses member luar memakai -> (s->mulai), sedangkan akses member dalam Waktu memakai . (s->mulai.jam) karena s->mulai menghasilkan instance Waktu, bukan pointer. Inisialisasi Sesi rapat = {{9, 30, 0}, {11, 15, 0}, "Sprint Planning"}; menunjukkan tiga lapis aggregate initializer: luar untuk Sesi, tengah untuk dua Waktu, dalam untuk int. Format %02d mencetak angka dengan dua digit, ditambah nol di depan bila perlu.