C’de Temel Giriş – Çıkış Fonksiyonları

Bilgisayarın temel mantığı belirli girdiler verip belirli çıktılar almaya dayanır. Burada girdi cihazları klavye, fare, oyun çubuğu veya tarayıcı gibi cihazlar olabilirken; çıktı cihazları monitör, yazıcı, hoparlör gibi cihazlar olabilir. İşte neredeyse bütün programlama dillerinde de programa girdi verebileceğimiz ve programdan çıktı alabileceğimiz belli başlı fonksiyonlar veya sınıflar bulunur. Genellikle temel giriş/çıkış fonksiyonları (basic input/output functions) olarak adlandırılan bu fonksiyonlar, girdi almak için varsayılan olarak klavye; çıktı almak için de varsayılan olarak monitörü (veya terminal ekranını) kullanırlar.

Programlara girdi (input) vermenin veya programdan çıktı (output) almanın pek çok yolu vardır. Örneğin bir programı komut satırından çalıştırırken, komut satırında programın isminin yanına yazdığınız argümanlar ile ona çeşitli bilgileri aktarabilirsiniz. Bunun yanında programın bir dosyayı açarak, klavyeden o anda bilgi isteyerek veya fare ile çizim yaparak çeşitli girdileri sizden alabilmesi de mümkündür.

Programın çeşitli şekillerde çıktılar vermesi de mümkündür. Örneğin uygulamanız bir konsol uygulaması ise, konsol (veya terminal) ekranına çeşitli bilgileri yazdırarak size bilgi verebilir. Yine dosyaya yazma, ağ üzerinden bir başka bilgisayara veri gönderme, grafiksel arayüzde resim veya metin gösterme gibi çeşitli yollarla da çıktı verebilir.

Bu yazıda bütün bunları C’de nasıl yapacağınızı anlatmayacağım. Sadece şimdiye kadar gördüğümüz sıradan girdi/çıktı fonksiyonlarına genel bir bakış atacağım. Burada sadece C’de konsol ekranına bir şeyler basmak veya konsol ekranından çeşitli verileri almak için kullanılan basit C fonksiyonlarını inceleyeceğiz. Diğer girdi/çıktı işlemlerini daha sonraki yazılarda ayrıntı bir biçimde ele almayı planlıyorum.

C’deki temel giriş/çıkış fonksiyonlarını incelemeden önce, C Standart Kütüphanesi (C Standard Library) adı verilen ve C derleyicileri ile birlikte gelen temel kütüphaneyi ve onun temel bileşenlerini kısaca incelemekte fayda olduğunu düşünüyorum. Çünkü C dilinin söz dizimi kısmından ziyade esas işlevsel kısmı bu kütüphanedir ve bu kütüphane içerisinde program yazarken işinize yarayacak pek çok fonksiyon bulundurmaktadır. Bu kütüphanenin ayrıntılarına yine ilgili yazılarda girilecektir.

C Standart Kütüphanesine Genel Bakış

Programlama dillerinin söz diziminde yer alan özelliklerle her şeyi yapmanız mümkün değildir. Özellikle işletim sistemi ile alakalı konularda ek kütüphanelere ihtiyaç duyarız. Bu kütüphaneler C gibi yapısal programlama dillerinde fonksiyonlardan (functions); C++, Java ve C# gibi nesne yönelimli programlamayı destekleyen dillerde ise sınıflardan (classes) meydana gelebilirler. İşte C programlama dilinde ISO standartlarında da ayrıntılı bir biçimde açıklanan ve programınızda çok çeşitli işlemleri yapmanıza yarayan makroları, fonksiyonları, tür tanımlarını içeren kütüphaneye C Standart Kütüphanesi (C Standard Library) adı verilmektedir.

C Standart Kütüphanesi libc veya ISO C Library gibi isimlerle de anılmaktadır. Bu kütüphane çok çeşitli başlık dosyalarından meydana gelmektedir. Kütüphanede yer alan başlık dosyaları genellikle #include önişlemci bildirimi kullanılarak şu şekilde projeye dahil edilir:

#include <stdio.h>
#include <stdbool.h>
#include <math.h>

Bu bildirimler genellikle kaynak kod dosyasının veya başka bir başlık dosyasının en başında yapılır. Bu başlık dosyaları fonksiyonların kategorilenmiş bir biçimde kütüphanede tutulmasını sağlar. Böylelikle örneğin sadece String işlemlerini kullanacağımız bir koda matematiksel kütüphane fonksiyonlarını eklemek zorunda kalmayız.

C Standart Kütüphanesi’nde yer alan bu başlık dosyalarını ileriki yazılarda fonksiyonlardan bahsederken tam liste halinde verip daha ayrıntılı açıklayacağım. Şimdilik en sık kullanılan başlık dosyalarını bir tabloda listeleyelim:

Başlık Dosyası Açıklama
<assert.h> Programda hata ayıklamaya (debugging) yardımcı olacak çeşitli makro tanımlarını içerir.
<ctype.h> Karakterlerin belli özelliklerini test etmeye yarayan fonksiyonları içerir.
<limits.h> Temel türlerin sınır bilgilerini içeren makroları içerisinde barındırır.
<math.h> Yaygın kullanılan matematiksel fonksiyonları içerir.
<stddef.h> Yaygın kullanılan makro tanımlarını içerir.
<stdint.h> (C99) Sabit boyutlu tamsayı türlerini ve bunlar ile ilgili sınır bilgilerini tanımlayan makroları içerisinde barındırır.
<stdio.h> Standart giriş/çıkış mekanizması ile ilgili türleri ve fonksiyonları içerir.
<stdlib.h> Bellek yönetimi, String dönüşümleri, rastgele sayı üretme ve birçok faydalı iş ile ilgili fonksiyonu içerisinde barındıran önemli bir standart başlık dosyasıdır.
<string.h> String işleme ile ilgili çeşitli fonksiyonları içerisinde barındırır.
<time.h> Zaman ve tarih ile ilgili çeşitli türleri ve fonksiyonları içerir.

