Pointerlar <Resim><Resim>

Genel Olarak Pointerlar
Tanımlama ve Kullanım Şekilleri
Pointer Aritmetiği
Tip Dönüşümleri
Fonksiyonların Referans İle Çağırılması
Fonksiyon Pointerlar

Genel Olarak Pointerlar <Resim><Resim>
Pointerlar en yalın şekilde değer olarak bir hafıza adresini tutan değişkenler olarak tanımlanır. Genelde bir değişkenlerin adreslerini içerirler ve bu değişkenlerin değerlerine dolaylı olarak ulaşılmasını sağlarlar.
Başlıca kullanım alanları dinamik olarak büyüyen veri yapılarının oluşturulması (linked list (bağlı liste), kuyruk (queue), yığın (stack) gibi), fonksiyonlara referans ile değer geçilmesidir. Büyük veri yapılarının işlenmesinde performansı arttırmak için bu verilerin adresleri üzerinde işlem yapılması için kullanılırlar.


Pointerlar C ve C++ ‘ın ayrılmaz bir parçasıdır. C ve C++ da pointer kullanmadan program yazmak düşünülemez. Bu kadar sık kullanılmasın yanıda en sık hata yapılan ve en zor anlaşılan kısımlarından biridir. Pointer kullanımında yapılan hataların bulunması genelde zordur ve sistemin kitlenmesine neden olur.



<Resim>Program yazarken programı çalıştırmadan önce yaptıklarınızı kaydetmeyi alışkanlık edinin. Özellikler C/C++ da program yazarken makinanın kitlenmesiyle sıkça karşılaşacaksınız. Saatlerce uğraşıp yazdığınız kodun kaybolması hiç te hoş birşey değil.

Pointer Operatörleri
Pointerlardan bahsedilince hemen aklımıza *, & operatörleri gelir.
& operatörü kendisinden sonra gelen ifadenin adresini bulur yani ".. nin adresi" olarak ifade edebiliriz.
Örneğin &val ifadesi “val’in adresi” anlamına gelir.
* operatörü ise kendisinden sonra gelen pointer’in gösterdiği değeri refreans eder. Yani “ .. nin gösterdiği adresteki değer” olarak ifade edilebilr. Örneğin *pVal ifadesi “pVal’in gösterdiği adresteki değer” olarak ifade edilebilir.
Tanımlama ve Kullanım Şekilleri <Resim><Resim>
Şu ana kadar pointerlardan ve pointerlar ile birlikte anılan *, & operatörlerinden kısaca bahsettik Şimdi artık C++’da bahsettiğimiz bu kavramların nasıl kullanıldıklarından bahsedeceğiz. Basit bir örnek ile işe başlıyalım.


#include <iostream.h>

main()

{

int val = 5;

//integer val değişkeni için hafıza yer ayarla ve 5 değerini ata.

int *pVal;

//integer pointer pVal değişkeni için hafızada yer ayır.

pVal = &val

//pVal değişkenine val değişkeninin adresini ata.

*pVal = 8;

//pVal değişkenin gösterdiği hafıza alanına 8 değerini ata

cout >> "val değiskenin adresi = " >> &val

cout >> "pVal değiskenin adresi=" >> &pVal

cout >> "val değiskenin değeri = " >> val;

cout >> "pVal değiskenin değeri =" >> pVal;

cout >> "pVal değiskenin gösterdiği yerdeki değer=" >> *pVal;

};





Yukarıdaki örnek program pVal ve val değişkenlerinin adreslerini ve içlerindeki değerleri ekrana yazıyor
Örnek 2.5.1

Programın ekran çıktısı ise aşağıdaki gibi.

val degiskenin adresi = 0x13df2914
pVal degiskenin adresi=0x13df2910
val degiskenin degeri = 8
pVal degiskenin degeri =0x13df2914
pVal degiskenin gösterdigi yerdeki deger=8


Ekran çıktısından da görüldüğü üzere pVal değişkeni hafızada ayrı bir yer ayrılmış durumda ve bu ayrılan yere program içerisinde val değişkenin adresi atanıyor. val değişkeni ile direkt olarak hiç bir işlem yapılmamasına rağmen val değişkenini dolaylı olarak gösteren pVal değişkenini gösterdiği adrese 8 değeri atandığında val değişkenin içeriği de değişti.
Hafızanın durumunu grafik olarak gösterirsek
Başlangıçta aşagıdaki gibidir. pVal değişkenin gösterdiği adresteki bilgi hakkında bir fikrimiz yok.


