Fonksiyon göstergeleri

C'de göstergeler fonksiyonlar(function pointers) herhangi bir fonksiyonun bellekteki adresine işaret eden göstergelerdir. Aynı diğer göstergelerde olduğu gibi işaret ettiği türün başlangıç adresini saklarlar. Diğer göstergelerin tanımlanması ve kullanılması ile aralarında ciddi benzerlikler olsa da çoğu kitapta kendisinden bahsedilmez bile. Benim şansım okuduğum ilk C kitabı olan K&R'in bu konuyu gayet detaylıca işlemesi oldu. Ve bu konuyu anladıktan sonra programlama ufkumda yeni alanlar açıldı diyebilirim.

Fonksiyon göstergelerini anlayabilmek için önce kesinlikle gösterge kavramının iyice anlaşılmış olması gerekiyor. Yoksa işler daha da karışabilir.

void testFonk(int x){
    printf("%d\n", x * x);
}

Burada geriye birşey döndürmeyen void türünde ve bir tane int türünde parametre alan testFonk isminde fonksiyon tanımladık. Diyelim ki bu fonksiyona işaret eden bir fonksiyon göstergesi tanımlamak istiyoruz. Bunun için klasik gösterge işlemlerine benzeyen aşağıdaki işlemleri yapıyoruz.

int main(){
    void (*testFonksiyonPt)(int);
    testFonksiyonPt = &testFonk;

    testFonksiyonPt(2);

    return 0;
}

Bu kısımda testFonksiyonPt isminde geriye birşey döndürmeyen ve int türünde bir tane parametre alan void türünde bir fonksiyon göstergesi tanımladık. Artık testFonksiyonPt göstergemiz başka bir fonksiyonu işaret etmeye hazır.

Başka bir fonksiyona işaret etmek içinse o fonksiyonun başlangıç adresini göstergemize veriyoruz. Bu sayede artık göstergemiz üzerinden ilgili fonksiyona ulaşabiliyoruz.

Fonksiyon göstergelerini bu şekilde kullanabileceğimiz gibi başka bir fonksiyona parametre olarak da verebiliriz. Ki fonksiyon göstergelerinin en fazla kullanım şekli de bu diyebilirim. Özellikle sistem tarafında çalışan kodlar da ismini bilmediğimiz bir çok fonksiyon olabilir. İşte bu tarz durumlarda fonksiyon göstergeleri gerçekten bulunmaz bir nimet.

#include <stdio.h>

void hesapla(int s1,int s2, int (*islem)(int, int)){
    int sonuc = islem(s1, s2);
    printf("Sonuc: %d\n", sonuc);
}

int topla(int a,int b){ return a + b;}
int carp(int a,int b){ return a * b;}
int cikar(int a,int b){ return a - b;}

int main(){
    hesapla(10,5,&topla);
    hesapla(10,5,&carp);
    hesapla(10,5,&cikar);
    return 0;
}

Yukarıdaki kod da önce hesapla isminde, 2 tane int türünde parametre ve iki parametreli bir fonksiyonu işaret eden gösterge olan fonksiyon tanımlanıyor. Yani biz hesapla fonksiyonuna iki adet int türünde sayı ile birlikte bu iki sayı ile işlem yapacak bir fonksiyonun adresini verebiliriz. Bu amaç için basit çaplı topla, carp ve cikar isimli fonksiyonlarımız var. main içerisinden görüldüğü gibi hesapla isimli fonksiyonumuzu 10 ve 5 sayılarını sirayla fonksiyonlarımıza versin ve sonuclarını ekrana bassın diye çağırıyoruz.

Fonksiyon içerisine başka bir fonksiyonu işaret eden göstergeyi alabileceğimiz gibi geriye de bu tarzda bir gösterge döndürebiliriz. Syntax olarak karışmaya meyilli olduğundan typedef ile yeni tanımlama yapmak fayda sağlıyor.

int (*alFonksiyonPtr(const char operator))(int, int){
   if(operator == '+') return &topla;
   else if(operator == '-') return &cikar;
   else if(operator == '*') return &carp;
}

Burada gördüğünüz gibi alFonksiyonPtr isimli char türünde parametre alan ve geriye iki int parametreli bir fonksiyona işaret eden göstergeyi döndüren fonksiyon tanımladık. Bunu aşağıdaki şekilde daha kolay okunabilir yapabiliriz.

typedef int (*fPtr)(int, int);
fPtr alFonksiyonPtr(const char operator);

Fonksiyon göstergelerini ilk gördüğüm anda aklıma Python'dan aşina olduğum map, reduce ve filter gibi kavramlar geldi. Hazır bu konuyu yeni öğrenmişken basit bir map fonksiyonu da yazayım dedim.

void map(int *dizi, int len,  int (*mapFunc)(int) ){
    int i;
    for(i = 0; i < len; i++){
        *dizi = mapFunc(*dizi);
        dizi++;
    }
}

Bu fonksiyonda verdiğimiz dizinin tüm elemanlarını teker teker mapFunc fonksiyonuna veriyor ve dönen değerleri elemanın bulunduğu konuma yazıyor. Örnek kullanımı da aşağıdaki gibi olabilir.

#include <stdio.h>

void map(int *dizi, int len,  int (*mapFunc)(int) ){
    int i;
    for(i = 0; i < len; i++){
        *dizi = mapFunc(*dizi);
        dizi++;
    }
}

int ekle2(int sayi){
    return sayi + 2;
}

int main(int argc, const char *argv[])
{
    int sayilar[] = { 1, 2, 3, 4, 5, 6};
    int len = (&sayilar)[1] - sayilar;
    int i;

    for(i = 0; i < len; i++)
        printf("%d -> %d\n", i, sayilar[i]);

    printf("-------------\n");

    map(sayilar, len, ekle2);

    for(i = 0; i < len; i++)
        printf("%d -> %d\n", i, sayilar[i]);

    return 0;
}

Bu tarz yaklaşımlar da aklınıza peki ya elemanlara uygulanacak olan fonksiyonun argüman sayısı belirli değilse ne yapacağız gibi bir soru gelebilir. Ki benimde ilk aklıma gelen bu olmuştu. Bunun çözümü de değişken sayılı argüman alan fonksiyon tanımlamaktan geçiyor. Bu konuyu da başka bir yazıda anlatmak istiyorum. Ayrıcana aşağıdaki linklere de bakmanızı tavsiye ederim.

http://stackoverflow.com/questions/4047431/implementing-a-generical-map-function-over-arrays-in-c

http://www.cprogramming.com/tutorial/function-pointers.html

http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_28.html

http://www.cs.cmu.edu/~guna/15-123S11/Lectures/Lecture14.pdf

http://c.learncodethehardway.org/book/ex18.html

comments powered by Disqus