Ana sayfa » C++’ta İşaretçiler Nedir? Nasıl Kullanılır?
C/C++

C++’ta İşaretçiler Nedir? Nasıl Kullanılır?

işaretçiler c++ pointer

C++ İşaretçiler, Pointerlar

İşaretçiler yani pointerlar C++ dilinin en temel yapıtaşlarından biridir. Hatta işaretçiler konusu için C++’ı diğer dillerden ayıran en önemli özelliklerinden biri de diyebiliriz. Adından da anlayabileceğiniz üzere C++’ta işaretçiler değişkenlerin bellekteki adreslerini tutabilme ve onlarla işlem yapabilme olanağı sağlar. Tabi pointer konusunu tam olarak anlayabilmek için öncelikle c++’ta adres kavramını ve değişkenlerin bellekteki yerleşimlerinin nasıl olduğunu incelememiz gerekecek. Lafı daha fazla uzatmadan konumuza başlıyoruz.

Değişkenler ve Bellekteki Yerleşimleri

Bildiğiniz üzere c++ dilinde birçok işlevi yerine getirebilmek için değişkenler mevcuttur. Mesela Integer bir sayıyı tutmak istiyorsak int veri tipini yani değişkenini, karakter tutmak istiyorsak char veri tipini oluşturup atama yapmamız gerekmektedir. Eğer ondalıklı bir sayı tutmak istiyorsak bunu da float veya double bir değişken oluşturup atamak gerekmektedir. Tabi bu anlatıklarım an itibari ile yüzeysel bilgilerdir ve hatırlatıcı mahiyettedir. Eğer bu konu hakkında yani c++’ta veri tipleri ile ilgili bilginiz yoksa öncelikle bu eksiğinizi kapatmanızı tavsiye ederim. Bizim asıl ilgileneceğimiz kısım; bir değişken tanımladığımız zaman arkaplanda nelerin olduğudur. İsterseniz integer bir değer tanımlayalım ve bellekte nelerin olduğunu izah etmeye çalışalım.

int sayi = 26;
  • Öncelikle stack bellek bölgesinde 4 byte’lık bir alan ayrılır. Bunun sebebi integer’ın c++’ta 4 byte yer kaplamasıdır. Eğer double tanımlasaydık 8 byte’lık bir alan ayrılacaktı.
  • Atadığımız değer olan 26, ikilik yani binary sayı sistemine dönüştürülür. 32 bit olacak şekilde dnüştürülen bu 26 sayısı daha önceden ayrılmış olan 4 byte’lık alana, her 1 bytle’lık birime 8 bit gelecek şekilde yerleştirilir. Şuan kafanızda canlanması zor, endişe etmeyin görsel olarak olayı iyice anlamış olacağız.
  • Artık bu işlemden sonra sayi değişkenini programlarımız içinde kullanabiliriz.
c++ işaretçiler
Veri Tiplerinin bellekteki yerleşimini gösteren görsel

Yukarıdaki görsel, anlattığım şeyleri çok güzel bir şekilde tasvir etmiş. Ben integer üzerinden örnek verdiğim için sarı ile gösterilmiş kısmı inceleyebilirsiniz. Ek olarak burada 255 sayısı 16 lık sayı sistemine çevrilip belleğe yerleştirilmiş, burada kolaylık olması açısından bu şekilde gösterilmiş. Bilgisayarlarımız 255 sayısını 0000 0000 1111 1111 şeklinde belleğe kaydeder.

C++’ta İşaretçiler ve Adres Kavramı

Adres Nedir?

