← Kursa Dön
📄 Text · 12 min

Pointer Aritmetiği ve Dizilerle İlişki

Pointer'lar sadece adres tutmakla kalmaz — üzerlerinde matematik işlemleri de yapabilirsin. Bir pointer'ı bir ileri kaydır, üç geri al, iki pointer arasındaki mesafeyi hesapla... Bunların hepsi mümkün.

Ve asıl büyülü kısım: C++'ta diziler (array) aslında pointer'larla iç içe geçmiş yapılardır. Bir diziyi anlamak, pointer'ları anlamaktan geçer. Bu derste ikisini birlikte keşfedeceğiz.


Pointer Aritmetiği Nedir?

Normal sayılarla toplama-çıkarma yapar gibi pointer'larla da yapabilirsin. Ama burada bir fark var: pointer aritmetiği byte bazında değil, eleman bazında çalışır.

Otel Koridoru Analojisi

Bir otelde koridordaki odaları düşün. Her oda farklı büyüklükte olabilir — standart oda 20 m², süit oda 40 m². Ama sen "3 oda ileri git" dediğinde, metrekareyle uğraşmazsın. Sadece 3 kapı sayarsın.

Pointer aritmetiği de böyle çalışır. ptr + 3 dediğinde, pointer 3 byte değil, 3 eleman ileri gider. Her elemanın kaç byte olduğunu derleyici zaten bilir.

#include <iostream>
using namespace std;

int main() {
    int dizi[] = {10, 20, 30, 40, 50};
    int* ptr = dizi;  // Dizinin ilk elemanini gosterir

    cout << "ptr adresi:     " << ptr << endl;
    cout << "ptr + 1 adresi: " << ptr + 1 << endl;
    cout << "ptr + 2 adresi: " << ptr + 2 << endl;

    // Adresler arasindaki fark (byte cinsinden)
    cout << "Fark: " << (char*)(ptr + 1) - (char*)ptr
         << " byte" << endl;

    return 0;
}

Çıktı (64-bit, int = 4 byte):

ptr adresi:     0x7ffd1234a000
ptr + 1 adresi: 0x7ffd1234a004
ptr + 2 adresi: 0x7ffd1234a008
Fark: 4 byte

Her +1 işlemi adresi 4 byte (bir int boyutu) ileri taşıdı. Eğer double* olsaydı, her adım 8 byte olacaktı.


Temel Pointer İşlemleri

ptr + n ve ptr - n

Pointer'ı n eleman ileri veya geri kaydırır:

#include <iostream>
using namespace std;

int main() {
    int dizi[] = {100, 200, 300, 400, 500};
    int* ptr = &dizi[2];  // 300'u gosteriyor

    cout << "ptr:       " << *ptr << endl;      // 300
    cout << "ptr + 1:   " << *(ptr + 1) << endl; // 400
    cout << "ptr + 2:   " << *(ptr + 2) << endl; // 500
    cout << "ptr - 1:   " << *(ptr - 1) << endl; // 200
    cout << "ptr - 2:   " << *(ptr - 2) << endl; // 100

    return 0;
}

ptr + 1 yeni bir adres verir ama ptr'yi değiştirmez. Tıpkı x + 1'in x'i değiştirmemesi gibi.

ptr++ ve ptr--

Pointer'ı bir eleman ileri veya geri kaydırır ve değerini günceller:

#include <iostream>
using namespace std;

int main() {
    int dizi[] = {10, 20, 30, 40, 50};
    int* ptr = dizi;

    cout << *ptr << endl;  // 10
    ptr++;
    cout << *ptr << endl;  // 20
    ptr++;
    cout << *ptr << endl;  // 30
    ptr--;
    cout << *ptr << endl;  // 20

    return 0;
}

ptr++ aslında ptr = ptr + 1 demektir — pointer bir sonraki elemana kayar.

İki Pointer Arasındaki Fark

Aynı dizideki iki pointer'ı çıkararak aralarındaki eleman sayısını bulabilirsin:

#include <iostream>
using namespace std;