Burada bizim ilgileneceğimiz başlık dosyası "<stdio.h>" isimli başlık dosyasıdır. Bu başlık dosyasında standart girdi cihazlarından veri alma ve standart çıktı cihazlarına çıktı vermenin yanında; dosya işleme, formatlı girdi/çıktı, formatsız girdi/çıktı gibi pek çok işlem ile ilgili fonksiyon, makro ve tür bildirimleri yer almaktadır. Elbette biz burada şimdilik sadece standart girdi/çıktı ile ilgili en sık kullanılan fonksiyonları inceleyeceğiz.

Fonksiyonlara Genel Bakış

Standart girdi/çıktı fonksiyonlarının hangi başlık dosyasında yer aldığından ve programınızda bu fonksiyonları kullanmadan önce, kaynak kodunuza bu fonksiyonların bildirimini nasıl ekleyebileceğinizden bahsettik. Ancak şu ana kadar fonksiyon (function) kavramı ile ilgili çok az şeyden bahsettiğimizden, bu konuyu biraz daha açmak istiyorum.

Elbette fonksiyon kavramını ileriki yazılarda çok daha ayrıntılı inceleyeceğim. Ancak şimdilik C Standart Kütüphanesi’nde yer alan fonksiyonları nasıl kullanabileceğimizi anlayabilmeniz açısından bazı açıklamaları yapmam gerekir. C’de fonksiyon (function) aslında bir veya birden fazla deyimden (statement) meydana gelen bir kod parçasını isimlendirerek, onu programın istediğimiz yerinde çağırabilmemize yarayan bir dil özelliğidir. Hemen birkaç örnek verelim:

void print()
{
    ...
}

int process(int a, int b)
{
    int result;
    ...
    return result;
}

double convert(int a)
{
    double result;
    ...
    return result;
}

Yukarıda gördüğünüz print, process ve convert isimli kod bloklarının hepsi birer fonksiyon tanımıdır. Bazı fonksiyonlar parametre (parameter) adı verilen değişkenleri alıp çeşitliliğini artırabilirken (process ve convert gibi), bazıları ise parametre almadan sadece belirlenmiş işlemleri yerine getirirler (print gibi).

Yine bazı fonksiyonlar return anahtar kelimesi ile belirtilen türden bir değer geri döndürürken (process ve convert gibi), bazıları ise void anahtar kelimesi ile tanımlanıp değer geri döndürmeyebilirler (print gibi).

Fonksiyon bildirimi veya tanımı yapmanın ayrıntısına ileriki yazılarda gireceğim. Bizim için burada esas önemli olan bu fonksiyonların nasıl çağrıldığıdır. Tanımı yapılmış olan bir fonksiyonu genel olarak şu şekilde çağırmamız mümkündür:

<fonksiyon_ismi>(<argüman1>, <argüman2>, <argüman3>, …);

C’de bu şekilde yazdığımız ifadelere fonksiyon çağrısı (function call) adı verilirken, bu şekilde kullandığımız parantez operatörüne ise fonksiyon çağrı operatörü (function call operator) adı verimektedir. Eğer fonksiyonun parametresi varsa, bu parametrelere verdiğimiz değerler artık argüman (argument) ismini almaktadır.

Parametre ve argüman kavramları birbirinden farklıdır. Parametre, fonksiyonu bildirme ve tanımlama aşamalarında fonksiyonun alacağı veri türlerine ve bunlara denk gelen değişken isimlerine verilen isimken, argüman ise fonksiyonun çağrılması sırasında fonksiyona gönderilen değerlere verilen isimdir.

Şimdi bir de geri dönüş değeri kavramına bakalım. Geri dönüş değeri (return value) fonksiyona verilen girdiler ile belirli işlemler yapıldıktan sonra çıkan sonuca verilen isimdir. Bazı programlama dillerinde fonksiyondan tek bir geri dönüş değeri alınabilirken, bazılarında birden fazla geri dönüş değeri üretmeye izin verilir. C programlama dilinde, fonksiyondan tek bir geri dönüş değeri alınabilmektedir. Temel kavramları açıkladıktan sonra, az önce tanımını yaptığımız 3 fonksiyonu çağıralım:

print();
int res1 = process(5, 6);
double res2 = convert(5);

Örneklerden de anlayabileceğiniz üzere; geri dönüş değeri üretmeyen fonksiyonların çıktısını bir yere atamazken, geri dönüş değeri üreten fonksiyonların çıktısını uygun türdeki değişkenlere atmamız mümkündür. Elbette atmadan da işlem yapabilirsiniz. Ancak bu sefer de derleyici size bir uyarı ile durumu bildirecektir.

