C Programlama Dersi – IX

Bu yazıda öğrenecekleriniz:

Fonksiyonlar

C gibi prosedürel dillerin önemli konularından birisi fonksiyonlardır.Java veya C# gibi dillerde metot (method) ismini alırlar. Adı n’olursa olsun,görevi aynıdır. Bir işlemi birden çok yaptığınızı düşünün. Her seferinde aynı işlemi yapan kodu yazmak oldukça zahmetli olurdu. Fonksiyonlar, bu soruna yönelikyaratılmıştır. Sadece bir kereye mahsus yapılacak işlem tanımlanır. Ardındandilediğiniz kadar, bu fonksiyonu çağırırsınız. Üstelik fonksiyonların yararı bununla da sınırlı değildir.

Fonksiyonlar, modülerlik sağlar. Sayının asallığını test eden bir fonksiyon yazıp,bunun yanlış olduğunu farkederseniz, bütün programı değiştirmeniz gerekmez.Yanlış fonksiyonu düzeltirsiniz ve artık programınız doğru çalışacaktır. Üstelikyazdığınız fonksiyonlara ait kodu, başka programlara taşımanız oldukça basittir.

Fonksiyonlar, çalışmayı kolaylaştırır. Diskten veri okuyup, işleyen; ardındankullanıcıya gösterilmek üzere sonuçları grafik hâline dönüştüren; ve işlemsonucunu diske yazan bir programı baştan aşağı yazarsanız, okuması çok güç olur. Yorum koyarak kodun anlaşılabilirliğini, artırabilirsiniz. Ancakyine de yeterli değildir. İzlenecek en iyi yöntem, programı fonksiyonparçalarına bölmektir. Örneğin, diskten okuma işlemini disten_oku( ) isimli bir fonksiyon yaparken; grafik çizdirme işini grafik_ciz( ) fonksiyonu ve diske yazdırma görevini de diske_yaz( ) fonksiyonuyapabilir. Yarın öbür gün, yazdığınız kodu birileri incelediğinde, sadece ilgilendiğiyapıya göz atarak, aradığını çok daha rahat bulabilir. Binlerce satır içindeçalışmaktansa, parçalara ayrılmış bir yapı herkesin işine gelecektir.

Bu yazımızda, fonksiyonları açıklayacağız.

main( ) Fonksiyonu

Şimdiye kadar yazdığımız bütün kodlarda, main( ) şeklinde bir notasyonkullandık. Bu kullandığımız ifade, aslında main( ) fonksiyonudur.C programlama dilinde, bir kodun çalışması main( ) fonksiyonuniçersinde olup olmamasına bağlıdır. Bir nevi başlangıç noktası olarakdüşünebiliriz. Her programda sadece bir tane main( ) fonksiyonubulunur. Başka fonksiyonların, kütüphanelerin, kod parçalarının çalıştırılması main( ) içersinde direkt veya dolaylı refere edilmesiyle alakalıdır.

main( ) fonksiyonuna dair bilgimizi pekiştirmek için bir programyazalım. Aşağıdaki çizimi inceleyip, C programlama diliyle bunu çizenprogramı oluşturalım.
/\
/ \
/ \
/ \
———-
| |
| |
| |
———-

Ev veya kule benzeri bu şekli aşağıdaki, kod yardımıyla gösterebiliriz:
/* Ev sekli cizen program */
#include<stdio.h>
int main( void )
{
printf( ” /\\ \n” );
printf( ” / \\ \n” );
printf( ” / \\ \n” );
printf( ” / \\\n” );
printf( “———-\n” );
printf( “| |\n” );
printf( “| |\n” );
printf( “| |\n” );
printf( “———-\n” );

return 0;
}

Programın özel bir yanı yok. ‘\’ simgesi özel olduğu için bundan iki taneyazmamız gerekti. Bunu önceki derslerimizde işlemiştik. Bunun dışında kodunherhangi bir zorluğu olmadığı için açıklamaya girmiyorum. Dikkat etmenizgereken tek şey, kodun main( ) fonksiyonuyla çalışması.

