← Kursa Dön
📄 Text · 15 min

Parametreler: Değer, Referans, Pointer ile Geçirme

Bir arkadaşına fotoğraf göndermek istiyorsun. Üç yolun var: fotoğrafın kopyasını gönderirsin (o değiştirebilir ama seninkine dokunmaz), fotoğrafın bağlantısını paylaşırsın (o değiştirirse seninkisi de değişir), ya da fotoğrafın adresini not kağıdına yazıp verirsin (adresi bildiği sürece orijinale ulaşabilir). C++'ta fonksiyonlara parametre geçirme de tam olarak bu üç yolla çalışır: değer (value), referans (reference), pointer (işaretçi).

Bu derste hangi yöntemin ne zaman kullanılacağını, avantaj ve dezavantajlarını detaylıca öğreneceksin.


Pass by Value — Kopyayı Gönder

Varsayılan davranış: fonksiyona bir değişken gönderdiğinde, değişkenin kopyası oluşturulur. Fonksiyon içinde yapılan değişiklikler orijinali etkilemez.

#include <iostream>
using namespace std;

void ikiyeKatla(int x) {
    x = x * 2;
    cout << "Fonksiyon içi: " << x << endl;  // 20
}

int main() {
    int sayi = 10;
    ikiyeKatla(sayi);
    cout << "Fonksiyon dışı: " << sayi << endl;  // 10 — değişmedi!

    return 0;
}

sayi değeri 10'dur. Fonksiyona geçirildiğinde x adında bir kopya oluşur. x'i ikiye katlasak bile sayi hâlâ 10'dur.

Ne Zaman Kullanılır?

  • Orijinal değişkenin değişmemesini istediğinde

  • Küçük tipler: int, double, char, bool gibi

  • Fonksiyonun "yan etkisiz" (side-effect free) olmasını istediğinde

Dezavantajı

Büyük nesneler (string, vector, struct) kopyalandığında performans maliyeti olur. 10.000 elemanlı bir vector'ü kopyalamak yavaştır.


Pass by Reference (&) — Orijinali Gönder

Referans ile geçirme, orijinal değişkenin kendisine takma ad (alias) oluşturur. Fonksiyon içindeki değişiklikler orijinali etkiler.

#include <iostream>
using namespace std;

void ikiyeKatla(int& x) {  // & işareti — referans
    x = x * 2;
    cout << "Fonksiyon içi: " << x << endl;  // 20
}

int main() {
    int sayi = 10;
    ikiyeKatla(sayi);
    cout << "Fonksiyon dışı: " << sayi << endl;  // 20 — değişti!

    return 0;
}

int& x demek "x, gönderilen değişkenin kendisine bir takma addır" demek. x'i değiştirmek = sayi'yı değiştirmek.

Birden Fazla Değer Döndürme

Bir fonksiyon normalde tek değer döndürür. Ama referans parametreler kullanarak birden fazla değeri "dışarıya aktarabilirsin":

#include <iostream>
using namespace std;

void bolmeIslemi(int bolunen, int bolen, int& bolum, int& kalan) {
    bolum = bolunen / bolen;
    kalan = bolunen % bolen;
}

int main() {
    int bolum, kalan;
    bolmeIslemi(17, 5, bolum, kalan);

    cout << "17 / 5 = " << bolum
         << " kalan " << kalan << endl;
    // Çıktı: 17 / 5 = 3 kalan 2

    return 0;
}

swap Örneği — Klasik

İki değişkenin değerini takas etme, referansın en klasik kullanımıdır:

#include <iostream>
using namespace std;

void takas(int& a, int& b) {
    int gecici = a;
    a = b;
    b = gecici;
}

int main() {
    int x = 5, y = 10;
    cout << "Önce: x=" << x << " y=" << y << endl;

    takas(x, y);

    cout << "Sonra: x=" << x << " y=" << y << endl;
    // Çıktı: Sonra: x=10 y=5

    return 0;
}

Eğer takas fonksiyonunu değer ile (pass by value) yazsaydık, kopyalar takas olur ama orijinaller değişmezdi.

💡 İpucu: Standart kütüphanede std::swap(a, b) zaten mevcut. Ama swap mantığını anlamak referansları kavramak için çok önemli.


const Referans — Oku Ama Değiştirme

Referansın performans avantajını istiyorsun ama orijinalin değişmesini istemiyorsun. İşte const referans tam bu iş için:

#include <iostream>
#include <string>
#include <vector>
using namespace std;

// const referans — büyük nesneyi kopyalamadan, güvenle oku
void bilgiYazdir(const string& isim, const vector<int>& notlar) {
    cout << "Öğrenci: " << isim << endl;
    cout << "Notlar: ";
    for (const auto& n : notlar) {
        cout << n << " ";
    }
    cout << endl;

    // isim = "Değiştirdim";  // DERLEME HATASI — const!
}

int main() {
    string ogrenci = "Ali";
    vector<int> notlar = {85, 92, 78, 95, 88};

    bilgiYazdir(ogrenci, notlar);
    // ogrenci ve notlar değişmedi — güvende

    return 0;
}

const string& demek: "Bu string'in referansını al ama değiştirme iznim yok." Bu sayede:

  • Kopyalama maliyeti yok (referans olduğu için)

  • Güvenlik var (const olduğu için değiştirilemez)

⚠️ Dikkat: Büyük nesneleri (string, vector, map, struct vb.) fonksiyona geçirirken const& kullanmak standart uygulamadır. Değer ile geçirmek gereksiz kopyalama yapar ve performansı düşürür.


Pass by Pointer (*) — Adres Gönder

Pointer ile geçirme, değişkenin bellek adresini fonksiyona gönderir. Fonksiyon bu adresi kullanarak orijinal değişkene erişir.

#include <iostream>
using namespace std;

void ikiyeKatla(int* ptr) {  // Pointer parametre
    *ptr = *ptr * 2;         // Adresteki değeri değiştir
    cout << "Fonksiyon içi: " << *ptr << endl;
}

int main() {
    int sayi = 10;
    ikiyeKatla(&sayi);  // Adresini gönder
    cout << "Fonksiyon dışı: " << sayi << endl;  // 20

    return 0;
}

&sayi — sayi'nın adresini al. int* ptr — bu adres bir pointer'da saklanır. *ptr — pointer'ın gösterdiği değere eriş (dereference).

Pointer ve nullptr Kontrolü

Pointer'ın referanstan önemli bir farkı: null olabilir. Bu da "hiçbir yeri göstermiyor" demek. Fonksiyon içinde bunu kontrol etmelisin:

#include <iostream>
using namespace std;

void guvenliBolme(double pay, double payda, double* sonuc) {
    if (sonuc == nullptr) {
        return;  // Geçersiz pointer, bir şey yapma
    }

    if (payda == 0) {
        cout << "Hata: Sıfıra bölme!" << endl;
        return;
    }

    *sonuc = pay / payda;
}

int main() {
    double sonuc;
    guvenliBolme(10.0, 3.0, &sonuc);
    cout << "Sonuç: " << sonuc << endl;  // ~3.333

    guvenliBolme(10.0, 0.0, &sonuc);  // Hata mesajı
    guvenliBolme(10.0, 2.0, nullptr); // Sessizce döner

    return 0;
}

Pointer vs Referans

ÖzellikReferans (&)Pointer (*)
Null olabilir miHayırEvet (nullptr)
Yeniden atanabilir miHayır (hep aynı değişkeni gösterir)Evet
Söz dizimiTemiz (x = 5)Karmaşık (*ptr = 5)
Opsiyonel parametreHayırEvet (nullptr geçilebilir)

Hangi Yöntemi Ne Zaman Kullan — Karar Tablosu

DurumYöntemNeden
Küçük tip (int, double, char, bool) — okumaPass by valueKopyalama ucuz, basit
Küçük tip — değiştirmek istiyorsunPass by reference (&)Orijinali değiştirmek için
Büyük nesne (string, vector, struct) — okumaconst&Kopyalamadan güvenli okuma
Büyük nesne — değiştirmek istiyorsunPass by reference (&)Orijinali doğrudan değiştir
Parametre opsiyonel olabilir (null)Pass by pointer (*)nullptr geçirilebilir
C kütüphaneleri ile çalışmaPass by pointer (*)C API'leri pointer bekler

Modern C++'ta genel kural:

  1. Küçük tipler: Değer ile geç

  2. Büyük tipler, sadece okuma: const& ile geç

  3. Değiştirmek istiyorsun: & ile geç

  4. Opsiyonel veya null olabilir: Pointer veya std::optional kullan