C++’ta veri tipleri ve bellekteki yerleşimleri ile ilgili yukarıda vermiş olduğumuz örnek ve açıklama umarım kafanızda bazı şeylerin oturmasını sağlamıştır. Şimdi olayın biraz daha derinine inerek bu belirtilen 4 byte’lık  bölümü nasıl manipüle edebileceğimizi öğreneceğiz.  Dikkat ettiyseniz yukarıdaki görselde Adress adında bir sütun var ve bu sütünlar bazı rakamları bünyesinde barındırıyor. İşte bu rakamlardan koyu ile gösterilen 90000000, 90000004, 9000000E olanları birer adrestirler ve bellekteki boş veya dolu bir hücreyi gösterirler. Mesela görseldeki sarı ile gösterilen integer örneğinden devam edecek olursak, 90000000 onun adresidir. Bu adresleri c++ programlama dilinde kullanarak 255 olan integer değerini manipüle etme konusunda elimizi bir hayli güçlendirmiş olacağız.

Aklınıza 90000001, 90000002, 90000003 adresleri noluyor ve neden kullanılmıyor sorusu gelmiş olabilir. Bu sorunun cevabı şudur; bellekteki 4 byte’lık bir bölüm 1’er byte olacak şekilde ardışık olarak belleğe yerleşir ve bu verinin başlangıç byte’nın adresi tüm verinin(örnekteki 255 sayısının) adresi olarak belirlenir.

İşaretçi veya Pointer Nedir?

C++’ta işaretçiler bünyelerinde veri değilde verilerin adreslerini tutarlar. Yani yukarıda 255 değerinin adresi olan 90000000 değerini bünyesinde tutan veri tiplerine İŞARETÇİ veya POİNTER diyoruz. İşte bünyesinde adres değeri tutan bu işaretçiler ile adresin gösterdiği hücreyi istediğimiz gibi yönetebiliriz.

Programımızda bir işaretçi tanımlamak istiyorsak iki temel kurala uymamız gerekmektedir. Bunlar;

  • Tanımlayacağımız işaretçi tutacağı adres değeri ile aynı veri tipinde olmalıdır. Yani char veri tipindeki bir değeri gidip te integer bir pointer’da tutamazsınız.
  • Pointer tanımlardan “*” karakterini kullanmamız zorunludur.

Hemen örneklere geçelim.

İşaretçi, Pointer Örnekleri

Öncelikle c++’ta işaretçi nasıl tanımlanır hemen onun bir örneğini verelim;

/*Öncelikle hangi tipte bir verinin adresini tutmak istiyorsunuz
onu belirlememiz gerekmektedir. Ben bu örnekte anlaşılır olması için
2 adet veri tipinin adresini tutacam. Bunlar integer ve float olacak.*/
int sayi = 10; /*Burada bir integer değer tanımladık*/
int *p; /*Burada integer adresi tutabilecek bir pointer tanımladık*/
p = &sayi; /*İşte en önemli kısım burası, burada sayının adresini işaretçiye atamış olduk*/
float sayif = 11.0;
float *i;
i = &sayif; /*Aynı işlemleri burada da yapmış olduk. Fakat bu sefer integer için değil float için 
yaptık*/

Şimdi bu kodumuzu açıklayarak adım adım anlatalım;

  • Öncelikle integer bir değer oluşturduk. (Bunun bellekte nasıl oluşturulduğunu kafanızda canlandırın)
  • Sonra bu oluşturduğumuz integer değerin ADRESİNİ tutacak bir işaretci veri tipi tanımladık.
  • Daha sonra “&sembolünü kullanarak integer değerin adresini işaretcimize atamış olduk.

Şimdi bu son maddeden bir şey daha öğrendik. C++’ta ve sembolü yani “&” bu semböl bizlere verilerin adreslerini verir. Çok basit bir örnekle bunu kanıtlayalım.

int sayi = 10;
cout << &sayi << endl;
----
0x7ffdc85681ac

-Bu kodları kendinizde yazarak mutlaka deneyin.

Gördüğünüz gibi cout bizlere sayi değişkeninin adresini ekrana bastı. Bu bilgiden sonra birazda “*” sembolünden bahsedelim.

C++’ta nerede bir yıldız “*” işareti görüyorsak orada işaretçiler ve adresler ile ilgili bir işlem var demektir. İşaretçi tanımlarken neden yıldız “*” sembolü kullanılıyor diye pek takılmamak lazım. Dili tasarlayanlar öyle tasarlamış biz ise sadece kullanıyoruz diyelim ve özet olarak pointer tanımlarken kullanacağım syntax yapısını tekrardan belirtelim.

