Pointer

Pointer konusuna değinilen kısım.

Pointer Nedir?

Pointer'lar verilerin bellekteki adreslerini saklamak, temsil etmek için kullanılan değişkenlerdir. Verilerin bellekte ki adreslerini saklayan bir pointer aynı zaman da o verinin değerine de erişebilir. Bellekte bir verinin adresi ve değeri nasıl tutuluyorsa aynı şekilde pointer'ın da bellekte bir kendi adresi ve tuttuğu değer (yani pointer'a atanan değerin adresi) vardır. Pointer kendisine atanan adrese (değere) her zaman işaret eder yani onu gösterir ancak onu değiştirene kadar. Bunlar pointer'ın tuttuğu adresin (değerinin) arttırılması, azaltılması, veya bambaşka bir adres atanması olabilir.

int x = 10;
int *ptr = &x;

Daha iyi anlaşılması adına şu şekilde bir pointer örneği gösterilebilir;

#include <stdio.h>

int main()
{
  int x = 10;
  int* ptr = &x;
  
  printf("Pointer'in kendi adresi: %p\n", &ptr);
  printf("Pointer'in degeri: %p\n", ptr);
  printf("x degiskeninin adresi: %p\n", &x);
  printf("Tutulan degerin degeri: %d\n", *ptr);
  
  return 0;
}

Çıktı;

&ptr --> Pointer'ın bellekte ki kendi adresi
ptr  --> Pointer'ın tuttuğu değer (yani x'in adresi)
*ptr --> Pointer'ın tuttuğu değerin değeri (yani 'x' o da '10')

Pointer'ın işaret ettiği değeri pointer aracılığıyla değiştirirsek o değeri tutan değişkende de değer değişecektir çünkü o pointer o değişkenin adresini tutuyor bu sayede (sanki kimliğini biliyormuş) değeri ile alakalı bir değişiklik yapması söz konusu oluyor;

#include <stdio.h>

int main()
{
  int a = 10;
  int* p = &a;
  printf("%d\n", *p);
  *p = 20;
  printf("%d\n", a);
}

Pointer'larda Postfix

Normal bir değeri postfix ifadelerini kullanarak arttırabildiğimiz gibi pointer'larda da bu ifadeleri kullanabiliriz. Ancak bu ifadeleri kullanırken işler karmaşıklaşabilir. Genel itibariyle şu şekilde ifade edilebilirler;

ptr++;     --> onu kullan ve sonraki int pozisyonuna geç
++ptr;     --> sonraki int'e geçin ve onu kullan
++*ptr;    --> ptr'nin değerinin değerini artır ve kullan
++(*ptr);  --> ptr'nin değerinin değerini artır ve kullan
++*(ptr);  --> ptr'nin değerinin değerini artır ve kullan
*ptr++;    --> ptr'nin değerinin değerini kullan ve bir sonra ki konuma geç
(*ptr)++;  --> ptr'nin değerinin değerini kullan ve kullanılan değeri artır
*(ptr)++;  --> ptr'nin değerinin değerini kullan ve bir sonra ki konuma geç
*++ptr;    --> bir sonra ki konuma geç ve ptr'nin değerinin değerini kullan
*(++ptr);  --> bir sonra ki konuma geç ve ptr'nin değerinin değerini kullan

İlk olarak, ++ operatörü * operatörüne göre önceliklidir ve () operatörleri diğer her şeye göre önceliklidir.

İkinci olarak, ++sayı operatörü, eğer onları herhangi bir şeye atamıyorsanız, sayı++ operatörüyle aynıdır. Aradaki fark, sayı++'nın sayıyı döndürmesi ve ardından sayıyı artırması, ++sayı'nın ise önce artırması ve sonra döndürmesidir.

Üçüncüsü, bir pointer'ın değerini artırarak onu içeriğinin boyutu kadar artırmış olursunuz, yani sanki bir dizide yineliyormuşsunuz gibi onu artırmış olursunuz.

Pointer Comparisons

Değerlerin birbirlerinden büyük, küçük eşit veya eşit olmadığını karşılaştırabildiğimiz gibi pointer'ların tuttuğu değerleri (yani adresleri) de karşılaştırabiliriz.

int a = 10;
int* p1, p2;

p1 = &a;
p2 = &a;

