sayfa başı

19 Mart 2019 Salı

C++ Referanslar

Referanslar


C++ bazı yönleri ile göstergelere(pointer) benzeyen başka bir değişken tipini içerir. Bir değişkeni tanımlanırken & sembolünü kullanılırsa referans türünden bir değişken tanımlanmış olur. Aşağıda referans tanımlamaya bir örnek verilmiştir.
int x;
int& r = x;
Yukarıdaki örnekte referansa ilk değer verilmesi keyfi değildir. Referanslar tanımlarken aynı türden bir değişken ile ilk değer verilerek tanımlanmak zorundadır. Göstergelerde bu zorunluluk söz konusu değildir. Bu yüzden göstergeler ile referansları birbirinden ayıran en büyük özelliklerden biri budur. Örneğin aşağıdaki kod derleme anında hata verir.
int a = 5;
int &r;
Derleyicinin vereceği hata: 'r' declared as reference but not initialized
Referans tanımlanmasında ilk değerini aldığı değişkenin yerine geçer. Bu  yüzden referansa bir nevi takma ad görevini görür.
int x;
int& r = x;
x değişkenine artık r referansı ile de ulaşılabilir. Referansın tanımlanmasından sonra x ve r birbirine bağlanmıştır. Bu bağ r referansının tüm ömrü boyunca devam edecektir. Yani referansa bundan sonra başka bir değişken atanamayacak. Bu durum referanslar ile göstergeleri birbirinden ayıran diğer önemli özellikten biridir. Aşağıdaki örnek kod bloğunda r referansı tanımlanırken a değişkeni ile ilk değer alıyor ve bundan sonraki r referansına atamalar tıpkı a değişkenine yapılan atamalar gibi sadece değer atamasıdır.
#include <iostream>
 
int main()
{
   int a = 5, b = 4;
   int &r = a; // r referansı tanımlanıyor
   std::cout << "a: " << a << " r: " << r << std::endl; // a'nın değeri ve r'nin değeri gösteriliyor
   
   r = b; // r referansına b değişkeninin değeri atanıyor. bu a = b; işlemi ile aynıdır.
   
   std::cout << "a: " << a << " r: " << r << std::endl; // a'nın değeri ve r'nin değeri gösteriliyor
}
r = b; atamasının farkı anlaşıldıktan sonra çıktı olarak a: 5 r: 5 ve a: 4 r: 4 sonuçlarını görürüz. Görüldüğü gibi r hem a değişkenin içeriğini tutuyor ve r yapılan atamalar a ya yapılmış oluyor. Bu yönüyle referanslar göstergelere benzer.

Referansların Kullanımı

Referanslarını nasıl tanımlanması gerektiğini gördük. Şimdi referansların kullanımına değinelim. Referanslar tanımlandığı değişkenin yerine geçtikleri için tıpkı o değişken gibi kullanılabilir. Değer atama, adresini alma ya da diğer işlem operatörleri ile kolayca kullanılabilir. Göstergelerden farklı olarak referansların kullanımında herhangi bir operatör yardımı gerekmez. Ama göstergelerde içerik ataması yapılırken * operatör kullanılması gerekiyor. Örneğin p bir gesterge olsun(int *p;) ++p; operatörün adresini arttırırken ++(*p) ise göstergenin gösterdiği değişkenin değerini bir arttırıyor. Kimi zaman bu durumlar göstergelerde zorluklara ve hatalara yol açıyor. Fakat referanslarda kullanım daha kolay. Aşağıda buna ait bir örnek görülmektedir. Örnekte referansın kullanımına dikkat edilerek incelenmelidir.
#include <iostream>
 
