Adres, işaretçi (pointer) ve referans kavramlarını örneklerle öğren; swap, const referans ve nullptr.
İşaretçiler, C++'ın "korkulan" konusudur ama aslında basit bir fikre dayanır: her değişkenin bellekte bir adresi vardır ve bu adresi tutan değişkenler yapabiliriz. Bu ders, hem işaretçilerin hem de daha güvenli kardeşleri olan referansların temelini atar.
Adres ve işaretçi
&x: x değişkeninin adresini verir.int* p: "int'e işaret eden işaretçi" tanımlar.*p: p'nin gösterdiği yerdeki değere ulaşır (dereference).nullptr: "hiçbir yere işaret etmiyorum" anlamına gelen güvenli boş değer.
Referans
Referans, mevcut bir değişkenin takma adıdır. int& r = x; yazdıktan sonra r ile x aynı kutudur. Referans tanımlanırken bir değişkene bağlanmak zorundadır ve sonradan başka bir değişkene bağlanamaz; nullptr olamaz. Bu yüzden işaretçiden daha güvenlidir.
#include <iostream>
#include <vector>
void degistir(int& a, int& b) { // referansla: orijinaller degisir
int gecici = a;
a = b;
b = gecici;
}
double ortalama(const std::vector<double>& v) { // kopyalamadan, degistirmeden oku
double t = 0.0;
for (double x : v) t += x;
return t / v.size();
}
void sifirla(double* p) { // isaretciyle: nullptr kontrolu sart
if (p != nullptr) *p = 0.0;
}
int main() {
int x = 10;
int* p = &x;
*p = 20; // p'nin gosterdigi yere yaz -> x = 20
int& r = x;
r++; // r, x'in takma adi -> x = 21
std::cout << "x = " << x << '\n';
int a = 3, b = 7;
degistir(a, b);
std::cout << "a=" << a << " b=" << b << '\n';
std::vector<double> v = {2.0, 4.0, 6.0};
std::cout << "ortalama = " << ortalama(v) << '\n';
double yuk = 5.5;
sifirla(&yuk);
std::cout << "yuk = " << yuk << '\n';
int dizi[3] = {10, 20, 30};
int* q = dizi; // dizinin ilk elemanina isaret eder
std::cout << *(q + 1) << ' ' << q[2] << '\n';
return 0;
}
x = 21
a=7 b=3
ortalama = 4
yuk = 0
20 30
Kodda neler oldu?
Referansla geçirme
Önceki derste parametrelerin kopyalandığını söylemiştik. degistir(int& a, int& b) fonksiyonunda ise & sayesinde orijinal değişkenler değiştirilir; bu yüzden a ve b gerçekten yer değiştirdi. Değiştirmeyecek ama büyük bir nesneyi geçireceksen const referans kullan: kopyalama maliyeti olmaz, fonksiyon da değiştiremez. ortalama(const std::vector<double>& v) bunun örneğidir.
İşaretçiyle geçirme
sifirla(&yuk) çağrısında adresi veriyoruz. İşaretçi boş olabileceği için fonksiyon önce nullptr kontrolü yapıyor. Yeni yazdığın kodda mümkünse referans, "değer olmayabilir" durumlarında işaretçi tercih et.
Dizi ve işaretçi ilişkisi
Dizi adı, ilk elemanının adresine dönüşebilir. q + 1 bir sonraki int'e (4 bayt ileri) gider; *(q + 1) onun değerini verir ve q[1] ile aynıdır. Buna işaretçi aritmetiği denir.
Gömülü sistemlerde donanım yazmaçlarına (register) erişim işaretçilerle yapılır. Örneğin bir port yazmacı bellekte sabit bir adreste durur ve şöyle gösterilir: volatile std::uint32_t* const port = reinterpret_cast<volatile std::uint32_t*>(0x40020014);. volatile derleyiciye "bu değer donanım tarafından değişebilir, optimize etme" der. Bu, C++ ile donanıma yazmanın temelidir.
Tehlikeli durumlar: (1) Başlangıç değeri verilmemiş işaretçiyi kullanmak, (2) nullptr'ı dereference etmek, (3) artık yaşamayan bir yerel değişkenin adresini tutmak (dangling pointer), (4) dizi sınırı dışına çıkmak. Hepsi tanımsız davranıştır: program bazen çalışır, bazen çöker, bazen sessizce yanlış sonuç üretir.
İşaretçi mi, referans mı?
| Özellik | Referans | İşaretçi |
|---|---|---|
| Boş olabilir mi? | Hayır | Evet (nullptr) |
| Sonradan başka şeye bağlanır mı? | Hayır | Evet |
| Kullanım | r (doğrudan) | *p ile |
Kendin dene
- İki
doubledeğeri yer değiştirenswapfonksiyonu yaz. - Bir vektörün bütün elemanlarını 2 ile çarpan fonksiyon yaz: parametresi
std::vector<double>&olsun. - Bir
intdizisini işaretçi aritmetiğiyle (*(q + i)) yazdır. int* p = nullptr;tanımla ve dereference etmeden önce kontrol eden birifyaz.
// Mini sınav
Bir seçeneğe tıkla; doğru mu yanlış mı, anında ve nedeniyle birlikte göreceksin.
Bitirdiğinde işaretle: ilerlemen kaydolur, +20 XP kazanırsın.
İpucu: ← → tuşlarıyla dersler arasında gezinebilirsin.