C ++ 'da işaretçi değişkeni ile başvuru değişkeni arasındaki farklar nelerdir?


3262

Referansların sözdizimsel şeker olduğunu biliyorum, bu yüzden kodun okunması ve yazılması daha kolay.

Peki farklılıklar nelerdir?


100
Bence nokta 2 "Bir işaretçi NULL olmasına izin verilir, ancak bir başvuru değil. Yalnızca hatalı biçimlendirilmiş kod bir NULL başvuru oluşturabilir ve davranışı tanımsız."
Mark Ransom

19
İşaretçiler yalnızca başka bir nesne türüdür ve C ++ 'da herhangi bir nesne gibi bir değişken olabilirler. Diğer yandan referanslar asla nesne değildir, sadece değişkenlerdir.
Kerrek SB

19
Bu uyarısız derler: int &x = *(int*)0;gcc. Referans gerçekten NULL değerine işaret edebilir.
Calmarius

20
başvuru değişken bir takma
addır

20
İlk cümlenin nasıl tam bir yanılgı olduğunu seviyorum. Referansların kendi semantiği vardır.
Orbit'te Hafiflik Yarışları

Yanıtlar:


1708
  1. Bir işaretçi yeniden atanabilir:

    int x = 5;
    int y = 6;
    int *p;
    p = &x;
    p = &y;
    *p = 10;
    assert(x == 5);
    assert(y == 10);
    

    Bir başvuru başlatılamıyor ve başlatma sırasında atanması gerekiyor:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. Bir işaretçinin yığın üzerinde kendi bellek adresi ve boyutu vardır (x86'da 4 bayt), ancak bir başvuru aynı bellek adresini (orijinal değişkenle) paylaşır, ancak yığın üzerinde biraz yer kaplar. Bir başvuru orijinal değişkenin kendisiyle aynı adrese sahip olduğundan, aynı değişken için başka bir ad olarak bir referans düşünmek güvenlidir. Not: Yığında ya da yığın üzerinde ne işaretçi olabilir. Bir referans. Bu ifadedeki iddiam, bir işaretçinin yığına işaret etmesi gerektiği değildir. İşaretçi, bir bellek adresini tutan bir değişkendir. Bu değişken yığın üzerindedir. Bir başvurunun yığın üzerinde kendi alanı olduğundan ve adres başvurduğu değişkenle aynı olduğundan. Yığın ve yığın hakkında daha fazla bilgi. Bu, derleyicinin size söylemeyeceği bir referansın gerçek bir adresi olduğunu gösterir.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. Ekstra dolaylı seviyeler sunan işaretçilerden göstericilere işaretçileriniz olabilir. Halbuki referanslar sadece bir seviye dolaylama sunar.

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
    
  4. Bir işaretçi nullptrdoğrudan atanabilirken referans gösterilemez. Yeterince denerseniz ve nasıl yapıldığını biliyorsanız, referansın adresini yapabilirsiniz nullptr. Benzer şekilde, yeterince denerseniz, bir işaretçiye bir referansınız olabilir ve daha sonra bu referans içerebilir nullptr.

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
    int &r = *p;  <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
    
  5. İşaretçiler bir dizi üzerinde yineleme yapabilir; Kullanabileceğiniz ++bir işaretçi işaret ettiğini sonraki öğeye gitmek için ve + 45 element gidin. Bu, işaretçinin işaret ettiği nesnenin boyutu ne olursa olsun.

  6. İşaretçinin işaret *ettiği bellek konumuna erişmek için kayıttan çıkarılması gerekirken, bir referans doğrudan kullanılabilir. Bir sınıf / yapıya işaretçi ->üyelerine erişmek için kullanır , referans ise a kullanır ..

  7. Referanslar bir diziye doldurulamaz, ancak işaretçiler olabilir (user @litb tarafından bahsedilir)

  8. Const başvuruları geçicilere bağlanabilir. İşaretçiler yapamaz (dolaylı olarak değil):

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.
    

    Bu, const&argüman listelerinde vb. Kullanım için daha güvenli hale getirir .


23
... ancak NULL adının silinmesi tanımsız. Örneğin, bir başvurunun NULL olup olmadığını test edemezsiniz (örn. & Ref == NULL).
Pat Notz

69
2 numara doğru değil . Bir referans sadece "aynı değişken için başka bir isim" değildir. Kaynaklar, göstergelere çok benzer bir şekilde fonksiyonlara aktarılabilir, sınıflarda saklanabilir vb. İşaret ettikleri değişkenlerden bağımsız olarak bulunurlar.
Derek Park

31
Brian, yığın ilgili değil. Başvurular ve işaretçiler yığın üzerinde yer almak zorunda değildir. Her ikisi de yığın üzerinde tahsis edilebilir.
Derek Park

22
Brian (bu durumda bir gösterici veya referans olarak) değişken etmez boşluk gerektirmesidir değil demek yığın alanı gerektirir. İşaretçiler ve referanslar yalnızca öbeğe işaret etmekle kalmaz aynı zamanda öbek üzerinde tahsis edilebilir .
Derek Park

38
başka bir önemli fark: referanslar bir diziye doldurulamaz
Johannes Schaub - litb

382

C ++ referansı nedir ( C programcıları için )

Bir referans , otomatik dolaylama ile sabit bir işaretçi (sabit bir değere işaretçi ile karıştırılmamalıdır!) Olarak düşünülebilir, yani derleyici *operatörü sizin için uygulayacaktır .

Tüm referanslar null olmayan bir değerle başlatılmalıdır, aksi takdirde derleme başarısız olur. Bir referansın adresini almak mümkün değildir - adres operatörü bunun yerine referanslanan değerin adresini döndürecektir - veya referanslar üzerinde aritmetik yapmak mümkün değildir.

C programcıları, C ++ referanslarından hoşlanmayabilir, çünkü dolaylı aktarım gerçekleştiğinde veya bir bağımsız değişken değer veya işaretçi tarafından işlev imzalarına bakmadan geçirilirse artık açık olmayacaktır.

C ++ programcıları, güvenli olmayan olarak kabul edildiğinden, işaretçileri kullanmaktan hoşlanmayabilir - referanslar en önemsiz durumlar dışında sabit işaretçilerden daha güvenli olmasa da, otomatik dolaylama kolaylığından yoksundur ve farklı bir anlamsal çağrışım taşırlar.

C ++ SSS bölümündeki şu ifadeyi düşünün :

Bir referans genellikle altta yatan montaj dilde bir adres kullanılarak uygulanır rağmen lütfen değil bir nesneye komik görünümlü işaretçi olarak bir referans düşünüyorum. Bir referans olan nesne. Nesneye bir işaretçi veya nesnenin bir kopyası değildir. O ise nesne.

Fakat bir referans gerçekten nesne olsaydı , sarkan referanslar nasıl olabilir? Yönetilmeyen dillerde, referansların işaretçilerden 'daha güvenli' olması imkansızdır - genellikle kapsam sınırları boyunca değerleri güvenilir bir şekilde takma adlamanın bir yolu yoktur!

C ++ referanslarını neden yararlı buluyorum

C arka plandan geliyor, C ++ referanslar biraz saçma bir kavram gibi görünebilir, ama hala mümkün işaretçiler yerine bunları kullanmak gerekir: Otomatik indirection olan kullanışlı ve ilgilenirken referanslar özellikle yararlıdır haline de ray - ancak herhangi algılanan güvenlik çünkü deyimsel kod yazmayı daha az garip hale getirdikleri için.

RAII, C ++ 'nın temel kavramlarından biridir, ancak kopyalama semantiği ile önemsiz bir şekilde etkileşime girer. Nesneleri başvuru ile iletmek, kopyalama yapılmadığı için bu sorunları önler. Dilde referanslar olmasaydı, bunun yerine daha hantal olan işaretçiler kullanmanız gerekir, böylece dil tasarımı ilkesini, en iyi uygulama çözümünün alternatiflerden daha kolay olması gerektiği ihlal edilir.


17
@kriss: Hayır, referansla otomatik bir değişken döndürerek sarkan bir referans da alabilirsiniz.
Ben Voigt

12
@kriss: Bir derleyicinin genel durumda algılaması neredeyse imkansızdır. Sınıf üyesi değişkenine başvuru döndüren bir üye işlevi düşünün: bu güvenli ve derleyici tarafından yasaklanmamalıdır. Sonra bu sınıfın otomatik bir örneğine sahip olan bir arayan, o üye işlevini çağırır ve başvuruyu döndürür. Presto: sarkan referans. Ve evet, belaya neden olacak, @kriss: bu benim açımdan. Birçok kişi, referansların işaretçiler üzerindeki bir avantajının, referansların her zaman geçerli olduğunu iddia eder, ancak bu öyle değildir.
Ben Voigt

4
@kriss: Hayır, otomatik depolama süresi nesnesine yapılan başvuru geçici bir nesneden çok farklıdır. Her neyse, ifadenize yalnızca geçersiz bir işaretçiyi kaydırarak geçersiz bir referans alabileceğiniz bir karşı örnek sağlıyordum. Christoph doğrudur - referanslar işaretçilerden daha güvenli değildir, sadece referansları kullanan bir program yine de tip güvenliğini kırabilir.
Ben Voigt

7
Referanslar bir tür işaretçi değildir. Varolan bir nesne için yeni bir addır.
catphive

18
@catphive: dil anlambilimiyle giderseniz doğru, gerçekte uygulamaya bakarsanız doğru değil; C ++, C'den çok daha 'büyülü' bir dildir ve sihri referanslardan kaldırırsanız, bir işaretçi ile sonuçlanırsınız
Christoph

191

Gerçekten bilgiçlikçili olmak istiyorsanız, bir işaretçi ile yapamayacağınız bir referansla yapabileceğiniz bir şey vardır: geçici bir nesnenin ömrünü uzatın. C ++ 'da geçici bir nesneye const başvurusu bağlarsanız, o nesnenin ömrü başvurunun ömrü haline gelir.

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

Bu örnekte s3_copy, birleştirme işleminin sonucu olan geçici nesneyi kopyalar. Oysa s3_reference özünde geçici nesne olur. Gerçekten de referansla aynı kullanım ömrüne sahip geçici bir nesneye yapılan bir referans.

Bunu onsuz denerseniz constderlemek başarısız olur. Const olmayan bir başvuruyu geçici bir nesneye bağlayamazsınız veya bu konu için adresini alamazsınız.


5
ama bunun kullanım durumu nedir?
Ahmad Mushtaq

20
Peki, s3_copy bir geçici oluşturur ve daha sonra kopyayı s3_copy içine kopyalar, s3_reference ise geçici olarak doğrudan kullanır. Daha sonra gerçekten bilgiç olmak için, derleyicinin ilk durumda kopya yapısını kullanmasına izin verilen Dönüş Değeri Optimizasyonuna bakmanız gerekir.
Matt Price

6
@digitalSurgeon: Oradaki sihir oldukça güçlü. Nesne ömrü, const &bağlanma olgusu ile uzatılır ve yalnızca referans kapsam dışına çıktığında, gerçek referans türünün yıkıcısı ( referans türüyle karşılaştırıldığında, bu bir taban olabilir) çağrılır. Bir referans olduğundan, aralarında dilimleme yapılmayacaktır.
David Rodríguez - dribeas

9
C ++ 11 Güncelleme: son cümle okumalısınız "You can not bağlamak geçici bir const olmayan lvalue referans" nedeniyle olabilir bir const olmayan bağlamak rvalue geçici referansı ve aynı ömür uzatıcı davranış vardır.
Oktalist

4
@AhmadMushtaq: Bunun temel kullanımı türetilmiş sınıflardır . Herhangi bir miras yoksa, RVO / move yapısından dolayı ucuz veya ücretsiz olacak değer semantiği de kullanabilirsiniz. Ama eğer Animal x = fast ? getHare() : getTortoise()o xzaman klasik dilimleme problemi ile karşı karşıya kalırsanız, Animal& x = ...doğru çalışır.
Arthur Tacca

128

Sözdizimsel şeker dışında bir referans bir constgöstericidir ( a'ya değilconst ). Referans değişkeni bildirirken ne anlama geldiğini belirlemelisiniz ve daha sonra değiştiremezsiniz.

Güncelleme: Şimdi biraz daha düşündüğüme göre, önemli bir fark var.

Bir sabit işaretçinin hedefi, adresi alarak ve sabit bir döküm kullanılarak değiştirilebilir.

Bir referansın hedefi UB'den kısa bir şekilde değiştirilemez.

Bu, derleyicinin bir referans üzerinde daha fazla optimizasyon yapmasına izin vermelidir.


8
Bence bu açık ara en iyi cevap. Diğerleri, farklı canavarlar gibi referanslar ve işaretçiler hakkında konuşur ve daha sonra davranışlarında nasıl farklılaştıklarını ortaya koyar. İşleri daha kolay hale getirmez imho. Referansları her zaman T* constfarklı bir sözdizimsel şeker olarak anladım (bu, kodunuzu * ve & 'dan çok fazla ortadan kaldırır).
Carlo Wood

2
"Bir sabit işaretçinin hedefi, adresi alarak ve sabit bir döküm kullanılarak değiştirilebilir." Bunu yapmak tanımsız davranıştır. Ayrıntılar için stackoverflow.com/questions/25209838/… adresine bakın.
dgnuff

1
Bir başvurunun başvurusunu veya bir sabit işaretçinin (veya herhangi bir sabit skalar) değerini değiştirmeye çalışmak eşitlik yasadışıdır. Ne yapabilirsiniz: örtük dönüşüm ile eklenen bir const kalifikasyonunu kaldırın: int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);tamam.
curiousguy

1
Buradaki fark UB ile tam anlamıyla imkansız. C ++ 'ta hangi referans noktalarında değişiklik yapabileceğiniz bir sözdizimi yoktur.

İmkansız değil, daha zor, sadece bu referansı modelleyen işaretçinin bellek alanına erişebilir ve içeriğini değiştirebilirsiniz. Bu kesinlikle yapılabilir.
Nicolas Bousquet

126

Popüler düşüncenin aksine, NULL olan bir referansa sahip olmak mümkündür.

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

Verilmiş, bir referansla yapmak çok daha zor - ama eğer yönetirseniz, saçınızı bulmaya çalışırken yırtacaksınız. Referanslar C ++ ' da doğal olarak güvenli değildir !

Teknik olarak bu geçersiz bir referans , boş bir referans değil. C ++, diğer dillerde bulabileceğiniz gibi bir kavram olarak null referansları desteklemez. Başka geçersiz referans türleri de vardır. Herhangi bir geçersiz başvuru , tıpkı geçersiz bir işaretçi kullanmak gibi tanımsız davranışların hayaletini yükseltir .

Asıl hata, bir referansa atamadan önce NULL işaretçisinin kaydının kaldırılmasıdır. Ama bu koşulda herhangi bir hata üretecek herhangi bir derleyici farkında değilim - hata kodda birlikte bir noktaya yayılır. Bu sorunu bu kadar sinsi yapan da budur. Çoğu zaman, bir NULL işaretçisi dereference, o noktada çöküyor ve bunu anlamak için çok hata ayıklama gerektirmez.

Yukarıdaki örneğim kısa ve tutarlı. İşte daha gerçek bir örnek.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

Bir null başvuru elde etmenin tek yolunun hatalı biçimlendirilmiş kod aracılığıyla olduğunu tekrarlamak istiyorum ve bir kez sahip olduğunuzda tanımsız davranış alıyorsunuz. Bu asla bir null başvuru olup olmadığını kontrol etmek mantıklı; örneğin deneyebilirsiniz, if(&bar==NULL)...ancak derleyici ifadeyi varlığını optimize edebilir! Geçerli bir başvuru hiçbir zaman NULL olamaz, bu nedenle derleyicinin görüşüne göre karşılaştırma her zaman yanlıştır ve ifcümleyi ölü kod olarak ortadan kaldırmak ücretsizdir - bu tanımlanmamış davranışın özüdür.

Beladan uzak durmanın uygun yolu, bir NULL işaretçisinin referansı oluşturmak için kayıttan çıkarılmasını önlemektir. İşte bunu başarmanın otomatik bir yolu.

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

Daha iyi yazma becerisine sahip birinden bu soruna daha eski bir bakış için Jim Hyslop ve Herb Sutter'den Null Referanslara bakın .

Bir boş göstergenin silinmesinin tehlikelerine başka bir örnek için bkz . Raymond Chen tarafından başka bir platforma kod aktarmaya çalışırken tanımlanmamış davranışı gösterme .


63
Söz konusu kod tanımsız davranış içeriyor. Teknik olarak, boş gösterici ile ayarlamak dışında hiçbir şey yapamaz ve karşılaştıramazsınız. Programınız tanımlanmamış davranışı başlattığında, büyük patrona bir demo verene kadar doğru görünmek dahil olmak üzere her şeyi yapabilir.
KeithB

9
işaretinin geçerli bir argümanı var. bir işaretçinin NULL olabileceği ve bunun için kontrol etmeniz gerektiği argümanı da gerçek değildir: eğer bir fonksiyonun NULL gerektirmediğini söylüyorsanız, arayan bunu yapmalıdır. bu yüzden arayan yapmazsa tanımsız davranışlar başlatır. tıpkı
mark'ın

13
Açıklama hatalı. Bu kod NULL olan bir başvuru oluşturabilir veya oluşturmayabilir. Davranışı tanımsız. Mükemmel geçerli bir referans oluşturabilir. Herhangi bir referans oluşturulamayabilir.
David Schwartz

7
@David Schwartz, standartlara göre işlerin nasıl çalışması gerektiğinden bahsediyor olsaydım, haklısın. Ama bundan bahsettiğim şey bu değil - Çok popüler bir derleyici ile gerçek gözlenen davranıştan ve tipik derleyiciler ve CPU mimarileri hakkındaki bilgilerime dayanarak muhtemelen ne olacağına dayalı olarak tahmin ediyorum . Referansların işaretçilerden daha üstün olduğuna inanıyorsanız ve daha güvenli olduklarını ve referansların kötü olabileceğini düşünmüyorsanız, bir gün olduğu gibi basit bir problemle karşılaşacaksınız.
Mark Ransom

6
Boş göstericinin kaydının silinmesi yanlış. Bir referans başlatmak için bile bunu yapan herhangi bir program yanlıştır. Bir işaretçiden referans başlatıyorsanız, işaretçinin geçerli olup olmadığını her zaman kontrol etmelisiniz. Bu başarılı olsa bile, temel alınan nesne, var olmayan nesneye başvuruda bulunmak için herhangi bir zamanda silinebilir, değil mi? Söylediğin iyi şeyler. Buradaki asıl sorun referansın "boşluk" için kontrol edilmesi gerekmediğini ve bir işaretçi gördüğünüzde en azından belirtilmesi gerektiğini düşünüyorum.
t0rakka

115

En önemli kısmı unuttun:

işaretçilerle ->
üye erişimi başvurularla üye erişimi.

foo.barolduğu açıkça daha üstün foo->barolduğu aynı şekilde vi olduğunu açık bir şekilde daha üstün Emacs :-)


4
@Orion Edwards> işaretçilerle üye erişimi kullanır ->> referanslarla üye erişimi kullanır. Bu% 100 doğru değil. Bir işaretçiye referans verebilirsiniz. Bu durumda, -> struct Node {Node * next; }; Önce düğüm *; // p, bir işaretçi voo foo'ya bir referanstır (Düğüm * & p) {p-> sonraki = ilk; } Düğüm * bar = yeni Düğüm; fan (bar); - OP: Değer ve değer kavramlarına aşina mısınız?

3
Akıllı İşaretçiler her ikisine de sahiptir. (akıllı işaretçi sınıfındaki yöntemler) ve -> (altta yatan türdeki yöntemler).
JBRWilkinson

1
@ user6105 Orion Edwards ifadesi aslında% 100 doğrudur. "atıfta bulunulan işaretçinin erişim üyeleri" Bir işaretçinin üyesi yoktur. İşaretçinin ->başvurduğu nesnenin üyeleri vardır ve bunlara erişim , tıpkı işaretçinin kendisinde olduğu gibi, işaretçilere referans sağlayan şeydir .
Max Truxa

1
neden bu .ve ->vi ve emacs ile ilgili bir şey var :)
artm

