C Programlama Dili'ne Giriş

  Dersler
* Önsöz

* Giriş
* Veri Tipleri, Değişkenler
* Operatörler
* Temel G/Ç Fonksiyonları
* Temel Kütüphane Fonksiyonları
* Karşılaştırma Deyimleri
* Döngüler
* Fonksiyonlar I
* Fonksiyonlar II
* Diziler
* Gösterici (Pointer) Kavramı
* Katarlar (Stringler)
* Dinamik Bellek Yönetimi
* Gösterici Uygulamaları
* Yapılar ve Birlikler
* Dosya Yönetimi
* Bit Düzeyinde Çalışmak
* Port Denetimi
* Grafik Kullanımı
* C Makroları

* Kısaca C++
* Derleme Seçenekleri
* Tarih-Saat Fonksiyonları
* Monte-Carlo Yöntemleri
* Fortran ve C

* Yararlanılan Kaynaklar

  C/C++ Derleyicileri
* Dev-C++
* Salford (silversoft FTN95)
* GCC
* Turbo C
* Eclipse IDE
* NetBeans IDE

  Dış Bağlantılar
* programlama.com
* C Programcıları Derneği

* C (wikipedia)
* C++ (wikipedia)
* cplusplus.com
* koders.com
* Hot scripts

 

Ders 11: Gösterici (Pointer) Kavramı

###################- (%99)

En son güncelleme: Wed, 30 Nov 2011 13:22:02 +0200

Giriş

Hemen hemen bütün programlama dillerinin temelinde gösterici (pointer) veri tipi bulunmaktadır. Bir çok dil gösterici kullanımını kullanıcıya sunmamıştır veya çok sınırlı olarak sunmuştur. Fakat C Progrmalama Dili'nde göstericiler yoğun olarak kullanılır. Hatta gösterici kavramı C dilinin bel kemiğidir. Kavranması biraz güç olan göstericiler için -latife yapılıp- C kullanıcılarını "gösterici kullanabilenler ve kullanmayanlar" olmak üzere iki gruba ayıranlar da olmuştur. Özetle, bir C programcısı gösterici kavramını anlamadan C diline hakim olamaz.

Türkçe yazılan C kitaplarda pointer kelimesi yerine aşağıdaki ifadelerden biri karşılaşılabilir:

pointer = işaretçi = gösterici = gösterge

Anlatımda, gösterici terimini kullanacağız.


11.1   Değişken ve Bellek Adresi

Bilgisayarın ana belleği (RAM) sıralı kaydetme gözlerinden oluşmuştur. Her göze bir adres atanmıştır. Bu adreslerin değerleri 0 ila belleğin sahip olduğu üst değere bağlı olarak değişebilir. Örneğin 1GB MB bir bellek, 1024*1024*1024 = 1073741824 adet gözden oluşur. Değişken tiplerinin bellekte işgal ettiği alanın bayt cinsinden uzunluğu sizeof() operatörüyle öğrenildiğini hatırlayın. (bkz: Program 2.1).

Bir programlama dillinde, belli bir tipte değişken tanımlanıp ve bir değer atandığında, o değişkene dört temel özellik eşlik eder:

  1. değişkenin adı
  2. değişkenin tipi
  3. değişkenin sahip olduğu değer (içerik)
  4. değişkenin bellekteki adresi

Örneğin tam adlı bir tamsayı değişkenini aşağıdaki gibi tanımladığımızı varsayalım:

    int tam = 33;

Bu değişken için, int tipinde bellekte (genellikle herbiri 1 bayt olan 4 bayt büyüklüğünde) bir hücre ayrılır ve o hücreye 33 sayısı ikilik (binary) sayı sitemindeki karşılığı olan 4 baytlık (32 bitlik):

00000000 00000000 00000000 00100001

sayısı elektronik olarak yazılır. tam değişkenine ait dört temel özellik Şekil 11.1'deki gibi gösterilebilir:



Şekil 11.1: Bir değişkene eşlik eden dört temel özellik

Bellek adresleri genellikle onaltılık (hexadecimal) sayı sisteminde ifade edilir. 0x3fffd14 sayısı onluk (decimal) sayı sisteminde 67108116 sayına karşık gelir. Bunun anlamı, tam değişkeni, program çalıştığı sürece, bellekte 67108116. - 67108120. numaralı gözler arasındaki 4 baytlık hücreyi işgal edecek olmasıdır. Şekil 11.1'deki gösterim, basit ama anlaşılır bir tasvirdir. Gerçekte, int tipindeki tam değişkeninin bellekteki yerleşimi ve içeriği (değeri) Şekil 11.2'de gösterildiği gibi olacaktır.



