C Programlama Dersi – 8

Güncelleme 15/06/2020

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önelik yaratılmıştır. Sadece bir kereye mahsus yapılacak işlem tanımlanır. Ardından dilediğ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. Üstelik yazdığı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ından kullanıcıya gösterilmek üzere sonuçları grafik hâline dönüştüren; ve işlem sonucunu diske yazan bir programı baştan aşağı yazarsanız, okuması çok güç olur. Yorum koyarak kodun anlaşılabilirliğini, artırabilirsiniz. Ancak yine de yeterli değildir. İzlenecek en iyi yöntem, programı fonksiyon parç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(  ) fonksiyonu yapabilir. Yarın öbür gün, yazdığınız kodu birileri incelediğinde, sadece ilgilendiği yapı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 notasyon kullandık. Bu kullandığımız ifade, aslında main(  ) fonksiyonudur. C programlama dilinde, bir kodun çalışması main(  ) fonksiyonun içersinde olup olmamasına bağlıdır. Bir nevi başlangıç noktası olarak düşünebiliriz. Her programda sadece bir tane main(  ) fonksiyonu bulunur. 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 program yazalım. Aşağıdaki çizimi inceleyip, C programlama diliyle bunu çizen programı 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 tane yazmamız gerekti. Bunu önceki derslerimizde işlemiştik. Bunun dışında kodun herhangi bir zorluğu olmadığı için açıklamaya girmiyorum. Dikkat etmeniz gereken tek şey, kodun main(  ) fonksiyonuyla çalışması.

Bilgilerimizi özetleyecek olursak; main(  ) fonksiyonu özel bir yapıdır. Hazırladığımız program, main(  ) fonksiyonuyla çalışmaya baş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 fonksiyon yazsaydı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ıca değ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 önemli bir fark içerir:. Bu programla birlikte ilk defa fonksiyon kullanmış olduk!

Fonksiyon kullanmanın, aynı şeyleri baştan yazma zahmetinden kurtaracağından bahsetmiş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çin dışardan gelecek bir değere ihtiyaç duymuyorsa, fonksiyon adını yazdıktan sonra parantez içini boş bırakabiliriz. Ya da void yazarak, fonksiyonun bir değer almayacağını belirtiriz. ( Sürekli olarak main(  ) fonksiyonuna void koymamızın sebebi de bundandır; fonksiyon argüman almaz. ) İkinci yöntem daha uygun olmakla birlikte, birinci yöntemi kullanmanı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 fonksiyon yapacağı 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ürmeme durumuna bakalım.

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

Argüman Aktarımı

Daha önce ki örneklerimiz de, fonksiyonlar dışardan değer almıyordu. Bu yüzden parantez içlerini boş bırakmayı ya da void ifadesini kullanmayı görmüştük. Her zaman 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ında kullandığımız değerlerdir. Biraz karmaşık mı geldi? O zaman bir örnekle açı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        %cn", sol, sag );
	printf( "%c        %cn", sol, sag );
	printf( "%c        %cn", 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 fonksiyona değ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ğıda bulunan 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 çift mi 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 ki her şeyi yapabilirsiniz. Değişken tanımlayabilir, fonksiyon içinden başka fonksiyonları çağırabilir veya dilediğiniz operatörü kullanabilirsiniz. Ancak değişken tanımlamalarıyla ilgili göz ardı etmememiz gereken bir konu bulunuyor. Bir fonksiyon içersinde tanımladığınız değişkenler, sadece o fonksiyon içersinde tanımlıdır. main(  ) veya kendinize ait fonksiyonlardan 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çbir ilişki yoktur. kup_hesapla(  ) içersinde geçen a değişkeninde yapacağı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şkenleri yerel olarak tanımladığımızı belirtelim.

Yerel değişken dışında, bir de global değişken tipi bulunur. Programın herhangi 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ı bellek adresi söz konusu olduğun için, programın herhangi bir noktasında yapacağınız değişiklik, global değişkenin geçtiği bütün yerleri etkiler. Aşağıdaki örneği inceleyelim:

#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 karesit: %dn", 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: %dn", a );
}