int main() {
    int dizi[] = {10, 20, 30, 40, 50};
    int* bas = &dizi[0];
    int* son = &dizi[4];

    cout << "Aradaki eleman sayisi: " << son - bas << endl;  // 4

    return 0;
}

⚠️ Dikkat: İki pointer'ı çıkarmak sadece aynı dizi (veya aynı bellek bloğu) içinde anlamlıdır. Farklı dizilerdeki pointer'ları çıkarmak undefined behavior'dır.


Pointer ve Dizi İlişkisi (Array Decay)

C++'ta diziler ve pointer'lar arasında çok sıkı bir ilişki var. Bir dizinin adı, aslında ilk elemanının adresine dönüşür. Bu olaya array decay (dizi çürümesi) denir.

#include <iostream>
using namespace std;

int main() {
    int dizi[] = {10, 20, 30, 40, 50};

    cout << "dizi:   " << dizi << endl;    // Dizinin adresi
    cout << "&dizi[0]: " << &dizi[0] << endl; // Ilk elemanin adresi

    // Ikisi ayni!
    if (dizi == &dizi[0]) {
        cout << "Evet, dizi adi = ilk elemanin adresi" << endl;
    }

    return 0;
}

Bu, şu demek: bir diziyi fonksiyona geçirdiğinde, aslında pointer geçirmiş olursun. Dizinin tamamı kopyalanmaz.

#include <iostream>
using namespace std;

void diziYazdir(int* ptr, int boyut) {
    for (int i = 0; i < boyut; i++) {
        cout << ptr[i] << " ";
    }
    cout << endl;
}

int main() {
    int dizi[] = {5, 10, 15, 20, 25};
    diziYazdir(dizi, 5);  // dizi otomatik olarak int*'a donusur

    return 0;
}

💡 İpucu: Array decay yüzünden fonksiyon içinde dizinin boyutunu sizeof ile öğrenemezsin. sizeof(ptr), dizinin boyutunu değil pointer'ın boyutunu (8 byte) verir. Bu yüzden boyutu ayrı parametre olarak geçiriyoruz.


Pointer ile Dizi Elemanlarına Erişim

Dizi elemanlarına erişmenin iki yolu var — ve ikisi de birbiriyle aynı şeyi yapar:

#include <iostream>
using namespace std;

int main() {
    int dizi[] = {10, 20, 30, 40, 50};
    int* ptr = dizi;

    // Yontem 1: Kose parantez (index) notasyonu
    cout << "dizi[2] = " << dizi[2] << endl;

    // Yontem 2: Pointer aritmetigi
    cout << "*(ptr + 2) = " << *(ptr + 2) << endl;

    // Yontem 3: Pointer uzerinde index
    cout << "ptr[2] = " << ptr[2] << endl;

    // Hatta bu bile calısır (ama yapma):
    cout << "2[dizi] = " << 2[dizi] << endl;

    return 0;
}
dizi[2] = 30
*(ptr + 2) = 30
ptr[2] = 30
2[dizi] = 30

Derleyici dizi[i]'yi *(dizi + i)'ye çevirir. Bu yüzden i[dizi] bile çalışır — çünkü toplama yer değiştirebilir: *(i + dizi). Tabii ki bunu gerçek kodda asla kullanma!

Pointer ile Dizi Üzerinde Döngü

#include <iostream>
using namespace std;

int main() {
    int notlar[] = {85, 92, 78, 95, 88};
    int boyut = sizeof(notlar) / sizeof(notlar[0]);

    // Index ile
    cout << "Index ile: ";
    for (int i = 0; i < boyut; i++) {
        cout << notlar[i] << " ";
    }
    cout << endl;

    // Pointer ile
    cout << "Pointer ile: ";
    int* ptr = notlar;
    int* son = notlar + boyut;
    while (ptr < son) {
        cout << *ptr << " ";
        ptr++;
    }
    cout << endl;

    return 0;
}

İki döngü de aynı çıktıyı verir. Modern C++'ta genellikle range-based for veya STL algoritmaları tercih edilir. Ama pointer döngüsünü anlamak, arka planda neler döndüğünü kavramak için önemli.


Pointer Karşılaştırma