Şekil 11.2: tam adlı değişkenin bellekteki gerçek konumu ve ikilik düzendeki içeriği

Değişkenin saklı olduğu adres, & karakteri ile tanımlı adres operatörü ile öğrenilebilir. Bu operatör bir değişkenin önüne konursa, o değişkenin içeriği ile değil adresi ile ilgileniliyor anlamına gelir. Aşağıdaki program parçasının:

          int tam = 33;

          printf("icerik: %d\n",tam);
          printf("adres : %p\n",&tam);

çıktısı:

          icerik: 33
          adres : 3fffd14

şeklindedir. Burada birinci satır tam değişkeninin içeriği, ikinci ise adresidir. Adres yazdırılırken %p tip belirleyicisinin kullanıldığına dikkat ediniz.


11.2   Gösterici Nedir?

Gösterici, bellek alanındaki bir gözün adresinin saklandığı değişkendir. Göstericilere veriler (yani değişkenlerin içeriği) değil de, o verilerin bellekte saklı olduğu hücrenin başlangıç adresleri atanır. Kısaca gösterici adres tutan bir değişkendir.

Bir gösterici, diğer değişkenler gibi, sayısal bir değişkendir. Bu sebeple kullanılmadan önce program içinde bildirilmelidir. Gösterici tipindeki değişkenler şöyle tanımlanır:

          tip_adı *gösterici_adı;

Burada tip_adı herhangi bir C tip adı olabilir. Değişkenin önünedeki * karakteri yönlendirme (indirection) operatörü olarak adlandırılır ve bu değişkenin veri değil bir adres bilgisi tutacağını işaret eder. Örneğin:

          char  *kr;             /* tek bir karakter için */
          int   *x;              /* bir tamsayı için */
          float *deger, sonuc;   /* deger gösterici tipinde, sonuc sıradan bir gerçel değişkenler */

Yukarıda bildirilen göstericilerden; kr bir karakterin, x bir tamsayının ve deger bir gerçel sayının bellekte saklı olduğu yerlerin adreslerini tutar.

Bir göstericiye, bir değişkenin adresini atamak için adres operatörünü kullanabiliriz. Örneğin tamsayı tipindeki tam adlı bir değişken ve ptam bir gösterici olsun. Derleyicide, aşağıdaki gibi bir atama yapıldığında:

          int *ptam, tam = 33;
          .
          .
          .
          ptam = &tam;

ptam göstericisinin tam değişkeninin saklandığı adresi tutacaktır. Bu durum Şekil 11.3'deki gibi tasvir edilir.



Şekil 11.3: Göstericinin bir değişkenin adresini göstermesi

Şekil 11.3'deki gösterimde, ptam göstericisinin içeriği tam değişkeninin içeriği (33) değil adresidir (0x3fffd14). Ayrıca, ptam değişkeni, bellekte başka bir hücrede saklandığına ve bu hücrenin int değil int * tipinde bir bölge olduğuna dikkat ediniz. Buraya kadar anlatılanlar, Program 11.1'de özetlenmiştir.

Program 11.1: Bir değişkenin içeriğini ve adresini ekrana yazdırma
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
/* 10prg01.c: ilk gösterici programı */

#include <stdio.h>

int main()
{
   int *ptam, tam = 33;

   ptam = &tam;

   printf("tam:  icerik = %d\n", tam);
   printf("tam:   adres = %p\n",&tam);
   printf("tam:   adres = %p\n",ptam);

 return 0;
}

7. satırda değişkenler bildirilmiştir. 9. satırdaki atama ile tam değişkeninin adresi, ptam göstericisine atanmıştır. Bu satırdan itibaren ptam, tam değişkeninin gösterir. 11. satıda tam'ın içeriği (33 sayısı), 12. ve 13. satırda tam'ın adresi, %p tip karakteri ile, ekrana yazdırılmıştır. Ekran çıktısı incelendiğinde, &tam ve ptam içereriğinin aynı anlamda olduğu görülür.

ÇIKTI

tam:  icerik = 33
tam:   adres = 0x3fffd14
tam:   adres = 0x3fffd14