if(p1 == p2)
 printf("İşaret edilen yer adresi aynı!");
else
 printf("İşaret edilen yer adresleri aynı değil!");

Pointer'ın gösterdiği adresin büyüklük veya küçüklüğü, o konumun bellekteki konumuna bağlıdır. Yani aslında derleyicinin ve işletim sisteminin nasıl bellek ayırdığına da bağlıdır. Bu yüzden bu materyallere bağlı olduğundan büyük veya küçük olduğu durumları değişebilir.

#include <stdio.h>

int main()
{
  int x = 10;
  int a = 10;
  int* p1 = &x;
  int* p2 = &a;
  
  if(p1 > p2)
    printf("Büyük");
  else
    printf("Küçük");  
  return 0;
}

Ancak bu örnek de bellekten belirli bir adres alanı tahsis edildiği için bu alanın sınırları içerisinde kimin daha büyük veya küçük mukayesesi yapılabilir. Bu da hangi adres daha ilerideyse o daha büyük olur anlamına geliyor. Yani sonuncu adres en büyükleri oluyor. Çünkü bu tahsis bellek tarafından ardışık olarak adreslendi.

#include <stdio.h>
#include <stdlib.h>

int main()
{
  char* str;
  str = malloc(10);
  
  printf("%p\n", (str + 4));
  printf("%p\n", (str + 3));
  
  if((str + 4) > (str + 3))
    printf("Büyük");
  else
    printf("Küçük");
  return 0;
}

Pointer ve Array

Array'de bir pointer gibi düşünülebilir. Ancak aralarında bazı farklar bulunur. Bunlardan biri Dinamik Bellek Tahsisi'dir. Çünkü bir array'e eğer ki 10 adet bir yer tahsisinde bulunması söylenirse bu program boyunca ihtiyacı karşılayamayabilir. Bu yüzden bu konuda yardıma pointer'lar koşar. Dinamik bellek tahsisi, pointer'lar ile birlikte programın ihtiyaç duyduğu bellek miktarını kontrol etmemize olanak tanır. Pointer'lar, programın çalışma süresi boyunca bellekte tahsis edilebilir, yeniden tahsis edilebilir ve silinebilir.

Birbirlerine benzemesinin sebebi ise array'ler tanımlandığında bellekte ardışık olarak yer tahsis eder ve array bu ardışık setin ilk adresini tutar. Bu yüzden de bir array'i bir pointer'a atarken normal bir değişkenlerde atama yapılırken kullanılan referans (&) işaretini kullanmayız. Array'ın ismi array'in ilk elemanının adresini temsil eder.

int numbers[5];
int num = 7;

int* ptr = numbers; // Dizi adını işaretçiye atar, "&" kullanmamız gerekmez.
int* ptr2 = &num;

Normal bir değişken, tek bir değeri temsil eder. Örneğin, int num = 7; ifadesinde num, bellekte 7 değerini tutar. Normal bir değişkenin adresine ayrıyeten erişmek isterseniz, & operatörünü kullanmanız gerekir.

Çok boyutlu Pointer ve Array

Çok boyutlu pointer veya array'i veri kümeleri olarak düşünebiliriz. Bunu şu şekilde ele almak isterim;

Sadece bir harfi depolamak için char tipi bize yetiyor. Ancak o harflerden oluşan bir kelime içinse bir char* pointer veya char[] array'i kullanmamız gerekiyor. Ve bu kelimelerden oluşan bir cümle için char** pointer veya char[][] çift boyutlu bir array gerekiyor. Bu cümleleri ise bir metin gibi saklamak istersek ise yine bunun bir üst seviyesi char*** pointer veya char[][][] boyutlu bir veri kümesine ihtiyaç duyabiliriz.

Aslında hepsinin üst seviyesi bir alt seviyesinin ilk elemanı oluyor böylece bir bütün oluşturulabiliyor.

Şayet bu cümleyi atomlarına ayırırsak _"deneme bir iki üç";

char    --> 'd'
char*   --> "deneme"
char**  --> "deneme", "bir", "iki", "üç"
char*** --> {"deneme", "bir", "iki", "üç"}, {...}

gibi düşünülebilir.

#include <stdio.h>

