C++ References

12 minute read
  • 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ı
 1#include <iostream>
 2int main() {
 3
 4    int &kerem;
 5    // Declaration of reference variable 'kerem' requires an initializer
 6
 7    return 0;
 8}
 9#include <iostream>
10int main() {
11
12    int x = 10;
13    int y = 30;
14
15    int &r = x; // artık r heryerde x'in yerine geçiyor
16
17    r = 45;
18    std::cout << "x -> " << x << std::endl;  // x -> 45
19
20    r = y;
21    std::cout << "x -> " << x << std::endl; // x -> 30
22
23    return 0;
24}
  • 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ı.
 1#include <iostream>
 2int main() {
 3
 4    int x = 10;
 5
 6    int &r1 = x;  // r1 x demek
 7    int &r2 = r1; // r2 de x demek
 8    // pointer to pointer DEGIL
 9
10    r1++;
11    r2++;
12
13    std::cout << x << std::endl; // 12
14
15    return 0;
16}
  • C++11 ile gelen yeni araçlarla birlikte tür kontrolü çok daha fazla artmıştır.
 1#include <iostream>
 2int main() {
 3
 4    int x = 10;
 5
 6    int &r = x;  // r demek = x demek
 7    int *ptr = &r; // ptr'ye x'in adresini atadim
 8    // pointer to pointer var fakat REFERANS to REFERANS YOK
 9
10    *ptr = 99;
11
12    std::cout << "x -> " << x << std::endl; // x -> 99
13
14    r++;
15    std::cout << "x -> " << x << std::endl; // x -> 100
16
17    return 0;
18}
  • 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.
 1#include <iostream>
 2void gf(int &r) //argumanlar parametre degiskenlerini initialize eder
 3{
 4    r = -1;
 5}
 6void func(int *ptr) // c dilinde CALL BY REF yapmak icin pointer gerekiyor
 7{
 8    *ptr = 99;
 9}
10void foo(int a)
11{
12    a = 33;
13}
14int main() {
15
16    int x = 10;
17
18    foo(x);
19    std::cout << "x -> " << x << std::endl; // x -> 10
20
21    func(&x);
22    std::cout << "x -> " << x << std::endl; // x -> 99
23
24    gf(x);
25    // C de CALL BY VALUE olacakti.(Fonksiyon koduna bile bakmaya gerek yok)
26    // C++ de kesin olarak fonksiyon koduna bakilmali. Bu bir yazim kolayligi
27
28    return 0;
29}

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.
 1#include <iostream>
 2
 3// referans semantigi kullanalim
 4void swap_r(int &r1, int &r2)
 5{
 6    int temp{r1}; // uniform initializer
 7    r1 = r2;
 8    r2 = temp;
 9}
10
11// pointer semantigi kullanalim
12void swap_p(int *p1, int *p2)
13{
14    int temp = *p1;
15    *p1 = *p2;
16    *p2 = temp;
17}
18
19int main() {
20
21    int x = 11;
22    int y = 33;
23
24    swap_p(&x, &y);
25    std::cout << x << " " << y << std::endl;   // 33 11
26
27    swap_r(x, y);
28    std::cout << x << " " << y << std::endl;   // 11 33
29
30    return 0;
31}

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.
 1#include <iostream>
 2int main() {
 3
 4    const int x = 10; // const semantigi
 5
 6    int *ptr = &x;
 7    // Cannot initialize a variable of type 'int *'
 8    // with an rvalue of type 'const int *'
 9
10    return 0;
11}
12#include <iostream>
13int main() {
14
15    const int x = 10; // const semantigi
16
17    int &r = x; // Binding value of type 'const int' to reference to type
18    // 'int' drops 'const' qualifier
19    // const T x turunden T x turune donusum YOK
20
21    const int &r = x; // GECERLI
22
23    return 0;
24}
25#include <iostream>
26
27void func(int &r);  // sabit gonderemeyiz
28void foo(const int &r); // okuma yapacagimizi belirttik - const
29
30int main() {
31
32    func(10); // GECERSIZ
33
34    foo(10); // GECERLI
35
36    return 0;
37}
  • 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.
