Setelah sebuah variabel struct dideklarasikan dan diisi, kita perlu membaca atau menulis nilai tiap member. Di C terdapat dua operator khusus untuk hal tersebut: operator titik (.) dipakai saat kita memiliki variabel struct langsung, dan operator panah (->) dipakai saat kita memiliki pointer ke struct. Pemilihan operator bukan gaya—keduanya tidak saling menggantikan, dan memakai operator yang salah akan memicu compile error. Pada chapter ini kita akan menelusuri kapan memakai . dan kapan memakai ->, serta mengapa pola pointer sangat penting saat ingin memodifikasi struct dari dalam fungsi.

Operator Titik (.) untuk Akses Langsung #

Operator titik dipakai ketika kita memiliki variabel struct itu sendiri (bukan pointer). Sintaksnya: nama_variabel.nama_member. Operator ini mengakses member pada offset yang sesuai dari alamat awal variabel. Untuk variabel lokal, akses langsung lewat . adalah cara tercepat dan paling alami.

Mekanisme kerja operator titik: ketika compiler melihat mhs.umur, compiler menghitung alamat member umur sebagai &mhs + offset_umur, lalu membaca atau menulis pada alamat tersebut. Karena offset ditentukan saat kompilasi, tidak ada overhead runtime.

c
#include <stdio.h>
#include <string.h>

struct Mahasiswa {
    char nama[50];
    int umur;
    float ipk;
};

/* Fungsi yang menerima struct by value */
void cetak_langsung(struct Mahasiswa m) {
    printf("[by value]  %s, umur %d, IPK %.2f\n", m.nama, m.umur, m.ipk);
}

/* Fungsi yang menerima pointer ke struct */
void cetak_pointer(const struct Mahasiswa *m) {
    /* Akses member via operator arrow */
    printf("[by pointer] %s, umur %d, IPK %.2f\n", m->nama, m->umur, m->ipk);
}

/* Fungsi yang mengubah member via pointer */
void ulang_tahun(struct Mahasiswa *m) {
    m->umur += 1;
}

int main(void) {
    struct Mahasiswa mhs = {"Budi Santoso", 20, 3.60f};

    /* Akses langsung pakai operator titik */
    printf("Nama    : %s\n", mhs.nama);
    printf("Umur    : %d\n", mhs.umur);
    printf("IPK     : %.2f\n", mhs.ipk);

    /* Modifikasi member langsung */
    mhs.ipk = 3.75f;
    printf("IPK baru: %.2f\n", mhs.ipk);

    /* Pass ke fungsi: by value */
    cetak_langsung(mhs);

    /* Pass ke fungsi: by pointer */
    cetak_pointer(&mhs);

    /* Modifikasi via pointer */
    ulang_tahun(&mhs);
    printf("Setelah ulang tahun: umur %d\n", mhs.umur);

    return 0;
}

Output yang Dihasilkan:

text
Nama    : Budi Santoso
Umur    : 20
IPK     : 3.60
IPK baru: 3.75
[by value]  Budi Santoso, umur 20, IPK 3.75
[by pointer] Budi Santoso, umur 20, IPK 3.75
Setelah ulang tahun: umur 21

Mari kita telaah baris-baris kode di atas:

  1. Baris 14-16 (cetak_langsung(struct Mahasiswa m)): fungsi menerima struct by value. Seluruh isi struct disalin ke parameter lokal m. Di dalam fungsi, kita pakai operator titik (m.nama) karena m adalah variabel struct, bukan pointer.
  2. Baris 20-22 (cetak_pointer(const struct Mahasiswa *m)): fungsi menerima pointer ke struct bertipe const. Di dalam fungsi, kita pakai operator panah (m->nama) karena m adalah pointer. Kata kunci const menjamin fungsi tidak akan memodifikasi struct yang ditunjuk.
  3. Baris 26-28 (ulang_tahun(struct Mahasiswa *m)): menerima pointer non-const. Fungsi menambah m->umur lewat operator panah. Karena yang ditunjuk adalah variabel mhs di main, perubahan ini terlihat setelah pemanggilan.
  4. Baris 35 (printf("Nama : %s\n", mhs.nama);): akses member nama lewat operator titik karena mhs variabel langsung.
  5. Baris 41 (cetak_pointer(&mhs);): mengoper alamat mhs (&mhs) ke fungsi yang menerima pointer.
  6. Baris 44 (ulang_tahun(&mhs);): pointer &mhs membuat fungsi ulang_tahun bisa mengubah mhs.umur dari dalam fungsi—sesuatu yang tidak mungkin jika kita mengoper struct by value.

Operator Panah (->) untuk Akses via Pointer #

Operator panah adalah singkatan dari kombinasi dereference dan akses member. Ekspresi ptr->member sepenuhnya setara dengan (*ptr).member. Operator panah hadir agar kita tidak perlu menulis tanda kurung yang berantakan; *ptr.member memiliki arti berbeda (akan mencoba dereference hasil ptr.member, yang biasanya tidak valid).

c
#include <stdio.h>

struct Titik {
    int x;
    int y;
};

int main(void) {
    struct Titik p = {3, 4};
    struct Titik *ptr = &p;   /* ptr menunjuk ke p */

    /* Tiga cara ekivalen untuk membaca member y */
    printf("p.y        = %d\n", p.y);
    printf("(*ptr).y   = %d\n", (*ptr).y);
    printf("ptr->y     = %d\n", ptr->y);

    /* Tiga cara ekivalen untuk menulis member x */
    p.x       = 10;
    (*ptr).x  = 20;
    ptr->x    = 30;

    printf("Setelah modifikasi: p.x = %d, p.y = %d\n", p.x, p.y);

    return 0;
}

Output yang Dihasilkan:

text
p.y        = 4
(*ptr).y   = 4
ptr->y     = 4
Setelah modifikasi: p.x = 30, p.y = 4

Pada contoh di atas, p.y, (*ptr).y, dan ptr->y menghasilkan nilai yang sama persis karena ptr memang menunjuk ke p. Hal yang sama berlaku untuk penulisan: tiga assignment terakhir (p.x = 10; (*ptr).x = 20; ptr->x = 30;) semua menulis ke p.x. Karena eksekusi berurutan dari atas ke bawah, nilai akhir p.x adalah 30—nilai dari penulisan terakhir.