Yine ileriki yazılarda ayrıntılı bir şekilde göreceğimiz fonksiyon bildirimi kavramından da bahsetmekte yarar var. Fonksiyon bildirimi (function declaration) bir fonksiyonun ismini, geri dönüş türünü ve alabileceği parametreleri derleyiciye bildiren bir söz dizimidir. Bu bildirimleri parametre isimlerini yazarak veya yazmayarak da yapabilirsiniz:

void test1(int a);
int test2(int a, int b);
double test3(double, double, double);

Bir de C’de değişken sayıda argüman alan fonksiyon (variadic function) olarak isimlendirilen bir fonksiyon çeşidi bulunmaktadır. Bu fonksiyonun son parametresi "…" ile belirtilerek, belli bir argümandan sonra istenilen sayıda argüman verilebilmektedir. Bu fonksiyonun da ayrıntısına çok fazla girmeden sadece bildirimini ve nasıl çağrılabildiğini göstereceğim (zaten bu yazıda bir örneği olacak):

// Bildirim
int printx(const char* test, ...);
double sum(double a, double b, ...);

// Çağrı
printx("Mustafa Yemural");
printx("Mustafa Yemural", 1, 2, 3);
sum(2.5, 3.6);
sum(4.5, 6.7, .97 .89, 78.8);

Bu örnekte gördüğünüz gibi parametre türü const char* olan bir yere String değişmezi ile değer vermemiz mümkündür. Bunun ayrıntısını elbette sonraki yazılarda açıklayacağız. Fonksiyonlar ile ilgili genel bilgileri verdikten sonra "<stdio.h>" içerisinde yer alan ve burada inceleyeceğimiz 4 fonksiyonun bildirimlerini verip sırayla bunları açıklayalım:

int printf( const char *format, ... );
int scanf( const char *format, ... );
int puts( const char* str );
char *gets( char *str );
char *gets_s( char *str, rsize_t n );

Bu arada açıklamalara geçmeden önce küçük bir not düşmekte fayda var: Bu bildirimler C99’a kadar olan bildirimlerdir, C99 ile bu bildirimlerden bazıları değişikliğe uğramıştır. Değişikliğe uğrayan fonksiyonların yeni bildirimlerini de birazdan göstereceğim. Bir de gets fonksiyonu C11 standartları ile dilden kaldırılıp yerine gets_s fonksiyonu getirilmiştir. Bu durum fonksiyon implementasyonunda yer alan bir güvenlik açığından kaynaklanmaktadır. Her iki fonksiyondan da birazdan bahsedeceğim.

printf Fonksiyonu

Şimdiye kadar çok farklı türlerden değişkenleri kodumuzda nasıl tanımlayabileceğimizi ve onlara sabitlerin de yardımı ile nasıl değerler verip temel işlemleri yapabileceğimizi gördük. Ancak bir noktada programların amacı bir yerden girdi alıp (veya almayıp) bir yerlere bir şekilde çıktı vermektir. Bunu da bu yazıda standart girdi cihazı olan klavye ve standart çıktı olan terminal (konsol) ekranı ile gerçekleştireceğiz.

Elbette girdi/çıktı konusu bunlarla sınırlı değildir. Dosya işlemleri, Stream (Akış) yönetimi, standart hataların yazımı gibi pek çok konu vardır. Ancak bu yazının amacı sadece belli türden değişkenlerin ve sabitlerin değerini belli formatlarla ekrana basmak veya belli formatlarda kullanıcıdan almak olacaktır. İleriki yazılarda ise diğer konuların ayrıntılarına girilecektir.

Öncelikle standart çıktı cihazına formatlı bir şekilde çıktı vermeye yarayan ve C’nin belki de en sık kullanılan fonksiyonu olan printf ile başlayalım. printf fonksiyonunun C99 öncesi bildirimi şöyledir:

int printf( const char *format, ... );

C99 ile şu hale gelmiştir:

int printf( const char *restrict format, ... );

Ancak buradaki restrict anahtar kelimesine şimdilik takılmayın. Bunun hakkında konuşacağız. Bu fonksiyonunun bildirimini incelediğimizde format isminde ve String değişmezi alan bir parametre olduğunu görmekteyiz. Bildiğiniz gibi String’ler programlamada karakterlerin bir araya gelip oluşturduğu yazıları temsil etmektedirler. Daha sonra ise "…" işaretini görmekteyiz. Bu şekilde bu fonksiyonun aslında değişken sayıda argüman alan fonksiyon olduğunu söyleyebiliriz. Örnek kullanımlar:

printf("Hello World"); // ÇIKTI: Hello World
printf("Sayi: %d", 15); // ÇIKTI: Sayi: 15
printf("Dizi: %d %d %d %d", 1, 2, 3, 4); // ÇIKTI: Dizi: 1 2 3 4

C’de String içerisinde kullanılan yüzde (%) işaretine format belirteci (format specifier) adı verildiğini söylemiştik. İşte bu fonksiyonun argüman mantığı aslında format parametresine verilen String içerisinde yer alan "%" sayısı kadar argüman almasıdır. Bu şekilde argümanlar sırasıyla String içerisindeki "%" işaretleri ile yer değiştirilir ve ekrana çıktı verilir.

Format belirtecinden sonra gelen karakter veya karakterler, printf fonksiyonu için özel anlamlara gelmektedir. Format belirtecinin ilk ve en önemli görevi, yerine yazılacak argümanın tür bilgisini printf fonksiyonuna bildirmesidir. Burada sürekli int türünden verileri ekrana basmaya yarayan "%d" format belirtecinden bahsettik. Aslında format belirteçlerinin çok farklı kullanım formları olsa da bunu bu yazıda anlatmayacağım. Şimdilik C23 standartlarına göre tür ile ilgili en sık kullanılan format belirteçlerine ve hangi türe karşılık geldiklerine bakalım:

Format Belirteci Açıklama Kullanılabilecek Veri Türü
%d, %i Her ikisi de değerleri onluk tabanda işaretli tam sayı olarak ekrana basmak için kullanılır. int
%ld, %li Her ikisi de değerleri onluk tabanda işaretli tam sayı olarak ekrana basmak için kullanılır. Daha büyük türler içindir. long
%lld, %lli (C99) Her ikisi de değerleri onluk tabanda işaretli tam sayı olarak ekrana basmak için kullanılır. Daha büyük türler içindir. long long
%o Değeri sekizlik tabanda işaretsiz tam sayı olarak ekrana basmak için kullanılır. unsigned int
%u Değeri onluk tabanda işaretsiz tam sayı olarak ekrana basmak için kullanılır. unsigned int
%lu Değeri onluk tabanda işaretsiz tam sayı olarak ekrana basmak için kullanılır. Daha büyük türler içindir. unsigned long
%llu (C99) Değeri onluk tabanda işaretsiz tam sayı olarak ekrana basmak için kullanılır. Daha büyük türler içindir. unsigned long long
%x Değeri onaltılık tabanda işaretsiz tam sayı olarak ekrana basmak için kullanılır. Onaltılık taban karakterleri küçük harfle basılır (abcdef). unsigned int
%X Değeri onaltılık tabanda işaretsiz tam sayı olarak ekrana basmak için kullanılır. Onaltılık taban karakterleri büyük harfle basılır (ABCDEF). unsigned int
%f, %F Her ikisi de değerleri onluk tabanda kayan noktalı sayı olarak ekrana basmak için kullanılır. double
%Lf, %LF Her ikisi de değerleri onluk tabanda kayan noktalı sayı olarak ekrana basmak için kullanılır. Daha büyük türler içindir. long double
%c Değeri tek bir karakter olarak ekrana basmak için kullanılır. int
%s Değeri String olarak ekrana basmak için kullanılır. char*
%p Değeri bir göstericinin adresi olarak ekrana basmak için kullanılır. void*

Elbette bundan daha fazla çeşitte ve formda format belirteci bulunsa da bunların ayrıntılarına bu yazıda girmeyeceğim. Aslında buradaki amaç şimdiye kadar gördüğümüz temel veri türlerinin çoğunu ekrana basabilmektir. Şimdilik bunlar üzerinden birkaç örnek gösterelim:

int a = 12;
char b = 'B';
double f = 5.23;
printf("Tamsayi degeri=%d, Karakter degeri=%c, Reel degeri=%f", a, b, f);
ÇIKTI
Tamsayı degeri=12, Karakter degeri=B, Reel degeri=5.230000

Gördüğünüz gibi format belirteçlerinin olduğu yerlere değişkenlerin o anki değerleri yerleştirildi ve ekrana basıldı. Burada "%c" ile ekrana karakter basma olayına dikkat etmenizi öneririm. char veri türü özünde verilen karakterlere denk gelen sayıyı tutsa da "%c" ile ekrana basılmaya çalışılınca o sayıya denk gelen karakteri ekrana basar. Şimdi printf ile ilgili tam bir örnek yapalım:

// Code 2.3.1: Code2_3_1.c
// printf Kullanımı
#include <stdio.h>

int main() 
{
    int a = 5, b = 8;
    unsigned int c = 90;
    long d = -5000;
    unsigned long e = 32000;
    long long f = -600000;
    unsigned long long g = 67000;

    float x = 15.875;
    double y = -23.12;
    long double z = 28.122434234;

    printf("Tamsayi degeri a = %d\t b = %i\n", a, b);
    printf("Isaretsiz tamsayi degeri c = %u\n", c);
    printf("Tamsayi degerleri toplami = %d\n", a + b);
    printf("Tamsayi degeri (long) d = %ld\n", d);
    printf("Isaretsiz tamsayi degeri (unsigned long) e = %lu\n", e);
    printf("Tamsayi degeri (long long) f = %lld\n", f);
    printf("Isaretsiz tamsayi degeri (unsigned long long) g = %llu\n", g);

    printf("Float degeri = %f\n", x);
    printf("Double degeri = %f\n", y);
    printf("Long Double degeri = %Lf\n", z);
    printf("Bu satirdan sonra ");
    printf("asagi inmeyi unuttum!");

    return 0;
}
ÇIKTI
Tamsayi degeri a = 5    b = 8
Isaretsiz tamsayi degeri c = 90
Tamsayi degerleri toplami = 13
Tamsayi degeri (long) d = -5000
Isaretsiz tamsayi degeri (unsigned long) e = 32000
Tamsayi degeri (long long) f = -600000
Isaretsiz tamsayi degeri (unsigned long long) g = 67000
Float degeri = 15.875000
Double degeri = -23.120000
Long Double degeri = 28.122434
Bu satirdan sonra asagi inmeyi unuttum!

Burada ilk gözümüze çarpan satır, virgül (,) atomu ile aynı türden birden fazla değişkeni tek satırda tanımlayabildiğimiz satırdır. C’de bu şekilde yapılan bildirime virgülle ayrılan liste (comma separated list) adı verilmektedir. Burada kullanılan virgül bir operatör değil, bir atomdur. Virgül operatörü ile ilgili başka yazıda konuşacağız. Bu atomu değişken tanımlarken kullandığınızda, şu şekilde tanımlama yapmak sık yapılan bir hatadır:

int a = 5, int b = 8; // Derleme zamanında hata!

Burada tanımladığınız değişkenlere ilk değer vermek zorunda da değilsiniz. Aşağıdaki yazımların hepsi geçerlidir:

int a, b, c = 0;
double d, e;
long long f = 10, g = 20;

Bu ufak açıklamadan sonra printf fonksiyonu ile devam edebiliriz. Yukarıda yazdığımız printf satırlarına baktığımızda, format String’i içerisinde sadece format belirteçleri değil, aynı zamanda kaçış dizgelerinin (escape sequences) de kullandığını görürüz. Kaçış dizgelerinden daha önce çeşitli yerlerde bahsetmiştim. Bunlar grafiksel olarak bir sembole karşılık gelmeyen veya çift tırnak içerisinde kullanıldığında özel anlamlar taşıdığı için doğrudan kullanılamayan karakterleri String içerisinde kullanmaya olanak sağlamaktaydı.

Kaçış dizgelerini tek tırnak (‘) içerisinde karakter sabiti olarak veya çift tırnak (") içerisinde String değişmezi olarak kullanmamız mümkündür. Genel olarak bir karakterin sekizlik, onaltılık tabandaki karşılıkları veya Unicode karşılığı da dahil olmak üzere pek çok farklı yazım şekli, kaçış dizgesi olarak bir String veya karakter sabitine yerleştirilebilir. Biz burada bunların hepsinin ayrıntısına girmeyecek, sadece basit karakter dizgelerinin bir listesini vereceğiz:

Kaçış Dizgesi Açıklama ASCII Karşılığı (Onluk Tabanda)
\’ Ekrana tek tırnak (‘) basmak için kullanılır. 39
\” Ekrana çift tırnak (“) basmak için kullanılır. 34
\? Ekrana soru işareti (?) basmak için kullanılır. 63
\\ Ekrana ters eğik çizgi (\\) karakterini basmak için kullanılır. 92
\a Bulunduğu yerde zil sesini çalmak için kullanılır. (Artık pek bir kullanım alanı yoktur.) 7
\b Ekrandan bir karakteri silmek için kulanılır. (Backspace) 8
\f Form besleme (form feed) veya yeni sayfa (new page) karakteri olarak bilinen karakteri ekrana basmak için kullanılır. 12
\n Satır besleme (line feed) veya yeni satır (new line) karakteri olarak bilinen karakteri ekrana basmak için kullanılır. 10
\r Taşıyıcı geri döndürme (carriage return) karakterini ekrana basmak için kullanılır. 13
\t Yatay TAB karakterini ekrana basmak için kullanılır. 9
\v Dikey TAB karakterini ekrana basmak için kullanılır. 11

Burada konuyu çok fazla uzatmamak için bütün karakterlerin ne yaptığını açıklamayacağım. Çünkü hepsinin isimlendirmesi ve kullanımı tarihsel süreçlere dayanmaktadır. Başka bir yazıda bunları ayrıntılı olarak anlatırım. Şimdilik "\t" karakterinin ekranda yatay bir TAB boşluğu bıraktığını ve "\n" karakterinin ekranda bir alt satıra inmek için kullanıldığını bilmeniz yeterlidir. Tek tırnak, çift tırnak, soru işareti ve ters eğik çizgi karakterleri de bazı yerlerde karşımıza çıkabilir. Ancak diğerlerini nadir olarak kodlarda görürsünüz (belki "\r" karakterini sık görebilirsiniz).

Son olarak float türünden değerleri ekrana basma kısmına bakalım. Evet bu örnekte bunun için "%f" format belirtecini kullandık. Bu belirtecin aslında double türden değerleri ekrana basmak için kullanıldığını söylemiştik. Ancak double türü float türünden büyük olduğundan, burada herhangi bir veri kaybı yaşanması mümkün olmaz. Bu nedenle bir problem çıkmaz.

Peki buradan hareketle bir format belirtecinin olduğu yere çok başka bir türden değer verdiğimizde ne olur? Bu aslında çok yakında başka bir yazıda bahsedeceğim tür dönüşümleri ile ilgili bir konudur. Ancak burada şu şekilde özetleyebilirim: Eğer derleyici orada bir veri kaybı olacağını düşünürse, oraya formatlama ile ilgili bir uyarı mesajı basar. Örnek:

long a = 1033453453324234234;
printf("Test: %d", a); // warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long int’ [-Wformat=]

Yukarıdaki kod GCC derleyicisinde derlenmiş ve belitilen uyarı mesajının basılmasına rağmen çalışıp ekrana bir şeyler basmıştır. Ancak bastığı değer yanlıştır. Burada bastığı değerin ne olduğundan ve neye göre o değer olduğundan bahsetmeyeceğim. Bu başka bir yazının konusudur. Ancak değer kaybı olan durumlarda derleyicinin bu gibi uyarılar verdiğine dikkat etmenizde fayda vardır.

Kayan noktalı sayıların ekrana basılması konusunda son bahsedeceğim şey ise virgülden sonraki basamak sayıdır. Gördüğünüz gibi örneğimizde virgülden sonra 6 basamak ekrana basıldı. Ancak format belirteçleri ile bunu değiştirmeniz mümkündür. Eğer "%" karakterinden sonra "." karakterini yazıp ve bundan sonra da sayı belirtip tür bilgisini girerseniz, virgülden sonra kaç basamağın ekrana basılacağını da belirlemiş olursunuz. Örnekler:

float x = 15.875324324324234;
double y = -23.12324324234324;
long double z = 28.122434234234324324234;
double w = 15.9;
double q = 15.2;

printf("x = %.5f\n", x); // 5 basamak (double)
printf("y = %.3f\n", y); // 3 basamak (double)
printf("z = %.2Lf\n", z); // 2 basamak (long double)
printf("w = %f\n", w); // Varsayılan olarak 6 basamak (double)
printf("q = %.8f", q); // 8 basamak (double)
ÇIKTI
x = 15.87532
y = -23.123
z = 28.12
w = 15.900000
q = 15.20000000

Bu şekilde virgülden sonra kaç basamağın ekrana basılacağına da karar verebilirsiniz. q değişkenini ekrana basarken de farkettiğiniz gibi; eğer virgülden sonra olduğundan fazla bir karakter sayısı belirttiyseniz, geri kalan yerlere "0" yerleştirilecektir. Bu kullanımın ve "%" ile yapılabileceklerin çok ayrıntılı ve farklı söz dizimleri bulunmaktadır. Ancak bunların hepsini bir başka yazıda ayrıntılı olarak inceleyeceğiz. Şimdilik bu kadarı bizim işimizi görecektir.

scanf Fonksiyonu

Windows, MacOs veya herhangi bir GNU/Linux dağıtımında bir terminal (konsol) ekranı açtığınızda, genellikle karşınıza kısa bir yazı ve onun yanında yanıp sönen bir çizgi gelmektedir. Buna komut istemi (command prompt) veya kısaca istem (prompt) adı verilmektedir. Bu noktada terminalde çalışan program (ki bu genelde kabuk (shell) isimli programdır) sizden bir girdi vermenizi ister. Siz de girdiyi verip enter tuşuna bastığınızda program işini yapmaya devam eder.

Bu ekrana girdiğiniz girdiler tek bir değer olabilirken genellikle birden fazla ve belli bir formatta olmaktadırlar. Bu nedenle eğer programınızda düz bir şekilde kullanıcıdan gelen bütün veriyi alan bir fonksiyon kullanırsanız, bu veriyi el ile parçalamanız gerekebilir. İşte scanf fonksiyonu bu işinizi bir nevi kolaylaştırır. Kullanıcıdan belli bir formatta girdi alıp bunları programınızda yer alan değişkenlere atayabilir. scanf fonksiyonunun bildirimi şu şekildedir (C99 öncesi):

int scanf( const char *format, ... );

C99 ile şu hale gelmiştir:

int scanf( const char *restrict format, ... );

Buradaki farka şimdilik takılmanıza gerek yoktur. Aslında bildirimi printf fonksiyonuna oldukça benzemektedir. Ancak scnaf fonksiyonunu kullandığınız yerde program kullanıcıdan bir girdi girip enter tuşuna basmasını isteyecektir. Bunu yaptığınızda ise scanf fonksiyonu verilen girdiyi yorumlayıp ilgili değişkene (veya değişkenlere) yeni değerlerini atayacaktır.

Örneğin elimizde x isimli bir int türünden değişken olsun. Bu değişkenin başta bir değere sahip olup olmaması çok önemli değildir. Bu değişkene scanf fonksiyonu ile bir değer atamak istediğimizde şu şekilde bir kod yazmamız gerekir:

int x;
scanf("%d", &x);

Burada scanf fonksiyonu kullanıcıdan bir tam sayı değeri almayı beklemektedir. Bunu aldıktan sonra da o noktada x değişkeninin değeri artık kullanıcıdan gelen değer olur. Elbette burada dikkat etmeniz gereken unsur, x değişkeninin sol tarafında yer alan "&" işraretidir. Peki bu ne anlama gelir?

Nesne belirten ifadelerin sol tarafına konulan ampersand (&) işaretine adres alma operatörü (address-of operator) adı verilir. Bu operatör o nesnenin o anki bellek bölgesinde bulunan başlangıç adresini geri döndürür. Bu şekilde bazı fonksiyonlar bu adres bilgileri ile bu nesnelere değer atayabilirler. Bunun ayrıntısını elbette göstericiler konusunu anlatırken inceleyeceğiz. Şimdilik scanf fonksiyonunun argüman olarak nesnenin kendisini değil, adresini aldığını bilmeniz yeterlidir.

scanf fonksiyonunun girdiyi formatlı bir şekilde alabildiğini söylemiştik. Örneğin bir tam sayı değeri değil de aralarına boşluklar koyarak 3 tam sayı değerini tek seferde alan bir scanf fonksiyonu yazalım:

int x, y, z;
scanf("%d %d %d", &x, &y, &z);

Burada kullanılan format belirteçleri printf fonksiyonu ile oldukça benzerdir. Örneğin siz float türünden değerler almak isterseniz "%f" format belirtecini kullanabilirsiniz. Ancak printf fonksiyonundan farklı olarak double türü burada "%f" ile değil "%lf" ile belirtilmektedir:

int x;
float y;
double z;

scanf("%d %f %lf", &x, &y, &z);

Böyle bir durumda siz ekrana 4 12.8 3434.23 gibi bir değer girip enter tuşuna bastığınızda sırasıyla x değişkenine 4, y değişkenine 12.8 ve z değişkenine 3434.23 değerleri atanacaktır. İşi daha ilginçleştirip değişik girdi formatları da deneyebilirsiniz:

int x;
int y;

scanf("%d ile %d", &x, &y); // GİRDİ: 10 ile 20
printf ("%d, %d", x, y); // ÇIKTI: 10, 20

Yorum satırlarına yazdığım girdi ve çıktı değerlerini inceleyebilirsiniz. Peki Biz diyelim ki bu örnekte ekrana 10 ile 20 değil de 10 ilee 20 yazdık diyelim. Bu durumda scanf fonksiyonu nasıl davranır? İşte bu gibi durumlar scanf fonksiyonunun programınızı mahvetmeye yatkın olduğu durumlardır. İstenilenden alakasız veya hatalı girdiler girdiğinizde, bu durum tanımsız davranışa (undefined behavior) yol açar. Örneğin benim denediğim derleyicide x değişkenine 10, y değişkenine 0 değeri atanmıştır. Ancak bu durum sizde farklı olabilir.

scanf fonksiyonunun kullanımı oldukça rahat olsa da günümüzdeki C uygulamalarında güvenlik sorunlarına yol açabileceğinden (az önceki örnek gibi) bu fonksiyon çok fazla tercih edilmemektedir. Fakat biz örneklerimizi bir yere kadar bu fonksiyon üzerinden yazacağız. Sonuçta şimdilik güvenlik gibi daha derin konuları burada anlatmayacak, bu fonksiyonları sadece sıradan örneklerimizde kullanacağız. Son olarak scanf kullanımı ile ilgili bir örnek daha verelim:

// Code 2.3.2: Code2_3_2.c
// scanf Kullanımı
#include  <stdio.h>

int main()
{
    float a, b;
    char character;

    printf("Lutfen a degerini girin: ");
    scanf("%f",&a);

    printf("Lutfen b degerini girin: ");
    scanf("%f",&b);

    printf("Lutfen bir karakter girin: ");
    scanf(" %c", &character);

    printf("a + b = %f\n", a + b);
    printf("a - b = %f\n", a - b);
    printf("a x b = %f\n", a * b);
    printf("Karakter: %c \t Karakterin ASCII Karsiligi: %d", character, character);

    return 0;
}
ÇIKTI
Lutfen a degerini girin: 12.34
Lutfen b degerini girin: 10.05
Lutfen bir karakter girin: A
a + b = 22.389999
a – b = 2.290000
a x b = 124.017006
Karakter: A    Karakterin ASCII Karsiligi: 65

Bu kodda sadece açıklamamız gereken kısımları açıklayalım. Kullanıcıdan sırasıyla iki float ve bir char türünden girdi alınıyor. Daha sonra float değerlerin sırasıyla toplamı, farkı ve çarpımı hesaplanarak ekrana basılıyor. En son ise alınan karakter ve ASCII olarak sayısal karşığı ekrana basılıyor. Öncelikle float değerlerin nasıl alındığına dikkat edin. Programa float bir değer girerken burada virgül (,) yerine nokta (.) kullandık. Ancak bu durum aslında sistemin yerelleştirme ile ilgili seçeneklerine bağlıdır. Her yerde aynı durum geçerli değildir. Bundan burada bahsetmeyeceğim.

Karakter değerlerinin alınmasına gelecek olursak, ekrana yazılan bir ASCII karakterini dorğudan alıp char türündeki değişkene atadık. Burada dikkat edilmesi gereken bir nokta var. scanf fonksiyonunu üçüncü kez çağırdığımız yerde, fonksiyona girdiğimiz ilk String parametresinde "%c" format belirtecinden önce bir boşluk bıraktık. Bazen karakter veri tiplerini scanf ile almadan önce bir başka girdi alma işlemi yaptığınızda, sıra o fonksiyona geldiğinde direk yeni satır (\n) karakterini girdi olarak aldığını görürsünüz. Bunu engellemek için benim gibi bir boşluk bırakabilirsiniz.

Son printf satırında ise karakterin kendisini ekrana basarken "%c" format belirtecini kullandık. Ancak karakterin ASCII tablosundaki onluk tabandaki değerini ekrana basmak istediğimizde "%d" format belirteci kullandığımıza dikkat edin. Burada girdi formatlamanın ayrıntısına girmeyecek, sadece scanf fonksiyonunda sık kullanılan format belirteçlerini ve anlamlarını bir tabloda verip konuyu sonlandıracağım:

Format Belirteci Açıklama Gereken Veri Türleri
%c Bir karakter veya karakter dizisi almak için kullanılır. char*
%s Boşluk (whitespace) karakteri içermeyen bir string almak için kullanılır char*
%d Onluk tabanda tam sayı değeri almak için kullanılır. int*, unsigned int*
%u Onluk tabanda işaretsiz tam sayı değeri almak için kullanılır. int*, unsigned int*
%o Sekizlik tabanda işaretsiz tam sayı değeri almak için kullanılır. int*, unsigned int*
%x, %X Onaltılık tabanda işaretsiz tam sayı değeri almak için kullanılır. int*, unsigned int*
%f, %F (C99) Onluk tabanda kayan noktalı sayı değeri almak için kullanılır. (float) float*
%lf, %lF (C99) Onluk tabanda kayan noktalı sayı değeri almak için kullanılır. (double) double*
%Lf, %LF (C99) Onluk tabanda kayan noktalı sayı değeri almak için kullanılır. (long double) long double*

Bu arada printf ve scanf fonksiyonları ile ilgili son bir hatırlatma yapmakta fayda var: Çok sık kullanımı olmasa da bu fonskiyonlar birer değer geri döndüren fonksiyonlardır. Basitçe printf fonksiyonu ekrana basılan katakter sayısını geri döndürürken, scanf fonksiyonu ise kullanıcıdan okunan karakter sayısını geri döndürür. Bunların ayrıntılı anlatımı bu yazıda yapılmayacaktır.

puts, gets ve gets_s Fonksiyonları

Bazen ekrana formatlı çıktı vermek yerine tek satırlık bir bilgi veya uyarı metni çıkarmak isteriz. Bu gibi durumlarda elbette printf fonksiyonunu şu şekilde kullanmanız mümkündür:

printf("Bu bir bilgi mesajidir!\n");

Bu gibi durumlarda aslında printf kullanmanıza gerek yoktur. C Standart Kütüphanesi’nde yer alan puts isimli fonksiyon, bunun gibi basit görevleri kolaylıkla yerine getirebilmektedir. Bu fonksiyon aldığı String’i ekrana basar ve bir alt satıra geçer. Fonksiyonun bildirimi şu şekildedir:

int puts( const char* str );

Bu fonksiyonun en büyük özelliklerinden bir tanesi, String’i ekrana bastıktan sonra imleci otomatik olarak bir alt satıra geçirmesidir. Yani String’in sonunda "\n" kaçış karakteri varmış gibi davranır. printf fonksiyonu ise böyle bir davranışta bulunmaz. Örneğin şu şekilde ard arda puts fonksiyonu kullanıldığında şöyle bir çıktı alırsınız:

puts("Merhaba dunya!");
puts("Bugun nasilsin?");
ÇIKTI
Merhaba dunya!
Bugun nasilsin?

Gördüğünüz gibi satır sonunda "\n" kaçış dizgesi varmış gibi davrandı. Bu fonksiyonun olayı aslında bundan ibarettir. Bir de kullanıcıdan String almaya yarayan standart gets fonksiyonundan bahsedelim. Daha String’lerin değişkenlerde nasıl tutulduğundan bahsetmediğimiz için çok fazla ayrıntıya girmeyeceğim. Öncelikle gets fonksiyonunun bildirimini verelim:

char *gets( char *str );

Program gets fonksiyonunun bulunduğu satıra geldiğinde bekler ve kullanıcıdan girdi gelip enter tuşuna basıldığında bu girdiyi parametresinde yer alan değişkene atar. Hemen bir örnek gösterelim:

char name[20];
gets(name);

Yukarıda 20 adet karakter içerebilen bir karakter dizisi tanımlamasını gördük. String’ler de karakterlerden meydana geldiği için bu şekilde dizilerde tutulurlar. Elbette onları tutmanın başka yöntemleri de vardır. String’ler ve diziler ile ilgili ayrıntılı bilgileri ileriki konularda göreceksiniz.

gets fonksiyonu ile bu karakter dizisine ekranda kullanıcının girdiği karakterleri verebiliriz. Ancak bu fonksiyon C11 standartları ile dilden kaldırılmıştır. Bunun en büyük nedeni bu fonksiyonu sınır kontolü (bounds checking) yapma yeteneğinin bulunmaması, yani ekrandan kaç karakter okunacağı bilgisinin fonksiyona verilmemesidir. Bu da fonksiyonu bazı saldırılara açık hale getirmektedir.

C11 standartları ile bu fonksiyonun yerine kütüphaneye gets_s fonksiyonu eklenmiştir. Bu fonksiyon yine yeni satır karakterini görünceye kadar ekran ayazılan yazıyı okur ve bu yazının sadece ikinci parametresine verilen n sayısının 1 eksiği kadarını diziye yazar. Bu fonksiyonun bildirimi şöyledir:

char *gets_s( char *str, rsize_t n );

Kullanımı yine gets fonksiyonuna oldukça benzemektedir:

char name[20];
gets_s(name, 20);

Bu şekilde daha güvenli bir okuma yapabilirsiniz. puts, gets ve gets_s fonksiyonlarının da geri dönüş değerleri ve bazı özelliklerinden bu yazıda bahsedilmeyecektir. Aslında bu yazıda şimdiye kadar bahsettiğim bütün fonksiyonların özellikleri bunlar ile sınırlı olmasa da String’ler, diziler, hata kontrolü gibi konuları anlattıktan sonra bu özelliklere değinmek istiyorum. Şimdilik bu fonksiyonlar ile ilgili bu kadar bilgi yeterlidir.

Bu yazıda genel olarak C’de standart giriş/çıkış fonksiyonlarından bahsettim. Bunlardan bahsetmeden önce elbette C Standart Kütüphanesi’ni ve fonksiyon kavramını kısaca açıklamaya çalıştım. printf, scanf, puts, gets ve gets_s fonksiyonlarını inceleyip kullanım örneklerini göstermeye çalıştım. Özellikle printf ile formatlı çıktı vermenin biraz da olsa detaylarına girmiş oldum. Bu yazıda gördüğünüz tüm fonksiyonların daha ayrıntılı kullanımları sonraki yazılarda açıklanacaktır.

5 1 vote
Article Rating
Subscribe
Bildir
guest

0 Yorum
Eskiler
En Yeniler Beğenilenler