Fonksiyonlar
Bölüm 5: Fonksiyonlar - Kodun Yapı Taşları: Derinlemesine Bir Bakış
Fonksiyonlar, C programlama dilinde kodun temel yapı taşlarıdır. Karmaşık problemleri daha küçük, yönetilebilir ve yeniden kullanılabilir parçalara ayırmamızı sağlarlar. Bu bölümde, fonksiyonların nasıl tanımlandığını, çağrıldığını ve kullanıldığını detaylı olarak inceleyeceğiz. Ayrıca fonksiyon prototiplerinin önemini, değişkenlerin kapsamını ve kullanımını öğreneceğiz.
5.1 Fonksiyon Tanımlama: Bir Görevi Paketleme
Fonksiyon tanımlamak, belirli bir görevi yerine getiren bir kod bloğu oluşturmak anlamına gelir. C dilinde bir fonksiyon tanımlamak için şu adımları izleriz:
- Geri Dönüş Tipi (Return Type): Fonksiyonun döndüreceği değerin veri tipini belirtir. Fonksiyon bir değer döndürmüyorsa, geri dönüş tipi
void
olarak belirtilir. - Fonksiyon Adı (Function Name): Fonksiyonun adı, fonksiyona erişmek için kullanılır. Fonksiyon adları, değişken isimlendirme kurallarına uymalıdır.
- Parametre Listesi (Parameter List): Fonksiyona geçirilen değerlerin listesidir. Parametreler, fonksiyon içinde kullanılacak değişkenlerdir.
- Fonksiyon Gövdesi (Function Body): Fonksiyonun gerçekleştirdiği işlemleri içeren kod bloğudur.
Fonksiyon Tanımlama Sözdizimi:
(parametre_listesi) {
geri_dönüş_tipi fonksiyon_adı// Fonksiyon gövdesi (işlemler)
return değer; // İsteğe bağlı
}
Örnek 1: İki Sayıyı Toplayan Fonksiyon
int topla(int sayi1, int sayi2) {
int toplam = sayi1 + sayi2;
return toplam;
}
Bu örnekte, topla
adında bir fonksiyon tanımladık. Fonksiyon, iki tam sayı (sayi1
ve sayi2
) alır ve bunları toplar. Sonucu, toplam
değişkenine atar ve return
ifadesi ile geri döndürür.
Örnek 2: Faktöriyel Hesaplayan Fonksiyon
int faktoriyel(int sayi) {
if (sayi == 0) {
return 1;
} else {
return sayi * faktoriyel(sayi - 1);
}
}
Bu örnekte, faktoriyel
adında bir fonksiyon tanımladık. Fonksiyon, bir tam sayı (sayi
) alır ve bu sayının faktöriyelini hesaplar. Bu fonksiyon, özyinelemeli (recursive) bir fonksiyondur, yani kendini çağırır.
Daha Fazla Fonksiyon Tanımlama Örneği:
- Bir sayının karesini hesaplayan fonksiyon:
int kare(int sayi) {
return sayi * sayi;
}
- Bir sayının tek mi çift mi olduğunu kontrol eden fonksiyon:
int tekMiCiftMi(int sayi) {
if (sayi % 2 == 0) {
return 1; // Çift
} else {
return 0; // Tek
}
}
- Bir string’in uzunluğunu hesaplayan fonksiyon (strlen fonksiyonunu kullanmadan):
int stringUzunlugu(char str[]) {
int uzunluk = 0;
while (str[uzunluk] != '\0') {
++;
uzunluk}
return uzunluk;
}
- Void Fonksiyon Örneği: Merhaba Dünya Yazdırma
void merhabaDunyaYazdir() {
("Merhaba Dünya!\n");
printf}
Bu örnekte, merhabaDunyaYazdir
fonksiyonu, ekrana “Merhaba Dünya!” mesajını yazdırır. Fonksiyon hiçbir değer döndürmediği için void
geri dönüş tipine sahiptir ve return
ifadesi kullanılmaz.
5.2 Fonksiyon Çağırma: Görevi Başlatma
Bir fonksiyonu çağırmak için, fonksiyonun adını ve parametrelerini (varsa) belirtmemiz gerekir. Fonksiyon çağrıldığında, program akışı fonksiyona geçer ve fonksiyon gövdesi çalıştırılır.
Fonksiyon Çağırma Sözdizimi:
(parametreler); fonksiyon_adı
Örnek: topla
ve faktoriyel
Fonksiyonlarını Çağırma
int main() {
int toplam = topla(5, 3); // toplam değişkenine 8 değeri atanır.
int fakt = faktoriyel(4); // fakt değişkenine 24 değeri atanır.
("Toplam: %d\n", toplam);
printf("Faktoriyel: %d\n", fakt);
printf
return 0;
}
Daha Fazla Fonksiyon Çağırma Örneği:
int main() {
int sayi = 5;
int karesi = kare(sayi); // karesi değişkenine 25 değeri atanır.
int sayi2 = 6;
int tekCift = tekMiCiftMi(sayi2); // tekCift değişkenine 1 (Çift) değeri atanır.
char metin[] = "Merhaba";
int uzunluk = stringUzunlugu(metin); // uzunluk değişkenine 7 değeri atanır.
("Karesi: %d\n", karesi);
printf("Tek mi Çift mi: %d\n", tekCift);
printf("Uzunluk: %d\n", uzunluk);
printf
(); // void fonksiyon çağrısı
merhabaDunyaYazdir
return 0;
}
5.3 return
ve printf
Arasındaki Fark
return
ifadesi, bir fonksiyonun değerini döndürür ve fonksiyonun çalışmasını sonlandırır. Döndürülen değer, fonksiyonu çağıran yere gönderilir ve başka işlemlerde kullanılabilir.printf
fonksiyonu ise, bir değeri ekrana yazdırır, ancak fonksiyonun değerini etkilemez veya fonksiyonu sonlandırmaz.printf
fonksiyonu, ekrana bilgi yazdırmak için kullanılır, ancak bu bilgi programın çalışmasını etkilemez.
Örnek:
#include <stdio.h>
int ikiKati(int x) {
int sonuc = x * 2;
("Fonksiyon içinde: %d\n", sonuc); // Ekrana yazdırır, ancak fonksiyon devam eder
printfreturn sonuc; // Fonksiyonun değeri döndürülür ve fonksiyon sona erer
}
int main() {
int a = 5;
int b = ikiKati(a);
("Fonksiyon dışında: %d\n", b);
printfreturn 0;
}
Bu örnekte, ikiKati
fonksiyonu, aldığı değeri iki ile çarpar ve sonucu hem ekrana yazdırır (printf
) hem de return
ifadesi ile döndürür. main
fonksiyonunda, ikiKati
fonksiyonu çağrıldığında döndürülen değer b
değişkenine atanır.
Çıktı:
Fonksiyon içinde: 10
Fonksiyon dışında: 10
5.4 Fonksiyon Prototipleri: Derleyiciye Bilgi Vermek
Fonksiyon prototipleri, derleyiciye fonksiyonun geri dönüş tipi, adı ve parametreleri hakkında bilgi verir. Bu bilgiler, derleyicinin fonksiyon çağrılarını doğru bir şekilde kontrol etmesini ve hataları önceden tespit etmesini sağlar. Fonksiyon prototipleri, fonksiyon tanımlamasından önce bildirilmelidir.
Fonksiyon Prototipi Sözdizimi:
(parametre_listesi); geri_dönüş_tipi fonksiyon_adı
Örnek 1: Prototip Kullanımı
#include <stdio.h>
// Fonksiyon prototipi
int usAl(int taban, int us);
int main() {
int x = 2, y = 3;
int sonuc = usAl(x, y); // Fonksiyon prototipi sayesinde derleyici bu çağrıyı anlayabilir.
("%d üssü %d = %d\n", x, y, sonuc);
printfreturn 0;
}
// Fonksiyon tanımı
int usAl(int taban, int us) {
int sonuc = 1;
for (int i = 0; i < us; i++) {
*= taban;
sonuc }
return sonuc;
}
Örnek 2: Prototip Kullanılmaması
#include <stdio.h>
int main() {
int x = 2, y = 3;
int sonuc = usAl(x, y); // Hata! Derleyici usAl fonksiyonunu henüz tanımıyor.
("%d üssü %d = %d\n", x, y, sonuc);
printfreturn 0;
}
// Fonksiyon tanımı
int usAl(int taban, int us) {
int sonuc = 1;
for (int i = 0; i < us; i++) {
*= taban;
sonuc }
return sonuc;
}
Daha Fazla Fonksiyon Prototipi Örneği:
#include <stdio.h>
int kare(int sayi); // Fonksiyon prototipi
int tekMiCiftMi(int sayi); // Fonksiyon prototipi
int stringUzunlugu(char str[]); // Fonksiyon prototipi
void yildizYazdir(int adet); // Fonksiyon prototipi (void fonksiyonlar için de prototip kullanabiliriz)
int main() {
// ... (Fonksiyon çağrıları ve diğer kodlar)
}
// Fonksiyon tanımları ...
5.5 Özyinelemeli Fonksiyonlar: Kendini Çağırmak
Özyinelemeli fonksiyonlar, kendini çağıran fonksiyonlardır. Bu tür fonksiyonlar, bir problemi daha küçük alt problemlere bölerek çözer ve her alt problem için kendini tekrar çağırır. Bu işlem, temel bir duruma ulaşılana kadar devam eder.
Örnek: Faktöriyel Hesaplama (Özyinelemeli)
#include <stdio.h>
int faktoriyel(int n) {
if (n == 0) {
return 1; // Temel durum: 0! = 1
} else {
return n * faktoriyel(n - 1); // Özyinelemeli adım
}
}
int main() {
int sayi = 5;
int sonuc = faktoriyel(sayi);
("%d! = %d\n", sayi, sonuc);
printfreturn 0;
}
Açıklama:
faktoriyel(5)
çağrılır. 5, 0’dan büyük olduğu içinelse
bloğuna girilir.5 * faktoriyel(4)
hesaplanır.faktoriyel(4)
’ü hesaplamak için fonksiyon kendini tekrar çağırır.faktoriyel(4)
,4 * faktoriyel(3)
olarak hesaplanır. Fonksiyon tekrar çağrılır.- Bu işlem,
faktoriyel(0)
çağrısına kadar devam eder. faktoriyel(0)
, temel duruma ulaşır ve 1 değerini döndürür.- Döndürülen değerler, çağrı zinciri boyunca geriye doğru işlenir: 1, 2, 6, 24, 120.
- Sonuç olarak,
faktoriyel(5)
, 120 değerini döndürür.
5.5.1 Döngüler vs. Özyineleme
- Hem döngüler hem de özyinelemeli fonksiyonlar, tekrarlayan işlemleri gerçekleştirmek için kullanılabilir.
- Döngüler, genellikle daha basit ve anlaşılır bir yapıya sahiptir ve daha az bellek kullanır.
- Bazı problemler özyinelemeli fonksiyonlar kullanılarak daha zarif ve anlaşılır bir şekilde çözülebilir, özellikle de problem kendi kendini tekrar eden bir yapıya sahipse (örneğin, ağaç veri yapıları gibi).
- Özyinelemede her fonksiyon çağrısı için bellek yığınında (stack) bir alan ayrılır. Çok derin özyineleme seviyeleri, “stack overflow” hatasına neden olabilir. Bu durum, belleğin tükendiği anlamına gelir. Döngüler ise bu riski taşımazlar.
5.5.2 Özyineleme Ne Zaman Kullanılır?
- Problem, daha küçük alt problemlere bölünebiliyorsa
- Temel bir durum (recursive çağrıların durduğu nokta) varsa
- Döngüler kullanarak çözüm karmaşıklaşıyorsa
5.6 static
Değişkenler: Değerini Koruma
- Bir fonksiyon içinde
static
anahtar kelimesi ile tanımlanan değişkenler, fonksiyon çağrıları arasında değerlerini korur. - Normalde, local değişkenler fonksiyon sona erdiğinde bellekten silinir, ancak
static
değişkenler programın sonuna kadar bellekte kalır. Bu, fonksiyonun bir sonraki çağrısında değişkenin değerinin korunmasını sağlar.
Örnek: Sayaç Fonksiyonu
#include <stdio.h>
int sayac() {
static int count = 0; // static değişken, ilk değer 0 olarak atanır
++;
countreturn count;
}
int main() {
("Sayaç: %d\n", sayac()); // Çıktı: 1
printf("Sayaç: %d\n", sayac()); // Çıktı: 2
printf("Sayaç: %d\n", sayac()); // Çıktı: 3
printfreturn 0;
}
Açıklama:
count
değişkenistatic
olarak tanımlandığı için, fonksiyon ilk kez çağrıldığında 0 olarak başlatılır ve değeri bellekte saklanır.- Fonksiyon sonraki çağrılarda,
count
değişkeni tekrar başlatılmaz, bellekteki son değeri kullanılır. - Her fonksiyon çağrısında
count
değişkeni 1 artırılır ve yeni değeri döndürülür.
Eğer static
anahtar kelimesi kullanılmasaydı, count
değişkeni her fonksiyon çağrısında 0 olarak başlatılır ve çıktı her zaman 1 olurdu.
static
Değişkenler Hakkında Uyarı:
static
değişkenlerin değerleri fonksiyon çağrıları arasında korunurken, bu durum beklenmedik sonuçlara yol açabilir. Özellikle, aynı static
değişkeni kullanan birden fazla fonksiyon varsa, bir fonksiyonun yaptığı değişiklik diğer fonksiyonları etkileyebilir. Bu nedenle, static
değişkenleri dikkatli kullanmak ve mümkün olduğunca local değişkenleri tercih etmek iyi bir programlama uygulamasıdır.
5.7 Değişkenlerin Kapsamı: Local ve Global
- Local Değişkenler: Bir fonksiyonun içinde tanımlanan değişkenlerdir. Sadece o fonksiyon içinde kullanılabilirler. Fonksiyon sona erdiğinde, local değişkenler bellekten silinir.
- Global Değişkenler: Bir fonksiyonun dışında tanımlanan değişkenlerdir. Programın her yerinde kullanılabilirler. Programın başlangıcından sonuna kadar bellekte kalırlar.
Örnek
#include <stdio.h>
int globalDegisken = 10; // Global değişken
void fonksiyon() {
int localDegisken = 5; // Local değişken
("Fonksiyon içinde globalDegisken: %d\n", globalDegisken);
printf("Fonksiyon içinde localDegisken: %d\n", localDegisken);
printf}
int main() {
("main içinde globalDegisken: %d\n", globalDegisken);
printf();
fonksiyon// printf("main içinde localDegisken: %d\n", localDegisken); // Hata! localDegisken, main fonksiyonunda tanımlı değil.
return 0;
}
5.8 Fonksiyonların Avantajları: Kodun Düzenlenmesi ve Yeniden Kullanımı
Fonksiyonlar kullanmanın birçok avantajı vardır:
- Kodun Okunabilirliğini Artırır: Fonksiyonlar, programı daha küçük ve daha anlaşılır parçalara böler.
- Kodun Yeniden Kullanımını Sağlar: Bir fonksiyon, programın farklı bölümlerinden tekrar tekrar çağrılabilir.
- Kodun Bakımını Kolaylaştırır: Bir fonksiyonda değişiklik yapıldığında, programın diğer bölümlerini etkilemez.
- Hata Ayıklamayı Kolaylaştırır: Fonksiyonlar, programı daha küçük parçalara böldüğü için hata ayıklamayı kolaylaştırır.
5.9 Alıştırmalar
- Kullanıcıdan alınan iki tam sayının en büyüğünü bulan bir fonksiyon yazın.
- Kullanıcıdan alınan bir sayının mükemmel sayı olup olmadığını kontrol eden bir fonksiyon yazın. (Mükemmel sayı, kendisi hariç pozitif bölenlerinin toplamına eşit olan sayıdır. Örneğin, 6 mükemmel bir sayıdır çünkü 1 + 2 + 3 = 6)
- Kullanıcıdan alınan iki sayının OBEB’ini (Ortak Bölenlerin En Büyüğü) bulan bir fonksiyon yazın.
- Kullanıcıdan bir sayı alıp bu sayıya kadar olan tüm asal sayıları ekrana yazdıran bir fonksiyon yazın.
- Kullanıcıdan bir sayı alıp bu sayının faktöriyelini hesaplayan bir fonksiyon yazın (Döngü kullanarak).
- 0 ile 100 arasında rastgele bir sayı üreten bir fonksiyon yazın.
5.10 Sonuç
Bu bölümde, fonksiyonları daha detaylı inceledik, fonksiyon prototiplerinin önemini vurguladık, değişkenlerin kapsamını anlattık ve özyinelemeli fonksiyonları öğrendik. Ayrıca, daha fazla örnek ile konuyu pekiştirdik. Unutmayın, fonksiyonlar kodun yapı taşlarıdır ve iyi bir programcı olmak için fonksiyonları etkin bir şekilde kullanmayı öğrenmek çok önemlidir.