10
@artM - bu bir şakaydı ve muhtemelen anadili İngilizce olmayan kişiler için bir anlam ifade etmiyor. Özür dilerim. Vi'nin emac'lardan daha iyi olup olmadığını açıklamak tamamen özneldir. Bazı insanlar vi'nın çok daha üstün olduğunu, bazıları ise tam tersini düşünüyor. Benzer şekilde, kullanmanın kullanmaktan .daha iyi olduğunu düşünüyorum ->, ama vi vs emacs gibi, tamamen özneldir ve hiçbir şeyi kanıtlayamazsınız
Orion Edwards

74

Referanslar işaretçilerle çok benzer, ancak derleyicileri optimize etmeye yardımcı olmak için özel olarak hazırlanmıştır.

  • Referanslar, derleyicinin hangi değişkenleri hangi değişkenlere referans olarak izlemesini önemli ölçüde kolaylaştıracak şekilde tasarlanmıştır. İki ana özellik çok önemlidir: "referans aritmetik" yoktur ve referansların yeniden atanması yoktur. Bunlar, derleyicinin, hangi değişkenlerin derleme sırasında hangi diğer adı referans aldığını anlamasına izin verir.
  • Referansların, derleyicinin kayıtlara koymayı seçtiği gibi, bellek adresi olmayan değişkenleri ifade etmesine izin verilir. Yerel bir değişkenin adresini alırsanız, derleyicinin bir kayıt defterine koyması çok zordur.

