|
C de Pointerlar
Pointerlar C de en ünlü ve en çok korkulan konulardan biridir. Bütün konular pointerlara kadar çok akıcı bir şekilde öğrenilir ama pointerlara gelindiğinde bir süre bir takılma yaşanır. Hocamız bunu “Eğer pointerları öğrenirseniz C için bir tepeyi aşmış olacaksınız, sonrasını öğrenmek artık çok kolay ve kendiliğinden olacaktır.” şeklinde değerlendirmişti. Pointerlar, C ye özgü bir konudur ve kod yazarken olmazsa olmazlardandır.( çoğu kitapta pointer ın Türkçesi “gösterici” olarak kullanılır. ) Şimdi pointerlara bir giriş yapalım ve sonrasında çeşitli kullanımlarını görerek ilerleyelim.
Pointer, aslında bir değişkendir. O da herhangi bir değişken gibi içinde sayısal bir değer tutar. Tek farkı; bu tuttuğu değişken, başka bir değişkenin bellekteki adresidir. Yani pointer değişkeni, içinde başka bir değişkenin bellek adresini tutar. Bu adres hexadecimal (onaltılık) sistemdedir. 3D245FF, 89E4 gibi.
Her değişkenin bellekte bir adresi vardır. Mesela int i=5; diye bir değişken tanımladığımızda, i değişkeni için bellekte 4 bytelık bir hafıza açılır. Bu hafızanın bellekte bir adresi vardır, 4C567 gibi. Değişkenin bellekteki adresini öğrenmek için & operatörünü kullanacağız. Bunu denemek için şöyle bir şey uygulayabiliriz:
Mesela kodumuzda herhangi bir int türünde a değişkeni tanımlayalım ve bunu ekrana yazdıralım.
int a=3;
printf ( “%d”,a);
Bunun çıktısı ekranda 3 olacaktır. Şimdi de tanımladığımız a değişkeninin bellekteki adresini görmek için daha önceden de alışık olduğunuz & operatörünü kullanarak
printf (“%X”,&a);
satırını ekleyelim. Şimdi ekranda, a değişkeninin o an bellekte ona ayrılmış adresi olan adresi görülecektir. Benim bilgisayarımda bu değer 417000.
İşte pointer değişkeni, içinde böyle bir adres tutabilen değişkendir. Peki pointerın içinde tuttuğu bu adres ne işe yarar? Pointer, içinde tuttuğu adresteki değişkeni gösterir ve bu adresteki değişkenin değerine ulaşmamıza yarar. Zaten pointerın da amacı budur.
Pointerların Tanımlanması
Pointerların aslında birer değişken olduğunu ama diğerlerinden biraz daha farklı olduğunu söylemiştik. Bu yüzden pointer tanımlarken diğer değişkenlerden daha farklı bir tanımlama yaparız. Bunu yaparken de ‘*’ operatörünü kullanırız. Mesela p isminde bir pointer tanımlayalım:
int *p;
İşte bu kadar kolay. Burada “ben bir değişken tanımlıyorum ama bu herhangi bir değişken değil, bir pointer” demiş oluyorsunuz. p ismindeki bu pointera şimdi de bir değer atayalım:
int a=20;
int *p = &a;
Pointerlar bellek adresi tutan bir değişken olduğu için, ona atadığımız değer başka bir değişkenin adresi olmalıdır. Burada ona “int türündeki a değişkeninin adresi”ni atadık. Fark ettiyseniz pointerın türüyle, içine değer olarak atadığımız değişkenin türü aynı, ikisi de int türünde.
Örnek:
//float tipinde bir değişken ve bir pointer tanımlayalım
float sayi=26;
float *ptr;
//tanımladıgımız ptr pointerı sayi degiskenini göstersin
ptr = &sayi;
//madem pointerımız artık sayi değişkenini gösteriyor o zaman sayi değişkeni üzerinde //oynayabiliriz
*ptr = 34;
//ptr pointerının gosterdigi degiskenin degerini 34 yaptık
*ptr = 1234;
//sayi değiskeninin degerini 1234 yaptık
Örnek:
int a = 5, *p;
p = &a;
//simdi p, a degiskenini gosteriyor
printf(“%d”, *p);
Ekrana 5 yazacaktır.
Örnek:
#include <stdio.h>
int main ( )
{
int *aptr ; //int turunde bir pointer
float *bptr ; //float turunde bir pointer
int a ; //bir int degisken
float b ; //bir float degisken
aptr = &a ; //aptr pointerı a degiskenini gostersin
bptr = &b ; //bptr pointerı b degiskenini gostersin
printf(“a = %d b = %f\n\n”, a, b);
printf(“*aptr = %d *bptr = %f\n\n”, *aptr, *bptr);
}
Çıktısı şöyle olacaktır:
Görüldüğü gibi a değişkeni ve onu gösteren aptr pointerının değerleri aynıdır. b değişkeniyle onu gösteren bptr pointerının da değerleri aynıdır.
Pointerların Dizilerle Kullanılması
Dizilerle pointerlar arasında çok büyük bir bağ vardır. Çünkü dizileri kullanırken aslında farkında olmadan pointerları kullanırız. Mesela int türünde dizi isminde 5 elemanlı bir dizi tanımlayalım.
int dizi[5] ;
Bu dizinin elemanları ise şöyledir:
İnt dizi[0];
İnt dizi[1];
İnt dizi[2];
İnt dizi[3];
İnt dizi[4];
Bir dizinin ismi, aslında o dizinin ilk elemanının bellek adresidir. Biliyoruz ki pointerlar da içinde bellek adresi tutan değişkenlerdi. Bu durumda, bir dizinin ismi aslında o dizinin ilk elemanını tutan bir pointer dır diyebiliriz. Yani buradaki örnekteki dizi ismi aslında dizinin ilk elementi olan dizi[0]’ı işaret ediyor.
Örnek:
int tekler[5] = {3,5,7,9,11};
int *p, n;
p = tekler; /* p, 3’u gosteriyor */
p = &tekler[0]; /* yukarıdakinin aynısı */
n = *p; /* n nin degeri 3 */
n = p[0]; /* yukarıdakinin aynısı */
p[4] = 13; /* tekler[4] un degeri 13 */
Pointerlar Üzerinde Aritmetik İşlemler
Pointer değişkenleri üzerinde çeşitli aritmetik işlemler yapılabilir. Bu da pointerları dizilerden ayıran en önemli özelliktir. Çünkü dizilerle bu işlemleri yapamıyorduk. Mesela dizi[3]++ gibi bir yazım sözkonusu değildi. Ama pointerlarla bunu yapabiliyoruz. Öncelikle artırma ve azaltma işlemlerinden bahsedelim.
Pointerlarla artırma ve azaltma işlemlerini yapabiliyoruz. Bir pointerın değeri bir artırıldığında, pointer bir sonraki alanı göstermeye başlayacaktır. Bunun gibi pointerın değeri bir azaltıldığında, pointer bir önceki alanı gösteriyor olacaktır.
Örnek:
char b;
char *ptr = &b;
//burada ptr pointerı 437000 degerini gosteriyordu
ptr++;
//simdi ise 1 byte ilerisini yani 437001’i gosteriyor
Buradaki örnekte değişken türünü char olarak aldığımız için pointerın değerini 1 artırdığımızda 1 byte ilerisini gösterdi. Çünkü char türünün alanı 1 byte’tır. Mesela bu türü int olarak alsaydık, pointerın gösterdiği alan 4 byte ilerisini gösterecekti.
Örnek:
int c;
int *ptr = &c;
//pointer su anda 561345 degerini gosteriyor
ptr – -;
//pointer su anda 4 byte geriyi yani 561341’i gosteriyor
Aynı şekilde float türünde bir pointer tanımlasaydık, pointerın değerini 1 artırdığımızda 8 byte ileriyi gösterecekti.
Pointerı dizilerle birlikte kullandığımızda ise gene aynı mantık geçerlidir. Pointerın değerini 1 artırdığımızda, pointer bir sonraki elemanı gösterecektir.
Örnek:
Aşağıdaki kodda her satırı birbirinden bağımsız düşünelim.
int tekler[5] = {3,5,7,9,11};
int *p, n;
p = tekler; /* p, basta ilk eleman olan 3’u gosteriyor*/
++p; /* p simdi 5’i gosteriyor */
p = (tekler + 1); /* yukarıdakiyle aynı */
p = (tekler + 4); /* p, 11’i gosteriyor */
p = &tekler[4]; /* yukarıdakiyle aynı */
Örnek:
Bu kodda ise doğru ve hatalı yazımları birlikte görelim.
int i[7];
int *j;
j= i; /* j, i[0]’ı gosteriyor */
j= j+1; /* j pointerının gosterdigi alanı bir artır */
*j= 3; /* j nin gosterdigi deger 3 olsun */
j= i; /* j= &i[0] */
*j= 10; /* i[0] = 10 */
j[1]=4; /* j+1 in gosterdigi deger 4 olsun */
j= j+5; /* j = &i[5] */
j[1]= 5;
i= i+4; /* Hatalı yazım dizilerle aritmetik işlem olmaz */
j= j+2; /* j yi iki element artır*/
*j= 4; /* Hata!! Burada bir şey yok*/
Pointerları Fonksiyonlarla Kullanmak
Pointerları fonksiyon içinde kullanırken bir farklılık yoktur. Farklılık, fonksiyonun parametrelerini yazarken eğer bu parametre bir pointersa yazım farklılığıdır. Önceden fonksiyon yazarken parametre olarak diğer türlerde değişken alıyorduk. Örneğin,
void yaz ( int a, int b ){
printf (“ilk parametre=%d”,a);
printf (“ikinci parametre=%d”,b);
}
şeklindeydi. Şimdi ise alacağımız parametre bir pointer olacağından,
void ptr_yaz ( int *a){
printf (“%X”,a);
}
şeklinde olacaktır. Görüldüğü gibi pointer tanımlamayla arasında bir fark yoktur. Burada parametre yazarken, fonksiyona kabul edilecek verinin bir adres bilgisi olacağını söylemiş oluyoruz. Dolayısıyla main( ) fonksiyonu içinde bu fonksiyona parametre gönderirken adres bunun adres bilgisi olması gerekiyor.
Örnek:
void ptr_yaz ( int *a){
printf (“%X”,a);
}
int main( ){
…
ptr_yaz ( &c );
…
}
Göndereceğimiz parametrenin bir dizi olduğunu düşünürsek, fonksiyona dizinin ilk elemanını göndermemiz yeterli olur. Hatırlayacağınız gibi dizinin ilk elemanı, dizinin isminin kendisiydi.
Örnek:
void yaz(int *dz)
{
printf(“%d”, dz[4]);
}
void main( )
{
int dizi[5];
yaz(dizi); //dizinin ilk elemanını gonderiyoruz
/*yaz(&(dizi[0]));*/
}
veya fonksiyona girecek parametreyi şöyle de yazabiliriz:
void yaz(int dz[ ])
{
printf(“%d”, dz[4]);
}
void main( )
{
int dizi[5];
yaz(dizi); //dizinin ilk elemanını gonderiyoruz
/*yaz(&(dizi[0]));*/
}
olarak yazılabilir. Yani bir dizinin tüm elemanlarını fonksiyona göndermek istiyorsak sadece ilk elemanın adresini göndermemiz yeterli oluyor.
Buraya kadar yazdıklarımı örnekler üzerinde daha da ayrıntılı bir şekilde anlatmaya çalışacağım. Bu şekilde pointerların daha iyi anlaşılacağını düşünüyorum.
Örnek:
Şimdi ortalama alan bir fonksiyon yazalım. Fonksiyonun iki parametresi olsun. Birincisi ortalamasını alacağı sayı dizisi, ikincisi ise bu dizinin kaç elemandan oluştuğu.
float ortalama(float a[ ], int n); /* fonksiyon tanımlamamıza dikkat edin */
int main (void)
{
float dizi[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
printf(“%f\n”, ortalama(dizi, 5)); /* fonksiyonu cagıralım */
}
float ortalama(float a[ ], int n)
{
float toplam = 0.0;
int i;
for (i=0; i < n; i++)
toplam += a[i];
return( toplam / n );
}
Kodumuzun çıktısı 3.0 olacaktır.
Örnek:
Pointerların nasıl işlediğini anlamak için bir dizi, iki pointer ve bir değişken tanımlayalım ve biraz üzerlerinde oynayalım.
#include <stdio.h>
void main( ){
int a[ ]={10,15,4,2,5};
int *j, *k, b=9;
j=a;
k=&b; // k, 9’u gosteriyor
// k nın gosterdigi degerin yerine j[3]ün gosterdigi degeri ata
*k=*(j+3); printf(“%d\n”,*k);
//k, j[3]ün degerini gostersin
k=j+3;
// k nın gosterdigi degeri 1 yap
*k=1; printf(“%d\n”,*k);
// once j[0]ın gosterdigi degerle k nın gosterdigi degeri carpıp j[0]a ata
// sonra k ya bir sonraki alanı gostert
(*j)*=*k++; printf(“%d\n”,*j);
// j pointerı simdi j[1]i gostersin
j++; printf(“%d\n”,*j);
printf(“%d\n”,b);
}
Başlangıçta j ve k pointerlarının gösterdiği değerler aşağıdaki gibidir.
|
j[0]
|
10
|
|
j[1]
|
15
|
|
j[2]
|
4
|
|
j[3]
|
2
|
|
j[4]
|
5
|
Önce k nın gösterdiği değeri j[3]ün gösterdiği değerle değiştirdik. Yani k artık 9u değil 2’yi göstermeye başladı. Şimdi kodun en önemli kısmına gelelim. k pointerı j[3]le aynı yeri göstersin diyoruz. Bu üstte yaptığımız işlemle aynı şey değil buna dikkat edin. Üsttekinde *k=*(j+3); ifadesiyle, k pointerının içindeki adres değeri değişmedi aynı kaldı. Sadece o adresteki değişkenin değerini değiştirdik. Ama şimdi k=j+3; ifadesiyle k pointerına j[3]ün adresini atıyoruz. Yani k pointerının içindeki adres değişti. Artık o içinde farklı bir adres tutuyor ve başka bir yeri gösteriyor. Hatta k pointerını k[3] diye de yazabiliriz, çünkü k pointerı içinde şu anda j[3]le aynı adresi tutuyor. Sonra k nın gösterdiği yerin değerini 1 yap diyoruz. Bu durumda k pointerıyla birlikte j[3]ün de gösterdiği değer 1 olmuş oluyor. Ardından j pointerının o an gösterdiği yerdeki değerle k pointerının gösterdiği yerdeki değeri çarpıpıyoruz. Bunu j pointerının gösterdiği değere atıyoruz. Bunu yaptıktan sonra k pointerının yerini bir artırıyoruz. Fark ettiyseniz artırma işlemini en son yaptık. Bu ilk konulardan hatırlayacağınız artırma operatöründen kaynaklanıyor. Yani eğer (*j)*=*++k; yazsaydık artırma işlemi once yapılacaktı. En son ise hala en başta j[0]ı gösteren j pointerını bir artırdık ve j[1]i göstermesini istedik.
Bu durumda kodun çıktısı şöyle olacaktır:

Örnek:
Şimdi de main fonksiyonunda f1 adında bir fonksiyon çağıralım. Fonksiyona gönderilen parametrelere ve fonksiyonun içine dikkat edelim.
#include <stdio.h>
int f1(int *a, int *b, int N)
{
int c=0;
int i;
*b=2;
*(a+3)=5;
for (i=0;i<N;++i)
c+=*(a+i) ; // a dizisinin degerlerini topla
return c;
// c nin degerini döndür ve fonksiyondan ÇIK
*(a+1)=7; *(b+1)=0;
// dolayısıyla bu satırlar işlemeyecek bu sizi yanıltmasın!
}
int main( ){
int a[ ]={8,3,2,1};
int b[ ]={0,2,5,2};
int i, d;
d=f1(a,b,4);
for (i=0;i<4;i++)
printf(“%d\n”,*(a+i));
for (i=0;i<4;i++)
printf(“%d \n”,b[i]);
printf(“%d \n%d\n”, d, a[1]);
return 0;
}
Kodun çıktısı:

olur. Öncelikle main fonksiyonunda tanımladığımız a ve b dizilerini fonksiyona gönderdik. Bildiğiniz gibi bir dizinin tümünü fonksiyona göndermek için, dizinin ilk elemanını yani ismini gönderiyorduk. Dolayısıyla burada fonksiyona gönderilen parametrelere a ve b yazmamız yeterli oldu. Fonksiyona girdiğimizde en başta b[0]’ı 2, a[3]’ü 5 yaptık. Sonra a dizisinin bu yeni değerlerini for döngüsü yardımıyla topladık ve c değişkenine atadık. Ardından return c; ifadesi var. Yani tam bu anda fonksiyon c değerini döndürüyor ve işini tamamlıyor. return c; nin altındaki satırlar çalışmayacak. Bu hata çok yapıldığı için böyle bir örnek vermek istedim. Sonrasında main fonksiyonunda, f1 fonksiyonunun döndürdüğü değer d değişkenine atanmış. Yani d değişkeninin değeri 18 oluyor. printf( ) le a ve b dizilerini yazdırdığımızda ise gerçekten return c; den sonraki satırların çalışmadığını görüyoruz.
Başak KOLDAŞ
www.basakkoldas.com
|
Son Yorumlar