C#nedir?com
 
YAZAR HAKKINDA
Çiğdem Çavdaroğlu
Çiğdem Çavdaroğlu
http://www.csharpnedir.com/
İletişme geçmek için tıklayın.
28 Makalesi yayınlanmakta.
Yazar hakkında detaylı bilgi için tıklayın.
Yayınlanan diğer makaleleri için tıklayın.
İlgili etiketler: (double *elems int dizinin double durumda farkli fonksiyon fonksiyonlarin fonksiyonun isimli ondalikli parametre result return tamsayi C / Sys Prog. Çiğdem Çavdaroğlu
 
YAZI HAKKINDA
Türü : Makale
Serbest Köşede C#nedir?com üyelerinin hazırladıkları yazılar yayınlanır. Bu yazılar editör incelemesine girmeden yayınlanır.
Seviyesi : Orta
Kategori : C / Sys Prog.
Yayınlanma Tarihi : 7.4.2005
Okunma Sayısı : 16174
Yorum Sayısı : 1     yorum yaz
Site İçi AramaSİTE İÇİ ARAMA
Üye Girişini AçÜye GİRİŞİ
Üye girişi için tıklayın.
Kullanıcı Adı
Şifre
 
Beni her zaman hatırla
Bir hafta boyunca kullanıcı bilgilerinizi kullanıcı çıkışı yapana kadar hatırlar. (Paylaşılan bilgisayarlarda önerilmez.)
 
Şifremi / Kullanıcı Adımı unuttum.
 
.net TV RSS Serbest KÖŞE (?)
Serbest Köşede C#nedir?com üyelerinin hazırladıkları yazılar yayınlanır. Bu yazılar editör incelemesine girmeden yayınlanır.
emre TAŞ
XML - Deniz Kılınç
emre TAŞ
yazının devamı >
emre TAŞ
Decompiling and Reverse Engineering .Net Radyo
emre TAŞ
yazının devamı >
emre TAŞ
Masaüstü mü ? İnternet Mi? .Net Radyo
emre TAŞ
yazının devamı >
emre TAŞ
.Net Kavramları - .Net Radyo
emre TAŞ
yazının devamı >
emre TAŞ
Yeni Başlayanlar için - .Net Radyo
emre TAŞ
yazının devamı >
Makale Gönder Bende Yazmak İstiyorum
.net TV RSSBlogroll
Turhal Temizer 'in Blogu
Conda install environment.yml Package 31.3.2020
Turhal Temizer 'in Blogu
Mac OS/X Removing CUDA 31.3.2020
Burak Selim Şenyurt
Sekiz Saatlik Sonsuz Döngü 31.3.2020
Burak Selim Şenyurt
Switch Case Kullanmadan Kod Yazılabilir mi? 31.3.2020
  Diğer Herşey
Sponsorlar
BT Akademi
Medya Portakal
Video Hosting Sponsoru
Csharpnedir.com bir Ineta üyesidir
Uzman Abi
Her Yönüyle C# - Sefer Algan
Fonksiyon Göstericilerinin Kullanılması
 
Kapat
Sayfayı Yazdır Sık Kullanılanlara Ekle Arkadaşıma Gönder MySpace Del.Ico.Us Digg Facebook Google Mixx Reddit StumbleUpon

Bu yazımızda fonksiyon göstericilerini inceleyeceğiz. Ancak konuya geçmeden önce, göstericilere bir göz atmamızın yararlı olacağını düşünüyorum. Bildiğimiz gibi gösterici (pointer), belli bir türden adres tutan veri türüdür. Eğer tutacağımız adresin türü int ise, yani int türünden bir nesnenin adresini tutmak istiyorsak bunun için tanımlamamız gereken gösterici (int *) türünden olmalıdır. Benzer şekilde double nesnelerin adresini tutmak için (double *) türünden gösterici tanımlanmalıdır. Bu ifade, C dilinin tüm doğal veri türleri için, ayrıca programcının tanımladığı veri türleri için geçerli bir ifadedir. Örneğin, test isimli bir yapı tanımladığımızı düşünelim. Bu yapı türünden bir nesnenin adresini tutmak için (test *) türünden bir gösterici tanımlamamız gereklidir. Bu durumda C dilinde sonsuz türde gösterici olabilir. Şimdi bir göstericiyi nasıl tanımladığımızı örneklerle hatırlayalım :

int a = 10;
int *pi = &a;
struct test {
    //...
};
struct test t;
struct test *pt = &t;
Peki fonksiyon göstericisi ne demektir? Gösterici, adres tutan bir tür olduğuna göre, fonksiyon göstericisi de adından anlaşılacağı üzere, bir fonksiyonun adresini tutacaktır. Peki fonksiyonun adresi ne demektir? Kaynak kod içerisinde programcı tarafından yazılan fonksiyonlar, derleme aşamasında derleyici tarafından makine koduna dönüştürülür. Fonksiyonun kodları da, tıpkı tanımlanan değişkenler gibi bellekte belli bir yer kaplarlar. Fonksiyonun adresi kavramı, fonksiyonun bellekte kapladığı adresin başlangıcı anlamına gelmektedir. Tıpkı dizi isimlerinde olduğu gibi, fonksiyonların isimleri, onların başlangıç adreslerini belirtir. Aşağıdaki örneği inceleyiniz :

int Add_i(int a, int b)
{
     return a + b;
}
şeklinde bir fonksiyon tanımlandığını düşünelim. Bu durumda, Add_i ismi, bu fonksiyonun başlangıç adresini göstermektedir. Tanımlayacağımız fonksiyon göstericilerine atayacağımız değerler de fonksiyon isimleri olacaktır. C dilinde, bir fonksiyonu, adını ve adının yanına da parantez içerisinde parametre değişkenlerini yazarak çağırırız. Bu fonksiyonu uygun türden bir göstericiye atadığımız zaman, fonksiyonu bu gösterici yoluyla da çağırabiliriz. Bu durumun bize nasıl bir fayda sağlayacağını örnek üzerinde göstereceğiz. Fonksiyon göstericisi tanımlama ifadesi şu şekildedir :


<fonksiyonun geri dönüş değeri türü>(*gösterici adı)([fonksiyonun parametre türleri])

int (*Func1) (int,int);    /* Geri dönüş değeri int olan, iki parametre değişkeni olan ve parametre değişkenlerinin türleri int olan fonksiyonları göstermeye aday Func1 ismindeki fonksiyon göstericisi */ void (*Func2) (void);    //Geri dönüş değeri ve parametre değişkeni olmayan fonksiyon göstericilerini göstermeye aday Func2 ismindeki göstericisi
int Add_i(int a, int b)
{
      return a + b;
}

int(*Func1) (int,int);

Func1 = Calculate;  /* Calculate ismi fonksiyonun adresini gösterir, Func1 göstericisi artık Calculate fonksiyonunu göstermektedir*/

Calculate(10,20);   //Fonksiyonun adı ve parametre değişkenleri ile çağrılması

Func1(10,20);       //Fonskiyonun kendisini gösteren bir gösterici ve parametre değişkenleri ile çağrılması
Peki bu durumdan nasıl bir fayda sağlayabiliriz ? Bilindiği üzere, C dilinde aynı isimli birden fazla fonksiyon bulunamaz. Nesne yönelimli tekniği uygulayan yüksek seviyeli dillerde (örneğin C#), fonksiyonlar aşırı yüklenerek birden fazla tür için çalışan aynı isimli fonksiyonlar yazılabilir. Durumu bir örnekle açıklayalım : Diziler üzerinde işlem yaptığımızı düşünelim. Hem tamsayı türünden diziler hem de ondalıklı sayı türünden diziler için sıralama algoritması yazmak istiyoruz. Bu durumda C dilinin kuralları gereğince, sort_int ve sort_double isimli iki ayrı fonksiyon yazarak isteğimizi karşılayabiliriz. İki ayrı fonksiyon yazma gereği, sıralanacak elemanların türlerinin farklılığından kaynaklanmaktadır. Kullanacağımız algoritma ise her iki fonksiyon için de aynı olacaktır. Büyük bir uygulamayı göz önünde bulundurduğumuzda bu gibi durumlar sıklıkla karşımıza çıkar. Belli bir noktadan sonra, farklı türler için aynı işi yapan fonksiyonların sayıları artacaktır. Bu fonksiyonların isimlerinin farklı olması programcının algılamasını zorlaştırır. Sıralama yapmak isteyen programcı, kullanacağı tür için uygun olan fonksiyonun ismini hatırlamak zorunda kalır. Oysa bir tane sıralama fonksiyonu olsa, ismi de örneğin "Sort" olsa, programcı sıralamayı yapacağı anda bu Sort fonksiyonunu çağıracaktır. Böyle bir kullanım, kodun okunabilirliğini de artırır. Fonksiyon göstericileri yardımıyla bunu yapabiliriz. Yine farklı türler için farklı isimli fonksiyonlar yazmamız gerekecek, ancak işlemi yapacağımız anda çağıracağımız fonksiyon bir tane olacak ve biz sadece bu fonksiyonun adını hatırlamak zorunda kalacağız. Fonksiyon göstericilerini, bir yapıya veri elemanı olarak ekleyebiliriz. C dilinde, yapı türleri içerisinde fonksiyon tanımlanamaz. Ancak yapıya bir fonksiyon göstericisi eklemek yoluyla dolaylı yoldan bunu yapabiliriz.

Şimdi örnek bir uygulama yapalım. Hem tamsayı türünden, hem de ondalıklı sayı türünden diziler için en büyük ve en küçük elemanı bulma, aritmetik ortalamayı bulma ve elemanların toplam değerlerini bulma işlemlerini yapmak istediğimizi düşünelim. Her tür için işlem yapan farklı fonksiyonlar yazacağız. Ancak en son, belli işleri yapan fonksiyon göstericileri tanımlayacağız ve kullanacağımız türe göre bu fonksiyon göstericilerine türlere özel yazdığımız fonksiyonları atayacağız. Şimdi bahsettiğimiz bu yapıyı oluşturmaya başlayalım :

// Tamsayı üzerinde mi ondalıklı sayı üzerinde mi çalışıyoruz ?
typedef enum _myTypes {
    t_integer = 0,
    t_double = 1,
}MyTypes; // Üzerinde çalışacağımız türe göre dizimizi tutacak birlik
typedef union _vals {
    double *dVals;
    int *iVals;
}Vals;

// Üzerinde çalışacağımız türe göre çağıracağımız fonksiyonların geri dönüş değerlerini tutacak birlik
typedef union _result {
    double dResult;
    int iResult;
}Result;

// Temel yapımız
typedef struct _myArray {
    MyTypes type;
    Vals vals;
    double (*GetMathAvr)(int elemNumber,Result res);
    Result (*GetSum)(void const *elems,int elemNumber);
    int (*GetMax)(void const *elems,int elemNumber);
    int (*GetMin)(void const *elems,int elemNumber);
}MyArray;

Buradaki tanımlamaları açıklayalım. MyArray isimli bir yapı tanımladık. Bu yapıda, üzerinde çalışacağımız tür bilgisini tutan tutan type isimli bir veri elemanı, dizimizin elemanlarını tutan vals veri elemanı ve dört tane de fonksiyon göstericisi bulunuyor. Eğer üzerinde çalışacağımız tür tamsayı ise type elemanına t_integer değerini, ondalıklı sayı ise t_double değerini atayacağız. Bu değeri de her iki tür için de ortak yazdığımız fonksiyonlarda, tür bilgisini alabilmek için kullanacağız. Yapımız içerisinde dört tane de fonksiyon göstericisi tanımladık. Bu fonksiyon göstericilerinin göstereceği fonksiyonların yapacağı işler şunlardır :

1- Dizinin aritmetik ortalamasını bulacak
2- Dizinin elemanlarının toplam değerini bulacak
3- Dizinin en büyük elemanını bulacak
4- Dizinin en küçük elemanını bulacak

Bu durumda, kullandığımız türe göre bu göstericilere uygun fonksiyonların adreslerini atayacağız. Farklı türler için fonksiyonlar çağırırken, farklı fonksiyon isimleri kullanmak zorunda kalmayacağız, bunun yerine tanımladığımız fonksiyon göstericilerinin adını kullanacağız.

Fonksiyon göstericilerinin göstereceği fonksiyonların parametre değişkenlerinde (void *) türünün kullanılmasının nedeni türden bağımsızlığı sağlayabilmektir. Farklı türler için aynı işlemleri yapan fonksiyonları çağırmak için bir tane fonksiyon göstericisi tanımladık. Bu göstericiye atayacağımız fonksiyonun parametrik yapısı da göstericinin parametrik yapısına uygun olmalıdır. Bu durumda kullanılabilecek en uygun tür de (void *) olacaktır. Fonksiyonlara göndereceğimiz (int *) ve (double *) türünden nesnelerimizi (void *) türüne tür dönüşümü ile dönüştüreceğiz. Türe bağımlı yazacağımız fonksiyonlarda ise, parametre değişkenini kullanılan türe göre (int *) veya (double *) türüne dönüştüreceğiz. Bu yapı biraz karmaşık gibi görünse de birkaç uygulama yapılarak rahatlıkla anlaşılabilir. Öncelikle türe bağımlı işlemleri yapacak fonksiyonları tanımlayalım :

Result GetSumDouble(void const *elems,int elemNumber)
{
    double const *myElems = (double *)elems;
    int i;
    double sum = 0.0;
    Result res;

    for (i = 0; i < elemNumber; ++i)
    sum += myElems[i];

    res.dResult = sum;
    return res;
}

Result GetSumInt(void const *elems,int elemNumber)
{
    int const *myElems = (int *)elems;
    int i,sum = 0;
    Result res;

    for (i = 0; i < elemNumber; ++i)
       sum += myElems[i];

    res.iResult = sum;
    return res;
}
Bu iki fonksiyon, parametre olarak geçilen dizinin elemanlarının değerleri toplamını Result türünden bir nesnenin ilgili elemanına atamaktadır. Dizi parametresini (void *) türünden tanımladık. Çünkü bu fonksiyonu yapımız içerisindeki GetSum fonksiyon göstericisine atayacağız ve parametrik uyum sağlamalıyız. Biz, her iki fonksiyon içerisinde de tür bilgisine sahip olacağımız için tür dönüşümü ile istediğimiz türe dönüşüm yapabiliriz. void göstericiler için, gösterici aritmetiği geçerli olmadığı için bu tür dönüşümünü yapmak zorundayız. Aksi durumda void bir gösterici için köşeli parantez operatörünü kullanmak mümkün değildir. Biz dizi elemanlarına ulaşmak için bu operatörü kullanacağız. Bu durumda tür bilgisine de sahip olmamız kaçınılmazdır. Yukarıdaki fonksiyonlarda da bu işlemi gerçekleştirdik. Tamsayı türü için yazılan fonksiyonda parametre değişkenini (int *) türüne, ondalıklı sayı türü için yazılan fonksiyonda ise (double *) türüne dönüştürdük. Bu sayede dizimizin elemanlarına köşeli parantez operatörü ile ulaşmayı mümkün kıldık.

int GetMaxInt(void const *elems,int elemNumber)
{
    int const *myElems = (int *)elems;

    int i, maxIndex = 0;
    int max = myElems[0];

    for (i = 1; i < elemNumber; ++i)
    {
       if (myElems[i] > max)
       {
          max = myElems[i];
          maxIndex = i;
       }
    }
    return maxIndex;
} int GetMaxDouble(void const *elems,int elemNumber)
{
    double const *myElems = (double *)elems;

    int i, maxIndex = 0;
    double max = myElems[0];

    for (i = 1; i < elemNumber; ++i)
    {
       if (myElems[i] > max)
       {
          max = myElems[i];
          maxIndex = i;
       }
    }
    return maxIndex;
}

Yukarıdaki iki fonksiyon, parametre olarak aldıkları dizilerin en büyük elemanlarının indekslerine geri dönmektedir. Bu fonksiyonlarda da yukarıda bahsettiğimiz tür dönüşümü işlemini gerçekleştirdik. En büyük elemanı bulma fonksiyonunun başında, tipik olarak öncelikle dizinin ilk elemanı en büyük eleman kabul edilir ve ilgili türden bir değişkene bu değer atanır. Sonraki adımlarda ise dizinin diğer elemanları, bu değişkenin değeri ile karşılaştırılarak işlemlere devam edilir. İlk fonksiyonda, bu değişkenin türü int, ikinci fonksiyonda ise double olmalıdır. Dolayısıyla bu iki fonksiyonu tek bir fonksiyon haline getirmek mümkün değildir. İki farklı isme sahip fonksiyon yazılması zorunludur. Ancak bir fonksiyon göstericisi tanımlarsak, bu iki fonksiyonu, tanımlayacağımız bu göstericiye atayabiliriz. En büyük elemanı bulmak için çağıracağımız fonksiyon her iki durumda da yapı içerisinde tanımladığımız fonksiyon göstericisinin gösterdiği fonksiyon olacaktır. Yukarıdaki fonksiyonlara benzer şekilde dizinin en küçük elemanının indeksine dönen fonksiyonlar da yazılır.

int GetMinInt(void const *elems,int elemNumber)
{
    int const *myElems = (int *)elems;

    int i, minIndex = 0;
    int min = myElems[0];

    for (i = 1; i < elemNumber; ++i)
    {
       if (myElems[i] < min)
       {
          min = myElems[i];
          minIndex = i;
       }
    }
    return minIndex;
} int GetMinDouble(void const *elems,int elemNumber)
{
    double const *myElems = (double *)elems;

    int i, minIndex = 0;
    double min = myElems[0];

    for (i = 1; i < elemNumber; ++i)
    {
       if (myElems[i] < min)
       {
          min = myElems[i];
          minIndex = i;
       }
    }
    return minIndex;
}

Aritmetik ortalamayı hesaplayan fonksiyonlar ise şu şekilde tanımlanır :

double GetMathAvrDouble(int elemNumber,Result res)
{
    return (double)res.dResult / elemNumber;
}

double GetMathAvrInt(int elemNumber,Result res)
{
    return (double)res.iResult / elemNumber;
}
Bu fonksiyonlar, parametre değişkeni olarak Result türünden bir değişken ve dizinin eleman sayısını tutan int türünden bir değişken almaktadır. Fonksiyonlar, üzerilerinde çalıştıkları türe göre, Result türünden nesnenin iResult ya da dResult değerine erişerek ortalamayı hesaplıyorlar. Bu durumda bu fonksiyonlara gönderilecek Result türünden değişkenin ilgili veri elemanında, dizi elemanlarının toplam değerleri atanmalıdır. Zaten biz bu işlemi yapan birer fonksiyon da yazmıştık. Şimdi tanımladığımız bu yapıyı nasıl kullanacağımızı inceleyelim. Programımız hem tamsayı dizileri, hem de ondalıklı sayılar dizileri üzerinde işlem yapacak. Kullanıcıdan bu bilgiyi alacağız. MyArray yapısı türünden bir nesne tanımlayarak elemanlara ilgili değerleri atayacağız. Bu yapı nesnesinin elemanlarının doldurulması işlemini de bir fonksiyon ile yapalım. Ancak her iki tür için de tek bir fonksiyon yazacağız. Bu noktada, yapımızın type elemanından faydalanacağız :

void StartOperation(MyArray *array,void *myVals)
{
    if (array->type == 0) //integer
    {
       array->vals.iVals = (int *)myVals;
       array->GetSum = GetSumInt;
       array->GetMin = GetMinInt;
       array->GetMax = GetMaxInt;
       array->GetMathAvr = GetMathAvrInt;
    }

    else if (array->type == 1) //double
    {
       array->vals.dVals = (double *)myVals;
       array->GetSum = GetSumDouble;
       array->GetMin = GetMinDouble;
       array->GetMax = GetMaxDouble;
       array->GetMathAvr = GetMathAvrDouble;
    }
}
Eğer türümüz tamsayı ise ilk koşul gerçeklenir ve bu bloktaki işlemler yapılır. Bu blokta yapımızın fonksiyon göstericisi elemanlarına, tamsayılar üzerinde işlem yapan fonksiyonların adresleri atanmıştır. Eğer türümüz ondalıklı sayı ise ikinci koşul gerçeklenir. Bu durumda da fonksiyon göstericisi elemanlarına ondalıklı sayılar üzerinde işlem yapan fonksiyonların adresleri atanır. Bu yapı nesnesini bir defa oluşturduktan sonra artık farklı fonksiyon isimlerini hatırlamak zorunda kalmayacağız. Göstericilerimiz ilgili fonksiyonları gösterdiklerine göre, bize düşen sadece yapının fonksiyon göstericisi isimlerini kullanarak ilgili parametrelerle fonksiyonları çağırmak olacak. Şimdi programımızın ana bloğunu inceleyelim :

int main()
{
    MyArray array;
    int opNum = 0,i;
    int myIntArray[] = {1,2,3,4,5,6,7,8,9,10};
    double myDoubleArray[] = {1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,10.10};
    Result tmpRes;


    while (opNum < 1 || opNum > 3)
    {
       printf("Tamsayi islemleri icin : 1\n");
       printf("Ondalikli sayi islemleri icin : 2\n");
       printf("Programdan cikmak icin : 3\n");
       printf("Giriniz..\n");
       scanf("%d",&opNum);
    }

    if (opNum == 1)
    {
       array.type = t_integer;
       StartOperation(&array,(void *)myIntArray);
       printf("Dizinin Elemanlari :\n");

       for (i = 0; i < ARRAY_SIZE; ++i)
          printf("%d\n",array.vals.iVals[i]);

       tmpRes = array.GetSum(array.vals.iVals,ARRAY_SIZE);
       printf("Toplam = %d\n",tmpRes.iResult);
       printf("En Kucuk Eleman = %d\n",array.vals.iVals[array.GetMin(array.vals.iVals,ARRAY_SIZE)]);
       printf("En Buyuk Eleman = %d\n",array.vals.iVals[array.GetMax(array.vals.iVals,ARRAY_SIZE)]);
       printf("Aritmetik Ortalama = %lf\n",array.GetMathAvr(ARRAY_SIZE,tmpRes));
    }

    else if (opNum == 2)
    {
       array.type = t_double;
       StartOperation(&array,(void *)myDoubleArray);
       printf("Dizinin Elemanlari :\n");

       for (i = 0; i < ARRAY_SIZE; ++i)
          printf("%lf\n",array.vals.dVals[i]);

       tmpRes = array.GetSum(array.vals.dVals,ARRAY_SIZE);
       printf("Toplam = %lf\n",tmpRes.dResult);
       printf("En Kucuk Eleman = %lf\n",array.vals.dVals[array.GetMin(array.vals.dVals,ARRAY_SIZE)]);
       printf("En Buyuk Eleman = %lf\n",array.vals.dVals[array.GetMax(array.vals.dVals,ARRAY_SIZE)]);
       printf("Aritmetik Ortalama = %lf\n",array.GetMathAvr(ARRAY_SIZE,tmpRes));
    }

    else if (opNum == 3)
    {
       printf("Program Sonlaniyor...\n");
    }

    return 0;
}
Kullanıcı klavyeden 1 değerini girerse, yapımızı tamsayı türüne göre, 2 değerini girerse ondalıklı sayı türüne göre oluşturuyoruz. Daha sonra yapımızın fonksiyon göstericilerini kullanarak istediğimiz fonksiyonların çağrılmasını sağlıyoruz. Programın çıktı görüntüleri şöyledir :

Seçeneklerin sunulması :

    

Tamsayı seçeneğinin seçilmesi :                                Ondalıklı Sayı Seçeneğinin Seçilmesi :

             

Uygulamanın tamamını aşağıdan indirebilirsiniz. MinGW 2.05 ve Microsoft Visual C++ 6.0 derleyicilerinde derlenmiştir.

Bir sonraki makalemizde görüşmek üzere..

Kaynak kod için tıklayın.

Makale:
Fonksiyon Göstericilerinin Kullanılması C ve Sistem Programlama Çiğdem Çavdaroğlu
  • Yazılan Yorumlar
  • Yorum Yaz
Bu konu hakkında yayınlanan yorum bulunmamaktadır.
"Yorum Yaz" tabını kullanarak sizde yorumlarınızı yazabilirsiniz.
Yorum yazabilmek için üye girişi yapmalısınız. Üye girişi için tıklayın.
Üye değilseniz Üyel Ol linkine tıklayarak üyeliğinizi hemen başlatabilirisniz.
 
  • Bu Konuda Son 10
  • Eklenen Son 10
  • Bu Konuda Geçmiş 10
Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
Son Eklenen 10 Makale Yükleniyor
Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor