C++ References

  • C dilinde kullandığımız pointerlar konusu C++ dilinde yerini referanslara bırakıyor. Ama C++ içinde de pointerları kullanabiliyorsunuz. Referanslar programcıya daha kolay bir kullanım sunuyor. Kullanıdığımız referanslar sadece notasyonel bir hile, arka planda aslında pointerlar var.

  • Referans makina ile ilgili bir kavram değil. Makinada yine herşey adresler.

  • & bu bir declarator, adres operatörle karıştırmamak gerekiyor. Çünkü bu tanımda kullanılıyor.

  • Referansların Pointerlardan tek farkı seviyesi yükseltilmiş.

  • İlk değer vermeden referans oluşturmak sentaks hatası

#include <iostream>
int main() {

int &kerem;
// Declaration of reference variable 'kerem' requires an initializer

return 0;
}
#include <iostream>
int main() {

int x = 10;
int y = 30;

int &r = x; // artık r heryerde x'in yerine geçiyor

r = 45;
std::cout << "x -> " << x << std::endl; // x -> 45

r = y;
std::cout << "x -> " << x << std::endl; // x -> 30

return 0;
}

  • Aslında ben r dediğimde *ptr gibi bir yapı kullanıyorum ama bu artık derleyiciyi ilgilendiren kısım. Bizim işimizi tamamen kolaylaştıran bir yapı.
#include <iostream>
int main() {

int x = 10;

int &r1 = x; // r1 x demek
int &r2 = r1; // r2 de x demek
// pointer to pointer DEGIL

r1++;
r2++;

std::cout << x << std::endl; // 12

return 0;
}

  • C++11 ile gelen yeni araçlarla birlikte tür kontrolü çok daha fazla artmıştır.
#include <iostream>
int main() {

int x = 10;

int &r = x; // r demek = x demek
int *ptr = &r; // ptr'ye x'in adresini atadim
// pointer to pointer var fakat REFERANS to REFERANS YOK

*ptr = 99;

std::cout << "x -> " << x << std::endl; // x -> 99

r++;
std::cout << "x -> " << x << std::endl; // x -> 100

return 0;
}

  • Referansları kullanma amacımız temel olarak call by reference yapmak.

  • C dilinde call by reference yapmanın tek yolu var, adresini fonksiyona göndermek.(fonksiyonun parametresi pointer olması gerekir)

  • C++ dilinde buna ikinci bir yöntem geliyor.

#include <iostream>
void gf(int &r) //argumanlar parametre degiskenlerini initialize eder
{
r = -1;
}
void func(int *ptr) // c dilinde CALL BY REF yapmak icin pointer gerekiyor
{
*ptr = 99;
}
void foo(int a)
{
a = 33;
}
int main() {

int x = 10;

foo(x);
std::cout << "x -> " << x << std::endl; // x -> 10

func(&x);
std::cout << "x -> " << x << std::endl; // x -> 99

gf(x);
// C de CALL BY VALUE olacakti.(Fonksiyon koduna bile bakmaya gerek yok)
// C++ de kesin olarak fonksiyon koduna bakilmali. Bu bir yazim kolayligi

return 0;
}

  • Soru: C++ dilinde biri *ptr ile yazılmış bir fonksiyon var. Biri de &r parametresiyle yazılmış bir fonksiyon var. Hangisi daha verimli çalışır?

  • Burada pointer ile referans arasında ki ilişki mantık anlaşılmış mı açısından güzel bir soru. Her iki semantik ile birer swap fonksiyonu yazalım.

#include <iostream>

// referans semantigi kullanalim
void swap_r(int &r1, int &r2)
{
int temp{r1}; // uniform initializer
r1 = r2;
r2 = temp;
}

// pointer semantigi kullanalim
void swap_p(int *p1, int *p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}

int main() {

int x = 11;
int y = 33;

swap_p(&x, &y);
std::cout << x << " " << y << std::endl; // 33 11

swap_r(x, y);
std::cout << x << " " << y << std::endl; // 11 33

return 0;
}

Cevap: Hiçbir farkı yok, ikisinde de aynı makina kodunu üretir. Kullanabildiğimiz heryerde referans kullanacağız, mecbur olduğumuz birkaç senaryo da pointer kullanacağız.

  • Referanslar kendisi const olan bir pointerın seviyesi yükseltilmiş halidir. Burayı anlamak çok önemli ve scope boyunca referans gösterdiği nesneyi temsil eder, başka bir nesneyi gösteremez. Yani referanslara ilk değer vermek zorunlu derken arka plan da const oldukları içindir.
#include <iostream>
int main() {

const int x = 10; // const semantigi

int *ptr = &x;
// Cannot initialize a variable of type 'int *'
// with an rvalue of type 'const int *'

return 0;
}
#include <iostream>
int main() {

const int x = 10; // const semantigi

int &r = x; // Binding value of type 'const int' to reference to type
// 'int' drops 'const' qualifier
// const T x turunden T x turune donusum YOK

const int &r = x; // GECERLI

return 0;
}
#include <iostream>

void func(int &r); // sabit gonderemeyiz
void foo(const int &r); // okuma yapacagimizi belirttik - const

int main() {

func(10); // GECERSIZ

foo(10); // GECERLI

return 0;
}
  • Böyle bir durumda func() fonksiyonunu sabit ile çağıramayız ancak nesne ile çağırabiliriz.

  • Ama foo() gibi bir fonksiyon gördüğümüzde buna sabitte gönderebiliriz.

void func(const double &r);
// sabit gonderebildigim gibi farkli turde bir nesnede gonderebilirim
// T &r yerine const T &r
  • Burada ki hikaye şöyle const bir referasa sabitle ilk değer verdiğimizde derleyici arka planda geçici bir nesne oluşturuyor.

  • Geçici nesneler bu dilde const kabul ediliyor.

ADRESE GERİ DÖNEN FONKSİYONLAR (C Pointer Semantiğinde)

  • Yapılabilecek en kaba hatalardan biri otomatik ömürlü nesnenin adresi ile geri dönmek, otomatik ömürlü demek bir fonksiyonun çalışmasının bitmesiyle nesnenin bellekten silinmesidir.

Adrese geri dönen fonksiyonlar run time hatası olmadan neye geri dönebilir?

  1. Static bir nesnenin adresi ile geri dönebilirler, programın başından sonuna bellekte duruyor.

    Static nesneler neler?

    • (static keywordü alan, global değişkenler, stringler)
      Kim beni çağırırsa çağırsın ona aynı değeri geri dönüyorum demek.
  2. Dinamik ömürlü nesne adresi,
  3. Kendi adresiyle geri dönen (sizden alıyor size veriyor)
int &func(void);
func() = 45; // C dilinde bu olamaz
// C dilinde boyle yapabiliriz
int *func(void);

int main() {

*func() = 45;

return 0;
}
// C++ dilinde boyle yapabiliriz
#include <iostream>

int &func(void);

int main() {

func() = 45;

return 0;
}
#include <iostream>
int g = 100;
int &func()
{
return g;
}

int main() {

std::cout << "g -> " << g << std::endl; // g -> 100

func() = 999; // g'ye atama yapmis oluruz
std::cout << "g -> " << g << std::endl; // g -> 999

++func();
std::cout << "g -> " << g << std::endl; // g -> 1000

int &r = func(); // Ilk deger vermek... r demek artik g demek
std::cout << "r -> " << r << std::endl; // r -> 1000

return 0;
}

  • Yeniden bir hatırlatma pointer to pointer var ama reference to reference yok
#include <iostream>

int main() {

int x = 10;

int &r1 = x;
int &r2 = r1; // ref to ref degil

return 0;
}
#include <iostream>

int func();

int main() {

int &&r = func(); // ref to ref DEGIL
// C++11 r value reference

return 0;
}
  • R value reference konusunu ilerde tek makale olarak anlatabiliriz.

  • Move semantiği için sağ taraf değeri uydurulmuş.

#include <iostream>

using namespace std;

ostream &func(ostream &r)
{
r << "\n ************************* \n";
return r;
}

int main() {

int x = 999;

// cout ve x referans yoluylu func fonksiyonuna gonderiliyor
func(cout) << x << endl;
// *************************
// 999

// func fonk'nun geri donus degeri referanssa aldigi nesneyi geri vericektir
// bu referansa geri donmekle yapilan bir hile

return 0;
}
  • Elemanları referans olan bir dizi diye bir kavram yok. Ama bir referans dizi yerine geçebilir.
int main() {

int a[5] = {0, 1, 2, 3, 4};
int(&r)[5] = a; // r demek = a demek
// bir diziye referans var
// elemanlari referans olan DIZI YOK

for (int k = 0; k < 5; k++)
r[k]++;
return 0;
}

POINTERLAR vs REFERANSLAR

Özetleyecek olursak;

  • Bir pointer kendisi const olmak zorunda değil, hayatı devam ettiği sürece farklı nesneleri gösterebilir.

  • Referansta böyle değil kendi const, referans hep aynı nesnenin yerine geçer.

  • Elemanların referans olan bir dizi diye birşey yok ama bir referans bir dizinin yerine geçebilir.

  • Pointer dizileri var.

#include <iostream>

int main() {

int x = 10;
int y = 22;
int *ptr = &x; // ptr'ye x'in adresi ile ilk deger verdik

*ptr = 20; // x = 20
int *&r = ptr; // r p ptr'nin yerine gecen bir isim, ptr'ye bir referans al
r = &y; // ptr'ye y'nin adresini atamak demek *r artik y
++r;

std::cout << "x -> " << x << std::endl; // x -> 20
std::cout << "y -> " << y << std::endl; // x -> 22

return 0;
}
#include <iostream>

int main() {

int x = 10;
int *ptr = &x; // ptr x'i gosteriyor
int **p = &ptr; // p ise ptr'yi gosteriyor

int **&r = p; // r demek = p demek
++**r; // r'nin yerine gecen x'i arttir

std::cout << "x -> " << x << std::endl; // x -> 11

return 0;
}
  • Referansın Referansı diye bir kavram yok.

  • C ve C++ pointer değişkenler iki konumda olabilir ya bir nesneyi gösterirler yada hiçbir şey göstermiyorlardır.

  • Bir nesneyi gösteriyorlarsa pointerın değeri o nesnenin adresi, hiçbir yeri göstermiyorsa null adresidir.