int *p;

Bu bilgiler ışığında pointerımızın yapısını ve nasıl kullanacağımızı inceleyelim.

Ensonki örneğimizden devam edelim. Hatırlarsanız Integer olan sayi değişkeninin adresini “p” pointerına atamıştık. Artık bu saatten sonra “p” değişkenini “sayi” değişkeni yerine kullanabiliriz, bir başka değişle sayi değişkeninde istediğimiz değişikliği yapabiliriz. Çünkü elimizde sayi değişkeninin adresi var ve tüm yetki bizde. Peki ama bunu nasıl yapacağız? Aşağıdaki örneği dikkatle inceleyelim.

int sayi = 10;
int *p = &sayi;
cout << "ilk hali: " << sayi << endl;
*p = 20; /*Buraya dikkat*/
cout <<"Son hali: " << sayi << endl;
---
ilk hali: 10
Son hali: 20

Gördüğünüz gibi pointerımız olan p yi örnekteki gibi  yıldız işareti “*” ile kullandığımız vakit, sayi değişkeninde istediğimiz değişikliği yapabiliyoruz. Kodumuzun *p = 20 olan kısmının açıklaması şu şekilde; p değişkeninin gösterdiği adres değerine git ve o adresin bulunduğu hücreye 20 değerini ata. Buradan çıkaracağımız sonuç şu; *p = sayi’dır. Şimdi aşağıdaki örnekle bu tezimizi kanıtlayalım.

int sayi = 10;
int *p = &sayi;
cout << "*p= " << *p << endl;
cout << "sayi= " << sayi <<endl;
---
*p= 10
sayi= 10

Gördüğünüz gibi ekrana aynı sonuç çıktı. Bu örneği sizinde yapmanızı tavsiye ediyorum.

Fonksiyonlara Parametre Olarak İşaretçi Vermek

Evet şimdiye kadar C++’ta işaretçiler konusunda bir hayli mesafe katettik. Son başlığımız olan fonksiyonlara parametre olarak adres verme işlemi ile bu konuyu iyice anlayacağınızı temenni ediyorum. Lafı fazla uzatmadan çok faydalı olduğuna inandığım bir örnekle konumuzu sonlandıralım.

#include <iostream>
#include "stdio.h"
using namespace std;
void PointersizDegistir(int sayi1, int sayi2){
int gecici = sayi1;
sayi1 = sayi2;
sayi2 = gecici;
}
int main() {
int sayi1 = 10;
int sayi2 = 20;
cout << "Fonksiyon çağrılmadan önce" << endl;
cout << "sayi1: " << sayi1 << endl;
cout << "sayi2: " << sayi2 << endl;
PointersizDegistir(sayi1,sayi2);
cout << "Fonksiyon çağrıldıktan sonra" << endl;
cout << "sayi1: " << sayi1 << endl;
cout << "sayi2: " << sayi2 << endl;
return 0;
}
---
Fonksiyon çağrılmadan önce
sayi1: 10
sayi2: 20
Fonksiyon çağrıldıktan sonra
sayi1: 10
sayi2: 20

Öncelikle parametre olarak pointer vermeden bir fonksiyon çağırdık ve sonuç istediğimiz gibi çıkmadı. Yukarıdaki örneği özetlersek;

  • Amacı, verdiğimiz değişkenleri değiştirmek olan PointersizDegistir adında bir fonksiyon oluşturduk.
  • Daha sonrasında bu fonksiyonun içinde değiştirme işlemini yapacak kodları yazdık.
  • Sonra bu fonksiyonu main fonksiyonu içinde çağırdık ve sonuçları iki duruma göre ekrana bastık.
  • Mantıken yer değiştirmesi gereken sayi1 ve sayi2 değerleri görüyoruz ki halen aynı duruyor.