<Resim>C ve C++'da değişkenlere otomatik olarak atanan bir ilk değer yoktur.Değişken tanımlandığında değişkene anlamlı bir değer atamak bizim sorumluluğumuzdadır. Bir pointer değişkenine anlamlı bir adres bilgisi atamadan değişkenin gösterdiği adrese değer atamak genellikler sistemin askında kalması , kitlenmesi veya başka bir programın hata vermesine neden olur.Sadece global ve statik değişkenlere derleyici tarafından ilk değer atanır

pVal 0x13df2910 ???
val 0x13df2914 5


val değişlenin adresi pVal değişkenine atanıyor.

pVal 0x13df2910 0x13df2914
val 0x13df2914 5


pVal değişkenin gösterdiği hafıza alana 5 atanıyor.

pVal 0x13df2910 0x13df2914
val 0x13df2914 8



<Resim>Pointer değişkeninin kendisi de hafızadan belli bir bölge işgal eder. pointer değişkenini adresi ile göstediği adres birbiri ile karıştırılmamalıdır
Pointer Aritmetiği <Resim><Resim>
Pointerların içerisindeki verinin normal değişkenlerin içindeki verilerden farklı yorumlandığını önceki konularda yukarıda gördük. Aynı durum pointerlar üzerinde yapılan aritmetik işlemlerde de geçerlidir.
Öncelikle pointerlar üzerinde toplama , çıkarma ve karşılaştırma işlemlerinin yapılabildiğini belirtelim. Fakat mod alma, bölme, çarpma gibi işlemler pointer değişkenler üzerinde yapılamaz.
Aritmetik işlem operatörlerinin pointerlar üzerindeki etkileri normal değişkenlerin üzerindekinden farklıdır. Örnek olarak aşağıdaki kodu inceliyelim.


#include <iostream.h>

main()

{

int *pIntVar, intVar = 5;

pIntVar = &intVar

cout>> "intVar =" >> intVar>> endl;

cout>> "pIntVar =" >> pIntVar>> endl;

cout>> "(intVar + 1) =" >> (intVar + 1)>> endl;

cout>> "(pIntVar + 1) =" >> (pIntVar + 1)>> endl;

};






Örnek 2.5.2

Yukarıdaki program biri integer diğeri ise integer pointer olmak üzere iki tane değişken tanımlar. Bunlardan integer olana 5 değerini pointer olana ise integer değişkenin değerinin atar. Bu değerleri sırası ile ekrana yazdırır. Daha sonra her iki değişkenin içlerindeki değerlerin bir fazlasını ekrana yazdırır. Programın ekran çıktısı aşağıdaki gibidir



intVar =5

pIntVar =0x359728b2

(intVar + 1) =6

(pIntVar + 1) =0x359728b4


intVar ve (intVar + 1) ifadelerinin değerleri kıyaslanırsa (intVar + 1) ifadesinin değerinin intVar ifadesinden bir fazla olduğu görülür. Buraya kadar herşey normal fakat pIntVar ve (pIntVar + 1) ifadelerini kıyaslarsak (pIntVar + 1) ifadesinin pIntVar değişkenin değerinden iki fazla olduğunu görürüz. Peki bunun nedeni nedir? Niden bir, üç, veya dört değil de iki ?
Pointer değişknelerinin tanımını yaparken pointerların bir değişkenin adresini tutuğunu söylemiştik. Integer bir pointer hafızadaki bir integer’ın bulunduğu adresi tutar. Bu adres bilgisini bir arttırmak bir sonraki integer’ın bulunduğu adrese gitmek demektir. Bizim oluşturduğumuz proje 16 bitlik program üretiyor dolayısıyla integer’ın büyüklüğü 2 byte olarak kabul ediliyor. Yani bir sonraki integer’ın bulunduğu adrese ulaşmak için pointer değişkenimizin içerdiği değer iki arttırılıyor. Aynı durum (- )operatörü için de geçerlidir. (-) operatöründe ise bir önceki integer’ın adresine konumlanılmaya çalışılacaktır dolayısıyla iki azaltılacaktır.

Olayı genelleştirirsek herhangi bir tipte tanımlanmış bir pointer’ın değerini belli bir m değeri kadar arttırmak değişkenin değerinde (pointer’in tanımlı olduğu tipin uzunluğu) * m kadar bir artıma sebep olur.

