Programlama Temelleri

5 - Fonksiyonlar

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2024

Fonksiyonlar: Kodun Yapı Taşları

  • Fonksiyonlar, belirli bir görevi yerine getiren kod bloklarıdır.
  • Programları daha küçük, yönetilebilir ve yeniden kullanılabilir parçalara ayırmamıza yardımcı olurlar.
  • Tıpkı bir arabayı oluşturan farklı parçaların (motor, tekerlekler, frenler gibi) bir araya gelerek arabayı çalıştırması gibi, fonksiyonlar da bir programı oluşturan temel parçalardır.

Fonksiyonların Faydaları

  • Kodun Okunabilirliğini Artırır: Fonksiyonlar, programı daha küçük parçalara böler, böylece kod daha kolay anlaşılır.
  • Kodun Yeniden Kullanımını Sağlar: Bir fonksiyon, programın farklı yerlerinde tekrar tekrar kullanılabilir, böylece aynı kodu tekrar tekrar yazmamıza gerek kalmaz.
  • Kodun Bakımını Kolaylaştırır: Bir fonksiyonda değişiklik yapılması gerektiğinde, sadece o fonksiyonu değiştirmemiz yeterli olur, programın diğer kısımlarını etkilemez.
  • Hata Ayıklamayı Kolaylaştırır: Fonksiyonlar, programı daha küçük parçalara böldüğü için, hataları bulmak ve düzeltmek daha kolay olur.

Fonksiyon Tanımlama: Bir Görevi Paketleme

Sözdizimi:

geri_dönüş_tipi fonksiyon_adı(parametre_listesi) {
  // Fonksiyon gövdesi (işlemler)
  return değer; // İsteğe bağlı
}
  • Geri Dönüş Tipi: Fonksiyonun döndüreceği değerin veri tipini belirtir (int, float, char vb.). Eğer fonksiyon bir değer döndürmüyorsa, geri dönüş tipi void olur.
  • Fonksiyon Adı: Fonksiyonu tanımlamak ve çağırmak için kullanılan isim.
  • Parametre Listesi: Fonksiyona geçirilen değerler (değişkenler). Fonksiyon parametre almıyorsa, parantezler boş bırakılır.
  • Fonksiyon Gövdesi: Fonksiyonun gerçekleştirdiği işlemleri içeren kod bloğu.
  • return İfadesi: Fonksiyonun sonucunu döndürür ve fonksiyonun çalışmasını sonlandırır. Geri dönüş tipi void olan fonksiyonlar değer döndürmez, bu yüzden return ifadesi kullanılmaz.

Örnek 1: Toplama Fonksiyonu

#include <stdio.h>

int topla(int a, int b) {
  int toplam = a + b;
  return toplam; // toplam değeri fonksiyonu çağıran yere döndürülür
}

int main() {
  int sayi1 = 5;
  int sayi2 = 10;
  int sonuc = topla(sayi1, sayi2); // topla fonksiyonu çağrılır ve döndürülen değer sonuc değişkenine atanır
  printf("%d + %d = %d\n", sayi1, sayi2, sonuc);
  return 0;
}

Açıklama

  • topla fonksiyonu, iki tamsayı (a ve b) alır ve bunları toplar.
  • return toplam; satırı, toplam değişkeninin değerini fonksiyonu çağıran yere (bu örnekte main fonksiyonu) döndürür ve topla fonksiyonunun çalışmasını sonlandırır.
  • main fonksiyonu içinde, topla(sayi1, sayi2) ifadesi, topla fonksiyonunu çağırır ve sayi1 ve sayi2 değişkenlerinin değerlerini fonksiyona parametre olarak geçirir.
  • topla fonksiyonu, sayi1 ve sayi2 değerlerini toplar ve sonucu (toplam değişkeninin değeri) döndürür.
  • Döndürülen değer, sonuc değişkenine atanır ve ekrana yazdırılır.

Örnek 2: Çift Sayı Kontrolü

#include <stdio.h>

int ciftMi(int sayi) {
  if (sayi % 2 == 0) {
    return 1; // 1, "doğru" anlamına gelir
  } else {
    return 0; // 0, "yanlış" anlamına gelir
  }
}

int main() {
  int sayi = 6;
  if (ciftMi(sayi)) { // ciftMi fonksiyonu 1 (doğru) döndürürse
    printf("%d çift bir sayıdır.\n", sayi);
  } else {
    printf("%d tek bir sayıdır.\n", sayi);
  }
  return 0;
}

Açıklama

  • ciftMi fonksiyonu, bir tamsayı (sayi) alır ve bu sayının çift olup olmadığını kontrol eder.
  • sayi % 2 == 0 ifadesi, sayının 2’ye bölümünden kalanın 0 olup olmadığını kontrol eder. Eğer kalan 0 ise, sayı çifttir ve fonksiyon 1 (true) değerini döndürür. Değilse, sayı tektir ve fonksiyon 0 (false) değerini döndürür.
  • main fonksiyonu içinde, ciftMi(sayi) ifadesi, ciftMi fonksiyonunu çağırır ve sayi değişkeninin değerini fonksiyona parametre olarak geçirir.
  • ciftMi fonksiyonundan döndürülen değer, if ifadesinin koşulunda kullanılır. Eğer fonksiyon 1 (true) döndürürse, if bloğu çalıştırılır ve “çift” mesajı yazdırılır. Eğer fonksiyon 0 (false) döndürürse, else bloğu çalıştırılır ve “tek” mesajı yazdırılır.

Örnek 3: void Fonksiyon Örneği

#include <stdio.h>

void merhabaDunyaYazdir() {
  printf("Merhaba Dünya!\n");
}

int main() {
  merhabaDunyaYazdir(); // Fonksiyon çağrısı
  return 0;
}

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.

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;
  printf("Fonksiyon içinde: %d\n", sonuc); // Ekrana yazdırır, ancak fonksiyon devam eder
  return sonuc; // Fonksiyonun değeri döndürülür ve fonksiyon sona erer
}

int main() {
  int a = 5;
  int b = ikiKati(a);
  printf("Fonksiyon dışında: %d\n", b);
  return 0;
}

Açıklama

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

void Fonksiyonları: Değer Döndürmeyen Fonksiyonlar

  • Bazı fonksiyonlar değer döndürmek yerine, belirli bir görevi yerine getirirler.
  • Örneğin, ekrana bir mesaj yazdırmak veya bir dosyaya veri kaydetmek gibi.
  • Bu tür fonksiyonlar, geri dönüş tipi olarak void kullanır ve return ifadesi kullanmazlar.

Örnek: Yıldız Yazdırma

#include <stdio.h>

void yildizYazdir(int adet) {
  for (int i = 0; i < adet; i++) {
    printf("*");
  }
  printf("\n");
}

int main() {
  yildizYazdir(5);
  return 0;
}

Bu örnekte, yildizYazdir fonksiyonu, parametre olarak aldığı adet değerine göre ekrana yıldız karakterleri yazdırır. Fonksiyon herhangi bir değer döndürmez, bu yüzden geri dönüş tipi void olarak belirtilmiştir ve return ifadesi kullanılmamıştır.

Fonksiyon Prototipleri: Derleyiciye Bilgi Vermek

  • Fonksiyon prototipleri, derleyiciye fonksiyonun geri dönüş tipi, adı ve parametreleri hakkında bilgi verir.
  • Derleme aşamasında kod yukarıdan aşağı doğru okunur. Eğer fonksiyon çağrısı, fonksiyon tanımlamasından önce gelirse, derleyici fonksiyon hakkında bilgi sahibi olmaz ve hata verir.
  • Fonksiyon prototipleri bu sorunu çözer. Derleyici, prototipi gördüğünde fonksiyonun nasıl çağrılacağını bilir, böylece fonksiyon tanımı daha sonra yapılsa bile hata oluşmaz.
  • Prototipler genellikle başlık dosyalarına veya kaynak dosyanın başına yazılır.

Sözdizimi:

geri_dönüş_tipi fonksiyon_adı(parametre_listesi);

Ö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.
  printf("%d üssü %d = %d\n", x, y, sonuc);
  return 0;
}

// Fonksiyon tanımı
int usAl(int taban, int us) {
  int sonuc = 1;
  for (int i = 0; i < us; i++) {
    sonuc *= taban;
  }
  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.
  printf("%d üssü %d = %d\n", x, y, sonuc);
  return 0;
}

// Fonksiyon tanımı
int usAl(int taban, int us) {
  int sonuc = 1;
  for (int i = 0; i < us; i++) {
    sonuc *= taban;
  }
  return sonuc;
}

Ö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);
  printf("%d! = %d\n", sayi, sonuc);
  return 0;
}

Açıklama

  1. faktoriyel(5) çağrılır. 5, 0’dan büyük olduğu için else bloğuna girilir.
  2. 5 * faktoriyel(4) hesaplanır. faktoriyel(4)’ü hesaplamak için fonksiyon kendini tekrar çağırır.
  3. faktoriyel(4), 4 * faktoriyel(3) olarak hesaplanır. Fonksiyon tekrar çağrılır.
  4. Bu işlem, faktoriyel(0) çağrısına kadar devam eder.
  5. faktoriyel(0), temel duruma ulaşır ve 1 değerini döndürür.
  6. Döndürülen değerler, çağrı zinciri boyunca geriye doğru işlenir: 1, 2, 6, 24, 120.
  7. Sonuç olarak, faktoriyel(5), 120 değerini döndürür.

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.

Ö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

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
  count++;
  return count;
}

int main() {
  printf("Sayaç: %d\n", sayac()); // Çıktı: 1
  printf("Sayaç: %d\n", sayac()); // Çıktı: 2
  printf("Sayaç: %d\n", sayac()); // Çıktı: 3
  return 0;
}

Açıklama

  • count değişkeni static 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ı:

Warning

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.

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
  printf("Fonksiyon içinde globalDegisken: %d\n", globalDegisken);
  printf("Fonksiyon içinde localDegisken: %d\n", localDegisken);
}

int main() {
  printf("main içinde globalDegisken: %d\n", globalDegisken);
  fonksiyon();
  // printf("main içinde localDegisken: %d\n", localDegisken); // Hata! localDegisken, main fonksiyonunda tanımlı değil.
  return 0;
}

Quiz

Soru 1

Aşağıdaki kodun çıktısı ne olur?

#include <stdio.h>

int fonksiyon(int x) {
  if (x < 5) {
    return x * 2;
  } else {
    return x - 2;
  }
}

int main() {
  int a = 3;
  int b = fonksiyon(a);
  int c = fonksiyon(b);
  printf("%d\n", c);
  return 0;
}

Soru 2

Aşağıdaki fonksiyon tanımı geçerli midir? Neden?

int fonksiyon(int x, int y) {
  int z = x + y;
  printf("%d\n", z);
}

Soru 3

Aşağıdaki kodun çıktısı ne olur?

#include <stdio.h>

int sayac() {
  static int count = 0;
  count++;
  return count;
}

int main() {
  printf("%d\n", sayac());
  printf("%d\n", sayac());
  return 0;
}

Soru 4

Aşağıdaki kodun çıktısı ne olur?

#include <stdio.h>

int fibonacci(int n) {
  if (n <= 1) {
    return n;
  } else {
    return fibonacci(n-1) + fibonacci(n-2);
  }
}

int main() {
  printf("%d\n", fibonacci(6));
  return 0;
}

Soru 5

Aşağıdaki kodun çıktısı ne olur?

#include <stdio.h>

int fonksiyon(int x, int y) {
    return (x > y) ? x : y;
}

int main() {
  int a = 15;
  int b = 20;
  int sonuc = fonksiyon(a, b);
  printf("%d\n", sonuc);
  return 0;
}

Soru 6

Aşağıdaki fonksiyon, kendisine parametre olarak verilen pozitif bir tamsayının kaç basamaklı olduğunu hesaplamaktadır. Kodda eksik olan kısmı tamamlayın.

int basamakSayisi(int sayi) {
  int basamak = 0;
  while (sayi > 0) {
    sayi /= 10;
    // Eksik kod buraya gelecek
  }
  return basamak;
}

Soru 7

Aşağıdaki kodun çıktısı ne olur?

#include <stdio.h>

int f(int x) {
  return x * x - 5;
}

int main() {
  int a = 3;
  int b = f(a) + 2;
  printf("%d\n", b);
  return 0;
}

Soru 8

Aşağıdaki kodun çıktısı ne olur?

#include <stdio.h>

int main() {
  int i = 1;

  while (i <= 5) {
    int j = i;
    while (j >= 1) {
      printf("*");
      j--;
    }
    printf("\n");
    i++;
  }
  return 0;
}

Alıştırmalar

  1. Kullanıcıdan alınan iki tam sayının en büyüğünü bulan bir fonksiyon yazın.
  2. 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)
  3. Kullanıcıdan alınan iki sayının OBEB’ini (Ortak Bölenlerin En Büyüğü) bulan bir fonksiyon yazın.
  4. Kullanıcıdan bir sayı alıp bu sayıya kadar olan tüm asal sayıları ekrana yazdıran bir fonksiyon yazın.
  5. Kullanıcıdan bir sayı alıp bu sayının faktöriyelini hesaplayan bir fonksiyon yazın (Döngü kullanarak).
  1. 0 ile 100 arasında rastgele bir sayı üreten bir fonksiyon yazın.
  2. min ve max değerleri arasında rastgele bir sayı üreten bir fonksiyon yazın.
  3. 10 tane rastgele üretilen sayılardan çift sayı olanlarının toplamını hesaplayan bir fonksiyon yazın. Rastgele sayı üretiminde bir önceki alıştırmada yazdığınız fonksiyonu kullanın.
  4. 1’den başlayarak verilen bir sayıya kadar olan tam sayıların toplamını özyineli fonksiyonla bulan bir program yazın.

Özet

  • Fonksiyonlar: Belirli görevleri yerine getiren kod bloklarıdır.
  • Fonksiyon Tanımlama: Fonksiyonun ne yapacağını belirler.
  • Fonksiyon Çağırma: Fonksiyonu çalıştırır.
  • Fonksiyon Prototipi: Derleyiciye fonksiyon hakkında bilgi verir.
  • void Fonksiyonları: Değer döndürmeyen fonksiyonlardır.
  • Özyinelemeli Fonksiyonlar: Kendini çağıran fonksiyonlardır.
  • static Değişkenler: Fonksiyon çağrıları arasında değerlerini korur.
  • Değişkenlerin Kapsamı: Değişkenin nerelerde erişilebilir olduğunu belirler.

Gelecek Hafta

  • Diziler: Veri Koleksiyonlarını Düzenlemek