Aynı dizi içindeki pointer'ları karşılaştırabilirsin:

#include <iostream>
using namespace std;

int main() {
    int dizi[] = {10, 20, 30, 40, 50};
    int* p1 = &dizi[1];  // 20
    int* p2 = &dizi[3];  // 40

    if (p1 < p2) {
        cout << "p1, p2'den once" << endl;
    }

    if (p1 != p2) {
        cout << "Farkli adresleri gosteriyorlar" << endl;
    }

    int* p3 = &dizi[1];
    if (p1 == p3) {
        cout << "p1 ve p3 ayni adresi gosteriyor" << endl;
    }

    return 0;
}
p1, p2'den once
Farkli adresleri gosteriyorlar
p1 ve p3 ayni adresi gosteriyor

Karşılaştırma operatörleri (<, >, <=, >=, ==, !=) pointer'larda çalışır. < ve > sadece aynı dizi içinde anlamlıdır — farklı dizilerdeki pointer'ları < ile karşılaştırmak tanımsız davranış (undefined behavior) verir.

nullptr Karşılaştırma

En yaygın pointer karşılaştırması nullptr kontrolüdür:

int* ptr = nullptr;

if (ptr == nullptr) { /* bos */ }
if (ptr != nullptr) { /* dolu */ }
if (!ptr)            { /* bos — kisa yol */ }
if (ptr)             { /* dolu — kisa yol */ }

void Pointer

void*, herhangi bir tipteki verinin adresini tutabilen özel bir pointer türüdür. Tip bilgisi taşımaz.

#include <iostream>
using namespace std;

int main() {
    int sayi = 42;
    double ondalik = 3.14;
    char harf = 'A';

    void* ptr;

    ptr = &sayi;
    cout << "int deger: " << *(static_cast<int*>(ptr)) << endl;

    ptr = &ondalik;
    cout << "double deger: " << *(static_cast<double*>(ptr)) << endl;

    ptr = &harf;
    cout << "char deger: " << *(static_cast<char*>(ptr)) << endl;

    return 0;
}

void* doğrudan dereference edilemez — önce doğru tipe cast etmen gerekir. Çünkü derleyici kaç byte okuyacağını bilmez.

void* Nerede Kullanılır?

void* genelde C API'lerinde ve çok düşük seviyeli kod yazarken karşına çıkar. Örneğin malloc fonksiyonu void* döndürür. Ama modern C++'ta void* kullanımı nadirdir — template'ler ve smart pointer'lar daha güvenli alternatifler sunar.

⚠️ Dikkat: void* üzerinde pointer aritmetiği yapamazsın. ptr + 1 dediğinde derleyici eleman boyutunu bilmez. Bazı derleyiciler buna izin verir (GCC extension olarak 1 byte sayar) ama standart C++ buna izin vermez.

void* ptr = &sayi;
// ptr++;          // HATA — standart C++'ta yasak
// *(ptr) = 10;    // HATA — void* dereference edilemez

Pointer Aritmetiği Kuralları — Özet Tablosu

İşlemAçıklamaÖrnek
ptr + nn eleman ileri*(ptr + 3) → 4. eleman
ptr - nn eleman geri*(ptr - 1) → önceki eleman
ptr++Bir eleman ileri (değiştirir)ptr++ sonra *ptr
ptr--Bir eleman geri (değiştirir)ptr-- sonra *ptr
ptr1 - ptr2Aradaki eleman sayısıson - bas → 4
ptr1 < ptr2Adres karşılaştırmaAynı dizi içinde geçerli
ptr == nullptrBoş mu kontrolüHer zaman geçerli

İzin Verilmeyen İşlemler

// ptr1 + ptr2;    // HATA — iki pointer toplanamaz
// ptr * 2;        // HATA — pointer carpılamaz
// ptr / 2;        // HATA — pointer bolunemez
// ptr % 2;        // HATA — mod alinamaz

Pointer'larla sadece toplama (pointer + tam sayı), çıkarma (pointer - tam sayı veya pointer - pointer) ve karşılaştırma yapabilirsin.


Pratik Örnek: Dizide Eleman Arama