İşte buradaki hatanın sebebi şu; biz fonksiyona değişkenlerin adreslerini değil, içinde tuttukları değerleri verdik. Fonksiyon da kendisi geçici olaraktan oluşturduğu int sayi1 ve int sayi2 değerlerine bu verdiğimiz değerleri atadı. Yani bizim main fonksiyonu içindeki sayi1 ve sayi2 ile PointersizDegistir fonksiyonu içindeki sayi1 ve sayi2 tamamen birbirinden farklı değişkenlerdir. Adlarının aynı olmasına kanmayın. Fonksiyon bittiği vakit yani çağırıldıktan sonraki satırda bu fonksiyon içinde tanımlanan geçici sayi1 ve sayi2 değişkenleri bellekten silinir. Main fonksiyonu içindeki sayi1 ve sayi2 değişkenleri ise kalır. Onlara bişey olmaz. Peki şimdi bu sorunu nasıl düzelteceğiz. Yani fonksiyonumuz gerçekten yapması gereken işi nasıl yapacak şimdi onu inceleyelim.

#include <iostream>
#include "stdio.h"
using namespace std;
void PointerliDegistir(int *sayi1, int *sayi2){ /*Değişti*/
int gecici = *sayi1;
*sayi1 = *sayi2;
*sayi2 = gecici;
}
int main() {
int sayi1 = 10;
int sayi2 = 20;
cout << "Fonksiyon çağrılmadan önce" << endl;
cout << "sayi1: " << sayi1 << endl;
cout << "sayi2: " << sayi2 << endl;
Pointerli(&sayi1,&sayi2); /*Değişti*/
cout << "Fonksiyon çağrıldıktan sonra" << endl;
cout << "sayi1: " << sayi1 << endl;
cout << "sayi2: " << sayi2 << endl;
return 0;
}
---
Fonksiyon çağrılmadan önce
sayi1: 10
sayi2: 20
Fonksiyon çağrıldıktan sonra
sayi1: 20
sayi2: 10

İşte şimdi oldu. Yukarıdaki örnekte main fonksiyonu içinde PointerliDegistir() fonksiyonumuza parametre olarak sayi1 ve sayi2’nin adreslerini verdik. Fonksiyonumuza adres gönderdiğimiz için bunları tutabilecek tek bir değer vardır onlar da işaretçilerdir. Bu sebeple PointerliDegistir() fonksiyonunu tanımlarken parametrelerini pointer cinsinden tanımladık. İşte şimdi herşey yerli yerine oturdu diyebiliriz. Kodumuzu adım adım açıklayacak olursak;

  • Değiştirme yapacak fonksiyonumuzu iki adet pointer türünden parametre ile tanımladık.
  • İçerisinde main fonksiyonundan gelen iki adet değişkenin adresini tutan bu pointerlar ile yer değiştirme işlemi yaptık.
  • İşte bu yer değiştirme işlemi adresler bazında olduğundan main fonksiyonu içindeki sayi1 ve sayi2 değişkenlerimiz yer değiştirmiş oldu.

Bu son örnekle beraber işaretçiler konusunu ile ilgili bu yazımızın sonuna gelmiş bulunuyoruz. Yazımın başında da belirttiğim gibi pointer konusu gerçekten önemli bir konudur ve C++’ta aktif bir şekilde kullanılır. Bu sebeple göstermiş olduğum örnekleri dikkatle ve deneyerek incelemenizi istiyorum. Bir sonraki yazımda görüşmek üzere.

Yazar Hakkında

Hakan İlbiz

Siber güvenlik, linux, programlama ve network gibi bilgisayar bilimlerini kapsayan alanlarda yaptığı çalışmalardan edindiği tecrübeleri, kurucusu olduğu kodputer.com adlı web sitesinde okurlarına sunan ve bundan büyük bir haz duyan Sakarya Üniversitesi Bilgisayar Mühendisliği öğrencisi.

Yorum Ekle

Yorum Yazmak İçin Tıkla