int main()
{
   int a = 5, b = 4;
   int &r = a; // r referansı tanımlanıyor
   int *p; // p pointer tanımlanıyor
   int *p2; // p2 pointer tanımlanıyor
   
   r = b; // referansa b değeri ya ni a ya b değeri atanıyor.
   
   p = &a; //p göstergesine a nın adresi atanıyor
   
   /*p2 göstergesine r referansının tanımlandığı değişkenin adresi atanıyor.
     refereans a değişkenine tanımlandığı için aslında a'nın adresi atanıyor
     Adres kullanımında da tıpkı normal değişkenler gibi kullanıldı*/
   p2 = &r;    

  return 0;
}

Const Referanslar

Referanslar tanımlandığı değişkenin yerine geçip nasıl atama ya da diğer operatörlerle rahatça kullanıyorsak tıpkı değişkenler gibi const olarak da tanımlanabilir. Const olarak tanımlanmasının kullanım yerine göre farklı amacı ve faydaları olabilir. Const kullanım örneklerine geçmeden önce aslında referansların bir const pointer olduğunu görmemiz gerekir. Referanslar adresi değiştirilemez bir const pointer gibidir.
  int a = 5, b = 4;
   int &r = a; // r referansı tanımlanıyor
   int * const p = &a; // const p pointer tanımlanıyor
   /* hatalı, kendisi const olarak tanımlandığı
      için adresi p pointer yeni adres atanamaz     
    */
   p = &b;

Referansa bağlanan değişken tıpkı yukarıdaki örnekteki const pointer gibi değiştirilemiyordu. Referansı const olarak tanımlarsak bu sefer de değer ataması işlemi yapamayız. Bu durum referansı sadece okunabilir bir değişken yapar. Aşağıdaki kodu derlemek istediğimizde derleyiciden hata alırız.
#include <iostream>
 
int main()
{
   int a = 5, b = 4;
   const int &r = a; // r referansı tanımlanıyor

   r = b; // hatalı. const referans, değer atanamaz

   b = r; // referans sadece okundu.

   return 0;
}
Alınan hata: [Error] assignment of read-only reference 'r'
Bu örnek ile const referansların kullanım amacı ve faydası ortaya çıkmış oldu. Const referanslar sadece okuma amaçlı olarak kullanılır. Bu sayede kullanıcının referansın taşıdığı değişkenin içeriğini bozması engellenmiş olur. Hata alımı derleme zamanında rahatlıkla görülür.
Const referanslar ayrıca iki farklı şekilde de kullanılır. Bir değişmezin değerini atama ve farklı türden değişkenin değerini atama işlemlerinde de kullanılabilinir.
#include <iostream>
 
int main()
{
   double d = 35.5;
   const int &r = d;  // farklı türden değişken atanıyor
   
   const int &r2 = 66;  // sabit bir değer atanıyor.
   
   std::cout << "r: " << r << " r2: " << r2 << std::endl; // r'nın değeri ve r2'nin değeri gösteriliyor

   return 0;
}
Yukarıdaki kod örneği hata almadan derlenir ve çıktı olarak r: 35  r2: 66 üretir.

Referansların Parametre Olarak Kullanılması

Referanslar parametre olarak kullanılırken tıpkı göstergeler gibi kullanılır. Parametre değişkeni referans olan fonksiyon aynı türden bir nesne ile çağrılmalıdır. Bu kurala uyarak yapılan çağrıda referans çağrımda kullanılan nesnenin yerine geçer. Referansların özelliklerinden dolayı fonksiyona nesnenin kendisi değil adresi aktarılmış olur. Bu durum tıpkı göstergelerde olduğu gibi hız ve yer kazancı sağlar. Bu çağırma türü call by referance olarak adlandırılır. Aşağıda örnek bir kullanım gösterilmektedir.
#include <iostream>

void func (int &r)
{
   r = 66;
}
int main()
{
   int z = 35;
   
   func (z);
   
   std::cout << z;

   return 0;
}