Bilgilerimizi özetleyecek olursak; main( ) fonksiyonu özel biryapıdır. Hazırladığımız program, main( ) fonksiyonuyla çalışmayabaşlar. main( ) fonksiyonu içersinde yer almayan kodlar çalışmaz.

Fonksiyon Oluşturma

Kendinize ait fonksiyonlar oluşturabilirsiniz. Oluşturacağınız fonksiyonlar,vereceğiniz işlemi yapmakla görevlidir ve çağrıldıkça tekrar tekrar çalışır.

Yukardaki ev örneğine geri dönelim. Her şeyi main( ) içinde,tek bir yerde yazacağımıza, çatıyı çizen ayrı, katı çizen ayrı birer fonksiyonyazsaydık daha rahat olmaz mıydı? Ya da birden çok kat çizmemiz gerekirse,tek tek kat çizmekle uğraşmaktansa, fonksiyon adını çağırmak daha akıllıcadeğil mi? Bu soruların yanıtı, bizi fonksiyon kullanmaya götürüyor. Şimdi yukarda yazdığımız kodu, iki adet fonksiyon kullanarak yapalım:
/* Ev sekli cizen program */
#include<stdio.h>
// Evin catisini cizen fonksiyon.
void catiyi_ciz( void )
{
printf( ” /\\ \n” );
printf( ” / \\ \n” );
printf( ” / \\ \n” );
printf( ” / \\\n” );
printf( “———-\n” );
}

// Evin katini cizen fonksiyon.
void kat_ciz( void )
{
printf( “| |\n” );
printf( “| |\n” );
printf( “| |\n” );
printf( “———-\n” );
}

// Programin calismasini saglayan
// ana fonksiyon.
int main( void )
{
catiyi_ciz( );
kat_ciz( );

return 0;
}

Yazdığımız bu kod, ilk başta elde ettiğimiz çıktının aynısını verir. Ama önemlibir fark içerir:. Bu programla birlikte ilk defa fonksiyon kullanmış olduk!

Fonksiyon kullanmanın, aynı şeyleri baştan yazma zahmetinden kurtaracağındanbahsetmiştik. Diyelim ki bize birden çok kat gerekiyor. O zaman kat_ciz( ) fonksiyonunu gereken sayıda çağırmamız yeterlidir.
/* Ev sekli cizen program */
#include<stdio.h>
// Evin catisini cizen fonksiyon.
void catiyi_ciz( void )
{
printf( ” /\\ \n” );
printf( ” / \\ \n” );
printf( ” / \\ \n” );
printf( ” / \\\n” );
printf( “———-\n” );
}

// Evin katini cizen fonksiyon.
void kat_ciz( void )
{
printf( “| |\n” );
printf( “| |\n” );
printf( “| |\n” );
printf( “———-\n” );
}

// Programin calismasini saglayan
// ana fonksiyon.
int main( void )
{
catiyi_ciz( );
// 3 adet kat ciziliyor.
kat_ciz( );
kat_ciz( );
kat_ciz( );

return 0;
}

Yukarda yazılı kod, bir üstekinden pek farklı durmasa bile, bu sefer üç katlı bir evin çıktısını elde etmiş olacaksınız.

Yaptığımız örneklerde, kullanılan void ifadesi dikkatinizi çekmiş olabilir. İngilizce bir kelime olan void, boş/geçersiz anlamındadır.C programlama dilinde de buna benzer bir anlam taşır. kat_ciz( ); fonksiyonuna bakalım. Yapacağı iş için herhangi bir değer alması gerekmiyor.Örneğin verilen sayının asallığını test eden bir fonksiyon yazsaydık,bir değişken almamız gerekirdi. Ancak bu örnekte gördüğümüz kat_ciz( ); fonksiyonu, dışardan bir değere gerek duymaz. Eğer bir fonksiyon, çalışmak içindışardan gelecek bir değere ihtiyaç duymuyorsa, fonksiyon adını yazdıktansonra parantez içini boş bırakabiliriz. Ya da void yazarak, fonksiyonunbir değer almayacağını belirtiriz. ( Sürekli olarak main( ) fonksiyonuna void koymamızın sebebi de bundandır; fonksiyon argümanalmaz. ) İkinci yöntem daha uygun olmakla birlikte, birinci yöntemikullanmanın bir mahsuru yok. Aşağıda bulunan iki fonksiyon aynı şekilde çalışır:

// Evin katini cizen fonksiyon.
// void var

void kat_ciz( void )
{
printf( “| |\n” );
printf( “| |\n” );
printf( “| |\n” );
printf( “———-\n” );
}

// Evin katini cizen fonksiyon.
// void yok

void kat_ciz( )
{
printf( “| |\n” );
printf( “| |\n” );
printf( “| |\n” );
printf( “———-\n” );
}

void ifadesinin, değer alınmayacağını göstermek için kullanıldığını gördünüz.Bir de fonksiyonun değer döndürme durumu vardır. Yazdığınız fonksiyonyapacağı işlemler sonucunda, çağrıldığı noktaya bir değer gönderebilir.Değer döndürme konusunu, daha sonra işleyeceğiz. Şimdilik değer döndürmemedurumuna bakalım.

Yukarda kullanılan fonksiyonlar, geriye bir değer döndürmemektedir. Birfonksiyonun geriye değer döndürmeyeceğini belirtmek için, void ifadesinifonksiyon adından önce yazarız. Böyleyece geriye bir değer dönmeyeceğibelirtilir.

Argüman Aktarımı

Daha önce ki örneklerimiz de, fonksiyonlar dışardan değer almıyordu. Bu yüzdenparantez içlerini boş bırakmayı ya da void ifadesini kullanmayı görmüştük. Herzaman böyle olması gerekmez; fonksiyonlar dışardan değer alabilirler.

Fonksiyonu tanımlarken, fonksiyona nasıl bir değerin gönderileceğini belirtiriz.Gönderilecek değerin hangi değişken tipinde olduğunu ve değişken adını yazarız. Fonksiyonu tanımlarken, yazdığımız bu değişkenlere ‘parametre'(parameter) denir. Argüman (argument) ise, parametrelere değer atamasındakullandığımız değerlerdir. Biraz karmaşık mı geldi? O zaman bir örnekleaçıklayalım.

Daha önce çizdiğimiz ev örneğini biraz geliştirelim. Bu sefer, evin duvarları düz çizgi olmasın; kullanıcı istediği karakterlerle, evin duvarlarını çizdirsin.
/* Ev sekli cizen program */
#include<stdio.h>
// Evin catisini cizen fonksiyon.
void catiyi_ciz( void )
{
printf( ” /\\ \n” );
printf( ” / \\ \n” );
printf( ” / \\ \n” );
printf( ” / \\\n” );
printf( “———-\n” );
}

// Evin katini cizen fonksiyon.
// sol ve sag degiskenleri fonksiyon
// parametreleridir.
void kat_ciz( char sol, char sag )
{
printf( “%c %c\n”, sol, sag );
printf( “%c %c\n”, sol, sag );
printf( “%c %c\n”, sol, sag );
printf( “———-\n” );
}

// Programin calismasini saglayan
// ana fonksiyon.
int main( void )
{
char sol_duvar, sag_duvar;
printf( “Kullanılacak karakterler> ” );
scanf( “%c%c”,&sol_duvar, &sag_duvar );
catiyi_ciz( );

// sol_duvar ve sag_duvar, fonksiyona
// giden argumanlardir.
kat_ciz( sol_duvar, sag_duvar );
kat_ciz( sol_duvar, sag_duvar );
kat_ciz( sol_duvar, sag_duvar );

return 0;
}

Argümanların değer olduğunu unutmamak gerekiyor. Yukardaki örneğimizden,değişken olması gerektiği yanılgısına düşebilirsiniz. Ancak bir fonksiyonadeğer aktarırken, direkt olarak değeri de yazabilirsiniz. Programı değiştirip, sol_duvar ve sag_duvar değişkenleri yerine, ‘*’ simgesini koyun. Şeklin duvarları, yıldız işaretinden oluşacaktır.

Yazdığımız kat_ciz( ) fonksiyonunu incelemek için, aşağıdabulunan grafiğe göz atabilirsiniz:

[Void Function Structure]

