Di C, fungsi juga memiliki alamat di memori—sama seperti variabel. Karena itu kita bisa menyimpan alamat fungsi dalam sebuah variabel khusus yang disebut function pointer, lalu memanggil fungsi tersebut secara tidak langsung melalui pointer. Function pointer memungkinkan pola pemrograman yang sangat fleksibel: callback, dispatch table, event handler, dan plugin system. Standar C bahkan memakai function pointer di API seperti qsort (yang menerima comparator sebagai parameter) dan signal (yang menerima handler). Pada chapter ini kita akan mendeklarasikan function pointer, mengisinya, lalu memanggil fungsi melaluinya; serta membangun dispatch table sederhana.
Deklarasi dan Pemakaian Dasar #
Sintaks deklarasi function pointer tampak rumit pada pandangan pertama karena tanda kurung sangat banyak. Bentuk umumnya: T (*nama)(params); di mana T adalah tipe kembalian, nama adalah nama variabel function pointer, dan params adalah daftar tipe parameter. Tanpa kurung di sekitar *nama, ekspresi akan dideklarasikan sebagai “fungsi yang mengembalikan pointer”, bukan “pointer ke fungsi”. Inilah mengapa kurung (*nama) wajib ada.
Mekanisme kerjanya: nama fungsi biasa (seperti tambah) otomatis decay menjadi pointer ke fungsi saat dipakai dalam ekspresi—mirip array yang decay ke pointer. Saat kita menulis fp = tambah;, compiler menyimpan alamat fungsi tambah ke variabel fp. Pemanggilan fp(10, 3) atau (*fp)(10, 3) (keduanya ekuivalen) lompat ke alamat fungsi tersebut dan menjalankannya. Dari sudut pandang pemanggilan, hasilnya sama dengan memanggil tambah(10, 3) secara langsung.
#include <stdio.h>
/* Dua fungsi biasa */
int tambah(int a, int b) { return a + b; }
int kurang(int a, int b) { return a - b; }
int main(void) {
/* Deklarasi function pointer:
int (*fp)(int, int) artinya:
fp adalah pointer ke fungsi yang menerima (int, int) dan mengembalikan int */
int (*fp)(int, int);
fp = tambah;
printf("fp(10, 3) = %d (via tambah)\n", fp(10, 3));
fp = kurang;
printf("fp(10, 3) = %d (via kurang)\n", fp(10, 3));
/* Cara panggil alternatif dengan dereference eksplisit */
fp = tambah;
printf("(*fp)(7, 8) = %d (deref eksplisit)\n", (*fp)(7, 8));
return 0;
}Output yang Dihasilkan:
fp(10, 3) = 13 (via tambah)
fp(10, 3) = 7 (via kurang)
(*fp)(7, 8) = 15 (deref eksplisit)Satu variabel fp bisa dipakai untuk memanggil tambah, kurang, atau fungsi apa pun yang tipenya cocok. Pemanggilan fp(10, 3) dan (*fp)(10, 3) menghasilkan output yang sama—keduanya valid secara standar C (ISO/IEC 9899 §6.5.2.2). Gaya pertama (fp(10, 3)) lebih ringkas dan umum; gaya kedua ((*fp)(10, 3)) lebih eksplisit memperlihatkan bahwa fp adalah pointer yang di-dereference.
Mari kita telaah baris-baris kode di atas:
- Baris 4-5 (
int tambah(int a, int b) { ... }): fungsi biasa dengan tanda tanganint (int, int). Namatambahdapat decay menjadi function pointer bertipeint (*)(int, int). - Baris 11 (
int (*fp)(int, int);): deklarasi function pointerfp. Kurung(*fp)wajib; tanpa kurung,int *fp(int, int)akan dideklarasikan sebagai fungsi yang mengembalikanint *. - Baris 13 (
fp = tambah;): assignment alamat fungsitambahkefp. Nama fungsi otomatis decay menjadi pointer ke fungsi. - Baris 14 (
printf("... %d", fp(10, 3));): panggilan fungsi melalui pointer. Compiler mengikuti alamat difp, lalu memanggil fungsi dengan argumen(10, 3). - Baris 19 (
printf("... %d", (*fp)(7, 8));): cara alternatif yang setara.(*fp)melakukan dereference untuk mendapatkan fungsi, lalu memanggilnya.
Dispatch Table dan Callback #
Salah satu kegunaan paling kuat function pointer adalah dispatch table—array of function pointer yang dipakai untuk memilih operasi berdasarkan indeks atau kunci. Pola ini menggantikan switch-case yang panjang dengan tabel lookup yang ringkas dan mudah diperluas. Contoh lain adalah callback: fungsi yang menerima fungsi lain sebagai parameter, lalu memanggilnya untuk setiap elemen data—mirip map di bahasa fungsional.
#include <stdio.h>
/* Aritmetika dasar sebagai fungsi terpisah */
int tambah(int a, int b) { return a + b; }
int kurang(int a, int b) { return a - b; }
int kali(int a, int b) { return a * b; }
typedef int (*operasi_fn)(int, int);
/* Callback generik */
void terapkan(int *arr, int n, int (*fn)(int)) {
for (int i = 0; i < n; i++) {
arr[i] = fn(arr[i]);
}
}
int kuadrat(int x) { return x * x; }
int main(void) {
operasi_fn ops[] = { tambah, kurang, kali };
const char *nama[] = { "tambah", "kurang", "kali" };
int x = 10, y = 4;
for (int i = 0; i < 3; i++) {
int hasil = ops[i](x, y);
printf("%s(%d, %d) = %d\n", nama[i], x, y, hasil);
}
/* Terapkan callback ke setiap elemen array */
int data[3] = {1, 2, 3};
terapkan(data, 3, kuadrat);
printf("\nSetelah terapkan kuadrat: ");
for (int i = 0; i < 3; i++) printf("%d ", data[i]);
printf("\n");
return 0;
}Output yang Dihasilkan:
tambah(10, 4) = 14
kurang(10, 4) = 6
kali(10, 4) = 40
Setelah terapkan kuadrat: 1 4 9typedef int (*operasi_fn)(int, int); membuat alias tipe operasi_fn untuk function pointer bertipe int (*)(int, int). Dengan alias ini, deklarasi array operasi_fn ops[] jauh lebih mudah dibaca daripada int (*ops[])(int, int). Array ops berisi tiga function pointer yang berbeda; loop for memanggil masing-masing dengan argumen (10, 4) dan mencetak hasilnya. Fungsi terapkan menerima callback fn bertipe int (*)(int) dan memanggilnya untuk setiap elemen array—pola yang sama dengan map di bahasa fungsional.