Örnek olarak:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

Optimize edici bir derleyici, [0] ve [1] gruplarına eriştiğimizi fark edebilir. Algoritmayı şu şekilde optimize etmek isteriz:

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

Böyle bir optimizasyon yapmak için, arama sırasında hiçbir şeyin diziyi [1] değiştiremeyeceğini kanıtlaması gerekir. Bunu yapmak oldukça kolay. i hiçbir zaman 2'den az değildir, bu nedenle [i] dizisi hiçbir zaman [1] dizisine atıfta bulunamaz. maybeModify () öğesine başvuru olarak a0 (diğer ad dizisi [0]) verilir. "Referans" aritmetik olmadığından, derleyici sadece belkiModify'ın x adresini asla almadığını kanıtlamak zorundadır ve hiçbir şeyin diziyi değiştirmediğini kanıtlamıştır [1].

Ayrıca, gelecekteki bir çağrının [0] 'ı okuyabilmesi / yazabilmesinin hiçbir yolu olmadığını kanıtlamak zorundayız. Bu genellikle kanıtlamak için önemsizdir, çünkü birçok durumda referansın asla sınıf örneği gibi kalıcı bir yapıda saklanmadığı açıktır.

Şimdi aynı şeyi işaretçilerle yapın

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

Davranış aynıdır; ancak şimdi maybeModify'ın dizi [1] 'i hiç değiştirmediğini kanıtlamak çok daha zordur, çünkü zaten bir işaretçi verdik; Kedi çantadan çıktı. Şimdi çok daha zor bir kanıt yapmak zorunda: maybeModify'ın statik analizi ve asla x x 1'e yazdığını kanıtlamak için. zor.

Modern derleyiciler statik analizde gittikçe daha iyi hale geliyor, ancak onlara yardımcı olmak ve referansları kullanmak her zaman iyidir.

Tabii ki, bu tür akıllı optimizasyonları yasaklayan derleyiciler, gerektiğinde referansları gerçekten işaretleyicilere dönüştürecektir.

DÜZENLEME: Bu yanıtı gönderdikten beş yıl sonra, referansların aynı adresleme kavramına bakmanın farklı bir yolundan farklı olduğu gerçek bir teknik fark buldum. Referanslar, geçici nesnelerin ömrünü işaretçilerin yapamayacağı şekilde değiştirebilir.

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

Normalde çağrının oluşturduğu gibi geçici nesneler createF(5)ifadenin sonunda yok edilir. Ancak, bu nesneyi bir başvuruya bağlayarak ref, C ++ refkapsam dışı olana kadar bu geçici nesnenin ömrünü uzatır .


Doğru, bedenin görünür olması gerekir. Bununla birlikte, maybeModifybununla ilgili herhangi bir şeyin adresini almadığını belirlemek, xbir grup işaretçi aritmetiğinin gerçekleşmediğini kanıtlamaktan çok daha kolaydır.
Cort Ammon

Optimize edicinin zaten bir sürü işaretçi aritemisi oluşmadığını kontrol etmesinin bir sürü başka nedeni kontrol ettiğini düşünüyorum.
Ben Voigt

"Referanslar işaretçilere çok benzer" - anlamsal olarak, uygun bağlamlarda - fakat üretilen kod açısından, sadece bazı uygulamalarda ve herhangi bir tanımlama / gereklilik yoluyla değil. Bunu işaret ettiğinizi biliyorum ve yazılarınızdan hiçbirine pratik terimlerle katılmıyorum, ancak 'referanslar gibi / genellikle işaretçiler olarak uygulanır' gibi kısa açıklamalara çok fazla okuma yapan insanlarla zaten çok fazla sorunumuz var. .
underscore_d