tam adlı değişkenin içeriğine ptam gösterici üzerinde de erişilebilir. Bunun için program içinde ptam değişkeninin önüne yönelendirme operatörü (*) koymak yeterlidir. Yani *ptam, tam değişkeninin adresini değil içeriğini tutar. Buna göre:

          *ptam = 44;

komutuyla, ptam'ın adresini tuttuğu hücreye 44 değeri atanır. Bu durum, Program 11.2'de gösterilmiştir.

Program 11.2: Bir değişkenin içeriğini ve adresini ekrana yazdırma
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
/* 10prg02.c: ikinci gösterici programı */

#include <stdio.h>

int main()
{
   int *ptam, tam = 33;

   ptam = &tam;    /* ptam -> tam */

   printf("&tam  = %p\n",&tam);
   printf("ptam  = %p\n",ptam);
   printf("\n");

   printf("tam   = %d\n",tam);
   printf("*ptam = %d\n",*ptam);
   printf("\n");

   *ptam = 44;     /* tam = 44 anlamında */

   printf("tam   = %d\n",tam);
   printf("*ptam = %d\n",*ptam);

 return 0;
}

ÇIKTI

&tam  = 0x3fffd14
ptam  = 0x3fffd14

tam   = 33
*ptam = 33

tam   = 44
*ptam = 44

Özetle ptam = &tam atamasıyla:
  • *ptam ve tam, tam adlı değişkenin içeriği ile ilgilidir.
  • ptam ve &tam, tam adlı değişkenin adresi ile ilgilidir.
  • * yönlendirme ve & adres operatörüdür.

11.3   Gösterici Aritmetiği

Göstericiler kullanılırken, bazen göstericinin gösterdiği adres taban alınıp, o adresten önceki veya sonraki adreslere erişilmesi istenebilir. Bu durum, göstericiler üzerinde, aritmetik işlemcilerin kullanılmasını gerektirir. Göstericiler üzerinde yalnızca toplama (+), çıkarma (-), bir arttırma (++) ve bir eksiltme (--) operatörleri işlemleri yapılabilir.

Aşağıdaki gibi üç tane gösterici bildirilmiş olsun:
          char   *kar;
          int    *tam;
          double *ger;

Bu göstericiler sırasıyla, bir karakter, bir tamsayı ve bir gerçel sayının bellekte saklanacağı adreslerini tutar. Herhangi bir anda, tuttukları adresler de sırasıyla 10000 (0x2710), 20000 (0x4e20) ve 30000 (0x7530) olsun. Buna göre aşağıdaki atama işelemlerinin sonucu:

          kar++;
          tam++;
          ger++;

sırasyla 10001 (0x2711), 20004 (0x4e24) ve 30008 (0x7538) olur. Bir göstericiye ekleme yapıldığında, o anda tuttuğu adres ile eklenen sayı doğrudan toplanmaz. Böyle olsaydı, bu atamaların sonuçları sırasıyla 10001, 20001 ve 30001 olurdu. Gerçekte, göstericiye bir eklemek, göstericinin gösterdiği yerdeki veriden hemen sonraki verinin adresini hesaplamaktır.

Genel olarak, bir göstericiye n sayısını eklemek (veya çıkarmak), bekllekte gösterdiği veriden sonra (veya önce) gelen n. elemanın adresini hesaplamaktır. Buna göre aşağıdaki atamalar şöyle yorumlanır.

          kar++;             /* kar = kar +   sizeof(char)   */
          tam = tam + 5;     /* tam = tam + 5*sizeof(int)    */
          ger = ger - 3;     /* ger = ger - 3*sizeof(double) */

Program 11.3, bu bölümde anlatlanları özetlemektedir. İnceleyiniz.

Program 11.3: Gösterici aritmetiği
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
/* 10prg03.c: gösterici aritmetiği */

#include <stdio.h>


int main()
{
   char   *pk, k = 'a';
   int    *pt, t = 22;
   double *pg, g = 5.5;

   pk = &k;
   pt = &t;
   pg = &g;

   printf("Onceki  adresler: pk= %p  pt= %p   pg= %p \n", pk, pt, pg);

   pk++;
   pt--;
   pg = pg + 10;

   printf("Sonraki adresler: pk= %p  pt= %p   pg= %p \n", pk, pt, pg);

 return 0;
}

ÇIKTI