Şimdi de başka bir örnek yapalım ve verilen herhangi bir sayının tek mi yoksa çiftmi olduğuna karar veren bir fonksiyon oluşturalım:
/* Sayının tek veya çift olmasını
kontrol eder. */
#include<stdio.h>
void tek_mi_cift_mi( int sayi )
{
if( sayi%2 == 0 )
printf( “%d, çift bir sayıdır.\n”, sayi );
else
printf( “%d, tek bir sayıdır.\n”, sayi );
}
int main( void )
{
int girilen_sayi;
printf( “Lütfen bir sayı giriniz> ” );
scanf( “%d”,&girilen_sayi );
tek_mi_cift_mi( girilen_sayi );

return 0;
}

Yerel ( Local ) ve Global Değişkenler

Kendi oluşturacağınız fonksiyon içersinde, main( ) fonksiyonunda kiher şeyi yapabilirsiniz. Değişken tanımlayabilir, fonksiyon içinden başkafonksiyonları çağırabilir veya dilediğiniz operatörü kullanabilirsiniz.Ancak değişken tanımlamalarıyla ilgili göz ardı etmememiz gereken bir konubulunuyor. Bir fonksiyon içersinde tanımladığınız değişkenler, sadeceo fonksiyon içersinde tanımlıdır. main( ) veya kendinize aitfonksiyonlardan bu değişkenlere ulaşmamız mümkün değildir. main( )içinde tanımladığınız a isimli değişkenle, kendinize özgü tanımladığınız kup_hesapla( ) içersinde tanımlanmış a isimli değişken,bellekte farklı adresleri işaret eder. Dolayısıyla değişkenlerin arasında hiçbirilişki yoktur. kup_hesapla( ) içersinde geçen a değişkenindeyapacağınız değişiklik, main( ) fonksiyonundakini etkilemez. Keza,tersi de geçerlidir. Şu ana kadar yaptığımız bütün örneklerde, değişkenleriyerel olarak tanımladığımızı belirtelim.

Yerel değişken dışında, bir de global değişken tipi bulunur. Programınherhangi bir noktasından erişebileceğiniz ve nerede olursa olsun aynı bellek adresini işaret eden değişkenler, global değişkenlerdir. Hep aynı bellekadresi söz konusu olduğun için, programın herhangi bir noktasında yapacağınızdeğişiklik, global değişkenin geçtiği bütün yerleri etkiler. Aşağıdaki örneğiinceleyelim:
#include<stdio.h>
// Verilen sayinin karesini hesaplar
void kare_hesapla( int sayi )
{
// kare_hesapla fonksiyonunda
// a degiskeni tanimliyoruz.
int a;
a = sayi * sayi;
printf( “Sayının karesi\t: %d\n”, a );
}

// Verilen sayinin kupunu hesaplar
void kup_hesapla( int sayi )
{
// kup_hesapla fonksiyonunda
// a degiskeni tanimliyoruz.
int a;
a = sayi * sayi * sayi;
printf( “Sayının küpü\t: %d\n”, a );
}

int main( void )
{
// main( ) fonksiyonunda
// a degiskeni tanimliyoruz.
int a;
printf( “Sayı giriniz> “);
scanf( “%d”,&a );
printf( “Girdiğiniz sayı\t: %d\n”, a );
kare_hesapla( a );
// Eger a degiskeni lokal olmasaydi,
// kare_hesapla fonksiyonundan sonra,
// a’nin degeri bozulur ve kup yanlis
// hesaplanirdi.
kup_hesapla( a );
return 0;
}

Kod arasına konulan yorumlarda görebileceğiniz gibi, değişkenler lokal olaraktanımlanmasa, a’nin değeri farklı olurdu. Sayının karesini bulduktan sonra,küpünü yanlış hesaplardık. Değişkenler lokal olduğu için, her aşamada farklı bir değişken tanımlandı ve sorun çıkartacak bir durum olmadı. Benzer bir programı global değişkenler için inceleyelim:
#include<stdio.h>
int sonuc = 0;

// Verilen sayinin karesini hesaplayip,
// global ‘sonuc’ degiskenine yazar.
void kare_hesapla( int sayi )
{
sonuc = sayi * sayi;
}