int main() 
{
    char*strs[] = {"deneme", "bir", "iki", "uc", NULL};
    char* str = *strs;
    char*** strss = strs;
    char c = **strss;
    
    printf("%c\n", c);
    return 0;
}

Pointer Aritmetiği

Pointer'larda bir indeksi göstermeye çalışırken array ifadesi kullanabiliriz veya pointer aritmetiği özelliği ile bazı işlemlerin aynısını ve fazlasını daha iyi uygulayabiliriz.

#include <stdio.h>

int main() 
{
    char str[6] = "deneme";
    ++str; // lvalue gerekli artırma işlemi için
    return 0;
}

Gibi bir örnekte array'lerin başlangıç adresleri hareket ettirilemez. Buna bir lvalue gerekli olduğu belirtilir. Bu yüzden bunun aynısının bir de pointer versiyonunu yazarsak şayet bu problemi çözmüş olacağız. Çünkü pointer'lar ile rahatlıkla gezinebiliriz.

#include <stdio.h>

int main() 
{
    char* str = "deneme";
    
    str++;
    printf("%c\n", str[0]);
    return 0;
}

Burada pointer'ın işaret ettiği adres d 'dir. Ancak str++ ifadesi ile işaret ettiği yeri e harfine yönlendiriyoruz. Bu yüzden str pointer'ın ilk elemanı artık e oluyor. Bu yüzden str[0] ifadesi d yerine artık e olmuş oluyor.

Ancak d tamamen kaybedilmiş olmuyor;

#include <stdio.h>

int main() 
{
    char* str = "deneme";
    ++str;
    printf("%c\n", *str);
    str--;
    printf("%c\n", *str);
    return 0;
}

Fark edildiği üzere str-- ifadesini kullanarak tekrardan d'ye geri dönebildik. Burada ki str[0] yerine kullanılan *str ifadesi pointer'ın işaret ettiği adresin değerini belirtir. Bu ifade sayesinde direkt olarak işaret edilen adresin değerine ulaşabiliriz.

YukarıdaPointer'larda Postfix konusunda bahsedilen şeyler de bir nevi Pointer aritmetiği'dir.

char* str = "deneme";

*(str + 2) // bize 'n' harfini verir

Bu tarz ifadeler ile (sanki array ifadeleri gibi) değerlere ulaşabiliriz.

#include <stdio.h>

int main() 
{
    char* str = "deneme";
    
    str += 2;
    printf("%c\n", *str);
    return 0;
}

Yine bu örnekte de pointer'ın işaret ettiği konumu 2 konum ilerlettik.

Bu tarz ifadeler ile bir pointer alanında istenilen şekilde gezilebilir.

Fonksiyon Pointer

Fonksiyon işaretçileri, fonksiyonları işaret eden pointer'lardır ve programın çalışma zamanında fonksiyonları dinamik olarak değiştirmenizi veya seçmenizi sağlar.

#include <stdio.h>

int topla(int x, int y) 
{
    return x + y;
}

int carp(int x, int y) 
{
    return x * y;
}

int main() 
{
    int (*hesapla)(int, int); // İşlev işaretçisi tanımı

    hesapla = topla; // İşaretçiyi 'topla' işlevine ayarla
    printf("Toplam: %d\n", hesapla(5, 3)); // 'topla' işlemini çağır

    hesapla = carp; // İşaretçiyi 'carp' işlevine ayarla
    printf("Carpim: %d\n", hesapla(5, 3)); // 'carp' işlemini çağır

    return 0;
}

Birden çok fonksiyona işaret edilmek istenirse (bir pointer fonksiyonunun fonksiyonları depolanması istenirse) şayet;

#include <stdio.h>

int topla(int x, int y) 
{
    return x + y;
}

int carp(int x, int y) 
{
    return x * y;
}

int bol(int x, int y) {
    if (y != 0)
        return x / y;
    else
        return 0;
}

int main() {
    int (*hesapla[])(int, int) = {topla, carp, bol}; // İşlev işaretçileri dizisi

    printf("Toplam: %d\n", hesapla[0](5, 3)); // 'topla' işlemini çağır
    printf("Carpim: %d\n", hesapla[1](5, 3)); // 'carp' işlemini çağır
    printf("Bolum: %d\n", hesapla[2](10, 2)); // 'bol' işlemini çağır

    return 0;
}

Last updated