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.

c
#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:

text
Nama  : Budi Santoso
Umur  : 21
Jalan : Jl. Merdeka No. 10
Kota  : Bandung
Pos   : 40123
Ukuran struct Mahasiswa: 152 byte

Mari kita telaah baris-baris kode di atas:

  1. Baris 5-9 (struct Alamat { ... };): definisi tipe struct Alamat dengan tiga member: jalan, kota, kode_pos. Tipe ini berdiri sendiri—bisa juga dipakai di luar konteks struct Mahasiswa jika dibutuhkan.
  2. Baris 12-16 (struct Mahasiswa { ... };): tipe struct Mahasiswa memuat member alamat bertipe struct Alamat. Member alamat bukan pointer, melainkan instance penuh, sehingga memori untuk jalan, kota, dan kode_pos dialokasikan langsung di dalam struct Mahasiswa.
  3. Baris 19-23 (struct Mahasiswa mhs = {...}): inisialisasi aggregate berlapis. Kurung kurawal {...} luar mengisi member Mahasiswa secara berurutan: nama, umur, lalu alamat. Untuk member alamat, kurung kurawal dalam {...} mengisi member Alamat secara berurutan: jalan, kota, kode_pos.
  4. Baris 28-30 (mhs.alamat.jalan dst.): akses member bersarang menggunakan operator titik berlapis. mhs.alamat menghasilkan instance struct Alamat, lalu .jalan mengakses member jalan dari instance tersebut.
  5. Baris 32 (sizeof(struct Mahasiswa)): ukuran total adalah 152 byte—jumlah dari semua member beserta padding. Member nama (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.

c
#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:

text
Rapat     : Sprint Planning
Mulai     : 09:30:00
Selesai   : 11:15:00
Durasi    : 105 menit

Pada 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.