Onceki  adresler: pk= 0xbfbbe88f  pt= 0xbfbbe888   pg= 0xbfbbe880
Sonraki adresler: pk= 0xbfbbe890  pt= 0xbfbbe884   pg= 0xbfbbe8d0


11.4   Gösterici ve Diziler Arasındaki İlişki

C dilinde göstericiler ve diziler arasında yakın bir ilişki vardır. Bir dizinin adı, dizinin ilk elemanının adresini saklayan bir göstericidir. Bu yüzden, bir dizinin herhangi bir elemanına gösterici ile de erişilebilir. Örneğin:

          int kutle[5], *p, *q;

şeklinde bir bildirim yapılsın. Buna göre aşağıda yapılan atamalar geçerlidir:

          p = &kutle[0];    /* birinci elemanın  adresi p göstericisne atandı */
          p = kutle;        /* birinci elemanın  adresi p göstericisne atandı */
          q = &kutle[4];    /* son      elemanın adresi q göstericisne atandı */

İlk iki satırdaki atamalar aynı anlamdadır. Dizi adı bir gösterici olduğu için, doğrudan aynı tipteki bir göstericiye atanabilir. Ayrıca, i bir tamsayı olmak üzere,

          kutle[i];
ile
          *(p+i);

aynı anlamdadır. Bunun sebebi, p göstericisi kutle dizisinin başlangıç adresini tutmuş olmasıdır. p+i işlemi ile i+1. elemanın adresi, ve *(p+i) ile de bu adresteki değer hesaplanır.

 NOT
Bir dizinin, i. elemanına erişmek için *(p+i) işlemi yapılması zorunludur. Yani
  *p+i;   /* p nin gösterdiği değere (dizinin ilk elemanına) i sayısını ekle */
  *(p+i); /* p nin gösterdiği adresten i blok ötedeki sayıyı hesapla */
anlamındadır. Çünkü, * operatörü + operatörüne göre işlem önceliğine sahiptir.

Program 11.4'de tanımlanan fonksiyon kendine parameter olarak gelen n elemanlı bir dizinin aritmetik ortlamasını hesaplar.

Program 11.4: Bir dizi ile gösterici arasındaki ilişki
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
/* 10prg04.c: gösterici dizi ilişkisi */

#include <stdio.h>

double ortalama(double dizi[], int n);