1void func(const double &r);
2// sabit gonderebildigim gibi farkli turde bir nesnede gonderebilirim
3// 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 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)

 1int &func(void);
 2func() = 45; // C dilinde bu olamaz
 3// C dilinde boyle yapabiliriz
 4int *func(void);
 5
 6int main() {
 7
 8    *func() = 45;
 9
10    return 0;
11}
12// C++ dilinde boyle yapabiliriz
13#include <iostream>
14
15int &func(void);
16
17int main() {
18
19    func() = 45;
20
21    return 0;
22}
23#include <iostream>
24int g = 100;
25int &func()
26{
27    return g;
28}
29
30int main() {
31
32    std::cout << "g -> " << g << std::endl; // g -> 100
33
34    func() = 999; // g'ye atama yapmis oluruz
35    std::cout << "g -> " << g << std::endl; // g -> 999
36
37    ++func();
38    std::cout << "g -> " << g << std::endl; // g -> 1000
39
40    int &r = func(); // Ilk deger vermek... r demek artik g demek
41    std::cout << "r -> " << r << std::endl;  // r -> 1000
42
43    return 0;
44}
  • Yeniden bir hatırlatma pointer to pointer var ama reference to reference yok
 1#include <iostream>
 2
 3int main() {
 4
 5    int x = 10;
 6
 7    int &r1 = x;
 8    int &r2 = r1;  // ref to ref degil
 9
10    return 0;
11}
12#include <iostream>
13
14int func();
15
16int main() {
17
18    int &&r = func(); // ref to ref DEGIL
19    // C++11 r value reference
20
21    return 0;
22}
  • R value reference konusunu ilerde tek makale olarak anlatabiliriz.
  • Move semantiği için sağ taraf değeri uydurulmuş.
 1#include <iostream>
 2
 3using namespace std;
 4
 5ostream &func(ostream &r)
 6{
 7    r << "\n ************************* \n";
 8    return r;
 9}
10
11int main() {
12
13    int x = 999;
14
15    // cout ve x referans yoluylu func fonksiyonuna gonderiliyor
16    func(cout) << x << endl;
17    //  *************************
18    // 999
19
20    // func fonk'nun geri donus degeri referanssa aldigi nesneyi geri vericektir
21    // bu referansa geri donmekle yapilan bir hile
22
23    return 0;
24}
  • Elemanları referans olan bir dizi diye bir kavram yok. Ama bir referans dizi yerine geçebilir
 1int main() {
 2
 3    int a[5] = {0, 1, 2, 3, 4};
 4    int(&r)[5] = a;  // r demek = a demek
 5    // bir diziye referans var
 6    // elemanlari referans olan DIZI YOK
 7
 8    for (int k = 0; k < 5; k++)
 9        r[k]++;
10    return 0;
11}

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.
 1#include <iostream>
 2
 3int main() {
 4
 5    int x = 10;
 6    int y = 22;
 7    int *ptr = &x;  // ptr'ye x'in adresi ile ilk deger verdik
 8
 9    *ptr = 20;  // x = 20
10    int *&r = ptr;  // r p ptr'nin yerine gecen bir isim, ptr'ye bir referans al
11    r = &y;  // ptr'ye y'nin adresini atamak demek    *r artik y
12    ++r;
13
14    std::cout << "x -> " << x << std::endl;  // x -> 20
15    std::cout << "y -> " << y << std::endl;  // x -> 22
16
17    return 0;
18}
19#include <iostream>
20
21int main() {
22
23    int x = 10;
24    int *ptr = &x;  // ptr x'i gosteriyor
25    int **p = &ptr;  // p ise ptr'yi gosteriyor
26
27    int **&r = p;  // r demek = p demek
28    ++**r;  // r'nin yerine gecen x'i arttir
29
30    std::cout << "x -> " << x << std::endl;  // x -> 11
31
32    return 0;
33}
  • 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.