Pointer aritmetiği diziler üzerinde yapılan işlemlerde sıkça kullanılırlar. pA ve pB iki pointer değişken olsun eğer pA ve pB aynı dizinin elemanlarını gösteriyorlarsa pA-pB işlemi anlamlı olur . pA nın dizinin i. elemanını pB’nin ise dizinin j. elemanımı gösterdiğini varsayarsak pA-pB = i-j olur.

include

main()

{

int* pA, *pB;

pA = new int[1];

pB = new int[1];

*pA = 5;

*pB = 150;

cout>> "pA pointer'inyn gösterdiği hafiza bölgesi ---< pA =" >> pA >> endl;

cout>> "pB pointer'inın gösterdiği hafiza bölgesi ---< pB =" >> pB >> endl;

cout>> "pA pointer'inın gösterdiği yerdeki değer ---< *pA =" >> *pA >> endl;

cout>> "pB pointer'inın gösterdiği yerdeki değer ---< *pB =" >> *pB >>endl;

pA = pB;

cout>> "pA pointer'inin gösterdiği yerdeki değer ---< *pA =" >> *pA >>endl;

cout>> "pB pointer'inin gösterdiği yerdeki değer ---< *pB =" >> *pB >>endl;

cout>> "pA pointer'inın gösterdiği hafiza bölgesi ---< pA =" >> pA >> endl;

cout>> "pB pointer'inın gösterdiği hafiza bölgesi --< pB =" >> pB >> endl;

delete []pA;

delete []pB;

);

Örnek 2.5.3

Yukarıdaki örnek program pointerlarda sıkça yapılan bir hatayı göstermektedir. Program pA ve pB isminde iki integer pointer tanımlayıp bunlara dinamik olarak tanımlanan birer integer’ın adresilerini atıyor. Daha sonra pA’nın gösterdiği adrese 5, pB’nin gösterdiği adrese ise 150 değerleri atanıyor. Buraya kadar herşey normal.

Fakat pA = pB satırı çok masumane bir atama işlemi gibi görünmesine karşın sıkça bir hataya karşılık geliyor.

Bu komut çalışınca ne olur ? pA değişkeni de pB’nin gösterdiği adresi göstermeye başlar. Güzel fakat pA’nın gösterdiği dinamik olarak ayırdığımız yerin adresi neydi ? O adrese bir daha nasıl ulaşabiliz? Ulaşamayız. Hafızadan dinamik olarak ayırdığımız o bölge kayboldu. Dinamik olarak ayırıp pA’ya atadığımız hafıza bölgesi bilgisayar kapana kadar kayıp olarak kalacak. Durun daha bu kadarla da bitmedi bu masumame atmama komutu başımıza daha neler açacak.

Dinamik yer ayırmaktan bahsettikya. Dinamik olarak ayırdığımız her yeri işimiz bittiğinde serbest bırakmalıyız ki bir süre sonra sistem, sistem kaynakları çok düstü şeklinde bir uyarı vermesin. Kodu incelemeye devam edersek en son iki komut dinamik olarak ayrılan hafızayı serbest bırakıyor. Fakat dikkatli bakılırsa bu iki komutun başımıza ne gibi büyük sorunlar açacağını görebiliriz. delete []pA

komutu pA

değişkeninin gösterdiği hafıza bölgesini serbest bırakmaya çalışıyor. Burada sorun yok gerçekten de bu bölgeyi program başında dinamik olarak ayırdık. Fakat pB pointer’ı da aynı adresi gösteriyordu o da aynı bölgeyi serbest bırakmaya çalışacak. Ama zaten orası serbest bırakıldı. O bölge artık bize ait değil ki bu durumda ne olacak. ? Kim bilir ? Büyük bir ihtimalle makinamız kitlenecektir.
Buradan çıkaracağımız sonuç pointer değişkenleri kullanırken dikkatli olmamız gerektiğidir

<Resim>Özellikle atama işlemlerinde dikkatli olmalıyız. Pointer’ın gösterdiği hafıza bölgesine değer atamaya çalışırken yukarıdaki programda olduğu gibi pointer’ın değerini değiştire biliriz. Bu da bulunması zor hatalara sebep olur.

pA pointer'inın gösterdiği hafiza bölgesi ---< pA =0x39570366

pB pointer'inın gösterdiği hafiza bölgesi ---< pB =0x3957035e

pA pointer'inın gösterdiği yerdeki değer ---< *pA =5

