Bayangkan program kalkulator yang menghitung luas segitiga di 5 tempat berbeda. Tanpa fungsi, rumus 0.5 * alas * tinggi harus ditulis di kelima tempat itu. Jika rumusnya keliru, harus diperbaiki di lima tempat. Dengan fungsi, cukup tulis sekali — perbaiki sekali, berlaku di semua tempat.
Fungsi adalah blok kode yang diberi nama, dapat menerima input (parameter), menjalankan serangkaian instruksi, dan mengembalikan output (nilai kembalian). Fungsi dipanggil dari manapun dalam program menggunakan namanya.
Manfaat utama fungsi:
main() adalah fungsi pertama yang kita kenal. printf() dan scanf() juga fungsi — hanya saja sudah disediakan oleh pustaka standar C. Di topik ini, kita belajar membuat fungsi kita sendiri.
void jika tidak mengembalikan nilai.#include <stdio.h> /* Fungsi DENGAN nilai kembalian (return type: int) */ int hitungLuas(int panjang, int lebar) { return panjang * lebar; // mengembalikan hasil perkalian } /* Fungsi TANPA nilai kembalian (void) */ void cetakGaris() { printf("========================\n"); // tidak ada return (atau: return; tanpa nilai) } int main() { int luas = hitungLuas(5, 3); // 5 dan 3 = argumen/parameter aktual cetakGaris(); printf("Luas = %d\n", luas); // Luas = 15 cetakGaris(); return 0; }
int panjang, int lebar.hitungLuas(5, 3) — di sini 5 dan 3 adalah argumennya.
Fungsi backward adalah pola di mana fungsi didefinisikan di atas fungsi yang memanggilnya dalam file kode. Disebut "backward" karena fungsi pembantu ditulis lebih awal dari fungsi utama. Ini adalah cara paling sederhana — compiler sudah mengetahui seluruh detail fungsi sebelum menemukannya dipanggil.
Compiler C membaca kode dari atas ke bawah, satu baris per satu baris. Ketika menemukan pemanggilan fungsi, ia harus sudah mengetahui fungsi tersebut — tipe return-nya, nama, dan parameternya. Jika definisi fungsi belum ditemukan saat titik pemanggilan tercapai, compiler akan error.
#include <stdio.h> /* ① Definisi fungsi di ATAS main() */ /* Compiler sudah tahu fungsi ini saat membaca main() */ int kuadrat(int x) { return x * x; } float rataRata(int a, int b, int c) { return (float)(a + b + c) / 3; } void cetakHasil(int n, int hasil) { printf("Kuadrat %d = %d\n", n, hasil); } /* ② main() dipanggil TERAKHIR — semua fungsi sudah dikenal */ int main() { int n = 7; int k = kuadrat(n); // ✓ compiler sudah kenal kuadrat() cetakHasil(n, k); // ✓ compiler sudah kenal cetakHasil() float avg = rataRata(80, 90, 70); printf("Rata-rata = %.1f\n", avg); return 0; } /* Output: Kuadrat 7 = 49 Rata-rata = 80.0 */
Fungsi forward adalah pola di mana main() atau fungsi pemanggil ditulis di atas, sedangkan definisi fungsi yang dipanggil berada di bawahnya. Nama "forward" merujuk pada forward declaration (prototipe) — deklarasi pendahulu yang memberitahu compiler tentang keberadaan fungsi sebelum definisi lengkapnya ditemukan.
main() memanggil fungsi yang belum dikenal compiler (definisinya ada di bawah dan tidak ada prototipe), compiler akan mengeluarkan warning atau error: "implicit declaration of function" atau "undefined reference".
Prototipe adalah deklarasi singkat yang hanya memberitahu compiler: "akan ada fungsi dengan nama, tipe return, dan parameter seperti ini." Prototipe ditulis di atas sebelum main(), sedangkan definisi lengkapnya bisa di mana saja sesudah itu.
#include <stdio.h> /* TIDAK ada prototipe */ int main() { /* kuadrat() belum dikenal! */ int k = kuadrat(5); printf("%d\n", k); return 0; } /* definisi ada di bawah */ int kuadrat(int x) { return x * x; }
#include <stdio.h> /* ① PROTOTIPE — deklarasi pendahulu */ int kuadrat(int x); int main() { /* kuadrat() sudah dikenal! */ int k = kuadrat(5); printf("%d\n", k); // 25 return 0; } /* ② Definisi lengkap di bawah */ int kuadrat(int x) { return x * x; }
#include <stdio.h> #include <math.h> /* ============================================ PROTOTIPE — semua fungsi dideklarasikan di atas Sintaks: tipe_return nama_fungsi(tipe param, ...); ============================================ */ int kuadrat(int x); float luasLingkaran(float r); void cetakHeader(char *judul); int faktorial(int n); /* ============================================ main() — bisa langsung gunakan semua fungsi ============================================ */ int main() { cetakHeader("KALKULATOR SEDERHANA"); printf("Kuadrat 8 = %d\n", kuadrat(8)); printf("Luas r=7 = %.2f\n", luasLingkaran(7)); printf("Faktorial 5 = %d\n", faktorial(5)); return 0; } /* ============================================ DEFINISI LENGKAP — bisa ditulis di mana saja (bahkan di file .c terpisah) ============================================ */ int kuadrat(int x) { return x * x; } float luasLingkaran(float r) { return 3.14159 * r * r; } void cetakHeader(char *judul) { printf("===== %s =====\n", judul); } int faktorial(int n) { if (n <= 1) return 1; return n * faktorial(n - 1); } /* Output: ===== KALKULATOR SEDERHANA ===== Kuadrat 8 = 64 Luas r=7 = 153.94 Faktorial 5 = 120 */
int kuadrat(int);{}. Tidak diakhiri titik koma. Hanya ada satu definisi per fungsi..h (header file), sedangkan definisi di file .c terpisah. Dengan begitu, fungsi bisa dibagi-pakai antar banyak file sumber tanpa menduplikasi kode.
Rekursif adalah teknik di mana sebuah fungsi memanggil dirinya sendiri untuk menyelesaikan masalah yang lebih kecil. Setiap pemanggilan menyelesaikan sebagian masalah, lalu meneruskan sisa masalah ke dirinya sendiri dengan input yang lebih sederhana, hingga mencapai kondisi berhenti.
① Base case (kondisi dasar) — kondisi berhenti yang menghentikan rekursif. Tanpa ini, fungsi memanggil dirinya selamanya (stack overflow!).
② Recursive case — pemanggilan fungsi itu sendiri dengan argumen yang semakin mendekati base case.
Faktorial didefinisikan: n! = n × (n-1) × (n-2) × ... × 1, dan 0! = 1 (base case). Sifat ini alami untuk rekursif: n! = n × (n-1)!
int faktorialLoop(int n) { int hasil = 1; for(int i=1; i<=n; i++) hasil *= i; return hasil; } // 5! = 1*2*3*4*5 = 120
int faktorial(int n) { if (n <= 1) // base case return 1; return n * faktorial(n-1); // recursive case }
#include <stdio.h> /* Fibonacci: fib(n) = fib(n-1) + fib(n-2) */ /* Base case: fib(0)=0, fib(1)=1 */ int fibonacci(int n) { if (n == 0) return 0; // base case 1 if (n == 1) return 1; // base case 2 return fibonacci(n-1) + fibonacci(n-2); // recursive } /* Pangkat: pangkat(base, exp) = base * pangkat(base, exp-1) */ int pangkat(int base, int exp) { if (exp == 0) return 1; // base case: x^0 = 1 return base * pangkat(base, exp-1); // recursive } int main() { printf("Fibonacci ke-8 = %d\n", fibonacci(8)); // 21 printf("Deret Fibonacci: "); for(int i=0; i<10; i++) printf("%d ", fibonacci(i)); // 0 1 1 2 3 5 8 13 21 34 printf("\n"); printf("2^10 = %d\n", pangkat(2, 10)); // 1024 return 0; }
fibonacci(n) rekursif murni sangat tidak efisien — fibonacci(40) melakukan sekitar 300 juta pemanggilan! Untuk nilai besar, gunakan pendekatan iteratif atau memoization. Fungsi fibonacci di sini bertujuan menunjukkan konsep, bukan untuk produksi.
Tidak semua variabel bisa diakses dari mana saja. Scope (cakupan) menentukan di mana sebuah variabel dapat dilihat dan digunakan. Ini penting dipahami agar tidak terjadi konflik nama dan bug yang susah dilacak.
| Jenis | Dideklarasikan | Dapat diakses | Umur hidup |
|---|---|---|---|
| Lokal | Di dalam fungsi atau blok {} |
Hanya dalam fungsi/blok itu | Dibuat saat fungsi dipanggil, dihapus saat fungsi selesai |
| Global | Di luar semua fungsi, biasanya di atas | Dari semua fungsi dalam file yang sama | Sejak program mulai hingga program berakhir |
| Statis (lokal) | Di dalam fungsi, dengan kata kunci static |
Hanya dalam fungsi itu | Tidak dihapus saat fungsi selesai — nilainya dipertahankan |
#include <stdio.h> int hitungan = 0; /* ← GLOBAL: diakses semua fungsi */ void demoLokal() { int x = 10; /* ← LOKAL: hanya ada di dalam demoLokal() */ x++; printf("Lokal x = %d\n", x); /* x dihapus dari memori saat fungsi ini selesai */ } void demoStatis() { static int count = 0; /* ← STATIS: inisialisasi hanya sekali! */ count++; printf("Static count = %d\n", count); /* count TIDAK dihapus saat fungsi selesai */ } void tambahHitungan() { hitungan++; /* akses variabel global */ } int main() { demoLokal(); // x = 11 demoLokal(); // x = 11 lagi! x selalu dimulai dari 10 printf("---\n"); demoStatis(); // count = 1 demoStatis(); // count = 2 ← nilai dipertahankan! demoStatis(); // count = 3 printf("---\n"); tambahHitungan(); tambahHitungan(); printf("Global hitungan = %d\n", hitungan); // 2 return 0; } /* Output: Lokal x = 11 Lokal x = 11 --- Static count = 1 Static count = 2 Static count = 3 --- Global hitungan = 2 */
Jika variabel lokal dan global memiliki nama yang sama, variabel lokal mengalahkan (menutupi) variabel global di dalam fungsi tersebut. Ini disebut variable shadowing dan bisa menyebabkan bug yang sulit dilacak.
#include <stdio.h> int nilai = 100; /* variabel GLOBAL */ void contohShadow() { int nilai = 50; /* variabel LOKAL — menutupi yang global! */ printf("Di dalam fungsi: nilai = %d\n", nilai); // Ini mencetak 50 (lokal), bukan 100 (global) } int main() { printf("Di main (sebelum): nilai = %d\n", nilai); // 100 contohShadow(); printf("Di main (sesudah): nilai = %d\n", nilai); // 100 — global tidak berubah! return 0; }
g_nilai) agar mudah dibedakan dan tidak sengaja ditimpa. Gunakan variabel global secara hemat — lebih banyak gunakan parameter dan return value.
Ketika memanggil fungsi dengan argumen, ada dua cara nilai dikirim: by value (kirim salinan) atau by reference (kirim alamat aslinya). Pilihan ini menentukan apakah perubahan di dalam fungsi berdampak pada variabel asli di luar fungsi.
Secara default, semua pengiriman argumen di C menggunakan by value. Fungsi menerima salinan nilai, bukan variabel aslinya. Perubahan apapun di dalam fungsi tidak mempengaruhi variabel di pemanggil.
#include <stdio.h> void cobaUbah(int x) { // x adalah SALINAN dari a x = x * 10; printf("Di dalam fungsi: x = %d\n", x); // 50 // perubahan pada x tidak memengaruhi variabel luar } int main() { int a = 5; printf("Sebelum: a = %d\n", a); // 5 cobaUbah(a); // kirim nilai 5, bukan variabel a printf("Sesudah: a = %d\n", a); // 5 — tidak berubah! return 0; }
Dengan by reference, yang dikirim ke fungsi bukan nilainya melainkan alamat memori variabel asli (menggunakan pointer). Fungsi langsung memanipulasi variabel asli melalui alamat tersebut.
Sintaks pointer: int *ptr — deklarasi pointer. &a — ambil alamat variabel a. *ptr — akses nilai di alamat tersebut (dereference).
#include <stdio.h> /* Parameter bertipe pointer: int *px */ /* Artinya: px menyimpan ALAMAT variabel int */ void ubahNilai(int *px) { *px = *px * 10; // *px: akses nilai di alamat yang ditunjuk px printf("Di dalam: *px = %d\n", *px); // 50 } /* Fungsi swap klasik — hanya bisa dengan by reference */ void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int a = 5; printf("Sebelum: a = %d\n", a); // 5 ubahNilai(&a); // kirim ALAMAT a, bukan nilainya printf("Sesudah: a = %d\n", a); // 50 — berubah! printf("---\n"); int x = 10, y = 20; printf("Sebelum swap: x=%d y=%d\n", x, y); // 10 20 swap(&x, &y); printf("Sesudah swap: x=%d y=%d\n", x, y); // 20 10 return 0; }
Ketika array dikirim ke fungsi, yang dikirim bukan salinan seluruh array — melainkan pointer ke elemen pertama (by reference secara implisit). Artinya: perubahan pada array di dalam fungsi akan berdampak pada array asli, berbeda dengan variabel biasa.
#include <stdio.h> /* Parameter array: int arr[] atau int *arr — sama saja */ /* Ukuran N dikirim terpisah karena fungsi tidak tahu ukuran array */ void isiArray(int arr[], int n) { for(int i=0; i<n; i++) arr[i] = i * 10; // mengubah array ASLI } int jumlahArray(int arr[], int n) { int total = 0; for(int i=0; i<n; i++) total += arr[i]; return total; } void cetakArray(int arr[], int n) { for(int i=0; i<n; i++) printf("%d ", arr[i]); printf("\n"); } int main() { int data[5]; // array belum terisi isiArray(data, 5); // isi array melalui fungsi cetakArray(data, 5); // 0 10 20 30 40 printf("Jumlah = %d\n", jumlahArray(data, 5)); // 100 return 0; }
int n), atau gunakan konstanta global.
| Aspek | Backward | Forward | Rekursif |
|---|---|---|---|
| Posisi definisi | Di atas pemanggil | Di mana saja (ada prototipe) | Memanggil dirinya sendiri |
| Perlu prototipe? | Tidak | Wajib ada | Tidak (tapi bisa pakai) |
| Cocok untuk | Program kecil, fungsi pembantu sederhana | Program besar, multi-file, library | Masalah yang bisa dipecah menjadi sub-masalah serupa |
| Kelebihan | Sederhana, tidak perlu prototipe | Fleksibel, standar profesional | Kode elegan untuk masalah rekursif alami |
| Kekurangan | Urutan penulisan bergantung ketergantungan | Harus ingat menulis prototipe | Boros memori (stack), bisa stack overflow |
| Contoh kasus | Fungsi utilitas, konversi satuan | Proyek nyata, header file | Faktorial, Fibonacci, traversal pohon |
Program berikut menggunakan prototipe (forward), variabel lokal, by value, by reference, dan array — semua dalam satu program terstruktur.
#include <stdio.h> #include <math.h> /* ===== PROTOTIPE (Forward Declaration) ===== */ int faktorial(int n); float rataRata(int arr[], int n); void statistik(int arr[], int n, int *maks, int *min); void cetakMenu(); /* ===== PROGRAM UTAMA ===== */ int main() { /* Uji faktorial rekursif */ printf("5! = %d\n", faktorial(5)); /* Uji array + by reference */ int data[] = {45, 82, 17, 93, 60}; int n = 5, maks, min; printf("Rata-rata: %.1f\n", rataRata(data, n)); statistik(data, n, &maks, &min); // by reference printf("Maks=%d Min=%d\n", maks, min); return 0; } /* ===== DEFINISI FUNGSI ===== */ int faktorial(int n) { // REKURSIF if (n <= 1) return 1; return n * faktorial(n-1); } float rataRata(int arr[], int n) { // array by ref implisit int total = 0; for(int i=0; i<n; i++) total += arr[i]; return (float)total / n; } void statistik(int arr[], int n, int *maks, int *min) { // by reference *maks = *min = arr[0]; for(int i=1; i<n; i++) { if(arr[i] > *maks) *maks = arr[i]; if(arr[i] < *min) *min = arr[i]; } } /* Output: 5! = 120 Rata-rata: 59.4 Maks=93 Min=17 */
lokal hanya hidup di fungsinya; global hidup sepanjang program; static nilainya dipertahankan antar pemanggilan&var) — variabel asli bisa diubah