Pointer Temelleri
Buraya kadar değişkenlerle çalıştık — sayılar, metinler, diziler. Ama hiç "bu değişken bellekte nerede duruyor?" diye sordun mu? İşte pointer'lar tam olarak bu sorunun cevabı. Bellekteki adresleri tutan özel değişkenler.
Bu ders, C++ öğrenme yolculuğunun en kritik dönemeçlerinden biri. Pointer'ları anlayan, C++'ı anlar. Hazırsan başlıyoruz.
Pointer Nedir?
Pointer, başka bir değişkenin bellek adresini tutan değişkendir. Hepsi bu kadar. Bir sayı tutmaz, bir metin tutmaz — bir adres tutar.
Bilgisayarında her değişken RAM'de bir yere yerleşir. Bu yerin bir numarası vardır — buna bellek adresi (memory address) diyoruz. Pointer, işte bu numarayı saklayan değişkendir.
Normal bir değişken "değer" tutar. Pointer ise "o değerin nerede olduğunu" tutar. Bu basit fark, inanılmaz güçlü şeyler yapmamızı sağlar.
Ev Adresi Analojisi
Bir arkadaşının evine gitmek istiyorsun. İki seçeneğin var:
Seçenek 1: Arkadaşının tüm eşyalarını kopyalayıp kendi evine getirmek. (Bu, değişkeni kopyalamak gibi.)
Seçenek 2: Arkadaşının ev adresini bir kağıda yazmak. O kağıtla istediğin zaman gidip eşyalarına ulaşabilirsin. (Bu, pointer kullanmak gibi.)
Pointer, o kağıttaki adrestir. Kağıt küçüktür, ama seni büyük bir eve götürür. Kağıttaki adresi okuyup eve gitmek ise "dereference" işlemidir — birazdan göreceğiz.
Adres Operatörü (&)
Her değişkenin bellekte bir adresi vardır. Bu adresi öğrenmek için & (address-of) operatörünü kullanırız.
#include <iostream>
using namespace std;
int main() {
int yas = 25;
double maas = 7500.50;
cout << "yas'in degeri: " << yas << endl;
cout << "yas'in adresi: " << &yas << endl;
cout << "maas'in degeri: " << maas << endl;
cout << "maas'in adresi: " << &maas << endl;
return 0;
}Çıktı şöyle bir şey olacak:
yas'in degeri: 25
yas'in adresi: 0x7ffd5e8a3b2c
maas'in degeri: 7500.5
maas'in adresi: 0x7ffd5e8a3b30O 0x7ffd... gibi görünen şeyler hexadecimal (onaltılık) formatta bellek adresleri. Her çalıştırmada farklı olabilir — işletim sistemi değişkenleri farklı yerlere koyabilir.
💡 İpucu: & operatörü "bu değişken bellekte nerede?" sorusuna cevap verir. Bir değişkenin önüne & koyduğunda, değerini değil adresini alırsın.
Pointer Tanımlama
Pointer tanımlamak için değişken tipinin yanına * (yıldız) koyarız. Sonra da ona bir adres atarız.
#include <iostream>
using namespace std;
int main() {
int x = 42;
int* ptr = &x; // ptr, x'in adresini tutuyor
cout << "x'in degeri: " << x << endl;
cout << "x'in adresi: " << &x << endl;
cout << "ptr'nin tuttugu adres: " << ptr << endl;
return 0;
}x'in degeri: 42
x'in adresi: 0x7ffd2a1b3c44
ptr'nin tuttugu adres: 0x7ffd2a1b3c44Gördüğün gibi &x ve ptr aynı adresi gösteriyor. Çünkü ptr, x'in adresini tutuyor.
Yıldızın Yeri
C++ dünyasında üç farklı yazım stili görebilirsin:
int* ptr; // Yıldız tipe yapışık (önerilen)
int *ptr; // Yıldız isme yapışık
int * ptr; // Yıldız ortadaÜçü de aynı anlama gelir. Ama modern C++ topluluğunda int* ptr stili tercih edilir çünkü "ptr, bir int pointer'dır" şeklinde okunur.
⚠️ Dikkat: Aynı satırda birden fazla pointer tanımlarken dikkatli ol:
int* a, b; // a pointer, ama b düz int!
int *a, *b; // ikisi de pointerBu yüzden pointer'ları ayrı satırlarda tanımlamak en güvenlisidir.
Pointer Tipleri
Pointer'lar tip bilgisi taşır. Bir int*, bir int'in adresini tutar. Bir double*, bir double'ın adresini tutar.
#include <iostream>
using namespace std;
int main() {
int sayi = 10;
double ondalik = 3.14;
char harf = 'A';
int* p1 = &sayi;
double* p2 = &ondalik;
char* p3 = &harf;
cout << "int pointer: " << p1 << endl;
cout << "double pointer: " << p2 << endl;
cout << "char pointer: " << static_cast<void*>(p3) << endl;
return 0;
}char* pointer'ını cout'a verirken static_cast<void*> kullandık. Çünkü cout, char*'ı string gibi yazdırmaya çalışır — adresi görmek istiyorsak dönüşüm gerekir.
💡 İpucu: Pointer tipi neden önemli? Çünkü derleyici, pointer üzerinden değere erişirken kaç byte okuyacağını bilmek zorunda. int* 4 byte okur, double* 8 byte okur. Yanlış tiple erişirsen çöp veri alırsın.
Dereference Operatörü (*)
Pointer bize bir adres verir. Peki o adresteki değere nasıl ulaşırız? İşte burada * operatörü devreye girer. Pointer'ın önüne * koyduğumuzda, o adresteki değeri okuruz. Buna dereference (değer çözümleme) denir.
Ev adresi analojimize dönersek: kağıttaki adresi okuyup o eve gitmek, dereference işlemidir.
#include <iostream>
using namespace std;
int main() {
int x = 42;
int* ptr = &x;
cout << "ptr'nin tuttugu adres: " << ptr << endl;
cout << "o adresteki deger: " << *ptr << endl;
// Dereference ile degeri degistirebiliriz
*ptr = 100;
cout << "x'in yeni degeri: " << x << endl;
return 0;
}ptr'nin tuttugu adres: 0x7ffd2a1b3c44
o adresteki deger: 42
x'in yeni degeri: 100Dikkat et: *ptr = 100 yazdığımızda, x'in değeri değişti! Çünkü ptr, x'in adresini tutuyor. O adrese gidip değeri değiştirmek, x'i değiştirmek demek.
Yıldızın İki Farklı Anlamı
Yeni başlayanları en çok karıştıran şey, * sembolünün iki farklı yerde kullanılması:
int* ptr = &x; // Burada * "pointer tanımlıyorum" demek
*ptr = 100; // Burada * "adresteki değere eriş" demekİlk satırda *, tip tanımının parçası. İkinci satırda *, dereference operatörü. Bağlama göre anlam değişir.
Pointer ile Değer Değiştirme
Pointer'lar sayesinde bir fonksiyona değişkenin kendisini gönderebilir, fonksiyon içinde gerçekten değiştirebilirsin.
#include <iostream>
using namespace std;
void ikiKatina(int* ptr) {
*ptr = (*ptr) * 2; // Adresteki degeri 2 katina cikar
}
int main() {
int sayi = 15;
cout << "Once: " << sayi << endl;
ikiKatina(&sayi); // Adresini gonder
cout << "Sonra: " << sayi << endl;
return 0;
}Once: 15
Sonra: 30Fonksiyona sayi'nın adresini gönderdik. Fonksiyon o adrese gidip değeri değiştirdi. Bu, C tarzı bir yaklaşım — C++'ta genelde referans tercih ederiz ama konsepti bilmek önemli.
İki Değişkeni Takas Etme
Klasik bir örnek: iki değişkenin değerlerini pointer ile takas etme.
#include <iostream>
using namespace std;
void takasEt(int* a, int* b) {
int gecici = *a;
*a = *b;
*b = gecici;
}
int main() {
int x = 5, y = 10;
cout << "Once -> x: " << x << ", y: " << y << endl;
takasEt(&x, &y);
cout << "Sonra -> x: " << x << ", y: " << y << endl;
return 0;
}Once -> x: 5, y: 10
Sonra -> x: 10, y: 5nullptr — Boş Pointer
Bir pointer tanımlayıp henüz bir adres atamadıysan ne olur? Pointer bellekte rastgele bir yeri gösterir — bu çok tehlikeli. İşte bu yüzden nullptr var.
nullptr, "bu pointer şu an hiçbir yeri göstermiyor" demektir. C++11 ile dile eklendi ve eski NULL makrosunun yerini aldı.
#include <iostream>
using namespace std;
int main() {
int* ptr = nullptr; // Hicbir yeri gostermiyor
if (ptr == nullptr) {
cout << "Pointer bos, kullanma!" << endl;
}
// Kisa yol: pointer'i dogrudan kosulda kullanabilirsin
if (!ptr) {
cout << "Hala bos!" << endl;
}
// Simdi bir yer gostertelim
int x = 42;
ptr = &x;
if (ptr) {
cout << "Artik dolu: " << *ptr << endl;
}
return 0;
}Pointer bos, kullanma!
Hala bos!
Artik dolu: 42nullptr olan bir pointer'ı dereference etmek programı çökertir (segmentation fault). Bu yüzden pointer kullanmadan önce mutlaka kontrol et.
⚠️ Dikkat: Eski C kodlarında NULL veya 0 kullanıldığını görebilirsin. Modern C++'ta her zaman nullptr kullan. nullptr tip güvenlidir — NULL ise aslında sadece 0 sayısıdır ve karışıklığa yol açabilir.
nullptr vs NULL vs 0
int* p1 = nullptr; // Modern C++ — her zaman bunu kullan
int* p2 = NULL; // C tarzı — kaçın
int* p3 = 0; // Çalışır ama okunması zornullptr'ın tipi std::nullptr_t'dir. Bu sayede fonksiyon overload'larında doğru fonksiyon seçilir:
void fonk(int x) { cout << "int" << endl; }
void fonk(int* ptr) { cout << "pointer" << endl; }
fonk(NULL); // Belirsiz! Derleyici hata verebilir
fonk(nullptr); // "pointer" — dogru secimPointer'ın Boyutu
Bir pointer kaç byte yer kaplar? Cevap, işletim sistemi mimarisine bağlı:
32-bit sistemde: Her pointer 4 byte (32 bit adres alanı, ~4GB RAM adreslenebilir)
64-bit sistemde: Her pointer 8 byte (64 bit adres alanı, çok daha fazla RAM)
Ve önemli bir detay: pointer'ın boyutu, gösterdiği verinin tipinden bağımsızdır.
#include <iostream>
using namespace std;
int main() {
int a = 10;
double b = 3.14;
char c = 'X';
int* p1 = &a;
double* p2 = &b;
char* p3 = &c;
cout << "int* boyutu: " << sizeof(p1) << " byte" << endl;
cout << "double* boyutu: " << sizeof(p2) << " byte" << endl;
cout << "char* boyutu: " << sizeof(p3) << " byte" << endl;
cout << endl;
cout << "int boyutu: " << sizeof(int) << " byte" << endl;
cout << "double boyutu: " << sizeof(double) << " byte" << endl;
cout << "char boyutu: " << sizeof(char) << " byte" << endl;
return 0;
}64-bit sistemde çıktı:
int* boyutu: 8 byte
double* boyutu: 8 byte
char* boyutu: 8 byte
int boyutu: 4 byte
double boyutu: 8 byte
char boyutu: 1 byteGördün mü? int 4 byte, double 8 byte, char 1 byte — ama hepsinin pointer'ı 8 byte. Çünkü pointer sadece bir adres tutuyor ve 64-bit sistemde adresler 8 byte.
Pointer'dan Pointer'a (Pointer to Pointer)
Bir pointer, başka bir pointer'ın adresini de tutabilir. Buna çift pointer (double pointer) veya pointer to pointer denir.
#include <iostream>
using namespace std;
int main() {
int x = 42;
int* ptr = &x; // x'in adresini tutar
int** pptr = &ptr; // ptr'nin adresini tutar
cout << "x'in degeri: " << x << endl;
cout << "*ptr ile x'e erisim: " << *ptr << endl;
cout << "**pptr ile x'e erisim: " << **pptr << endl;
cout << endl;
cout << "x'in adresi: " << &x << endl;
cout << "ptr'nin degeri: " << ptr << endl;
cout << "ptr'nin adresi: " << &ptr << endl;
cout << "pptr'nin degeri: " << pptr << endl;
return 0;
}Bu bir zincir gibi düşünülebilir: pptr → ptr → x. Her * bir halka atlamamızı sağlar.
Çift pointer'lar günlük kullanımda çok nadir karşına çıkar. Ama dinamik 2D diziler veya bazı C API'lerinde görebilirsin. Şimdilik konsepti bilmen yeterli.
const ve Pointer
const anahtar kelimesi pointer'larla birleşince işler biraz karışır. Üç farklı durum var:
1. Gösterilen Değer Sabit (Pointer to const)
int x = 10;
const int* ptr = &x; // Gosterdigi deger degistirilemez
// *ptr = 20; // HATA! Deger degistirilemez
ptr = &y; // OK — pointer baska yeri gosterebilir"Bu pointer üzerinden değeri değiştiremezsin, ama pointer'ı başka yere yönlendirebilirsin."
2. Pointer Kendisi Sabit (const Pointer)
int x = 10;
int y = 20;
int* const ptr = &x; // Pointer sabitleniyor
*ptr = 30; // OK — degeri degistirebiliriz
// ptr = &y; // HATA! Pointer baska yeri gosteremez"Bu pointer hep aynı yeri gösterir, ama o yerdeki değeri değiştirebilirsin."
3. Her İkisi de Sabit
int x = 10;
const int* const ptr = &x;
// *ptr = 20; // HATA!
// ptr = &y; // HATA!"Ne değer değişir ne pointer başka yere bakar."
💡 İpucu: Okuma kuralı basit — const'ı sağdan sola oku. const int* → "int const'a pointer" (değer sabit). int* const → "const pointer to int" (pointer sabit).
Sık Yapılan Hatalar
1. Başlatılmamış Pointer Kullanmak
int* ptr; // Rastgele bir adresi gosteriyor!
// *ptr = 10; // Tehlikeli! Undefined behaviorPointer tanımlarken ya bir adres ata ya da nullptr yap:
int* ptr = nullptr; // Guvenli2. Geçersiz Adrese Erişmek
int* ptr = nullptr;
// *ptr = 42; // Segmentation fault — program cokerDereference etmeden önce kontrol et:
if (ptr != nullptr) {
*ptr = 42;
}3. Tip Uyumsuzluğu
double x = 3.14;
// int* ptr = &x; // HATA! double* beklerken int* veriyorsun
double* ptr = &x; // DogruPointer Ne İşe Yarar?
Belki "neden bu kadar uğraşıyoruz?" diye soruyorsun. Pointer'ların hayati olduğu durumlar:
Dinamik bellek yönetimi — Programın çalışma zamanında bellek ayırmak (
new/delete). Sonraki derslerde göreceğiz.Büyük veri yapılarını verimli geçirme — 1 milyon elemanlı bir diziyi kopyalamak yerine adresini geçirmek çok daha hızlı.
Polimorfizm — Nesne yönelimli programlamada temel sınıf pointer'ları ile alt sınıf nesnelerine erişim.
Veri yapıları — Linked list, tree, graph gibi yapılar pointer'sız düşünülemez.
C kütüphaneleriyle çalışma — Birçok sistem API'si pointer tabanlıdır.
Tam Bir Örnek
Öğrendiklerimizi birleştiren bir program yazalım:
#include <iostream>
using namespace std;
void bilgiGoster(const int* ptr, const char* isim) {
if (ptr != nullptr) {
cout << isim << " -> adres: " << ptr
<< ", deger: " << *ptr << endl;
} else {
cout << isim << " -> nullptr (bos)" << endl;
}
}
int main() {
int a = 10, b = 20;
int* ptr = &a;
bilgiGoster(ptr, "ptr");
ptr = &b;
bilgiGoster(ptr, "ptr");
*ptr = 99;
cout << "b'nin yeni degeri: " << b << endl;
ptr = nullptr;
bilgiGoster(ptr, "ptr");
return 0;
}ptr -> adres: 0x7ffd..., deger: 10
ptr -> adres: 0x7ffd..., deger: 20
b'nin yeni degeri: 99
ptr -> nullptr (bos)Özet
Pointer, başka bir değişkenin bellek adresini tutan değişkendir. Ev adresi yazılı kağıt gibi düşün.
`&` operatörü bir değişkenin adresini verir, `*` operatörü (dereference) o adresteki değere erişir.
`nullptr` boş pointer'ı temsil eder — pointer kullanmadan önce mutlaka nullptr kontrolü yap.
Pointer'ın bellekteki boyutu gösterdiği tipten bağımsızdır — 64-bit sistemde her pointer 8 byte'dır.
`const` ile pointer birleşince üç durum oluşur: değer sabit, pointer sabit veya ikisi birden sabit.
Başlatılmamış pointer kullanmak undefined behavior'dır — her pointer'ı ya bir adresle ya da
nullptrile başlat.
AI Asistan
Sorularını yanıtlamaya hazır