int main( void )
{
	// main( ) fonksiyonunda 
	// a degiskeni tanimliyoruz. 
	int a;
	printf( "Sayı giriniz> ");
	scanf( "%d",&a );
	printf( "Girdiğiniz sayıt: %dn", 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 olarak tanı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: %dn", a );
	kare_hesapla( a );
	printf("Sayının karesit: %dn", sonuc );
	return 0;
}

Gördüğünüz gibi, sonuc isimli değişken her iki fonksiyonun dışında bir alanda, programın en başında tanımlanıyor. Bu sayede, fonksiyon bağımsız bir değ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ız noktanın altında kalan bütün fonksiyonlar, bu değişkeni tanır. Fakat tanımlanma noktasının üstünde kalan fonksiyonlar, değişkeni görmez. Bu yüzden, bütün programda geçerli olacak gerçek anlamda global bir değişken istiyorsanız, #include ifadelerinin ardından tanımlamayı yapmanız gerekir. Aynı ismi taşıyan yerel ve global 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şken bulunduruyorsanız, bu durumda lokal değişkenle işlem yapılır. Açıkcası, sınırsız sayıda değişken ismi vermek mümkünken, global değişkenle aynı adı vermenin uygun olduğunu düşünmüyorum. Program akışını takip etmeyi zorlaştıracağından, ortak isimlerden kaçınmak daha akıllıca.

Lokal ve global değişkenlere dair son bir not; lokal değişkenlerin sadece fonksiyona özgü olması gerekmez. Bir fonksiyon içersinde ‘daha lokal’ değişkenleri tanı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 : %dn",i);		/* 'main' icinde tanımlanmis 'i' degiskeni */
	}
     
	if (j > 0){
		int i=100;		/* 'i' sadece bu if blogunda gecerli 
					   olmak uzere tanimlaniyor. */ 
		printf("i : %dn",i);
	}                              	/* if blogunda tanimlanan ve 100 degerini 
					tasiyan 'i' degiskeni burada sonlaniyor. */
     
	printf("i : %dn",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ğinden bahsetmiştik. Bir fonksiyonun geriye değer döndürüp döndürmemesi, o fonksiyonu genel yapı içersinde nasıl kullanacağınıza bağlıdır. Eğer hazırlayacağınız fonksiyonun, ç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 fonksiyonun geriye 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, hangi tipte değer dönecekse, onu fonksiyon adının başına koyuyoruz. Diyelim ki fonksiyonumuz 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. Önemli olan döndürülecek değişken tipiyle, döndürülmesi vaad edilen değişken tipinin birbirinden 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 bir sayı girilmesi istensin; girilen sayı asal değilse, tekrar ve tekrar sayı girmesi gereksin:

#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âl sonlanmasıdır. Fonksiyonun kalan kısmı çalışmaz. Geriye değer döndürmeye fonksiyonlar için de aynı durum geçerlidir, onlarda da return ifadesini kullanabilirsiniz. Değer döndürsün, döndürmesin yazdığınız fonksiyonda herhangi bir 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 ait genel 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, return koymanız gerekir.

Yazar: Ali Celal

5f59ca35fd9ac7f00cde62f0b0cd0d07?s=90&d=blank&r=g- Elektronik Mühendisi
- E.Ü. Tıp Fakültesi Kalibrasyon Sorumlusu Test kontrol ve kalibrasyon sorumlu müdürü (Sağ.Bak. ÜTS)
- X-Işınlı Görüntüleme Sistemleri Test Kontrol ve Kalibrasyon Uzmanı (Sağ.Bak.)
- Usta Öğretici (MEB)
- Hatalı veya kaldırılmasını istediğiniz sayfaları diyot.net@gmail.com bildirin