pB pointer'inın gösterdiği yerdeki değer ---< *pB =150

pA pointer'inın gösterdiği yerdeki değer ---< *pA =150

pB pointer'inın gösterdiği yerdeki değer ---< *pB =150

pA pointer'inın gösterdiği hafiza bölgesi ---< pA =0x3957035e

pB pointer'inın gösterdiği hafiza bölgesi ---< pB =0x3957035e


<Resim>Pointerların kullanımında sıkça yapılan diğer bir hata ise pointer değişkenine bize ait olduğundan emin olduğumuz geçerli bir hafıza adresi atamadan pointer’ın gösterdiği adrese bilgi yazılmasıdır.

Bir fonksiyon içinde kullanılan yerel değişkenler otomatik olarak yaratılır. Bu şekilde tanımlanan değişkenlere derleyici tarafından bir ilk değere verilmez, bu değişken pointer tipindeyse gösterdiği adresin ne olacağı önceden kestirilemez. Bu değer işletim sisiteminin bulunduğu bölgelerden birinin adresi olabileceği gibi o an için kullanılmayan bir hafıza bölgesinin adresi de olabilir. Dolayısıyla ilk değer atanmamış pointer (global ve statik değişkenlere derleyici tarafından ilk değer atanır) değişkenlerinin gösterdiği hafıza bölgesine değere atama işleminin davranışı belirsizdir. Hiç bir sorun çıkmayayabileceği gibi makinanın kitlenmesine de sebep olabilir. Kimbilir?
Tip Dönüşümleri <Resim><Resim>
Pointerlar önceki konularda da ifade edildiği gibi bir hafıza adresinin değerini tutarlar. Pointerların tanımlandıkları tip gösterdikleri yerde bulunan veri tipi ile doğrudan alakalıdır. Aşağıdaki örnek program pointerların tipleri ile gösterdikleri veriler arasındaki ilişkiyi net bir şekilde ortaya koymaktadır.


#include <iostream.h>

main()

{

int i= 5;

double d = 1213;

int *pInt = &i

cout>> "int i değişkenin değeri i = ">> i>>endl;

cout>> "int i değişkenin değeri i = *pInt = ">> *pInt>>endl;

cout>> "double d değişkenin değeri d = ">> d>>endl;

pInt = (int *)&d

cout>> "double d değişkenin değeri d = *pInt = ">> *pInt>>endl;

cout>> "double d değişkenin değeri d = *(double *)pInt = ">> *(double *)pInt>>endl;

};

Yukarıdaki programda öncelikle int, double ve int pointer tiplerinde birer tane değişken tanımlanıyor. pInt int pointer değişkenine i değişkenin adresi atanıyor. Sırası ile i değişkeninin değeri ile pInt değişkenin gösterdiği adresteki değer ekrana yazdırılıyor. pInt değişkeni int bir pointer olarak tanımlandı ve aksi belirtilmediği için derleyici pInt değişkenin dösterdiği yerde bir int değer bulunduğunu kabul edip pointer değişkeninin gösterdiği adresten itibaren bir int 'in uzunluğundaki hafıza bloğunun içindeki değeri int 'e çevirir.
Daha sonraki satırlarda pInt değişkenine double tipindeki t değişkeninin adresi atanıyor. (Bu işlemi yaparken derleyiciye d double değişkenin adresinin bir int değişkenin adresi olduğunu bildirerek hata vermesinin engelliyoruz. Artık mesuliyeti tamamen üzerimize almış durumdayız.)
Ekrana sırası ile d değişkenin, pInt değişkenini gösterdiği adresteki değeri ve pInt değişkenin gösterdiği adresteki double değişkenin değerleri sırası ile yazdırılıyor.
Aşağıdaki ekran çıktısında da görüldüğü gibi pInt değişkenin d değişkenin adresini göstermesine rağmen derleyiciyi uyarmadığımız için derleyici pInt in gösterdiği adresten itibaren bir int boyutu kadarlık alandaki değeri int 'e çevirir. d değişkenin içerdiği değerin hafızadaki gösteriminde bizim değerlendirdiğimiz kısmında 0 değeri bulunuyormuş. pInt değişkeni d değişkenin adresigi gösteriyor dolayısıyla gösterdiği adresteki değerin değişkenin içindeki değer ile aynı olmalı gibi bir yorum yapılabilir
Pointer aritmetiği konusunda da belirttiğimiz gibi derleyici pointerlar üzerinde işlem yaparken değişkenin tip bilgisinden yararlanıyor. Yukarıda int int tipindeki bir pointer'a double tipinde bir değişkenin adresini atarken yaptığımız gibi derleyici tekrar kandırıp sen int tipinde bir pointersın fakat gösterdiğin adres bir double 'ın adresi ona göre davran diye biliriz. Aşağıdaki ekran çıktısında da görüldüğü gibi pInt değişkenini double pointer' a cast edip ekrana yazdırdığımızda doğru değeri yazıyor.

