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,boolgibiFonksiyonun "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
| Özellik | Referans (&) | Pointer (*) |
|---|---|---|
| Null olabilir mi | Hayır | Evet (nullptr) |
| Yeniden atanabilir mi | Hayır (hep aynı değişkeni gösterir) | Evet |
| Söz dizimi | Temiz (x = 5) | Karmaşık (*ptr = 5) |
| Opsiyonel parametre | Hayır | Evet (nullptr geçilebilir) |
Hangi Yöntemi Ne Zaman Kullan — Karar Tablosu
| Durum | Yöntem | Neden |
|---|---|---|
| Küçük tip (int, double, char, bool) — okuma | Pass by value | Kopyalama ucuz, basit |
| Küçük tip — değiştirmek istiyorsun | Pass by reference (&) | Orijinali değiştirmek için |
| Büyük nesne (string, vector, struct) — okuma | const& | Kopyalamadan güvenli okuma |
| Büyük nesne — değiştirmek istiyorsun | Pass by reference (&) | Orijinali doğrudan değiştir |
| Parametre opsiyonel olabilir (null) | Pass by pointer (*) | nullptr geçirilebilir |
| C kütüphaneleri ile çalışma | Pass by pointer (*) | C API'leri pointer bekler |
Modern C++'ta genel kural:
Küçük tipler: Değer ile geç
Büyük tipler, sadece okuma:
const&ile geçDeğiştirmek istiyorsun:
&ile geçOpsiyonel veya null olabilir: Pointer veya
std::optionalkullan
// 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 kopyalanmazortalamaHesapla:&— struct'ı değiştirirnotHarfi: 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 gerekliconst referans (
const&) büyük nesneleri kopyalamadan güvenle okur — modern C++'ın standart yöntemiPass 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
AI Asistan
Sorularını yanıtlamaya hazır