Pointer denilen şey adı üzerinde bir "işaretçi" adres tutan bir yapı. Sadece adres tutar.
En temelden başlayalım. Meşhur klasik örnek.
#include <stdio.h>
int main()
{
int data = 100;
int* ptr = &data;
printf("Value : %d\n", *ptr);
return 0;
}
Burada data adlı değişken dolaylı yoldan erişilebilir ve değiştirilebilir. Bunu sağlayan yapıya pointer denir.
Pointer adres tutar demiştik.
ptr adlı pointer yapısının göstereceği adresi data adlı değişkenin adresine eşitlersek artık ptr data'nın adresini gösterir.
Artık elimizde data adlı değişkenin adresini tutan bir yapı var. Bu yapıda "dereference"
özelliğini kullanarak dolaylı yoldan değere erişip okuma/yazma yapabiliriz. Dereference etmek oldukça kolay.
*ptr
Bu işleme "dereference etmek" adı verilir.
Genelde bu konuyu ilk öğrendiğimizde bir değişkenin değerini değiştirmek/okumak için bu kadar zahmete ne gerek var ey adem oğlu deriz.
Öğretici örnekten sonra uygulama üzerinden gidelim.
Bildiğiniz üzere C programlama dilinde string diye bir şey yoktur. Türkçede katarlar olarak tabir edilen string ifadesini andıran karakter dizileri mevcuttur.
İşte bu dizi/string(çakma) gibi ifadeleri anlayabilmek adına pointer kullanırız.
Pointer'ın bir sonraki adımında diziler mevcuttur.
Bir dizi'nin şöyle tanımlandığını biliyoruz.
int ictr[4] = {2, 0, 2, 0};
4 elemanlı bir dizinin ilk elamanına ulaşmak istediğimizde şöyle bir işlem yaparız.
ictr[0] = 3
int val = ictr[0];
Burada sorgulamanız gereken şey bir diziye erişmekten kastımız nedir ? Aslında farkında olmadan pointer kullanırız. Köşeli parantez kullanmak sadece görsel açıdan kolaylık sağlar. Yukarıda ki örneğin pointer eş değeri aslında şundan ibarettir.
*(ictr + 0) = 3;
int val = *(ictr + 0);
Görüldüğü üzere bir diziye erişmek aslında bir pointer kullanmaktan ibarettir.
Burada adresi tutan şey dizinin ismidir.
Dizinin ismine ekleyeceğiniz offset değeri index değeri olur.
Adres değerine eklenmiş index değerini dereference ederseniz o adresteki değere erişirsiniz.
Pointer ile fonksiyonları kullanmak bu konuya başka bir örnek olabilir. Bir fonksiyona parametre olarak değişken verebiliyoruz. Buna "Call by value" denir. Aşağıda bir örnek mevcut.
#include <stdio.h>
void test_func(int data)
{
printf("Data : %d", data);
}
int main()
{
int value = 3;
test_func(value);
return 0;
}
Yukarıdaki örnekten anlaşılacağı üzere call by value tekniği sadece fonksiyona değişkenin değerini gönderir. Eğer bu fonksiyon o değişkenin değerini değiştirmek isteyip bizde parametre olarak geri göndermek isterse bunu yapamaz. Bunun sebebi fonksiyon o değişkenin nerede tutulduğunu bilmediğindendir. Fonksiyon sadece değere ulaşabilir.
Biz bu değerin değiştirilip bize parametre olarak gönderilmesini istiyorsak fonksiyon o değişkenin adresini vermemiz gerekiyor. Buna "Call by Reference" deniyor.
Call by Reference ile aslında fonksiyona değişkenin adresini veriyoruz. Fonksiyon değişkenin nerede tutulduğunu bildiğini için o noktaya erişip değeri değiştirebiliyor/okuyabiliyor. Bir örnek aşağıda mevcuttur. Fonksiyon içerisinden değere erişirken dereference operatörünü kullanmayı unutmayın. (*) ve fonksiyon adresi verirken adres işaretini kullanmayı da unutmayın. (&)
#include <stdio.h>
void test_func(int* data)
{
*data = 7;
}
int main()
{
int value = 3;
printf("Value : %d\n", value);
test_func(&value);
printf("Value : %d\n", value);
return 0;
}
Şimdi fonksiyonlar ve dizilere birer örnek verelim. Fonksiyonlara parametre geçerken ister pointer ister dizi gösterimini kullanın. Bu yapacağınız çalışma ile alakalı bir durum. Dizi üzerinden gidersek bir fonksiyona şu şekilde dizi parametresi geçilebilir.
int myArray[4] = {2, 0, 2, 0};
print_array(myArray, 4);
Burada yaptığınız şey nedir biliyor musunuz ? myArray diyerek aslında dizini başlangıç adresinin verirsiniz. İlk elemanı işaret edersiniz. Şu iki satır arasında hiçbir fark yoktur.
print_array(myArray, 4);
print_array(&myArray[0], 4);
Bu iki kod satırında da aynı işi yaparız. Birisi direk olarak başlangıç adresini işaret eder. Diğeri ise 0. elemanın bulunduğu adresi verir. Yani ikisi de en başta bulunan adresi gösterir.
Fonksiyon ve dizi ise şu şekilde kullanılır.
void print_array(int data[], int len)
{
int i;
for (i = 0; i < len; i++)
printf("%d", data[i]);
printf("\n");
}
Gördüğünüz üzere adres elimizde olduğu için istediğimiz elemana gönül rahatlı ile ulaşabiliyoruz. Eğer bir çılgınlık yapmak istersek şu şekilde bir kullanımda önceden anlattığımız üzere geçerlidir.
void print_array(int data[], int len)
{
int i;
for (i = 0; i < len; i++)
printf("%d", *(data + i));
printf("\n");
}
Bunu dizi olarak değilde pointer olarak da yapabilir. Sonuçta adres üzerinden çalışıyoruz.
void print_array(int* data, int len)
{
int i;
for (i = 0; i < len; i++)
printf("%d", *(data + i));
printf("\n");
}
Son olarak stringleri inceleyelim. String bahsettiğimiz gibi özel bir veri tipi olarak C içerisinde bulunmaz. Katarlar konu altında araştırma yaparsanız string dene şeyi aslında bir diziden/işaretçiden ibaret olduğunu göreceksiniz. Buna ek olarak bir string en son eleman olarak null karakteri barındırır. Bu bir dizi ile string arasındaki en belirgin farktır. Evet işte bu bir dizi değil, bir string! diyebilmek için null karakteri gerekir.
O halde bir dizi kullanarak özel bir string tanımlayalım.
int myString[5] = {'I', 'C', 'T', 'R', '\0'};
Manuel olarak sonuna bir null ekledim. Ohalde bu string'İ ekrana basarken null görene kadar devam etmek yeterli.
void print_str_like_array(char str[])
{
int i;
for (i = 0; str[i] != '\0'; i++)
printf("%c", str[i]));
}
Dikkat ettiyseniz burada eleman sayısını vermedim. Çünkü eleman sayısı yerine string'in sonuna geldiğimi belirten bir karakter zaten mevcut.
Şimdi bunu pointer gösterimi ile yapalım.
void print_str_like_array(char* str)
{
while(*str != '\0')
{
printf("%c", *str);
str++;
}
}
Gördüğünüz gibi null değere gelene kadar karakterleri basıyoruz ve her seferinde str++ diyerek işaretçiyi bir arttırıyoruz. Bu for döngülerin de yaptığımız i++ gibi düşünülebilir yada data[i] yerine *(data + i) kullanmak gibi.
Son olarak bir pointer init işlemiyle konuyu tamamlayalım.
#include <stdio.h>
void print_str_like_array(char* str)
{
while(*str != '\0')
{
printf("%c", *str);
str++;
}
}
int main()
{
char *char_ptr = "ICTR"; // null karakteri nerede ? Ödev !
print_str_like_array(char_ptr);
return 0;
}
Gördüğünüz gibi pointer kavramının sadece bir adresten ibaret olması her şeyi özetliyor. Diziler ve pointer yapılarını daha bir çok farklı şekilde bir arada görebilirsiniz. String denen kavramın ise bir dizinin sonuna yerleştirilmiş null karakterinden ibaret olması konuyu özetliyor. Geriye değer döndürme konularını da bir blog yazısında toparlamak daha iyi olacaktır. Burası için oldukça uzun bir yazı oldu.
İyi çalışmalar.