Birisi void maybeModify(int& x) { 1[&x]++; }, yukarıdaki diğer yorumların tartıştığı satırlarda yanlış yorum olarak eski olarak işaretlendiğini hissediyorum
Ben Voigt

68

Aslında, bir referans gerçekten bir işaretçi gibi değildir.

Bir derleyici, değişkenleri "başvurular" olarak saklar; bir ad bir bellek adresiyle ilişkilendirilir; bu, derleme sırasında herhangi bir değişken adını bir bellek adresine çevirmektir.

Bir başvuru oluşturduğunuzda, derleyiciye yalnızca işaretçi değişkenine başka bir ad atadığınızı bildirirsiniz; bu nedenle başvurular "null değerini gösteremez", çünkü bir değişken olamaz ve olamaz.

Göstergeler değişkenlerdir; başka bir değişkenin adresini içerir veya boş olabilir. Önemli olan bir işaretçi bir değere sahipken, bir referans sadece referans aldığı bir değişkene sahiptir.

Şimdi gerçek kodun bir açıklaması:

int a = 0;
int& b = a;

Burada işaret eden başka bir değişken oluşturmuyorsunuz a; sadece değerini tutan bellek içeriğine başka bir ad ekliyorsunuz a. Bu belleğin şimdi iki adı vardır ave her iki ad bkullanılarak da adreslenebilir.

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

Bir işlevi çağırırken, derleyici genellikle kopyalanacak argümanlar için bellek alanları oluşturur. İşlev imzası, yaratılması gereken boşlukları tanımlar ve bu boşluklar için kullanılması gereken adı verir. Bir parametrenin başvuru olarak bildirilmesi, derleyiciye yöntem çağrısı sırasında yeni bir bellek alanı ayırmak yerine giriş değişkeni bellek alanını kullanmasını söyler. İşlevinizin doğrudan çağrı kapsamında bildirilen bir değişkeni manipüle edeceğini söylemek garip gelebilir, ancak derlenmiş kodu çalıştırırken daha fazla kapsam olmadığını unutmayın; sadece düz bir bellek var ve fonksiyon kodunuz herhangi bir değişkeni değiştirebilir.

Şimdi, derleyicinizin, bir extern değişkeni kullanırken olduğu gibi, derleme sırasında referansı bilemeyebileceği bazı durumlar olabilir. Dolayısıyla, referans, temel kodda bir işaretçi olarak uygulanabilir veya uygulanmayabilir. Ancak size verdiğim örneklerde, büyük olasılıkla bir işaretçi ile uygulanmayacaktır.


2
Bir başvuru, mutlaka bir değişkene değil, l-değerine bir göndermedir. Bu nedenle, bir işaretçiye gerçek bir takma addan (bir derleme zamanı yapısı) çok daha yakındır. Referans verilebilecek ifade örnekleri * p veya hatta * p ++

5
Doğru, sadece referansın her zaman yeni bir değişkeni yığında yeni bir işaretçinin istediği şekilde zorlamayabileceğini işaret ediyordum.
Vincent Robert

1
@VincentRobert: İşaretçi ile aynı işlevi görür ... işlev satır içine alınmışsa, hem başvuru hem de işaretçi en iyi duruma getirilir. Bir işlev çağrısı varsa, nesnenin adresinin işleve iletilmesi gerekir.
Ben Voigt

1
int * p = NULL; int & r = * p; NULL'a işaret eden referans; eğer (r) {} -> boOm;)
sree

2
Derleme aşamasındaki bu odaklama, referansların çalışma zamanında iletilebileceğini hatırlayıncaya kadar, statik yumuşatma pencereden dışarı çıkar. (Ve sonra referanslar genellikle işaretçi olarak uygulanır, ancak standart bu yöntemi gerektirmez.)
underscore_d

45

Bir referans asla olamaz NULL.


10
Karşı bir örnek için Mark Ransom'un cevabına bakınız. Bu referanslar hakkında en sık iddia edilen efsanedir, ancak bu bir efsanedir. Standartta sahip olduğunuz tek garanti, bir NULL referansınız olduğunda hemen UB'ye sahip olmanızdır. Ama bu, "Bu araba güvende, asla yoldan
inemez

17
@cmaster: Geçerli bir programda başvuru boş olamaz. Ama bir işaretçi olabilir. Bu bir efsane değil, bu bir gerçek.
user541686

8
@Mehrdad Evet, geçerli programlar yolda kalıyor. Ancak, programınızın gerçekte uyguladığı bir trafik engeli yoktur. Yolun büyük bölümlerinde aslında eksik işaretler var. Bu yüzden geceleri yoldan çıkmak son derece kolaydır. Ve bunun olabileceğini bildiğiniz bu tür hataların hatalarını ayıklamak için çok önemlidir : null referans, bir null pointer'ın yapabileceği gibi programınızı çökmeden önce yayılabilir. Ve ne zaman bu void Foo::bar() { virtual_baz(); }segfaults gibi bir kod var . Referansların null olabileceğinin farkında değilseniz, null değerini başlangıç ​​noktasına kadar izleyemezsiniz.
cmaster - reinstate monica

4
int * p = NULL; int & r = * p; NULL'a işaret eden referans; eğer (r) {} -> boOm;) -
sree

10
@sree int &r=*p;tanımsız bir davranıştır. Bu noktada, bir yok "NULL referans işaret," Eğer bir programa sahip artık hakkında gerekçeli edilebilir hiç .
17'de cdhowie

35

Hem referanslar hem de işaretçiler dolaylı olarak başka bir değere erişmek için kullanılırken, referanslar ve işaretçiler arasında iki önemli fark vardır. Birincisi, referansın her zaman bir nesneye atıfta bulunmasıdır: Bir referansı başlatmadan tanımlamak bir hatadır. Atamanın davranışı ikinci önemli farktır: Bir referansa atama, referansın bağlı olduğu nesneyi değiştirir; başka bir nesneye yapılan başvuruyu yeniden hatırlatmaz. Başlatıldıktan sonra, başvuru her zaman aynı temel nesneyi ifade eder.

Bu iki program parçasını düşünün. İlkinde, bir işaretçiyi diğerine atarız:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

Atamadan sonra, ival, pi ile adreslenen nesne değişmeden kalır. Atama, pi değerini değiştirerek farklı bir nesneye işaret eder. Şimdi iki referans atayan benzer bir programı düşünün:

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

Bu atama ival değerini, ri tarafından başvurulan değeri ve referansın kendisini değiştirmez. Atamadan sonra, iki referans hala orijinal nesnelerine atıfta bulunur ve bu nesnelerin değeri de şimdi aynıdır.


"referans her zaman bir nesneye atıfta bulunur" tamamen yanlıştır
Ben Voigt

32

Bilgisayar dillerini soyut ya da akademik bir şekilde incelemeye aşina değilseniz ezoterik görünebilecek bir anlamsal fark vardır.

En üst düzeyde, referans fikri şeffaf "takma adlar" dır. Bilgisayarınız çalışmasını sağlamak için bir adres kullanabilir, ancak bunun için endişelenmeniz gerekmez: bunları mevcut bir nesne için "sadece başka bir ad" olarak düşünmeniz gerekir ve sözdizimi bunu yansıtır. İşaretçilerden daha katıdırlar, böylece derleyici sarkan bir referans oluşturmak üzereyken, sarkan bir işaretçi oluşturmaktan daha güvenilir bir şekilde sizi uyarabilir.

Bunun ötesinde, işaretçiler ve referanslar arasında elbette bazı pratik farklılıklar vardır. Bunları kullanmak için sözdizimi açıkça farklıdır ve referansları "yeniden oturtamaz", hiçbirliğe referans veremez veya referanslara işaret edemezsiniz.


27

Bir başvuru, başka bir değişkenin takma adıdır, oysa bir işaretçi bir değişkenin bellek adresini tutar. Başvurular genellikle işlev parametreleri olarak kullanılır, böylece iletilen nesne kopya değil nesnenin kendisidir.

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use. 

20

Ne kadar yer kapladığı önemli değildir, çünkü ne kadar yer kaplayacağına dair herhangi bir yan etki (kod çalıştırmadan) göremezsiniz.

Öte yandan, referanslar ve işaretçiler arasındaki en büyük fark, const referanslarına atanan geçicilerin const referansı kapsam dışına çıkana kadar yaşamasıdır.

Örneğin:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

yazdıracak:

in scope
scope_test done!

Bu, ScopeGuard'ın çalışmasına izin veren dil mekanizmasıdır.