int i değişkenin değeri i = 5

int i değişkenin değeri i = *pInt = 5

double d değişkenin değeri d = 1213

double d değişkenin değeri d = *pInt = 0

double d değişkenin değeri d = *(double *)pInt = 1213

Fonksiyonları Referans İle Çağırılması <Resim><Resim>

Fonksiyonlara üç değişik şekilde parametre geçilebilir.
Değer geçilerek (Call By Value), referans parametreleri ile referans geçerek veya pointer parametreler ile referans geçerek. Bizi burada ilgilendiren pointer tipinde referans geçilmesi ve değer geçerek parametre yollama yöntemi ile arasındaki fark.
Aşağıdaki program her iki yöntemin de kullanımını göstermektedir.


include <iostream.h>

void DegerIleCagirma(int parametre)

{

parametre = 8;

}



void PointerReferansIleCagirma(int* parametre)

{

*parametre = 8;

}
main()

{

int i = 100;

cout>> "i = " >> i >> endl;

DegerIleCagirma(i);

cout>> "DegerIleCagirma(i) fonksiyonundan sonra i = " >> i >> endl;

PointerReferansIleCagirma(&i);

cout>> "PointerRefreransIleCagirma(i) fonksiyonundan sonra i = " >> i >> endl;

}

ornek 2.5.4
Programda main fonksiyonundan başka iki fonksiyon daha bulunmakta. . DegerIleCagirma fonksiyonu parametre olarak bir int alır . Bu çağırım şeklinde derleyici parametrenin otomatik olarak yerel bir kopyasını çıkartır ve parametrenin değerinin atar. Fonksiyon içinde parametre üzerinde yapılan işlemler aslında yerel kopya üzerinde gerçekleştirilir. Foksiyondan çıkılırken tüm yerel değişkenler yok edildiği için parametreler üzerinde yaptığımız değişiklikler yok olur. Diğer fonksiyon ise PointerReferansIleCagirma parametre olarak int tipinde bir pointer alır. Bu fonksiyon çağırıldığında integer pointer tipinde yerel bir değişken oluşturulur ve parametere olarak geçilen değişken adresi bu yerel değişkene atanır.Fonksiyon çıkışında yerel değişkenlerin yokedilmesine karşın yaptığımız değişiklik kaybolmamış olur.Fonksiyon içinde yerel değişkenin gösterdiği hafıza bölgesine 8 değeri atanır. Programın ekran çıktısı aşağıdaki

i = 100

DegerIleCagirma(i) fonksiyonundan sonra i = 100

PointerRefreransIleCagirma(i) fonksiyonundan sonra i = 8


Fonksiyonların pointer referans ile çağırılması çok büyük veri yapılarının foksiyonlara parametre olarak geçilmesi gerektiğinde oldukça büyük avantajlar sağlar. Aşağıdaki program parçasını ele alalım. Kartvizit yapısı 337 byte büyüklüğündedir. F1 ve F2 fonksiyonlarının aynı işi yapan iki farklı fonksiyon olduğunu varsayalım. F2 fonksiyonu F1 fonksiyonundan daha hızlı çalışır? Neden ? F1 fonksiyonu cağırıldığında Kartvizit tipindeki parametrenin bir kopyası yaratılır ve 337 byte’lık bilgi bu yeni yapıya kopyalanır. Halbuki F2 fonksiyonu çağrıldığında Kartvizit pointer tipinde bir parametre yaratılır ve sadece parametere olarak geçilen yapının adresi kopyalanır. Bu adres bilgisinin büyüklüğü kullanılan hafıza modeline göre değişir fakat her durumda 337 byte’tan çok ufaktır.

struct Kartvizit{

char ad[30];

char soyad[30];

char adres[255];

char evTel[11];

char isTel[11];

};


void F1(Kartvizit kart)

{

……….

}



void F2(Kartvizit *kart)

{



……….



}