// Modern C++ fonksiyon imzaları
int kare(int x);                               // Küçük tip, değer
double ortalama(const vector<int>& notlar);     // Büyük tip, okuma
void sirala(vector<int>& dizi);                 // Büyük tip, değiştir
void kaydet(const string& dosya, const Data* veri);  // Opsiyonel

💡 İpucu: "Şüpheye düşersen const& kullan" iyi bir başlangıç kuralıdır. Yanlış yapma olasılığın en düşük yöntemdir.


Dizi Parametreleri

C-style diziler fonksiyonlara her zaman pointer olarak geçirilir — kopyalanmaz. Bu yüzden boyutu ayrı parametre olarak göndermen gerekir:

#include <iostream>
using namespace std;

// Diziler fonksiyonlara pointer olarak geçer
void diziYazdir(const int dizi[], int boyut) {
    for (int i = 0; i < boyut; i++) {
        cout << dizi[i] << " ";
    }
    cout << endl;
}

double ortalama(const int dizi[], int boyut) {
    int toplam = 0;
    for (int i = 0; i < boyut; i++) {
        toplam += dizi[i];
    }
    return static_cast<double>(toplam) / boyut;
}

int main() {
    int notlar[] = {80, 90, 75, 85, 95};
    int boyut = sizeof(notlar) / sizeof(notlar[0]);

    diziYazdir(notlar, boyut);
    cout << "Ortalama: " << ortalama(notlar, boyut) << endl;

    return 0;
}

Modern C++'ta std::vector veya std::array kullanmak çok daha güvenli:

#include <iostream>
#include <vector>
using namespace std;

// vector ile — boyut bilgisi nesnenin içinde
double ortalama(const vector<int>& notlar) {
    int toplam = 0;
    for (const auto& n : notlar) {
        toplam += n;
    }
    return static_cast<double>(toplam) / notlar.size();
}

int main() {
    vector<int> notlar = {80, 90, 75, 85, 95};
    cout << "Ortalama: " << ortalama(notlar) << endl;

    return 0;
}

Pratik Örnek — Öğrenci Kayıt Sistemi

Öğrendiklerimizi birleştirelim:

#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Ogrenci {
    string isim;
    vector<int> notlar;
    double ortalama;
};

// const& — sadece okuma
void bilgiYazdir(const Ogrenci& ogr) {
    cout << ogr.isim << " — Ort: " << ogr.ortalama << endl;
}

// & — değiştirme (ortalamayı hesapla)
void ortalamaHesapla(Ogrenci& ogr) {
    if (ogr.notlar.empty()) {
        ogr.ortalama = 0;
        return;
    }

    int toplam = 0;
    for (const auto& n : ogr.notlar) {
        toplam += n;
    }
    ogr.ortalama = static_cast<double>(toplam) / ogr.notlar.size();
}

// Değer ile — küçük tip
char notHarfi(double ortalama) {
    if (ortalama >= 90) return 'A';
    if (ortalama >= 80) return 'B';
    if (ortalama >= 70) return 'C';
    if (ortalama >= 60) return 'D';
    return 'F';
}

int main() {
    Ogrenci ali = {"Ali", {85, 92, 78, 95}, 0};
    ortalamaHesapla(ali);
    bilgiYazdir(ali);
    cout << "Not: " << notHarfi(ali.ortalama) << endl;

    return 0;
}

Her fonksiyon farklı bir geçirme yöntemi kullanıyor:

  • bilgiYazdir: const& — okuma amaçlı, struct kopyalanmaz

  • ortalamaHesapla: & — struct'ı değiştirir

  • notHarfi: değer ile — küçük tip (double)


Özet

  • Pass by value değişkenin kopyasını gönderir — orijinal değişmez, küçük tipler için ideal

  • Pass by reference (&) orijinale takma ad oluşturur — değişiklikler orijinali etkiler, swap gibi işler için gerekli

  • const referans (const&) büyük nesneleri kopyalamadan güvenle okur — modern C++'ın standart yöntemi

  • Pass by pointer (*) adres gönderir — null olabilir, C API'leri ile uyumlu, opsiyonel parametreler için kullanışlı

  • Karar kuralı: Küçük tip → değer, büyük tip okuma → const&, değiştirme → &, opsiyonel → pointer

  • Şüpheye düştüğünde const& kullan — en güvenli ve en performanslı seçimdir