Örnekte görüldüğü gibi parametresi referans olarak tanımlanmış func fonksiyonu çağrılırken z nesnesinin kendisi kullanılmıştır. Adres ya da herhangi bir operatör kullanılmamıştır.  func fonksiyonuna z’nın değeri değil adresi verilir. Fonksiyon içinde r artık z’nin yerine geçer. Bu durumda r’ ye atanan 66 değeri aslında z’ ye atanmış olur.
Fonksiyon çağrılırken nesnenin sadece ismi kullanıldığı için C++ dilinde bu şekilde çağrımın call by value ya da call by reference olup olmadığını anlamak için fonksiyonun bildirimini yada tanımlamasını görmek gerekir. Örneğin aşağıdaki örnekte z nesnesi iki fonksiyon için argümandır ve fonksiyonların çağrılma biçimleri aynıdır. Fakat fonksiyonların paremetreleri farklı olduğu için işlevleri farklıdır.
#include <iostream>

void foo(int v)
{
   v = 27;
}

void func (int &r)
{
   r = 66;
}
int main()
{
   int z = 35;
   
   foo (z);    
   func (z);
   
   std::cout << z;

   return 0;
}

Diğer bir konu ise referans parametrelerinde const kullanılması. Const referans kullanıldığında ilgili referansa herhangi bir atama yapamadığımızı hatırlamak gerekir. Hatta ata yapılmak istendiğinde hatanın derleme zamanında alındını da biliyoruz. Const referansa atama yapılamadığından bu tür parametreler fonksiyon içinde sadece okuma amaçlı kullanılır. Referans tanımlanmasında fonksiyonu kullanacak kişiye bu değişkenin sadece okuma amaçlı kullanılacağını ve değerinin değişmeyeceği bilgisini vermiş oluruz. Kendi kodlarımızı yazarken bu kurala uyarak kodumuzun okunabilirliğini arttırmış oluruz.
Yukarıda verilen bilgilerin tümü referans aktarılan sınıflar için de geçerli. Bir sınıfı bir fonksiyona argüman olarak gönderdiğimizde aslında sınıfın adresini göndermiş oluruz. Bu sayede sınıf ne kadar büyük olursa olsun yalnızca sınıfın adresi gönderilir.

Referansların Geri Dönüş Değeri Olarak Kullanılması

Bir fonksiyonun geri dönüş değeri referans olarak tanımlanabilir. Bu biçimce kullanımlarda tıpkı geri dönüş değeri gösterge olan fonksiyonlardaki gibi dikkatli olmalıyız. En çok yapılan hata yerel bir değişkeni göstergeyi geri dönüş değeri olarak kullanmaktır. Aynı tehlike referanslar için de geçerlidir. Geri dönülecek değer fonksiyon bitiminden sonra da hala aktif oluyor olması lazım. Bu duruma örnek olarak global değişkenler ya da static değişkenler verilebilir. Aşağıda örnek kullanımı gösterilmiştir.
#include <iostream>

int gValue = 66;

int & getGlobalValue (void)
{
   int &r = gValue;    
   return r;
}
int & getStaticValue(int v)
{
   static int s;
   int &r = s;

   s = v;        
   return r;
}
int main()
{
   int z;
   int p;
   
   z = getGlobalValue();
   std::cout << z <<std::endl;
   
   p = getStaticValue(z);
   std::cout << p <<std::endl;
   
   getGlobalValue() = getStaticValue(0);
   
   std::cout << gValue <<std::endl;

   return 0;
}
Yukarıdaki örnekte özellikle getGlobalValue() = getStaticValue(0); satırını anlamadan geçmeyin. Referanslar bir değişkenin yerine geçtiğini unutmayın.
Kod yazarken referansmı yoksa gösterge mi kullanılmalı ikilemine girmemek için aklınızda şu kuralı tutun: Referans kullanımı mümkün ise referans kullan, aksi durumda gösterge kullan. Yani kısaca gösterge kullanmanın çok fazla bir artısı yok ise hiç düşünmeden referans kullanın.

İyi Çalışmalar. 22.03.2019-İzmir

1 yorum:

Son Ütücü