int main( void )
{
// main( ) fonksiyonunda
// a degiskeni tanimliyoruz.
int a;
printf( “Sayı giriniz> “);
scanf( “%d”,&a );
printf( “Girdiğiniz sayı\t: %d\n”, a );
kare_hesapla( a );
printf(“Sayının karesi\t: %d\n”, sonuc );
return 0;
}

Gördüğünüz gibi, sonuc isimli değişken her iki fonksiyonun dışında biralanda, programın en başında tanımlanıyor. Bu sayede, fonksiyon bağımsız birdeğişken elde ediyoruz.

Global değişkenlerle ilgili dikkat etmemiz gereken bir iki ufak nokta bulunuyor:Global bir değişkeni fonksiyonların dışında bir alanda tanımlarız. Tanımladığımıznoktanın altında kalan bütün fonksiyonlar, bu değişkeni tanır. Fakat tanımlanmanoktasının üstünde kalan fonksiyonlar, değişkeni görmez. Bu yüzden, bütün programdageçerli olacak gerçek anlamda global bir değişken istiyorsanız, #includeifadelerinin ardından tanımlamayı yapmanız gerekir. Aynı ismi taşıyan yerel veglobal değişkenleri aynı anda kullanıyorsak, iş birazcık farklılaşır.

Bir fonksiyon içersinde, Global değişkenle aynı isimde, yerel bir değişkenbulunduruyorsanız, bu durumda lokal değişkenle işlem yapılır. Açıkcası, sınırsızsayıda değişken ismi vermek mümkünken, global değişkenle aynı adı vermenin uygunolduğunu düşünmüyorum. Program akışını takip etmeyi zorlaştıracağından, ortakisimlerden kaçınmak daha akıllıca.

Lokal ve global değişkenlere dair son bir not; lokal değişkenlerin sadecefonksiyona özgü olması gerekmez. Bir fonksiyon içersinde ‘daha lokal’ değişkenleritanımlayabilirsiniz. Internet’te bulduğum aşağıdaki programı incelerseniz,konuyu anlamanız açısından yardımcı olacaktır.
#include<stdio.h>
int main( void )
{
int i = 4;
int j = 10;

i++;

if( j > 0 ){
printf(“i : %d\n”,i); /* ‘main’ icinde tanımlanmis ‘i’ degiskeni */
}

if (j > 0){
int i=100; /* ‘i’ sadece bu if blogunda gecerli
olmak uzere tanimlaniyor. */
printf(“i : %d\n”,i);
} /* if blogunda tanimlanan ve 100 degerini
tasiyan ‘i’ degiskeni burada sonlaniyor. */

printf(“i : %d\n”,i); /* En basta tanimlanan ve 5 degerini tasiyan
‘i’ degiskenine donuyoruz */
}

return İfadesi

Yazımızın üst kısımlarında fonksiyonların geriye değer döndürebileceğindenbahsetmiştik. Bir fonksiyonun geriye değer döndürüp döndürmemesi, o fonksiyonugenel yapı içersinde nasıl kullanacağınıza bağlıdır. Eğer hazırlayacağınızfonksiyonun, çalışıp, üreteceği sonuçları başka yerlerde kullanmayacaksanız,fonksiyondan geriye değer dönmesi gerekmez. Ancak fonksiyonunürettiği sonuçları, bir değişkene atayıp kullanacaksanız, o zaman fonksiyonungeriye değer döndürmesi gerekir. Bunun için ‘return’ ifadesini kullanırız.

Daha önce gördüğümüz geriye değer döndürmeyen fonksiyonları tanımlarken, başına void koyuyorduk. Geriye değer döndüren fonksiyonlar içinse, hangitipte değer dönecekse, onu fonksiyon adının başına koyuyoruz. Diyelim kifonksiyonumuz bir tamsayı döndürecekse, int; bir karakter döndürecekse char diye belirtiyoruz. Fonksiyon içersinden neyin döneceğine gelince,burada da return ifadesi devreye giriyor.

Fonksiyonun neresinde olduğu farketmez, return sonuç döndürmek üzere kullanılır.Döndüreceği sonuç, elle girilmiş veya değişkene ait bir değer olabilir. Önemliolan döndürülecek değişken tipiyle, döndürülmesi vaad edilen değişken tipininbirbirinden farklı olmamasıdır. Yani int kup_hesapla( ) şeklinde bir tanımlama yaptıysanız, double tipinde bir sonucu döndüremezsiniz.Daha doğrusu döndürebilirsiniz ama program yanlış çalışır. Tip uyuşmazlığı genel hatalardan biri olduğu için, titiz davranmanızı öğütlerim.

Şimdi return ifadesini kullanabileceğimiz, bir program yapalım. Kullanıcıdan birsayı girilmesi istensin; girilen sayı asal değilse, tekrar ve tekrar sayı girmesigereksin:
#include<stdio.h>

// Verilen sayinin asal olup olmadigina
// bakar. Sayi asalsa, geriye 1 aksi hâlde
// 0 degeri doner.
int sayi_asal_mi( int sayi )
{
int i;
for( i = 2; i <= sayi/2; i++ ) {
// Sayi asal degilse, i’ye tam olarak
// bolunur.
if( sayi%i == 0 ) return 0;
}
// Verilen sayi simdiye kadar hicbir
// sayiya bolunmediyse, asaldir ve
// geriye 1 doner.
return 1;
}

// main fonksiyonu
int main( void )
{
int girilen_sayi;
int test_sonucu;
do{
printf( “Lütfen bir sayı giriniz> ” );
scanf( “%d”,&girilen_sayi );
test_sonucu = sayi_asal_mi( girilen_sayi );
if( !test_sonucu )
printf(“Girilen sayı asal değildir!\n”);
} while( !test_sonucu );
printf( “Girilen sayı asaldır!\n” );

return 0;
}

Dikkat edilmesi gereken bir diğer konu; return koyduğunuz yerde, fonksiyonun derhâlsonlanmasıdır. Fonksiyonun kalan kısmı çalışmaz. Geriye değer döndürmeyefonksiyonlar için de aynı durum geçerlidir, onlarda da return ifadesinikullanabilirsiniz. Değer döndürsün, döndürmesin yazdığınız fonksiyonda herhangibir yere ‘return;’ yazın. Fonksiyonun bu noktadan itibaren çalışmayı kestiğini farkedeceksiniz. Bu fonksiyonu çalıştırmanın uygun olmadığı şartlarda,kullanabileceğiniz bir yöntemdir. Bir kontrol ekranında, kullanıcı adı ve/veya şifresini yanlış girildiğinde, programın çalışmasını anında kesmek isteyebilirsiniz.Böyle bir durumda ‘return;’ kullanılabilir.

Dersimizi burada tamamlayıp örnek sorulara geçmeden önce, fonksiyonlara aitgenel yapıyı incelemenizi öneririm.
donus_tipi fonksiyon_adi( alacagi_arguman[lar] )
{
.
.
FONKSİYON İÇERİĞİ
( YAPILACAK İŞLEMLER )
.
.
[return deger]
}

NOT: Köşeli parantez gördüğünüz yerler opsiyoneldir. Her fonksiyonda yazılması gerekmez.Ancak oluşturacağınız fonksiyon yapısına bağlı olarak yazılması şartta olabilir.Mesela dönüş tipi olarak void dışında bir değişken tipi belirlediyseniz, returnkoymanız gerekir.

Örnek Sorular

Soru 1: Kendisine argüman olarak verilen bir tamsayıyı tersine çevirip,sonucu ekrana yazacak bir fonksiyon yazınız.

Cevap için tıklayınız…

Soru 2: Kendisine argüman olarak verilen bir tamsayıyı tersine çevirip,sonucu döndürecek bir fonksiyon yazınız.

Cevap için tıklayınız…

Soru 3: En ve boy parametrelerine göre, ‘*’ simgeleriyledikdörtgen çizen bir fonksiyon yazınız.

Cevap için tıklayınız…

Soru 4: Kendisine verilen iki sayının OBEB (Ortak Bölenlerin En Büyüğü)değerini hesaplayıp, geriye döndüren fonksiyonu yazınız.

Cevap için tıklayınız…

Soru 5: Kendisine verilen iki sayının OKEK (Ortak Katların En Küçüğü)değerini hesaplayıp, geriye döndüren fonksiyonu yazınız.

Cevap için tıklayınız…