1
Bir referansın adresini alamazsınız, ancak bu fiziksel olarak yer kaplamadığı anlamına gelmez. Optimizasyonları kısıtlamak, kesinlikle yapabilirler.
Yörüngedeki Hafiflik Yarışları

2
Etki ne olursa olsun, "Yığındaki bir referans hiç yer kaplamıyor" patentleri yanlıştır.
Yörüngedeki Hafiflik Yarışları

1
@Tomalak, derleyiciye de bağlı. Ama evet, bunu söylemek biraz kafa karıştırıcı. Sanırım bunu kaldırmak daha az kafa karıştırıcı olurdu.
MSN

1
Herhangi bir özel durumda olabilir veya olmayabilir. Kategorik bir iddia yanlış olduğu için "öyle değil". Benim dediğim de o. :) [Standardın konuyla ilgili söylediklerini hatırlayamıyorum; referans üyelerinin kuralları "referanslar yer kaplayabilir" genel bir kural verebilir, ama ben burada sahilde standart benim kopyasını yok: D]
Orbit'te Lightness Yarışları

20

Bu öğreticiye dayanmaktadır . Yazılanlar bunu daha açık hale getirir:

>>> The address that locates a variable within memory is
    what we call a reference to that variable. (5th paragraph at page 63)

>>> The variable that stores the reference to another
    variable is what we call a pointer. (3rd paragraph at page 64)

Basitçe hatırlamak için,

>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)

Dahası, neredeyse herhangi bir işaretçi öğreticisine başvurabileceğimizden, işaretçi, işaretçiyi bir diziye benzer hale getiren işaretçi aritmetiği tarafından desteklenen bir nesnedir.

Aşağıdaki ifadeye bakın,

int Tom(0);
int & alias_Tom = Tom;

alias_Tomolarak anlaşılabilir alias of a variable(ile farklı typedefolan alias of a type) Tom. Ayrıca böyle bir ifadenin terminolojisinin bir referans oluşturmak olduğunu unutmak da iyidir Tom.


1
Bir sınıfın başvuru değişkeni varsa, başlatma listesinde bir nullptr veya geçerli bir nesne ile başlatılmalıdır.
Yanlış Evlenme

1
Bu cevaptaki ifadeler, bunun gerçek kullanım için çok kafa karıştırıcıdır. Ayrıca, @Misgevolution, okuyuculara bir referansı başlatmalarını ciddiye alıyor nullptrmusunuz? Bu konunun başka bir bölümünü gerçekten okudunuz mu, yoksa ...?
underscore_d

1
Kötüüm, söylediğim aptalca şey için özür dilerim. O zaman uykusuz kalmalıydım. 'nullptr ile başlat' tamamen yanlış.
Yanlışevaz

19

Referans, bir hafızaya verilen başka bir isim değildir. Kullanım sırasında otomatik olarak atıfta bulunulan değişmez bir işaretçi. Temel olarak aşağıdakilere kadar kaynar:

int& j = i;

Dahili olarak olur

int* const j = &i;

13
Bu C ++ Standardının söylediği gibi değildir ve derleyicinin referansları cevabınız tarafından açıklanan şekilde uygulaması gerekmez.
jogojapan

@jogojapan: Bir C ++ derleyicisinin başvuru gerçekleştirmesi için geçerli olan herhangi bir yol da bir constişaretçi uygulamak için geçerli bir yoldur . Bu esneklik, referans ve işaretçi arasında bir fark olduğunu kanıtlamaz.
Ben Voigt

2
@BenVoigt Birinin geçerli herhangi bir uygulamasının diğerinin geçerli bir uygulaması olduğu doğru olabilir, ancak bu, bu iki kavramın tanımlarından bariz bir şekilde gelmez. Tanımlardan iyi bir cevap başlamıştı ve bu ikisinin nihayetinde aynı olduğu iddiasının neden doğru olduğunu gösterdi. Bu cevap diğer bazı cevaplar hakkında bir çeşit yorum gibi görünüyor.
jogojapan

Referans , bir nesneye verilen başka bir isimdir. Derleyicinin farkı anlayamadığınız sürece her türlü uygulamaya sahip olmasına izin verilir, bu "as-if" kuralı olarak bilinir. Buradaki önemli kısım farkı anlayamamanız. Bir işaretçinin depolama alanı olmadığını keşfederseniz, derleyici hatalıdır. Bir referansın depolama alanı olmadığını keşfederseniz, derleyici yine de uyumludur.
sp2danny

18

Doğrudan cevap

C ++ 'da başvuru nedir? Nesne türü olmayan bazı özel tür örnekleri .

C ++ 'da işaretçi nedir? Nesne türü olan belirli türden bazı örnekler .

Gönderen nesne türünün ISO C ++ tanımı :

Bir nesne türü bir (muhtemelen bir ev bir işlev türü, bir referans tipi, ve değildir ulaşım kolaylığı) tip ev dışında tutulacaktır.

Bilmek önemli olabilir, nesne türü C ++ 'da tür evreninin üst düzey bir kategorisidir. Referans ayrıca üst düzey bir kategoridir. Ama işaretçi değil.

İşaretçiler ve referanslar bileşik türü bağlamında birlikte belirtilir . Bunun temel nedeni, referansı olmayan C'den miras alınan (ve uzatılmış) deklaratör sözdiziminin doğasıdır. (Ayrıca, C ++ 11'den bu yana birden fazla referans bildirgesi vardır, ancak işaretçiler hala "birleştirilmiş": &+ &&vs.. *) Bu nedenle, bu bağlamda benzer C stiline sahip "uzantı" ile spesifik bir dil hazırlamak biraz makul . (Hala declarators atıkların sözdizimsel anlamlılık sözdizimi olduğunu iddia edecektir çok insan kullanıcılar ve uygulamalar hem sinir bozucu hale getirir. Böylece, hepsi edilecek kalifiye değilseniz yerleşikyeni bir dil tasarımında. Bu PL tasarımı hakkında tamamen farklı bir konudur.)

Aksi takdirde, işaretçilerin birlikte referanslarla birlikte belirli bir tür tür olarak nitelendirilebilmesi önemsizdir. Sözdizimi benzerliğinin yanı sıra çok az ortak özelliği paylaşırlar, bu nedenle çoğu durumda bunları bir araya getirmeye gerek yoktur.

Yukarıdaki ifadelere yalnızca "işaretçilerden" ve "referanslardan" tür olarak bahsedilmiştir. Örnekleri ile ilgili bazı sorular var (değişkenler gibi). Ayrıca çok fazla yanlış anlama var.

Üst düzey kategorilerin farklılıkları, zaten işaretçilerle doğrudan bağlantılı olmayan birçok somut farkı ortaya çıkarabilir:

  • Nesne türlerinde en üst düzey cvniteleyiciler bulunabilir. Referanslar yapılamaz.
  • Nesne türlerinin çeşitliliği , soyut makine semantiğine göre depolamayı işgal eder . Referans, depolama alanı gerektirmez (ayrıntılar için aşağıdaki yanlış anlamalarla ilgili bölüme bakın).
  • ...

Referanslarla ilgili birkaç özel kural daha:

  • Bileşik bildiriciler referanslar konusunda daha kısıtlayıcıdır.
  • Referanslar çökebilir .
    • &&Şablon parametre indirimi sırasında referans daralmasına dayalı olarak parametrelerle ilgili özel kurallar ("yönlendirme referansları" olarak) parametrelerin "kusursuzca iletilmesine" olanak tanır .
  • Referansların başlatılmasında özel kuralları vardır. Referans türü olarak bildirilen değişkenin ömrü, uzatma yoluyla normal nesnelerden farklı olabilir.
    • BTW, başlatma gibi birkaç başka bağlamda std::initializer_listreferans ömrünün uzatılması için benzer bazı kurallar geçerlidir. Bir başka solucan konservesi.
  • ...

Yanılgılar

Sözdizimsel şeker

Referansların sözdizimsel şeker olduğunu biliyorum, bu yüzden kodun okunması ve yazılması daha kolay.

Teknik olarak, bu oldukça yanlıştır. Referanslar C ++ 'daki diğer özelliklerin sözdizimsel şekeri değildir, çünkü herhangi bir anlamsal farklılık olmadan tam olarak diğer özelliklerle değiştirilemezler.

(Benzer şekilde, lambda-sentezleme ler vardır değil tam da böyle "belirtilmemiş" özellikleri olan simüle olamaz çünkü C ++ da başka özellikler sözdizimsel şeker ele değişkenlerin tanımlanması için bu değişkenlerin başlatma sırası olabilir, çünkü önemli olabilir, önemli.)

