Temel C Programlama -33- İşaretçileri Referans Olarak Aktarmak

Buraya kadar işaretçileri sadece değişkenlerle kullandık ve aslında çok da bir şey yapmadık. Sadece size işaretçi mantığını ve işaretçinin ne olduğunu size göstermeye çalıştık. Şimdi ise işaretçilerin asıl kullanım alanlarına geçeceğiz. Basit bir konu olduğunu düşündüğüm için ilk olarak işaretçileri referans olarak aktarmaktan bahsedeceğim. Bu işaretçilerin fonksiyonlar ile kullanımına bir örnek olacaktır. C++ dilinde referans argüman adını verdiğimiz değişkenlerin adreslerinin aktarılması ve kapsam dışındaki değişkenler üzerinde işlem yapılması C dilinde yoktur. Fakat bu bizim C dilinde işaretçileri fonksiyonlarla beraber kullanamayacağımız anlamına gelmez. C++ dilinde fonksiyonlar daha özelliklidir ve işlevseldir. Çok daha sonra C++ dilini anlattığımız zamanlar bunları size açıklayacağız ve karşılaştırmasını yapacağız.

Şimdi bir program yazalım ve bu programın fonksiyonu argüman olarak işaretçi alsın. Biz ana programda bir değişkeni aktardığımız zaman fonksiyona değişkenin kopyası yerine adresi gitsin ve fonksiyon “doğrudan” değişken üzerinde değişiklik yapsın. Bunun için şöyle bir program yazalım.

#include <stdio.h>
#include <stdlib.h>
void kare_al( int *kareptr);
int main(int argc, char *argv[]) {
    int sayi;
    printf("Karesini Almak Istediginiz Sayi:");
    scanf("%i",&sayi);
    kare_al(&sayi);
    printf("\nSayinin Karesi: %i", sayi);
    
	return 0;
}

void kare_al( int *kareptr)
{
	*kareptr = *kareptr * *kareptr;
}

Bu program önceki anlattığımız programlar kadar basit değil. Çünkü işaretçiler aslında basit olsa da oldukça esnek bir kullanıma sahip olduklarından bazı yerlerde oldukça zorlayıcı olabilir. Yine de bu programı anladığınız zaman oldukça basit olacağını göreceksiniz. İyi anlamanız için bunu satır satır açıklayalım.

void kare_al( int *kareptr); Burada kare_al adında bir fonksiyon oluşturuyoruz. Bu fonksiyonun void değerini döndürdüğünü unutmayın. Yani fonksiyon değer döndürmüyor. Değer döndürmediği halde yaptığı işlemi nasıl öğreneceğiz derseniz bu fonksiyon doğrudan main kapsamı içindeki değer üzerinde işlem yapıp o değeri değiştirecek. Yani fonksiyona ne değer gidecek ne de ondan değer gelecek. Aradaki yavaşlatıcı transfer işlemlerinden kurtulmuş olacağız ve bu da programımızın performansını artıracak. Sonrasında fonksiyonun aldığı değere baktığımızda int *kareptr olarak bir işaretçi değerini aldığını görmekteyiz. Yani bu fonksiyon değer olacak değişkenin kopyasını değil bir adres değerini almakta. Biz de değişkenin adresini yolladığımızda değişkenin adres değeri fonksiyona gidecek.

kare_al(&sayi); Burada addressof (&) operatörü ile sıradan bir değişkenin adresi fonksiyona argüman olarak aktarılmıştır. Normalde fonksiyonlara değişkenlerin kendisi aktarılsa da aslında fonksiyona giden değer değişkenin değerinin kopyası olmaktadır. Yani program akışı fonksiyona gittiği zaman fonksiyon parantezlerine yazdığımız değişkenlerle hiçbir bağımız olmamaktadır. Sadece o değerlerin kopyaları elimizde olmakta ve onlar üzerinde işlem yapmaktayız. Fonksiyonların sadece bir değer döndürebildiğini unutmayın. Fonksiyona giden değerler değişkenlerin kopyası ve fonksiyondan çıkan bir değer de bir değişkene aktarılabildiği için fonksiyonların esnekliği kısıtlanmış olmaktadır. Burada fonksiyonlara değişkenlerin kendisini aktarabildiğimiz gibi fonksiyonlar birden fazla değer üzerinde işlem yapıp kayıt etme imkanına sahiptir. Bu satırda sadece &sayi diye sayi değişkeninin adresini yollamaktayız.

*kareptr = *kareptr * *kareptr; Burada dikkat ederseniz fonksiyon bloku içerisinde fonksiyon aldığı değer üzerinde işlem yapıyor ve return komutu ile herhangi bir değer geri döndürmüyor. Buna rağmen fonksiyon doğrudan sayi değişkeni üzerinde kalıcı olarak değişiklik yapmakta. Dikkat etmeni gereken bir nokta ise “*” operatörü ile işaretçinin işaret ettiği değişkenin değerine erişmektir. Eğer böyle olmasaydı ve kareptr işaretçisini düz halde yazsaydık adresleri birbirine çarpmanın hiçbir anlamı olmayacaktı! Daha sonra dizilerle beraber kullandığımız işaretçilerde aritmetik işlemlerin bir anlamı olsa da burada herhangi bir anlamı yoktur. O yüzden biz sayı değerine erişip sonra tekrar sayı değerine eriştikten sonra bu ikisini yine (*) operatörü ile birbiriyle çarpmaktayız. Burada işaretçi erişim operatörü ile çarpma işleminin operatörünün birbirinin aynısı olması biraz kafa karıştırıcıdır. Sonrasında ise atama operatörü ile *kareptr yani sayi değişkeninin değerine değerimizi atıyoruz.

Tekrar söyleyelim. Bizim burada sayı değişkenine kare_al fonksiyonu içerisinden erişme imkanımnız yoktur. C dilinde kapsamlar veri erişiminde sınırlayıcı özelliğe sahip olurlar. Burada ya global değişken tanımlayacağız bütün fonksiyonlar tarafından erişilebilecek ya da bu şekilde işaretçiler vasıtasıyla farklı bir kapsamdaki değişkenin kendisine ulaşacağız. Global değişkenleri kullanmanın hem RAM belleği aşırı derecede işgal edeceğini hem de güvenlik sorunlarına yol açabileceğini şimdiden belirtelim. Global değişkenler eğer çok fazla olursa program yazarken de hata yapma oranımızı artıracaktır. Bu şekilde daha performanslı ve ekonomik bir yöntemi görmüş olduk.

Programımız çalıştığında şu şekilde bir ekran görüntüsü verebilir.

Gömülü sistemlerde çalışacaksanız kütüphanelerdeki fonksiyonların (Mesela STM32 HAL) değişkenlerin kendisini değil de adresini aldığını göreceksiniz. Bunun neden böyle olduğunu bu dersimizde anlayabilirsiniz. Performans açısından değişkenlerin kopyalarını aktarmak yerine adreslerini aktarmak her zaman daha iyidir. Eğer bir de yapı değişkenleri üzerinde çalışıyorsanız belki onlarca farklı değişkeni aktarmanız gerekebilir. Elinizde kısıtlı bir donanım varsa gereksiz yere kopyasının RAM belleği işgal etmesini istemeyebilirsiniz.

Last updated