Bir Şeye Atıfta Bulunmanın İki Yolu
İşaretçileri zaten biliyorsunuz: bir adres saklayan ve orada yaşayan nesneye ulaşmanızı sağlayan değişkenler. Referanslar ise C++'ın mevcut bir nesneyle dolaylı olarak çalışmanız için size sunduğu diğer araçtır. Yeterince örtüşürler ki yeni başlayanlar çoğu zaman hangisini seçeceğini bilemez; bu yüzden bu sayfa onları yan yana koyuyor.
Kısa versiyonu: bir referans bir takma addır. int& r = x; çalıştıktan sonra r, x'in kendisidir: aynı nesne, farklı isim. Bir işaretçi ise ayrı bir nesnedir ve tesadüfen başka bir nesnenin adresini tutar. Bu tek fark, diğer her şeyi belirler.
Bir Referans Bir Takma Addır
Bir referans, oluşturulduğu anda bir nesneye bağlanmalıdır ve o andan itibaren referansın her kullanımı orijinali etkiler.
Kullanım noktasında değerine erişmek için bir * veya "adresini almak" için bir & olmadığına dikkat edin; alias'ı tıpkı sıradan bir int gibi okur ve yazarsınız. int& alias'taki &, adres-alma operatörü değil, türün bir parçasıdır.
Nerede Ayrışırlar
Aşağıdaki davranışlar, her iki aracın da var olmasının tek nedenidir. Ezberlenmesi gereken tablo budur.
// reference pointer
// must be initialized? yes no (but should be)
// can be null? no yes (nullptr)
// can be reseated? no yes
// pointer arithmetic? no yes
// syntax to use it just the name *p or p->member
// taking address &ref == &original &p is the pointer's own address
Bunlardan ikisi insanları en çok şaşırtır. Birincisi, bir referans asla yeniden bağlanamaz: ona atama yapmak, bir değeri atıfta bulunulan nesnenin içine kopyalar, referansı yeni bir şeye işaret ettirmez.
Bir işaretçi ise tam tersine, herhangi bir anda başka bir yere işaret etmekte özgürdür:
İkincisi, bir referans asla yasal olarak null olamaz, oysa bir işaretçi olabilir. Bu, "değer yok" durumunun işaretçiyle ifade edilebilmesini ama referansla edilememesini sağlar; bu da sürekli yararlanacağınız bir özelliktir.
Fonksiyon Parametrelerinde Seçim Yapmak
Seçimin en çok ortaya çıktığı yer burasıdır. Bir fonksiyonun, çağıranın nesnesini okuması veya değiştirmesi gerektiğinde her ikisi de işe yarar ama farklı bir niyet bildirirler.
Referans sürümü (addTax(cart)) "hiçbir şey" ile çağrılamaz, bu yüzden fonksiyonun içinde asla null kontrolü yapmazsınız; nesnenin orada olduğu garantilidir. İşaretçi sürümü (applyDiscount(&cart)) çağrı noktasında, & aracılığıyla, argümanın değiştirilebileceğini ilan eder ve çağıranın "geçerli değil" anlamında nullptr geçirmesine izin verir. Garantisi fonksiyonunuza uyanı seçin.
Büyük türlerin salt okunur parametreleri için deyimsel seçim const T&'dir: bir kopyalamayı önler ve değiştirmeyeceğine söz verir. Değere göre geçirme ile referansa göre geçirme hakkında daha fazlası için fonksiyon parametreleri sayfasına bakın.
Basit Bir Pratik Kural
Emin olamadığınızda varsayılan olarak bir referans seçin ve yalnızca referansın sahip olmadığı bir yeteneğe ihtiyaç duyduğunuzda işaretçiye yükseltin:
- Nesne her zaman var olduğunda ve kimliği asla değişmediğinde bir referans kullanın: fonksiyon parametreleri ve takma adlar için yaygın durum.
- Şunlardan herhangi biri doğru olduğunda bir işaretçi kullanın:
- "Hiçbir şey" geçerli bir durumdur (isteğe bağlı argüman, eşleşme bulamayabilecek bir arama): bir işaretçi
nullptrolabilir. - Zaman içinde farklı nesnelere işaret etmeniz gerekir: bir işaretçi yeniden bağlanabilir.
deleteile serbest bırakacağınız heap belleğini yönetiyorsunuz veya bir diziyi işaretçi aritmetiğiyle dolaşıyorsunuz.
- "Hiçbir şey" geçerli bir durumdur (isteğe bağlı argüman, eşleşme bulamayabilecek bir arama): bir işaretçi
Bunların hiçbiri geçerli değilse, bir referans daha temiz ve daha güvenli seçimdir, çünkü derleyici sizin için "her zaman geçerli, asla yeniden bağlanmamış" kuralını uygular.
Kaçınılması Gereken Yaygın Hatalar
ref = other'ın yeniden bağlamasını beklemek. Bunun yerine atıfta bulunulan nesnenin içine bir değer atar. Referanslar ömür boyu bağlıdır; yeniden bağlamaya ihtiyacınız varsa bir işaretçi kullanın.- Yerel bir değişkene referans (veya işaretçi) döndürmek.
int& f() { int x = 5; return x; }bir sarkan referans döndürür:fdöndüğündexölür ve sonucu kullanmak tanımsız davranıştır. Aynı tuzak işaretçileri de vurur (return &x;). - "Null referans" uydurmak.
pnullptrikenint& r = *p;yazmak, güvenli bir "boş" referans değil, değerine eriştiğiniz anda tanımsız davranıştır. İsteğe bağlılığı bir işaretçi veyastd::optionalile ifade edin. - Alışkanlıktan işaretçiye sarılmak. Argüman her zaman var olacaksa ve asla yeniden bağlamayacaksanız, bir referans tüm bir null kontrolü ve çökme sınıfını ortadan kaldırır. Kullanmadığınız yeteneklerin bedelini ödemeyin.
Sonraki: Dinamik Bellek
Şimdiye kadar referans verdiğiniz veya işaret ettiğiniz her nesne yığında (stack) otomatik olarak oluşturuldu. Bir sonraki sayfa, dinamik bellek, new ve delete'i ele alır: çalışma zamanında işletim sisteminden bellek istemek, neden onu referansların değil işaretçilerin sahiplendiğini ve serbest bırakmayı unutmanın nasıl sızıntılara yol açtığını.
Sıkça Sorulan Sorular
C++'ta bir referans ile bir işaretçi arasındaki fark nedir?
Referans, mevcut bir nesnenin takma adıdır: ilklendirilmesi zorunludur, asla null olamaz ve sonradan asla başka bir nesneye atıfta bulunacak şekilde değiştirilemez. İşaretçi ise bir adres tutan ayrı bir değişkendir: null olabilir, başka bir yere işaret edecek şekilde yeniden bağlanabilir ve işaretçi aritmetiğini destekler. Referanslarda & sözdizimini, işaretçilerde */-> sözdizimini kullanın.
C++'ta referans yerine ne zaman işaretçi kullanmalıyım?
"Hiçbir şey" geçerli bir durum olduğunda (isteğe bağlı bir argüman, bulunamayan bir sonuç), zaman içinde farklı nesnelere işaret edecek şekilde yeniden bağlamanız gerektiğinde veya delete ile serbest bırakacağınız heap belleğine sahip olduğunuzda bir işaretçi kullanın. Nesne her zaman var olduğunda ve kimliği asla değişmediğinde bir referans kullanın; bu durum çoğu fonksiyon parametresini kapsar.
C++'ta bir referans null olabilir mi?
Hayır. Geçerli bir referans her zaman gerçek bir nesneye atıfta bulunur, bu yüzden onu hiçbir zaman null olup olmadığına bakarak kontrol etmezsiniz. Bir referansı, null bir işaretçinin değerine erişerek oluşturursanız (p null iken int& r = *p;) null bir referans değil, tanımsız davranış elde edersiniz. "Belki hiçbir şey" ifadesini belirtmeniz gerektiğinde bir işaretçi veya std::optional kullanın.