Program yang bermanfaat hampir selalu bertukar data dengan penggunanya, yaitu menampilkan keluaran (output) dan menerima masukan (input). Topik ini membahas fungsi printf dan scanf beserta penentu formatnya, pasangan getchar dan putchar, serta perilaku buffer masukan yang menjadi sumber keanehan klasik pada program C. Setelah membacanya, program sederhana dapat menerima data dari pengguna dan membaca pesan error yang muncul bila penentu format keliru.
printf dan penentu format#
Fungsi printf mencetak teks sesuai teks pemformat (format string) yang menjadi argumen pertamanya. Di dalam teks pemformat, setiap tanda % diikuti satu karakter membentuk penentu format (format specifier), yaitu slot yang digantikan nilai argumen berikutnya. Program berikut menguji penentu paling umum beserta pengaturan lebar dan presisinya:
#include <stdio.h>
int main(void) {
int stok = 120;
double harga = 12500.5;
printf("Bentuk default : %d dan %f\n", stok, harga);
printf("Dengan lebar : %8d dan %10.2f\n", stok, harga);
printf("Rata kiri : %-8d|\n", stok);
printf("Presisi : %.1f dan %.3f\n", harga, harga);
printf("Tanda persen : 100%%\n");
return 0;
}Bentuk default : 120 dan 12500.500000
Dengan lebar : 120 dan 12500.50
Rata kiri : 120 |
Presisi : 12500.5 dan 12500.500
Tanda persen : 100%Penentu %8d menyiapkan ruang selebar delapan karakter dan mengisi sisanya dengan spasi, sedangkan tanda - membuat nilai rata kiri alih-alih rata kanan. Untuk tipe pecahan, bagian setelah titik mengatur jumlah digit desimal, sehingga %.2f menampilkan dua digit. Tabel berikut merangkum penentu yang paling sering dipakai.
| Penentu | Tipe yang sesuai | Contoh keluaran |
|---|---|---|
%d | int | 120 |
%u | unsigned int | 4294967295 |
%f | double pada printf | 12500.500000 |
%c | char | B |
%s | teks (string) | Halo |
%zu | hasil sizeof | 4 |
%% | tanda persen itu sendiri | % |
scanf dan tanda &#
Fungsi scanf membaca masukan teks dan mengubahnya menjadi nilai bertipe tertentu. Argumen pertamanya adalah teks pemformat berisi penentu format, dan setiap penentu berikutnya wajib dipasangi alamat variabel penampung yang diawali tanda &. Tanda itu penting karena scanf harus mengetahui alamat tempat hasil pembacaan ditulis, bukan sekadar salinan isi variabel:
// tidak-dijalankan
#include <stdio.h>
int main(void) {
int a = 0;
int b = 0;
printf("Masukkan dua bilangan bulat: ");
scanf("%d %d", &a, &b);
printf("Jumlah %d + %d = %d\n", a, b, a + b);
return 0;
}Program ini menunggu masukan pengguna, sehingga saat diuji dari terminal masukannya diberikan terlebih dahulu lewat pipa (pipe). Perintah berikut mengompilasi lalu menyodorkan teks 12 30 sebagai masukan:
# kompilasi tambah.c
gcc tambah.c -o tambah
# jalankan dengan masukan 12 dan 30
printf "12 30\n" | ./tambahMasukkan dua bilangan bulat: Jumlah 12 + 30 = 42Spasi di antara kedua %d menyatakan pembatas yang boleh berupa spasi, tab, maupun pindah baris, sehingga masukan 12 30 maupun dua baris terpisah sama-sama diterima. Apabila tanda & pada argumen dilupakan, kompiler dengan -Wall memberi peringatan yang wajib dianggap serius:
salah-ampersand.c: In function ‘main’:
salah-ampersand.c:6:13: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int’ [-Wformat=]
6 | scanf("%d", n);
| ~^ ~
| | |
| | int
| int *Program dengan warning tersebut bisa saja berhenti mendadak saat berjalan, karena scanf akan menulis ke alamat yang tidak sah. Makna penuh tanda & dibahas pada modul Pointer, sementara itu cukup diingat bahwa setiap argumen scanf selalu diawali &.
Menilai keberhasilan scanf#
Fungsi scanf mengembalikan jumlah data yang berhasil dibaca dan diubah, sehingga nilai kembanya dapat dipakai untuk menilai apakah masukan benar-benar berupa angka. Apabila teks yang ditemui tidak cocok dengan penentu format, pembacaan berhenti dan jumlahnya lebih kecil dari yang diminta:
// tidak-dijalankan
#include <stdio.h>
int main(void) {
int n = 0;
int terbaca = scanf("%d", &n);
printf("Jumlah data terbaca: %d\n", terbaca);
printf("Nilai n : %d\n", n);
return 0;
}# kompilasi cek-scanf.c
gcc cek-scanf.c -o cek-scanf
# coba masukan berupa angka
printf "42\n" | ./cek-scanf
# coba masukan berupa teks
printf "abc\n" | ./cek-scanfJumlah data terbaca: 1
Nilai n : 42
Jumlah data terbaca: 0
Nilai n : 0Masukan abc tidak cocok dengan %d sehingga nilai kembannya 0, dan variabel n tetap berisi nilai awalnya. Apabila masukan sudah habis sebelum ada data yang cocok, misalnya pembacaan dilakukan pada berkas kosong, scanf mengembalikan EOF, yaitu penanda habisnya masukan yang bernilai negatif. Idiom pemeriksaan yang paling umum adalah membandingkan nilai kembannya langsung dengan jumlah data yang diminta, misalnya kondisi if (scanf("%d", &n) != 1) untuk menangkap masukan yang tidak berupa angka. Tanpa pemeriksaan itu, nilai variabel penampung yang tidak jelas ikut dipakai dalam perhitungan, dan kesalahannya sering baru muncul jauh dari sumbernya. Memeriksa nilai kembali ini sebelum memakai hasil scanf adalah kebiasaan dasar penanganan masukan yang tangguh.
Sisa buffer setelah scanf#
scanf tidak membaca langsung dari keyboard, melainkan dari buffer, yaitu area penampung sementara untuk data masukan. Data yang sudah masuk buffer tetap tersisa di sana sampai benar-benar dibaca, dan inilah sumber keanehan yang paling sering ditemui. Diagram berikut menggambarkan buffer setelah scanf("%d") membaca angka 25 dari masukan yang juga berisi huruf B.
Karakter pindah baris yang ditekan pengguna setelah mengetik angka juga ikut masuk buffer dan tidak dibaca oleh %d. Program berikut membaca satu angka lalu satu karakter, dan hasilnya terlihat tidak terduga:
// tidak-dijalankan
#include <stdio.h>
int main(void) {
int umur = 0;
char huruf = 0;
printf("Masukkan umur : ");
scanf("%d", &umur);
printf("Masukkan satu huruf : ");
scanf("%c", &huruf);
printf("Umur = %d, kode huruf = %d\n", umur, huruf);
return 0;
}# kompilasi buffer-char.c
gcc buffer-char.c -o buffer-char
# masukan berupa angka 25 lalu huruf B
printf "25\nB\n" | ./buffer-charMasukkan umur : Masukkan satu huruf : Umur = 25, kode huruf = 10Kode huruf yang terbaca adalah 10, yaitu kode karakter pindah baris, bukan 66 untuk huruf B. Penentu %c membaca karakter berikutnya apa adanya tanpa melewati spasi, dan karakter berikutnya di buffer adalah pindah baris yang ditinggalkan pembacaan angka. Perbaikannya sederhana, yaitu menambahkan satu spasi sebelum %c agar karakter pemisah dilewati lebih dahulu:
// tidak-dijalankan
#include <stdio.h>
int main(void) {
int umur = 0;
char huruf = 0;
printf("Masukkan umur : ");
scanf("%d", &umur);
printf("Masukkan satu huruf : ");
scanf(" %c", &huruf);
printf("Umur = %d, kode huruf = %d\n", umur, huruf);
return 0;
}# kompilasi buffer-char-fix.c
gcc buffer-char-fix.c -o buffer-char-fix
# masukan yang sama, hasilnya berbeda
printf "25\nB\n" | ./buffer-char-fixMasukkan umur : Masukkan satu huruf : Umur = 25, kode huruf = 66Dengan %c, kode huruf yang terbaca menjadi 66, yaitu huruf B yang sebenarnya. Spasi pada teks pemformat scanf memiliki makna khusus, yaitu membaca dan membuang semua karakter spasi termasuk pindah baris, sehingga pola %c menjadi idiom lazim saat membaca satu karakter setelah angka.
getchar dan putchar#
Pasangan getchar dan putchar bekerja pada tingkat karakter tunggal. Fungsi getchar mengembalikan kode karakter berikutnya dari buffer masukan, sedangkan putchar mencetak satu karakter. Nilai kembali getchar sengaja bertipe int dan bukan char, agar fungsi dapat mengembalikan penanda khusus bernama EOF (end of file) yang melambangkan habisnya masukan:
// tidak-dijalankan
#include <stdio.h>
int main(void) {
int kode;
printf("Ketik satu huruf lalu tekan Enter: ");
kode = getchar();
putchar(kode);
putchar('\n');
printf("Kode karakter: %d\n", kode);
return 0;
}# kompilasi baca-karakter.c
gcc baca-karakter.c -o baca-karakter
# sodorkan huruf K sebagai masukan
printf "K\n" | ./baca-karakterKetik satu huruf lalu tekan Enter: K
Kode karakter: 75Fungsi getchar membaca dari buffer yang sama dengan scanf, sehingga sisa buffer yang dibahas di atas berlaku pula untuk fungsi ini. Konstanta EOF dideklarasikan pada stdio.h dan nilainya selalu negatif, sehingga ia tidak mungkin tertukar dengan kode karakter yang semuanya bernilai nol ke atas. Argumen putchar juga bertipe int, dan putchar('\n') menjadi cara yang lazim untuk mencetak pindah baris tanpa menyusun teks lengkap. Kombinasi getchar dengan perulangan menjadi cara standar membaca masukan baris demi baris, dan perulangan sendiri dibahas pada modul Alur Kontrol.
Kesalahan umum#
Kesalahan pertama adalah memakai %f pada scanf untuk variabel double. Pada printf penentu %f memang menerima double, tetapi pada scanf nilai pecahan yang ditampung variabel double wajib memakai %lf, dan salah penentu membuat scanf menulis ke area memori yang ukurannya tidak sesuai.
Kesalahan kedua adalah mengabaikan nilai kembali scanf dan langsung memakai variabel penampungnya, padahal masukan yang tidak cocok membuat variabel tetap berisi nilai lama sebagaimana ditunjukkan cek-scanf.c. Kesalahan ketiga terjadi saat teks pemformat memuat karakter selain spasi dan penentu format, misalnya koma pada scanf("%d,%d"). Karakter itu wajib diketik pengguna secara persis, dan pembacaan berhenti bila karakter tersebut tidak ditemui. Semuanya terdeteksi lebih mudah bila nilai kembali scanf selalu diperiksa dan kompilasi selalu dilakukan dengan -Wall.
Ringkasan#
printfmencetak sesuai teks pemformat, dan setiap penentu%harus cocok tipenya, termasuk%zuuntuksizeofserta%%untuk tanda persen.scanfmenerima alamat variabel melalui tanda&, dan nilai kembanya berupa jumlah data yang berhasil dibaca untuk diperiksa.- Masukan ditampung di buffer, sehingga karakter pindah baris sisa
%dakan dibaca oleh%cberikutnya, dan pola%cmelewatinya. getchardanputcharmemproses satu karakter, dengan nilai kembaligetcharbertipeintagar dapat menandaiEOF.
Referensi#
- scanf, fscanf, sscanf — cppreference
— Dokumentasi
scanfbeserta penentu format dan nilai kembaliannya. - Character input/output functions — cppreference
— Dokumentasi
getchar,putchar, dan konstantaEOF. - printf(3) — Linux manual pages
— Man page resmi keluarga
printfdi Linux, termasuk aturan lebar dan presisi.