C ++ bu katı anlamda sadece birkaç çeşit sözdizimsel şekere sahiptir. Bir örnek (C'den devralınmıştır) yerleşik (aşırı yüklenmemiş) operatördür []; bu , yerleşik operatör tek *ve ikili üzerinde belirli kombinasyon biçimlerinin tam olarak aynı semantik özelliklerine sahip olarak tanımlanır+ .

Depolama

Dolayısıyla, bir işaretçi ve bir referans aynı miktarda bellek kullanır.

Yukarıdaki ifade yanlıştır. Bu tür yanlış anlamaları önlemek için, bunun yerine ISO C ++ kurallarına bakın:

Kaynaktan [intro.object] / 1 :

... Bir nesne, inşaat döneminde, ömrü boyunca ve yıkım döneminde bir depolama bölgesini işgal eder. ...

Kaynaktan [dcl.ref] / 4 :

Bir referansın depolama gerektirip gerektirmediği belirtilmemiştir.

Bunların semantik özellikler olduğuna dikkat edin .

Edimbilim

Bu işaretçiler, dil tasarımı anlamında referanslarla bir araya getirilecek kadar nitelikli olmasalar bile, yine de, diğer bazı bağlamlarda, örneğin parametre türlerinde seçim yaparken aralarında seçim yapmayı tartışmalı hale getiren bazı argümanlar vardır.

Ama bu tüm hikaye değil. Demek istediğim, dikkat etmeniz gereken işaretçiler ve referanslardan daha fazla şey var.

Bu kadar özel seçimlere bağlı kalmanız gerekmiyorsa, çoğu durumda cevap kısadır: işaretçi kullanma zorunluluğunuz yoktur, bu yüzden kullanmazsınız . İşaretçiler genellikle yeterince kötüdür, çünkü beklemediğiniz kadar çok şey ima ederler ve kodun sürdürülebilirliğini ve (hatta) taşınabilirliğini zedeleyen çok fazla zımni varsayımlara güveneceklerdir. İşaretçilere gereksiz yere güvenmek kesinlikle kötü bir stildir ve modern C ++ anlamında kaçınılmalıdır. Amacınızı yeniden düşünün ve sonunda işaretçinin çoğu durumda son türlerin özelliği olduğunu göreceksiniz .

  • Bazen dil kuralları açıkça belirli türlerin kullanılmasını gerektirir. Bu özellikleri kullanmak istiyorsanız kurallara uyun.
    • Kopya yapıcılar , 1. parametre türü olarak belirli cv - &başvuru türü türlerini gerektirir . (Ve genellikle constnitelikli olmalıdır .)
    • Move yapıcıları , 1. parametre türü olarak belirli cv - &&başvuru türü türleri gerektirir . (Ve genellikle niteleyici olmamalıdır.)
    • Operatörlere özgü aşırı yükler, referans veya referans olmayan tipler gerektirir. Örneğin:
      • operator=Özel üye işlevleri olarak aşırı yüklenmiş , kopyalama / taşıma yapıcılarının 1. parametresine benzer referans türleri gerektirir.
      • Postfix ++kukla gerektirir int.
      • ...
  • Geçiş değerinin (yani referans olmayan türlerin kullanılması) yeterli olduğunu biliyorsanız, özellikle C ++ 17 zorunlu kopya seçimini destekleyen bir uygulama kullanırken doğrudan kullanın. ( Uyarı : Ancak, zorunluluk hakkında kapsamlı bir akıl yürütmek çok karmaşık olabilir .)
  • Bazı tutamaçları sahiplikle kullanmak istiyorsanız , ham işaretçiler yerine unique_ptrve shared_ptr(veya opak olmalarını istiyorsanız kendiniz homebrew olanlar gibi) akıllı işaretçiler kullanın .
  • Bir aralıkta bazı yinelemeler yapıyorsanız, ham işaretçilerin çok daha iyi (örneğin daha az başlık bağımlılıkları için) daha iyi olduğuna ikna olmadıkça ham işaretçiler yerine yineleyiciler (veya henüz standart kütüphane tarafından sağlanmayan bazı aralıklar) kullanın. vakalar.
  • By-pass değerinin yeterli olduğunu biliyorsanız ve bazı açık nullable semantikleri istiyorsanız std::optional, ham işaretçiler yerine sarıcı kullanın .
  • Geçiş değerinin yukarıdaki nedenlerle ideal olmadığını biliyorsanız ve null olabilecek anlambilim istemiyorsanız, {lvalue, rvalue, forwarding} -references kullanın.
  • Geleneksel işaretçi gibi semantik isteseniz bile observer_ptr, Library Fundamental TS'deki gibi genellikle daha uygun bir şey vardır .

Tek istisna mevcut dilde çözülemez:

  • Yukarıdaki akıllı işaretçileri uygularken, ham işaretçilerle uğraşmanız gerekebilir.
  • Belirli dil birlikte çalışma yordamları gibi işaretçiler gerektirir operator new. (Bununla birlikte, cv - void*sıradan nesne işaretleyicilerine kıyasla hala oldukça farklı ve daha güvenlidir, çünkü GNU'lar gibi bazı uygun olmayan uzantılara güvenmediğiniz sürece beklenmedik işaretçi aritmetiklerini dışlar void*.)
  • İşlev işaretçileri yakalama olmadan lambda ifadelerinden dönüştürülebilirken işlev başvuruları dönüştürülemez. Bu tür durumlar için jenerik olmayan kodda işlev işaretçileri kullanmanız gerekir, hatta kasıtlı olarak null değer değerleri istemezsiniz.

Yani, pratikte cevap çok açık: şüphe duyduğunuzda işaretçilerden kaçının . İşaretçileri yalnızca, başka hiçbir şeyin daha uygun olmadığının çok açık nedenleri olduğunda kullanmalısınız. Yukarıda belirtilen birkaç istisnai durum dışında, bu tür seçimler neredeyse her zaman sadece C ++ 'a özgü değildir (ancak dile uygulamaya özel olması muhtemeldir). Bu tür örnekler şunlar olabilir:

  • Eski stil (C) API'lara sunmanız gerekir.
  • Belirli C ++ uygulamalarının ABI gereksinimlerini karşılamanız gerekir.
  • Belirli uygulamaların varsayımlarına dayanarak çalışma zamanında farklı dil uygulamalarıyla (çeşitli montajlar, dil çalışma zamanı ve bazı üst düzey istemci dillerinin FFI dahil) birlikte çalışmanız gerekir.
  • Bazı aşırı durumlarda çevirinin (derleme ve bağlantı) verimliliğini arttırmanız gerekir.
  • Bazı aşırı durumlarda sembol patlamasından kaçınmalısınız.

Dil tarafsızlığı uyarıları

Soruyu bazı Google arama sonuçlarıyla (C ++ 'a özgü olmayan) görmeye gelirseniz , bunun yanlış yer olması muhtemeldir.

C ++ 'da referanslar oldukça "tuhaftır", çünkü esasen birinci sınıf değildir: atıfta bulunulan nesneler veya işlevler olarak ele alınacaklardır, böylece sol işlenen olmak gibi birinci sınıf işlemleri destekleme şansları yoktur . üye erişim operatörü belirtilen nesnenin tipine bağımsız olarak. Diğer diller referanslarında benzer kısıtlamalara sahip olabilir veya olmayabilir.

C ++ 'da yapılan başvurular muhtemelen farklı dillerdeki anlamı korumaz. Örneğin, genel olarak referanslar C ++ 'da olduğu gibi değerler üzerinde null olmayan özellikler anlamına gelmez, bu nedenle bu tür varsayımlar diğer dillerde çalışmayabilir (ve karşı örnekleri çok kolay bulacaksınız, örneğin Java, C #, ...).

Genel olarak farklı programlama dillerinde referanslar arasında bazı ortak özellikler olabilir, ancak SO'daki diğer bazı sorular için bırakalım.

(Bir yan not: soru ALGOL 68 ve PL / I gibi herhangi bir "C benzeri" dilin dahil edilmesinden daha önce önemli olabilir .)


17

C ++ 'da bir işaretçiye başvuru mümkündür, ancak bunun tersi mümkün değildir, referansa bir işaretçinin mümkün olmadığı anlamına gelir. İşaretçiye yapılan başvuru, işaretçiyi değiştirmek için daha temiz bir sözdizimi sağlar. Şu örneğe bakın:

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

Ve yukarıdaki programın C sürümünü düşünün. C'de işaretçi işaretçi kullanmak zorunda (çoklu dolaylı), ve bu karışıklığa yol açar ve program karmaşık görünebilir.

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

İşaretçiye başvuru hakkında daha fazla bilgi için aşağıdakileri ziyaret edin:

Dediğim gibi, referansa bir işaretçi mümkün değildir. Aşağıdaki programı deneyin:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}

16

Bunlardan herhangi birine ihtiyaç duymadıkça referansları kullanıyorum:

  • Boş göstergeler, işlev aşırı yüklenmesini veya bir bool kullanımını önlemek için genellikle ucuz bir yol olan sentinel değeri olarak kullanılabilir.

  • Bir işaretçi üzerinde aritmetik yapabilirsiniz. Örneğin,p += offset;


5
Referans olarak &r + offsetnerede rbeyan edildiğini yazabilirsiniz
MM

15

İşaretçiler ve referanslar arasında kimsenin bahsetmediğini gördüğüm temel bir fark vardır: referanslar fonksiyon argümanlarında referans by-pass anlambilimini etkinleştirir. İşaretçiler, başlangıçta görünmese de şunları yapmazlar: yalnızca by-pass semantik sağlarlar. Bu, bu makalede çok güzel açıklanmıştır .

Saygılarımızla, & rzej


1
Referanslar ve işaretçiler her ikisi de tanıtıcıdır. Her ikisi de size nesnenizin referans olarak geçtiği semantiği verir , ancak tanıtıcı kopyalanır. Fark yok. (Sözlükte arama anahtarı gibi tutamaçlara sahip olmanın başka yolları da vardır)
Ben Voigt

Ben de böyle düşünürdüm. Ancak neden böyle olmadığını açıklayan bağlantılı makaleye bakın.
Andrzej

2
@Andrzj: Bu, yorumumdaki tek cümlenin çok uzun bir versiyonu: Tutamak kopyalandı.
Ben Voigt

Bu "tanıtıcı kopyalandı" hakkında daha fazla açıklamaya ihtiyacım var. Bazı temel fikir anlıyorum ama fiziksel referans ve işaretçi değişkenin bellek konumunu işaret düşünüyorum. Takma ad değer değişkenini depolar ve değişkenin değeri değişim veya başka bir şey olarak günceller mi? Ben acemiyim ve lütfen bunu aptalca bir soru olarak işaretleme.
Asim

1
@Andrzej Yanlış. Her iki durumda da, by-pass değeri gerçekleşir. Referans değere göre ve işaretçi değere göre iletilir. Aksini söylemek yenileri karıştırır.
Miles Rout

14

Karışıklık ekleme riski altında, bazı girdileri atmak istiyorum, eminim çoğunlukla derleyicinin referansları nasıl uyguladığına bağlıdır, ancak gcc durumunda bir referansın yalnızca yığındaki bir değişkeni gösterebileceği fikri aslında doğru değil, örneğin bunu ele alalım:

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

Hangi çıktı:

THIS IS A STRING
0xbb2070 : 0xbb2070

Bellek adreslerinin bile tamamen aynı olduğunu fark ederseniz, referans, öbek üzerindeki bir değişkeni başarıyla işaret ediyor demektir! Şimdi gerçekten garip olmak istiyorsanız, bu da işe yarar:

int main(int argc, char** argv) {
    // In the actual new declaration let immediately de-reference and assign it to the reference
    std::string &str_ref = *(new std::string("THIS IS A STRING"));
    // Once again, it works! (at least in gcc)
    std::cout << str_ref;
    // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
    delete &str_ref;
    /*And, it works, because we are taking the memory address that the reference is
    storing, and deleting it, which is all a pointer is doing, just we have to specify
    the address with '&' whereas a pointer does that implicitly, this is sort of like
    calling delete &(*str_ptr); (which also compiles and runs fine).*/
}

Hangi çıktı:

THIS IS A STRING

Bu nedenle bir referans başlık altında bir işaretçi IS, her ikisi de sadece adresin işaret ettiği bir bellek adresi saklıyor, std :: cout << str_ref denirse ne olacağını düşünüyorsunuz; Delete & str_ref çağrıldıktan SONRA? Açıkçası iyi derler, ancak çalışma zamanında bir segmentasyon hatasına neden olur, çünkü artık geçerli bir değişkene işaret etmiyor, aslında hala var olan (kapsamın dışına çıkana kadar), ancak işe yaramaz olan kırık bir referansımız var.

Başka bir deyişle, bir referans, işaretçi mekaniğinin soyutlanmış bir işaretçiden başka bir şey değildir, böylece daha güvenli ve kullanımı daha kolay hale getirir (yanlışlıkla işaretçi matematiği, karıştırma yok. 'Ve' -> 'vb.) yukarıdaki örneklerim gibi saçmalık denemeyin;)

Şimdi , bir derleyicinin referansları nasıl ele aldığından bağımsız olarak, her zaman kaputun altında bir tür işaretçi olacaktır , çünkü bir referansın beklendiği gibi çalışması için belirli bir bellek adresindeki belirli bir değişkene başvurması gerekir , bu nedenle bununla uğraşmak mümkün değildir (bu nedenle 'referans' terimi).

Referanslarla hatırlanması gereken tek önemli kural, bildirim sırasında tanımlanması gerektiğidir (bir başlıktaki bir referans haricinde, bu durumda, içerdiği nesne, onu tanımlamak için çok geç).

Unutmayın, yukarıdaki örneklerim sadece bir referansın ne olduğunu gösteren örnekler, asla bu referansı kullanmak istemeyeceksiniz! Bir referansın doğru kullanımı için, burada zaten kafasına çiviyi çarpan birçok cevap var.


14

Başka bir fark, bir void tipine işaretçileriniz olabilmesidir (ve herhangi bir şeye işaretçi anlamına gelir), ancak void referanslarının yasaklanmasıdır.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

Bu özel farktan gerçekten memnun olduğumu söyleyemem. Ben çok bir adres ve başka bir şekilde referanslar için aynı davranış ile bir şey anlam referans izin olacağını tercih ediyorum. Referanslar kullanarak memcpy gibi C kütüphanesi fonksiyonlarının bazı eşdeğerlerinin tanımlanmasına izin verir.


13

Ayrıca, satır içine alınmış bir işlevin parametresi olan bir başvuru, bir işaretçiden farklı bir şekilde işlenebilir.

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

Birçok derleyici, işaretçi sürümünü satır içine alırken, aslında belleğe bir yazma zorlar (adresi açıkça alıyoruz). Ancak, referansı daha uygun bir kayıtta bırakacaklardır.

Tabii ki, satır içi olmayan işlevler için işaretçi ve başvuru aynı kodu oluşturur ve işlev tarafından değiştirilmedikleri ve döndürülmedikleri takdirde, içsel değerleri değere göre geçirmek her zaman daha iyidir.


11

Referansların bir başka ilginç kullanımı, kullanıcı tanımlı türde varsayılan bir argüman sağlamaktır:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

Varsayılan lezzet, referansların 'geçici olarak bind başvurusu' yönünü kullanır.


11

Bu program sorunun cevabını anlamada yardımcı olabilir. Bu "j" referansının basit bir programı ve "x" değişkenini gösteren "ptr" işaretçisi.

#include<iostream>

using namespace std;

int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"

cout << "x=" << x << endl;

cout << "&x=" << &x << endl;

cout << "j=" << j << endl;

cout << "&j=" << &j << endl;

cout << "*ptr=" << *ptr << endl;

cout << "ptr=" << ptr << endl;

cout << "&ptr=" << &ptr << endl;
    getch();
}

Programı çalıştırın ve çıktı bir göz atın ve anlayacaksınız.

Ayrıca, 10 dakika ayırın ve bu videoyu izleyin: https://www.youtube.com/watch?v=rlJrrGV0iOg


11

Burada ele alınmamış başka bir nokta daha var gibi hissediyorum.

İşaretçilerin aksine, başvurular söz ettikleri nesneye sözdizimsel olarak eşdeğerdir , yani bir nesneye uygulanabilecek herhangi bir işlem bir başvuru için çalışır ve tam olarak aynı sözdizimiyle çalışır (istisna elbette başlatmadır).

Bu yüzeysel görünebilir olsa da, bu özellik C ++ özellikleri bir dizi için çok önemli olduğuna inanıyorum, örneğin:

  • Şablonlar . Şablon parametreleri ördek türünde olduğundan, türün sözdizimsel özellikleri önemlidir, bu nedenle genellikle aynı şablon hem Tve ile birlikte kullanılabilir T&.
    (veya std::reference_wrapper<T>hangi hala bir örtük döküm dayanır T&)
    o kapak Templates hem T&ve T&&hatta daha yaygındır.

  • Değerler . str[0] = 'X';Referans olmadan ifadeyi düşünün, sadece c-dizeleri ( char* str) için işe yarayacaktır . Karakteri başvuru ile döndürmek, kullanıcı tanımlı sınıfların aynı gösterime sahip olmasını sağlar.

  • Kopyalayıcılar . Sözdizimsel olarak, nesneleri kopyalayıcılara değil, nesnelere işaretleyicilere geçirmek mantıklıdır. Ancak, bir kopya oluşturucunun bir nesneyi değere göre almasının bir yolu yoktur - aynı kopya kurucusuna yinelemeli çağrı yapılmasına neden olur. Bu, referansları burada tek seçenek olarak bırakır.

  • Operatör aşırı yükleri . Referanslarla operator+(const T& a, const T& b), aynı infix gösterimini korurken bir operatör çağrısına dolaylı yoldan giriş yapmak mümkündür . Bu aynı zamanda aşırı yüklü işlevler için de geçerlidir.

Bu noktalar C ++ 'ın ve standart kütüphanenin önemli bir bölümünü güçlendirir, bu nedenle bu referansların oldukça büyük bir özelliğidir.


" örtük kadro " kadro bir sözdizimi yapısıdır, dilbilgisinde vardır; bir oyuncu her zaman açıktır
curiousguy

9

İşaretçiler ve referanslar arasında çok önemli bir teknik olmayan fark vardır: Bir işleve işaretçi ile iletilen bir argüman, sabit olmayan bir başvuru ile bir işleve iletilen bir argümandan çok daha görünürdür. Örneğin:

void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);

void bar() {
    std::string x;
    fn1(x);  // Cannot modify x
    fn2(x);  // Cannot modify x (without const_cast)
    fn3(x);  // CAN modify x!
    fn4(&x); // Can modify x (but is obvious about it)
}

C olarak, benzeyen bir çağrı fn(x)sadece değere göre iletilebilir, bu yüzden kesinlikle değiştirilemez x; bir argümanı değiştirmek için bir işaretçiyi iletmeniz gerekir fn(&x). Dolayısıyla, bir argümandan önce bir argüman gelmediyse, &değiştirilmeyeceğini biliyordunuz. ( &Değiştirilen anlamına gelen tersi doğru değildi, çünkü bazen büyük salt okunur yapıları constişaretçiyle iletmeniz gerekir.)

Bazıları bunun kodu okurken böyle kullanışlı bir özellik constolduğunu, işlev hiç beklemese bile , işaretçi parametrelerinin referans olmayanlardan ziyade değiştirilebilir parametreler için her zaman kullanılması gerektiğini savunur nullptr. Yani, bu insanlar fn3()yukarıdaki gibi işlev imzalarına izin verilmemesi gerektiğini savunuyorlar . Google'ın C ++ stil yönergeleri buna bir örnektir.


8

Belki bazı metaforlar yardımcı olacaktır; Masaüstü ekran alanınız bağlamında -

  • Referans, gerçek bir pencere belirtmenizi gerektirir.
  • Bir işaretçi, o pencere türünün sıfır veya daha fazla örneğini içereceğinden emin olmak için ekranda bir alanın yerini gerektirir.

6

İşaretçi ve referans arasındaki fark

Bir işaretçi 0 olarak başlatılabilir ve bir referans değil. Aslında, başvuru bir nesneye de başvurmalıdır, ancak bir işaretçi boş gösterici olabilir:

int* p = 0;

Ama sahip olamayız int& p = 0;ve aynı zamanda int& p=5 ;.

Aslında bunu doğru bir şekilde yapmak için, önce bir nesneyi bildirmiş ve tanımlamış olmalıyız, o zaman o nesneye referans yapabiliriz, böylece önceki kodun doğru uygulanması şöyle olur:

Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;

Bir başka önemli nokta, işaretleyicinin bildirimini başlatma olmadan yapabilmemizdir, ancak her zaman değişken veya nesneye referans olması gereken referans durumunda böyle bir şey yapılamaz. Bununla birlikte, bir işaretçinin bu şekilde kullanılması risklidir, bu nedenle genellikle işaretçinin gerçekten bir şeye işaret edip etmediğini kontrol ederiz. Referans olması durumunda böyle bir kontrol gerekli değildir, çünkü bildirim sırasında bir nesneye göndermenin zorunlu olduğunu zaten biliyoruz.

Başka bir fark, işaretçinin başka bir nesneye işaret edebilmesidir, ancak referans her zaman aynı nesneye başvuruyor, bu örneği alalım:

Int a = 6, b = 5;
Int& rf = a;

Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.

rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

Başka bir nokta: STL şablonu gibi bir şablonumuz olduğunda, bu tür bir sınıf şablonu, operatörü [] kullanarak kolayca okunmasını veya yeni değer atamasını sağlamak için her zaman bir işaretçi değil bir referans döndürür:

Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="

1
Hala sahip olabiliriz const int& i = 0.
Revolver_Ocelot

1
Bu durumda referans yalnızca okumada kullanılacaktır, "const_cast" bile olsa bu const referansını değiştiremeyiz çünkü "const_cast" sadece referans değil işaretçiyi kabul eder.
dhokar.w

1
const_cast referanslarla oldukça iyi çalışır: coliru.stacked-crooked.com/a/eebb454ab2cfd570
Revolver_Ocelot

1
bir referans yayınlamamak için bir döküm yapıyorsunuz, bunu deneyin; const int & i =; const_cast <int> (i) referansa yeni bir değer yazmak ve atamak için referansın sabitliğini atmaya çalışıyorum ama bu mümkün değil. lütfen odaklanın !!
dhokar.w

5

Fark, sabit olmayan işaret değişkeninin (sabit bir işaretçiyle karıştırılmaması gereken) programın yürütülmesi sırasında bir zamanda değiştirilebilmesidir, başlatma sırasında işaretçi semantiklerinin (&, *) kullanılmasını gerektirirken referanslar ayarlanabilir yalnızca (bu yüzden bunları yalnızca yapıcı başlatıcı listesinde ayarlayabilirsiniz, ancak bir şekilde değil) ve normal değer erişim semantiğini kullanabilirsiniz. Temelde referanslar, çok eski bir kitapta okuduğum gibi aşırı yükleme operatörlerine destek sağlamak için tanıtıldı. Bu iş parçacığında belirtildiği gibi - işaretçi 0 veya istediğiniz herhangi bir değere ayarlanabilir. 0 (NULL, nullptr), işaretçinin hiçbir şeyle başlatılmadığı anlamına gelir. Boş gösterici boş göstergesine bir hata. Ancak aslında işaretçi bazı doğru bellek konumlarına işaret etmeyen bir değer içerebilir. Başvurular sırayla, her zaman doğru türde bir rvalue sağladığınız için kullanıcının referans verilemeyen bir şeye referans başlatmasına izin vermemeye çalışır. Referans değişkeninin yanlış bir bellek konumuna başlatılmasının birçok yolu olmasına rağmen - bunu ayrıntılara derinlemesine incelemeniz daha iyidir. Makine düzeyinde hem işaretçi hem de referans, işaretçilerle eşit olarak çalışır. Diyelim ki temel referanslarda sözdizimsel şeker. rvalue referansları bundan farklıdır - doğal olarak yığın / yığın nesneleridir. Referans değişkeninin yanlış bir bellek konumuna başlatılmasının birçok yolu olmasına rağmen - bunu ayrıntılara derinlemesine incelemeniz daha iyidir. Makine düzeyinde hem işaretçi hem de referans, işaretçilerle eşit olarak çalışır. Diyelim ki temel referanslarda sözdizimsel şeker. rvalue referansları bundan farklıdır - doğal olarak yığın / yığın nesneleridir. Referans değişkeninin yanlış bir bellek konumuna başlatılmasının birçok yolu olmasına rağmen - bunu ayrıntılara derinlemesine incelemeniz daha iyidir. Makine düzeyinde hem işaretçi hem de referans, işaretçilerle eşit olarak çalışır. Diyelim ki temel referanslarda sözdizimsel şeker. rvalue referansları bundan farklıdır - doğal olarak yığın / yığın nesneleridir.


4

basit bir ifadeyle, bir referansın bir değişken için alternatif bir isim olduğunu söyleyebiliriz, ancak bir işaretçi başka bir değişkenin adresini tutan bir değişkendir. Örneğin

int a = 20;
int &r = a;
r = 40;  /* now the value of a is changed to 40 */

int b =20;
int *ptr;
ptr = &b;  /*assigns address of b to ptr not the value */
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.