Dasar-dasar fungsi dan prototipe

Deklarasi, definisi, pemanggilan, dan pemisahan header dari implementasi.

Daftar isi

Fungsi adalah blok kode yang diberi nama dan dapat dipanggil berulang kali dari bagian lain program. Topik ini membahas cara mendefinisikan fungsi, cara memanggilnya, urutan deklarasi yang dipersyaratkan compiler, serta pemisahan prototipe ke dalam berkas header terpisah dari implementasinya. Setelah membacanya, program yang semula menumpuk di satu berkas dapat dipecah menjadi unit-unit kecil yang bernama, teruji, dan dapat dipakai ulang.

Deklarasi, definisi, dan pemanggilan#

Definisi fungsi terdiri atas tipe kembalian, nama fungsi, daftar parameter, dan badan fungsi yang berada di dalam kurung kurawal. Pemanggilan mengeksekusi badan fungsi, lalu kontrol kembali ke pemanggil bersama nilai kembaliannya. Program berikut mendefinisikan fungsi konversi suhu dan memanggilnya dari main.

cpp konversi-suhu.cpp
#include <iostream>

double celsiusKeFahrenheit(double celsius) {
    return celsius * 9.0 / 5.0 + 32.0;
}

int main() {
    std::cout << "20 derajat Celsius = " << celsiusKeFahrenheit(20.0) << " derajat Fahrenheit\n";
    return 0;
}
text Keluaran
20 derajat Celsius = 68 derajat Fahrenheit

Baris double celsiusKeFahrenheit(double celsius) menyatakan bahwa fungsi menerima satu parameter double dan mengembalikan satu nilai double. Pernyataan return mengakhiri eksekusi fungsi dan mengirimkan nilainya ke pemanggil. Fungsi yang tidak mengembalikan apa pun dinyatakan bertipe void, dan fungsi semacam itu boleh berjalan tanpa pernyataan return sama sekali.

Nama parameter hanya hidup di dalam badan fungsinya. Ketika main memanggil celsiusKeFahrenheit(20.0), nilai 20.0 disalin ke parameter celsius, dan perubahan terhadap parameter tidak memengaruhi variabel milik pemanggil. Mekanisme penyalinan ini dibahas lebih lanjut pada topik tentang pass by value, reference, dan pointer.

Nama parameter pada prototipe boleh dihilangkan, sehingga double celsiusKeFahrenheit(double); juga merupakan deklarasi yang sah. Nama yang ditulis tetap berguna sebagai dokumentasi, karena arti setiap parameter menjadi jelas tanpa membaca definisinya. Beberapa compiler juga memanfaatkan nama tersebut untuk menampilkan saran perbaikan yang lebih tepat saat pemanggilan keliru.

Fungsi dapat dipanggil berkali-kali dengan argumen yang berbeda-beda, dan setiap pemanggilan berjalan mandiri dengan salinan parameternya sendiri. Fungsi main sendiri juga sebuah fungsi, sehingga mengikuti kaidah yang sama, yaitu bertipe kembalian int dan mengembalikan nilainya ke sistem operasi sebagai status program. Nilai 0 menandakan program berakhir tanpa masalah, sedangkan nilai lain umumnya menandakan ada kegagalan.

Prototipe dan urutan deklarasi#

Compiler C++ membaca berkas sumber dari atas ke bawah, sehingga nama fungsi harus sudah dikenal sebelum dipanggil. Apabila pemanggilan muncul lebih dulu daripada definisinya, compiler menolak program tersebut dengan pesan error.

cpp Pemanggilan sebelum definisi ditolak compiler
// tidak-dijalankan
#include <iostream>

int main() {
    std::cout << celsiusKeFahrenheit(20.0) << '\n';
    return 0;
}

double celsiusKeFahrenheit(double celsius) {
    return celsius * 9.0 / 5.0 + 32.0;
}

Pesan errornya lazimnya berbunyi celsiusKeFahrenheit was not declared in this scope, yang berarti compiler belum mengenal nama tersebut pada baris pemanggilan. Cara mengatasinya adalah prototipe, yakni deklarasi fungsi yang hanya memuat tipe kembalian, nama, dan daftar parameter, diakhiri tanda titik koma tanpa badan. Prototipe memberi tahu compiler bahwa definisi fungsi ada di tempat lain.

cpp urutan-panggilan.cpp
#include <iostream>

double celsiusKeFahrenheit(double celsius);

int main() {
    std::cout << "20 derajat Celsius = " << celsiusKeFahrenheit(20.0) << " derajat Fahrenheit\n";
    return 0;
}

double celsiusKeFahrenheit(double celsius) {
    return celsius * 9.0 / 5.0 + 32.0;
}
text Keluaran
20 derajat Celsius = 68 derajat Fahrenheit

Program ini memanggil fungsi sebelum badannya muncul, tetapi tetap dikompilasi tanpa error karena prototipe pada bagian atas sudah memperkenalkan nama fungsinya. Dengan prototipe, urutan definisi antarfungsi menjadi bebas, dan pekerjaan yang biasanya dikerjakan prototipe akan dialihkan ke berkas header pada bagian berikutnya.