int main()
{

   double a[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
   double o;

   o = ortalama(a,5);

   printf("Dizinin ortalaması = %lf\n",o);

 return 0;
}

double ortalama(double dizi[], int n)
{
   double *p, t=0.0;
   int i;
  
   p = dizi;    /* veya p = &dizi[0] */

   for(i=0; i<n; i++)
      t += *(p+i);

   return (t/n);
}

ÇIKTI

Dizinin ortalaması = 3.300000

20. - 31. satırda tanımlanan fonksiyon aşağıdaki gibi de yazılabilirdi:

          double ortalama(double dizi[], int n)
          {
             double *p, t=0.0;
   
             for(p=dizi; p < &dizi[n]; p++)
                t += *p;
  
             return (t/n);
          }

Bu fonksiyonda, döngü sayacı için (i değişkeni) kullanılmayıp, döngü içinde dizinin başlangıç adresi p göstericisine atanmış ve koşul kısmında adres karşılaştırılması yapılmıştır. Bu durumda döngü, p'nin tuttuğu adresten başlar, ve p'nin adresi dizinin son elemanının adresinden (&dizi[n-1]) küçük veya eşit olduğu sürece çevrim yinelenir.


11.5   Fonksiyon Parametresi Olan Göstericiler

C (ve C++) programlama dilinde fonksiyon parametreleri değer geçerek (pass by value) yada adres geçerek (pass by reference) olarak geçilebilir. Bölüm 8'deki uygulamalarda fonksiyonlara parametreler değer geçerek taşınmıştı. Bu şekilde geçirilen parametreler, fonksiyon içersinde değiştirilse bile, fonksiyon çağılıldıktan sonra bu değişim çağrılan yerdeki değerini değiştirmez. Fakat, bir parametre adres geçerek aktarılısa, fonksiyon içindeki değişikler geçilen parametreyi etkiler. Adres geçerek aktarım, gösterici kullanmayı zorunlu kılar.

Örneğin, Program 11.5'de fonksiyonlara değer ve adres geçerek aktarımın nasıl yapılacağı gösterilmiştir.

Program 11.5: Bir değişkenin içeriğini ve adresini ekrana yazdırma
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
/* 10prg05.c: Değer geçerek ve adres geçerek aktarım */

#include <stdio.h>

void f1(int  );   /* iki fonksiyon */
void f2(int *);

int main()
{
   int x = 55;

   printf("x in degeri,\n");
   printf("Fonksiyonlar cagrilmadan once: %d\n",x);

   /* f1 fonksiyonu çağrılıyor...*/ 
   f1(x);     
   printf("f1 cagirildiktan sonra       : %d\n",x);


   /* f2 fonksiyonu çağrılıyor...*/ 
   f2(&x);
   printf("f2 cagirildiktan sonra       : %d\n",x);

 return 0;
}


/* Değer geçerek aktarım */
   void f1(int n){
      n = 66;
      printf("f1 fonksiyonu icinde         : %d\n",n);
   }

/* Adres geçerek aktarım */
   void f2(int *n){
      *n = 77;
      printf("f2 fonksiyonu icinde         : %d\n",*n);
   }

5. ve 6. satırlada kendine geçilen parametrenin değerini alan f1 fonksiyonu ve parametrenin adresini alan f2 adlı iki fonksiyon örneği belirtilmişdir. 11. satırdaki x değişkeni 16. ve 21. satırlarda, f1(x) ve f2(&x) fonksiyonlarına, sırasıyla değer ve adres geçerek aktarılmıştır. f1 içinde x (n = 66; işlemi ile) değişime uğramış, fakat çağrılma işleminin sonucunda, x'in değeri değişmemiştir. Ancak f2 içinde x'in ( *n = 77 işlemi ile) değişimi, çağrıldıktan sonrada korunmuştur. Yani, adres geçerek yaplıan aktarımda, f2'ye aktarılan değer değil adres olduğu için, yollanan x parametresi f2 içinde değişikliğe uğrayacak ve bu değişim çağrıldığı 21. satırdan itibaren devam edecektir.

ÇIKTI

x in degeri,
Fonksiyonlar cagrilmadan once: 55
f1 fonksiyonu icinde         : 66
f1 cagirildiktan sonra       : 55
f2 fonksiyonu icinde         : 77
f2 cagirildiktan sonra       : 77

Program 11.6'da iki tamsayı değişkeninin nasıl takas (swap) edileceği gösterilmiştir. Bu işlemi C porgramlama dilinde, eğer değişkenler global olarak bildirilmemişse, gösterici kullanmadan bu işlemi yapmak imkansızdır.

Program 11.6: İki tamsayının birbiri ile takas edilmesi
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
/* 10prg06.c: iki sayının birbiri ile takas edilmesi */

#include <stdio.h>

void takas(int *, int *);

int main()
{
   int a, b;

   a=22; b=33;

   printf("takas oncesi : a=%d   b=%d\n",a,b);

   takas(&a, &b);

   printf("takas sonrasi: a=%d   b=%d\n",a,b);


 return 0;
}


void takas(int *x, int *y)
{
  int z;

   z = *x;
  *x = *y;
  *y =  z;
}

ÇIKTI

takas oncesi : a=22   b=33
takas sonrasi: a=33   b=22


11.6   Geri Dönüş Değeri Gösterici Olan Fonksiyonlar

Fonkiyonların geri dönüş değeri bir gösterici olabilir. Bu durumda fonksiyon bir değer değil adres döndürecek demektir.

Program 11.7'da önce bir dizinin indisleri, dizi değerleri ve dizi elemanlarının adresleri ekrana basılır. Daha sonra, maxAdr(); fonksiyonu ile dizinin en büyük elemanının adresi döndürülür. Bu örnek progam, göstericilerin gücünü çok zarif bir biçimde bize sunmaktadır. Lütfen inceleyiniz.

Program 11.7: Bir dizinin en büyük elemanının adresini öğrenmek
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
/* 10prg07.c:  geri donus degeri gosterici olan fonksiyon */

#include <stdio.h>

double* maxAdr(double a[], int boyut){
  double  ebd =  a[0];
  double *eba = &a[0];
  int i;
  for(i=1; i<boyut; i++){
    if(a[i]>ebd){
      ebd =  a[i]; // en büyük deger
      eba = &a[i]; // en büyük adres
    }
  }
  return eba;
}


int main()
{
  double x[6] = {1.1, 3.3, 7.1, 5.4, 0.2, -1.5};
  double *p;
  int k;
  // indis, dizi ve adresini ekrana bas
  for(k=0; k<6; k++){
    printf("%d %lf %p\n", k, x[k], &x[k]);
  }

  p = maxAdr(x,6);

  printf("En büyük deger: %lf\n", *p);
  printf("En büyük adres: %p \n",  p);
  printf("En büyük konum: %d \n",  int(p-&x[0]));

  return 0;
}

Dizi elemanları 21. satırda belirlenir. Bu dizinin indisleri, değerleri ve adresleri 26. satırda ekrana basılmıştır. En büyük elemanın adresi 29. satırdaki p = maxAdr(a,6); ile p göstericisine atanmıştır. 5. satırda bildirilen maxAdr(); fonksiyonu, en büyük elemanın adresini hesaplayıp çağrılan yere gönderir. Burada dikkat edilmesi gereken husus, fonksiyonun dönüş değerinin yerel eba göstericisi olmasıdır. eba göstericisi 12. satırda hesaplanan ve fonksiyon parametersi olan dizinin en büyük elemanın adresini tutmaktadır. Son olarak, fonksiyon çağırıldıktan sonra, p göstericisin gösterdiği değer, tuttuğu adres ve dizinin birinci elemanına göre konumu (indisi) ekrana basılmıştır. Indis hesabı int(p-&x[0]) işlemi ile yapılabilir. Bu aslında, p göstericisin tuttuğu adres ile dizinin ilk elemanının adresi arasındaki farktır. Sonuç yine bir adres olduğu için tamsayı değer elde etmek için int() takısı kullanılmıştır. Netice itibarıyla bir fonksiyon ile üç şey aynı anda öğrenilmiş olur.

ÇIKTI

0 1.100000 0x7fff41b29ec0
1 3.300000 0x7fff41b29ec8
2 7.100000 0x7fff41b29ed0
3 5.400000 0x7fff41b29ed8
4 0.200000 0x7fff41b29ee0
5 -1.500000 0x7fff41b29ee8

En büyük deger: 7.100000
En büyük adres: 0x7fff41b29ed0
En büyük konum: 2


11.7   Fonksiyon Göstericileri

Fonksiyon göstericileri, gösterici (pointer) kavramının gücünü gösterin diğer bir uygulama alanıdır. Dizilerde olduğu gibi, fonksiyon adları da sabit göstericidir.
Fonksiyon betiğinin (kodlarının) bellekte bir adreste tutulduğu şeklinde düşünebiliriz. Fonksiyon göstericisi basit olarak fonksiyon adının saklandığı bellek adresini tutan bir göstericidir. Fonksiyon göstericileri sayesinde fonksiyonlar başka fonksiyonlara parametre olarak aktarılabilmektedir.

Fonksiyon adının bellete yer işgal ettiği şöyle öğrenilebilir:

          int f(int);      /* fonksiyon bildirimi */ 
          int (*pf)(int);  /* fonksiyon göstericisi bildirimi */ 
          pf = &f;         /* f'nin adresini pf'ye ata! */

Program 11.8: Bir fonksiyonun 'adresini' iki yoldan öğrenme
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
/* 10prg08.c:  Bir fonksiyonun 'adresini' öğrenme */

#include <stdio.h>

int f(int n){
  int f=1, i;
  for(i=1; i<n; i++)
    f*=i;
  return f;
}

int main()
{
  int (*pf)(int);
  pf = &f;

  printf("Fonksiyonun adresi = %p\n", &f);
  printf("Fonksiyonun adresi = %p\n", pf);

  return 0;
}

ÇIKTI

Fonksiyonun adresi = 0x4005b0
Fonksiyonun adresi = 0x4005b0

Aşağıdaki ikinci örnekte, bir fonksiyon diğer fonksiyona parametre olarak geçirilmiş ve sayısal türevi hesaplanmıştır. Türev hesaplanırken merkezi fark yaklaşımı (central difference approximation) yöntemi kullanılmıştır.

 NOT
mfy yönteminde f(x) fonksiyonunun (h küçük bir değer olmak üzere) Taylor açılımları söyledir:

      f(x+h) = f(x) + h*f'(x) + h2*f''(x)/2! + h3*f'''(x)/3!  + ...
      f(x-h) = f(x) - h*f'(x) + h2*f''(x)/2! - h2*f'''(x)/3!  + ...
   -
   -----------------------------------------------------------------------
      f(x+h) - f(x-h) = 2*h*f'(x) + O(h3)
Burada O(h3)'lü terimler ihmal edilirse birinci türev yaklaşık olarak:

  f'(x) = [f(x+h) - f(x-h)]/2h
formülü ile hesaplanır.

Program 11.9: Türev alan fonksiyon
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
/* 10prg09.c:  Fonksiyon Göstericisi ile türev hesabı */

#include <stdio.h>

double f(double);
double turev( double (*)(double), double);

int main()
{

  double x = 1.1;

  printf("Fonksiyon x = %lf deki degeri = %lf\n", x, f(x));
  printf("Fonksiyon x = %lf deki turevi = %lf\n", x, turev(f, x) );

  return 0;
}


// türevi hesaplanacak fonksiyon
double f(double x){
  return x*x*x - 2*x + 5.;
}
// sayısal türev alan fonksiyon
double turev( double (*fonk)(double x), double x){
  double h = 1.0e-3;
  return (fonk(x+h)-fonk(x-h)) / (2*h);
}

ÇIKTI

Fonksiyon x = 1.100000 deki degeri = 4.131000
Fonksiyon x = 1.100000 deki turevi = 1.630001


11.8   NULL Gösterici

Bir göstericinin bellekte herhangi bir adresi göstermesi, veya öncden göstermiş olduğu adres iptal edilmesi istemirse NULL sabiti kullanılır. Bu sabit derleyicide ASCII karakter tablosunun ilk karakteridir ve '\0' ile sembolize edilir.

          int *ptr, a = 12;
          .
          .
          ptr = &a;     /* ptr bellekte a değişkenin saklandığı yeri gösteriyor  */
          .
          .
          ptr = NULL;   /* ptr bellekte hiç bir hücreyi göstermiyor */
          *ptr = 8      /* hata! NULL göstericinin gösterdiği yere bir değer atanamaz */

11.9   void Tipindeki Göstericiler

void göstericiler herhangi bir veri tipine ait olmayan göstericilerdir. Bu özelliğinden dolayı, void gösterici genel gösterici (generic pointer) olarak da adlandırılır.

void göstericiler, void anahtar sözcüğü ile bildirilir. Örneğin:

          void *adr;
gibi.

void göstericiler yalnızca adres saklamak için kullanılır. Bu yüzden diğer göstericiler arasında atama işlemlerinde kullanılabilir. Örneğin aşağıdaki atamada derleyici bir uyarı veya hata mesajı vermez:

          void *v;
          char *c;
          .
          .
          .
          v = c;   /* sorun yok !*/

Program 11.10'de void tipindeki bir göstericinin, program içinde, farklı tipteki verileri nasıl göstereceği ve kullanılacağı örneklenmiştir. İnceleyiniz.

Program 11.10: void gösterici ile farklı tipteki verileri gösterme
01: 
02: 
03: 
04: 
05: 
06: 
07: 
08: 
09: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
/* 10prg10.c:  void gosterici (generic pointer) uygulamasi */

#include <stdio.h>

int main()
{
  char    kar = 'a';
  int     tam = 66;
  double  ger = 1.2;
  void   *veri;

  veri = &kar;
  printf("veri -> kar: veri  %c  karakter degerini gosteriyor\n", *(char *) veri);

  veri = &tam;
  printf("veri -> tam: simdi veri  %d  tamsayi degerini gosteriyor\n", *(int *) veri);

  veri = &ger;
  printf("veri -> ger: simdi de veri  %lf  gercel sayi degerini gosteriyor\n", *(double *) veri);

  return 0;
}

ÇIKTI

veri -> kar: veri  a  karakter degerini gosteriyor
veri -> tam: simdi veri  66  tamsayi degerini gosteriyor
veri -> ger: simdi de veri  1.200000  gercel sayi degerini gosteriyor

Benzer olarak, fonksiyon parameterelerinin kopyalanması sırasında da bu türden atama işlemleri kullanılabilir. Uygulamada, tipten bağımsız adres işlemlerinin yapıldığı fonksiyonlarda, parametre değişkeni olarak void göstericiler kullanılır. Örneğin

          void free (void *p)
          {
             .
             .
             .
          }

Parametresi void *p olan free fonksiyonu, herhangi türden gösterici ile çağrılabilir.



Powered by PHP