Pointer aritmetiği ile basit bir arama fonksiyonu yazalım:

#include <iostream>
using namespace std;

int* elemanBul(int* bas, int* son, int aranan) {
    while (bas < son) {
        if (*bas == aranan) {
            return bas;  // Bulunan elemanin adresini don
        }
        bas++;
    }
    return nullptr;  // Bulunamadi
}

int main() {
    int dizi[] = {15, 42, 8, 23, 77, 31};
    int boyut = sizeof(dizi) / sizeof(dizi[0]);

    int* sonuc = elemanBul(dizi, dizi + boyut, 23);

    if (sonuc != nullptr) {
        cout << "Bulundu! Deger: " << *sonuc << endl;
        cout << "Index: " << sonuc - dizi << endl;
    } else {
        cout << "Bulunamadi." << endl;
    }

    return 0;
}
Bulundu! Deger: 23
Index: 3

Bu fonksiyon, STL'deki std::find algoritmasının basitleştirilmiş hali. Başlangıç ve bitiş pointer'ları alıyor, aralarında dolaşıyor. sonuc - dizi ifadesi bize index'i veriyor.


Pratik Örnek: Diziyi Tersine Çevirme

#include <iostream>
using namespace std;

void tersineCevir(int* bas, int* son) {
    son--;  // son, dizinin disini gosteriyor, bir geri al
    while (bas < son) {
        int gecici = *bas;
        *bas = *son;
        *son = gecici;
        bas++;
        son--;
    }
}

int main() {
    int dizi[] = {1, 2, 3, 4, 5};
    int boyut = 5;

    cout << "Once:  ";
    for (int i = 0; i < boyut; i++) cout << dizi[i] << " ";
    cout << endl;

    tersineCevir(dizi, dizi + boyut);

    cout << "Sonra: ";
    for (int i = 0; i < boyut; i++) cout << dizi[i] << " ";
    cout << endl;

    return 0;
}
Once:  1 2 3 4 5
Sonra: 5 4 3 2 1

İki pointer — biri baştan, biri sondan — ortada buluşana kadar elemanları takas ediyor. Bu pattern, birçok algoritmada karşına çıkacak.


C-String ve Pointer

C tarzı stringler (char dizileri) pointer'larla çok sık kullanılır:

#include <iostream>
#include <cstring>
using namespace std;

int main() {
    const char* mesaj = "Merhaba Dunya";

    // Pointer ile karakter karakter dolasma
    const char* ptr = mesaj;
    while (*ptr != '\0') {
        cout << *ptr;
        ptr++;
    }
    cout << endl;

    // String uzunlugu — pointer farki ile
    cout << "Uzunluk: " << ptr - mesaj << endl;

    return 0;
}
Merhaba Dunya
Uzunluk: 13

*ptr != '\0' kontrolü, string'in sonunu bulana kadar döngüyü sürdürür. C-string'ler null-terminated'dır — yani sonlarında '\0' karakteri vardır.

💡 İpucu: Modern C++'ta std::string kullan. C-string'ler ve char* pointer'lar, C kütüphaneleriyle çalışırken veya düşük seviyeli kod yazarken karşına çıkar. Ama pointer mantığını anlamak için mükemmel bir örnektir.


Özet

  • Pointer aritmetiği byte değil eleman bazında çalışır — ptr + 1, pointer'ı sizeof(tip) byte ileri taşır.

  • Array decay: Dizi adı, çoğu bağlamda ilk elemanın adresine dönüşür. dizi[i] aslında *(dizi + i) demektir.

  • İki pointer farkı (ptr1 - ptr2) aradaki eleman sayısını verir — sadece aynı dizi içinde geçerlidir.

  • Pointer karşılaştırma (<, >, ==, !=) aynı dizi içindeki pointer'lar arasında anlamlıdır.

  • `void*` herhangi bir tipin adresini tutabilir ama dereference edilemez — önce doğru tipe cast gerekir.

  • Modern C++'ta ham pointer döngüleri yerine range-based for veya STL algoritmaları tercih edilir, ama pointer aritmetiğini bilmek temeli anlamak için şarttır.