Kebebasan urutan ini berlaku di dalam satu unit penerjemahan, sedangkan pemanggilan antarberkas tetap membutuhkan header. Dengan prototipe di bagian atas, fungsi-fungsi dapat saling memanggil tanpa memandang urutan penulisannya, kemampuan yang tidak mungkin dicapai tanpa deklarasi terpisah.

Memisahkan header dan implementasi#

Pada program kecil, prototipe cukup ditulis di berkas yang sama. Program yang lebih besar meletakkan prototipe di berkas header berekstensi .h, sedangkan definisinya berada di berkas sumber berekstensi .cpp. Berkas header dipakai bersama melalui perintah praprosesor #include, yang menyalin isi header ke tempat perintah itu berada. Struktur konversi suhu di atas dapat dipecah menjadi tiga berkas.

cpp konversi.h
// tidak-dijalankan (butuh build multi-berkas; perintah ada di bawah)
#ifndef KONVERSI_H
#define KONVERSI_H

double celsiusKeFahrenheit(double celsius);
double fahrenheitKeCelsius(double fahrenheit);

#endif
cpp konversi.cpp
// tidak-dijalankan (butuh build multi-berkas; perintah ada di bawah)
#include "konversi.h"

double celsiusKeFahrenheit(double celsius) {
    return celsius * 9.0 / 5.0 + 32.0;
}

double fahrenheitKeCelsius(double fahrenheit) {
    return (fahrenheit - 32.0) * 5.0 / 9.0;
}
cpp main.cpp
// tidak-dijalankan (butuh build multi-berkas; perintah ada di bawah)
#include <iostream>
#include "konversi.h"

int main() {
    std::cout << "25 C = " << celsiusKeFahrenheit(25.0) << " F\n";
    std::cout << "77 F = " << fahrenheitKeCelsius(77.0) << " C\n";
    return 0;
}

Setiap berkas .cpp yang dikompilasi bersama header-nya menjadi satu unit penerjemahan (translation unit), yaitu hasil praprosesor yang sesungguhnya diterima compiler. Kedua berkas .cpp di atas menghasilkan dua unit penerjemahan, dan linker menyatukan keduanya menjadi satu program dengan mencocokkan nama fungsinya. Tanda kutip pada #include "konversi.h" menyatakan bahwa header dicari di direktori proyek, berbeda dari tanda kurung sudut pada #include <iostream> yang menunjuk pustaka standar. Header konversi.h di atas juga memakai include guard untuk mencegah penyisipan ganda, mekanisme yang dijelaskan penuh pada topik header dan proyek banyak berkas.

Pembagian ini memindahkan biaya perubahan ke tempat yang tepat. Apabila rumus di dalam konversi.cpp diperbaiki, hanya berkas itu yang perlu dikompilasi ulang, sedangkan main.cpp tidak tersentuh karena antarmukanya tidak berubah. Pada proyek dengan ratusan berkas, pemisahan semacam inilah yang menjaga waktu kompilasi tetap wajar dan tanggung jawab setiap berkas tetap tunggal.

bash
# kompilasi program dari dua berkas sumber
g++ main.cpp konversi.cpp -o aplikasi-suhu
# jalankan program hasil kompilasi
./aplikasi-suhu
text Keluaran
25 C = 77 F
77 F = 25 C

Kesalahan umum#

Tanda titik koma yang hilang di akhir prototipe membuat compiler mengira deklarasi tersebut adalah awal dari definisi. Error yang muncul biasanya berupa keluhan tentang kurung kurawal atau tipe pada baris berikutnya, sehingga letak kesalahannya sering tidak terlihat langsung. Periksa setiap prototipe apabila pesan error menunjuk ke baris yang tampaknya sudah benar.

Prototipe yang berbeda dari definisinya juga ditolak compiler. Apabila prototipe menyatakan parameter double, sedangkan definisinya menerima int, compiler melaporkan bahwa tidak ada deklarasi yang cocok dengan definisi tersebut. Kebiasaan yang aman adalah menyalin baris prototipe dari header ke definisi lalu menghapus titik koma, sehingga keduanya pasti identik.

Menuliskan seluruh badan fungsi di dalam header tanpa kata kunci inline berisiko menghasilkan error linker apabila header tersebut disertakan oleh lebih dari satu berkas .cpp, karena setiap unit penerjemahan mendefinisikan fungsi yang sama. Solusinya, header hanya memuat prototipe, atau definisi di header ditandai inline sebagaimana dibahas pada topik fungsi inline.

Ringkasan#

  • Definisi fungsi memuat tipe kembalian, nama, parameter, dan badan fungsi, sedangkan prototipe hanya memuat tiga hal pertama tanpa badan.
  • Compiler harus mengenal nama fungsi sebelum pemanggilannya, dan prototipe adalah cara memperkenalkan nama tersebut lebih awal.
  • Prototipe lazim ditempatkan di berkas .h, definisinya di berkas .cpp, dan #include menyalin isi header ke setiap unit penerjemahan yang membutuhkannya.

Referensi#