Soru Bir işaretçi değişkeni ile C ++'daki bir referans değişkeni arasındaki farklar nelerdir?


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

Ama farklılıklar neler?


Aşağıdaki cevaplardan ve bağlantılardan özet:

  1. Bir işaretçi, yeniden bağlama işleminden sonra yeniden atanamazken, herhangi bir sayıda yeniden atanabilir.
  2. İşaretçiler hiçbir yere işaret edemez (NULL), bir referans her zaman bir nesneyi ifade eder.
  3. İşaretçi ile yapabileceğiniz bir referansın adresini alamazsınız.
  4. "Referans aritmetiği" yoktur (ancak bir referansın işaret ettiği bir nesnenin adresini alabilir ve üzerinde olduğu gibi işaretçi aritmetiğini yapabilirsiniz. &obj + 5).

Bir yanlış anlaşılmayı netleştirmek için:

C ++ standardı, bir derleyicinin nasıl olabileceğini dikte etmekten kaçınmak için çok dikkatli   referanslar uygulamak, ancak her C ++ derleyici uygular   işaretçi olarak başvurular. Yani, aşağıdaki gibi bir beyanname:

int &ri = i;

tamamen optimize edilmediyse, aynı miktarda depolama alanı ayırır   işaretçi olarak ve adresi yerleştirir   arasında i bu depoya.

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

Genel bir kural olarak,

  • Faydalı ve kendi kendini belgeleyen arayüzler sağlamak için işlev parametrelerindeki referansları ve dönüş türlerini kullanın.
  • Algoritma ve veri yapılarını uygulamak için işaretçiler kullanın.

İlginç okuma:


2625
2017-09-11 20:03


Menşei


Bence point 2 "Bir işaretçinin NULL olmasına izin verilir, ancak bir referans değildir. Sadece hatalı biçimlendirilmiş kod bir NULL başvurusu oluşturabilir ve davranışı tanımsızdır." - Mark Ransom
İşaretçiler yalnızca başka bir nesne türüdür ve C ++'daki herhangi bir nesne gibi, değişken olabilirler. Öte yandan referanslar asla nesneler değildir. bir tek değişkenler. - Kerrek SB
Bu uyarı olmadan derlenir: int &x = *(int*)0; gcc'de. Referans aslında NULL'a işaret edebilir. - Calmarius
referans değişken bir takma addır - Khaled.K
İlk cümlenin nasıl tamamen yanlış olduğunu seviyorum. Referansların kendi semantikleri vardır. - Lightness Races in Orbit


Cevaplar:


  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ılamaz ve başlangıçta atanmalıdır:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. Bir işaretçinin kendine ait bellek adresi ve boyutu (x86 üzerinde 4 bayt) iken, bir referans aynı bellek adresini (orijinal değişkenle) paylaşır ancak aynı zamanda yığın üzerinde biraz yer kaplar. Bir referansın, orijinal değişkenin kendisi ile aynı adrese sahip olması nedeniyle, aynı değişken için başka bir isim olarak bir referans düşünmek güvenlidir. Not: Yığın veya yığın üzerinde işaretçi ne olabilir. Bir referans. Bu ifadedeki iddiam, bir işaretçinin yığına işaret etmesi gerektiği anlamına gelmiyor. Bir işaretçi sadece bir hafıza adresini tutan bir değişkendir. Bu değişken yığınta. Bir referans yığında kendi alanı olduğundan ve adres referans aldığı değişkenle aynı olduğundan. Daha fazla yığın vs yığın. Bu, derleyicinin size söylemeyeceği bir referansın gerçek bir adresi olduğunu ima eder.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. Ekstra düzeydeki indirmeler sunan işaretçilere işaretçiler için işaretçiler olabilir. Oysa referanslar sadece bir düzey dolaylı yol önermektedir.

    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. İşaretçi atanabilir nullptr doğrudan, referans olamaz. Eğer yeterince uğraşırsan, ve nasıl olduğunu bilirsen, referansın adresini yapabilirsin. nullptr. Aynı şekilde, eğer yeterince uğraşırsanız, bir işaretçiye referans alabilirsiniz, ve o zaman bu referans şunları 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 yineleyebilir, kullanabilirsiniz ++Bir işaretçinin işaret ettiği bir sonraki öğeye gitmek için + 4 5. öğeye gitmek için. Bu, nesnenin, işaretçinin işaret ettiği boyut ne olursa olsun.

  6. Bir işaretçinin dereferride olması gerekir * Bir referans doğrudan kullanılabiliyorken, hafıza konumuna işaret eder. Bir sınıf / struct için bir işaretçi kullanır -> bir üyeye erişmek için bir referans kullanır ..

  7. İşaretçi, bir bellek adresini tutan bir değişkendir. Bir referansın nasıl uygulandığına bakılmaksızın, bir referans referans aldığı öğe ile aynı hafıza adresine sahiptir.

  8. Referanslar bir diziye eklenemez, oysa işaretçiler (Kullanıcı @litb tarafından söylenen) olabilir

  9. Const referansları, tempoya bağlı olabilir. İşaretçiler (dolaylı olarak) yapamazlar:

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

    Bu yapar const& argüman listelerinde kullanmak için daha güvenli ve benzerleri.


1376
2018-02-27 21:26



... ancak Dereferencing NULL undefined. Örneğin, bir referansın NULL olup olmadığını test edemezsiniz (ör. & Ref == NULL). - Pat Notz
2 numara değil doğru. Bir referans sadece "aynı değişken için başka bir isim" değildir. Referanslar, işaretlere çok benzer bir şekilde, sınıflara, vb. Kaydedilmiş fonksiyonlara aktarılabilir. İşaret ettikleri değişkenlerden bağımsız olarak var olurlar. - Derek Park
Brian, yığın uygun değil. Referanslar ve işaretçiler yığında yer almak zorunda değildir. Her ikisi de öbek üzerinde tahsis edilebilir. - Derek Park
Brian, bir değişkenin (bu durumda bir işaretçi veya referans) boşluk gerektirmesi gerçeği değil yığında boşluk gerektirdiği anlamına gelir. İşaretçiler ve referanslar sadece puan yığına, aslında olabilir tahsis yığınta. - Derek Park
Başka önemli bir farklılık: referanslar bir diziye eklenemez - Johannes Schaub - litb


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

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

Tüm referanslar sıfır olmayan bir değerle başlatılmalıdır veya derleme başarısız olur. Bir referansın adresini almak mümkün değildir - adres operatörü referans verilen değerin adresini döndürür - referanslar üzerinde aritmetik yapmak da mümkün değildir.

C programcıları, C ++ referanslarını beğenmeyebilirler, çünkü dolaylılık gerçekleştiğinde veya argüman, fonksiyon imzasına bakmadan değer veya işaretçi tarafından geçilirse, artık belli olmayacaktır.

C ++ programcıları, güvensiz oldukları düşünüldüğünde imleçleri kullanmayı sevmeyebilirler - referanslar, en önemsiz vakalar dışında sabit işaretçilerden daha güvenli olmasa da, otomatik dolaylılıktan yoksun ve farklı anlamsal bir çağrışım taşır.

Aşağıdaki ifadeyi dikkate alın C ++ SSS:

Bir başvuru genellikle bir adres kullanılarak uygulanmış olsa bile   alttaki montaj dili, lütfen değil referans olarak düşünmek   bir nesneye komik görünümlü işaretçi. Referans olduğu nesne. Bu   nesneye bir işaretçi veya nesnenin bir kopyası değil. O olduğu    nesne.

Ama bir referans varsa Gerçekten mi Nesne vardı, sarkan referanslar nasıl olabilir? Yönetilmeyen dillerde, referansların işaretçilerden daha güvenli olması imkânsızdır - genellikle sınırlar arası değerlerin güvenilir bir şekilde taklit edilmesinin bir yolu yoktur!

Neden C ++ referanslarını yararlı buluyorum?

C arkaplanından gelen C ++ referansları biraz aptalca bir konsepte benzeyebilir, ama yine de mümkün olan yerlerde işaretçiler yerine bunları kullanmalıdır: Otomatik indirme olduğu uygun ve referanslar özellikle RAII - ancak algılanan güvenlik avantajından dolayı değil, daha ziyade yazımsal kod yazmayı daha az zorlaştırdıkları için.

RAII, C ++ 'nın temel kavramlarından biridir, ancak kopyalama semantiği ile önemsiz olarak etkileşir. Nesneleri referans olarak almak, kopyalama yapılmadığı için bu sorunları önler. Eğer dilde referanslar mevcut değilse, kullanmak yerine daha elverişli olan işaretçiler kullanmanız gerekir, bu nedenle en iyi uygulama çözümünün alternatiflerden daha kolay olması gerektiği dil tasarım ilkesini ihlal eder.


301
2017-09-11 21:43



Krisler: Hayır, ayrıca bir otomatik değişkeni referans olarak döndürerek sarkan bir referans elde edebilirsiniz. - Ben Voigt
Kriss: Bir derleyicinin genel durumda tespit edilmesi neredeyse imkansız. Bir sınıf üyesi değişkenine bir başvuru döndüren üye işlevini düşünün: Bu güvenlidir ve derleyici tarafından yasaklanmamalıdır. Daha sonra, o sınıfın otomatik bir örneğine sahip bir arayan, üye işlevini çağırır ve referansı döndürür. Presto: sarkan referans. Ve evet, sorun yaratacak, Kriss: bu benim amacım. Pek çok kişi, işaretçilerle ilgili referansların avantajının, referansların her zaman geçerli olduğunu, ancak böyle olmadığını iddia ediyor. - Ben Voigt
Krisler: Hayır, otomatik depolama süresine bir başvuru geçici bir nesneden çok farklıdır. Her neyse, yalnızca geçersiz bir işaretçiyi iptal ederek geçersiz bir başvuru alabileceğinizi beyanınıza karşı bir örnek teşkil ediyordum. Christoph doğrudur - referanslar işaretçilerden daha güvenli değil, referansları kullanan bir program hala tip güvenliği kırıyor olabilir. - Ben Voigt
Referanslar bir çeşit işaretçi değildir. Mevcut bir nesne için yeni bir isim. - catphive
@catphive: Eğer gerçekte uygulamaya bakacak olursanız, dil anlambilimine göre giderseniz, doğru değil; C ++, C'den çok daha 'büyülü' bir dildir ve sihiri referanslardan çıkarırsanız, bir işaretçi ile sonuçlanırsınız. - Christoph


Gerçekten akılda kalıcı olmak istiyorsanız, bir işaretçiyle 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 bir const referansı bağlarsanız, o nesnenin ömrü referansın ömrü olur.

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 sonucu olan geçici nesneyi kopyalar. Oysa s3_reference özünde geçici nesne olur. Bu gerçekten referans ile aynı ömrüne sahip geçici bir nesneye referanstır.

Eğer bunu denemeden yaparsan const Derleme başarısız olmalı. Yapılandırılmamış bir başvuruyu geçici bir nesneye bağlayamazsınız veya bu konuyla ilgili adresi alamazsınız.


152
2017-09-11 21:06



ama bunun için kullanım durumu nedir? - Ahmad Mushtaq
Eh, s3_copy bir geçici oluşturmak ve sonra s3_reference doğrudan geçici kullanmaktadır, sonra s3_copy içine kopyalamak. Daha sonra gerçekten akılda kalıcı olmak için, derleyicinin ilk durumda kopya oluşturma işlemini eline almasına izin verilen Dönüş Değeri Optimizasyonuna bakmanız gerekir. - Matt Price
@digitalSurgeon: Orada sihir oldukça güçlüdür. Nesne ömrü, gerçekte const & bağlama ve sadece referans kapsam dışında kaldığında gerçek başvurulan tip (bir taban olabilir referans türüne kıyasla) denir. Bir referans olduğundan, aralarında dilimleme olmayacaktır. - David Rodríguez - dribeas
C ++ 11 için güncelleştirme: son tümceyi "const olmayan bir lvalue başvurusunu geçici olarak bağlayamazsınız" okumanız gerekir. kutu const olmayan rvalue geçici bir referans ve aynı ömür boyu uzanan davranışa sahiptir. - Oktalist
@AhmadMushtaq: Bunun anahtar kullanımı türetilmiş sınıflar. Eğer miras dahil edilmemiş ise, RVO / Taşınması nedeniyle ucuz veya bedelsiz olan değer semantiklerini de kullanabilirsiniz. Ama eğer sahipseniz Animal x = fast ? getHare() : getTortoise() sonra x klasik dilimleme problemiyle karşı karşıya gelecek Animal& x = ... doğru çalışacaktır. - Arthur Tacca


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)

Verilen bir referansla yapılması çok daha zordur, ancak bunu yönetirseniz, saçınızı bulmaya çalışarak koparırsınız. Referanslar değil C ++ 'da doğal olarak güvenli!

Teknik olarak bu bir geçersiz referans, boş bir başvuru değil. C ++, diğer dillerde bulabileceğiniz gibi null referanslarını bir kavram olarak desteklemez. Başka türde geçersiz referanslar da var. herhangi geçersiz referans hayaleti yükseltir tanımlanmamış davranışSadece geçersiz bir işaretçi kullanmak gibi.

Asıl hata, bir referansa atanmadan önce NULL işaretçisinin kaldırılmasıdır. Ama bu durumda herhangi bir hata üretecek herhangi bir derleyiciden haberdar değilim - hata kod boyunca bir noktaya yayılıyor. Bu sorunu bu kadar sinsice yapan şey budur. Çoğu zaman, bir NULL işaretçisini kullanırsanız, o noktaya doğru kilitlenirsiniz ve bunu çözmek için çok fazla hata ayıklama yapmazsınız.

Yukarıdaki örneğim kısa ve çekişmeli. İşte daha gerçek bir dünya örneği.

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);

Null referansı almanın tek yolunun hatalı biçimlendirilmiş kodlar olduğunu ve bunu yaptıktan sonra tanımlanmamış davranışlar aldığını tekrarlamak istiyorum. O asla boş bir başvuru olup olmadığını kontrol etmek anlamlıdır; örneğin deneyebilirsiniz if(&bar==NULL)... Ancak derleyici ifadeyi varoluştan en iyi duruma getirebilir! Geçerli bir başvuru asla NULL olamaz, bu yüzden derleyicinin görünümünden karşılaştırma her zaman yanlıştır ve ortadan kaldırılır. if Ölü kod olarak fıkra - bu tanımlanmamış davranışların özüdür.

Sorunlardan uzak kalmanın doğru yolu, bir referans oluşturmak için bir NULL işaretçisini devre dışı bırakmaktan kaçınmaktır. İş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 bkz. Boş Referanslar Jim Hyslop ve Herb Sutter'den.

Bir dereferencenin tehlikelerinin başka bir örneği için boş bir işaretçi bkz. Başka bir platforma bağlantı kodu denerken tanımlanmamış davranışları gösterme Raymond Chen tarafından.


104
2017-09-11 20:07



Söz konusu kod tanımlanmamış davranış içerir. Teknik olarak, ayarlamak dışında bir boş gösterici ile hiçbir şey yapamazsınız ve karşılaştırın. Programınız tanımlanmamış bir davranışı çağırdığında, büyük patrona bir demo verene kadar doğru şekilde çalışmayı içeren her şeyi yapabilir. - KeithB
işareti geçerli bir argüman var. Bir işaretçinin NULL olabileceğine dair argüman ve kontrol etmek zorunda olduğunuz argüman da gerçek değildir: Eğer bir fonksiyonun NULL gerektirmediğini söylerseniz, arayan kişi bunu yapmak zorundadır. böylece arayan, tanımlanmamış davranışları çağırıyorsa. mark gibi kötü referans ile yaptı - Johannes Schaub - litb
Açıklama hatalı. Bu kod, NULL olan bir referans oluşturabilir veya oluşturmayabilir. Davranışı tanımlanmamıştır. Mükemmel bir referans oluşturabilir. Hiçbir şekilde referans oluşturulamayabilir. - David Schwartz
@David Schwartz, eğer işler standartlara göre çalışmak zorunda olsaydı, doğru olursun. Ama bu değil Ne hakkında konuştuğum - Çok popüler bir derleyici ile gerçek gözlemlenen davranıştan bahsediyorum ve tipik derleyiciler ve CPU mimarileri hakkındaki bilgi birikimine dayanarak ekstrapolasyon yapıyorum. muhtemelen olmak. Referansların, daha güvenli olduklarından ve bu referansların kötü olabileceğini düşünmediklerinden, işaretçilerden daha üstün olacağına inanıyorsanız, bir gün benim gibi basit bir sorunla karşılaşacaksınız. - Mark Ransom
Boş bir işaretçiyi iptal etme yanlış. Bunu yapan herhangi bir program, bir referansı başlatmak için bile yanlıştır. Bir işaretçiden referans başlatıyorsanız, işaretçinin geçerli olduğunu her zaman kontrol etmelisiniz. Altında yatan nesne başarılı olsa bile, herhangi bir anda var olmayan nesneye başvurmak için referans bırakarak silinebilir, değil mi? Söylediklerin iyi şeyler. Bence asıl mesele şu ki, referansın “boşluksuzluk” için kontrol edilmesi gerekmediğini ve işaretçinin, en azından, iddia edildiği gibi olması gerektiğini. - t0rakka


Sözdizimsel şekerden ayrı olarak, bir referans const Işaretçi (değil bir işaretçi const). Referans değişkenini bildirdiğinizde başvurduğu şeyi kurmalısınız ve daha sonra değiştiremezsiniz.

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

Bir const işaretçisinin hedefi adresini alarak ve bir const cast kullanarak değiştirilebilir.

Bir referansın hedefi UB'nin herhangi bir yolunda değiştirilemez.

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


97
2017-09-11 22:10



Bunun en iyi cevap olduğunu düşünüyorum. Diğerleri, farklı canavarlar oldukları gibi referanslar ve işaretçiler hakkında konuşurlar ve daha sonra davranışlarında nasıl farklılaştıklarını söylerler. İşleri kolaylaştıramaz. Ben her zaman referansları T* const farklı sözdizimsel şeker ile (çok fazla * ve kodunuzdan kurtulmak için). - Carlo Wood
yapmaya çalışmayı hayal et cout << 42 << "hello" << endl referanslar olmadan - pm100
Kısa ve en iyi cevap - Kad
"Bir const işaretçisinin hedefi adresini alarak ve bir const cast kullanarak değiştirilebilir." Bunu yapmak, tanımlanmamış davranışlardır. Görmek stackoverflow.com/questions/25209838/... detaylar için. - dgnuff
Bir başvurunun başvurusunu veya bir const işaretçisinin değerini (veya herhangi bir const scalar) değiştirmeye çalışmak eşitliktir. Yapabilecekleriniz: örtülü dönüşüm tarafından 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


En önemli kısmı unuttun:

işaretçi ile üye erişimi kullanır -> 
referansları ile üye erişim .

foo.bar olduğu Açıkça a karşı üstün foo->bar aynı şekilde vi olduğu Açıkça a karşı üstün Emacs :-)


96
2017-09-19 12:23



@Orion Edwards> işaretçi ile üye erişimi kullanır ->> referansları kullanan üye girişi. Bu% 100 doğru değildir. İşaretçiye referans alabilirsiniz. Bu durumda, -> struct Node {Node * 'i kullanarak referans dışı işaretçinin üyelerine erişirsiniz; }; Düğüm * ilk; // p, bir işaretçi void foo'yu (Düğüm * & p) {p-> next = first; } Düğüm * bar = yeni Düğüm; fan (bar); - OP: Rvalues ​​ve lvalues ​​kavramlarını biliyor musunuz?
Akıllı İşaretçiler'in ikisi de var. (akıllı işaretçi sınıfındaki yöntemler) ve -> (temeldeki yöntemler). - JBRWilkinson
@ user6105 Orion Edwards ifade aslında% 100 doğrudur. "referans verilen işaretçinin erişim üyelerine" Bir işaretçinin herhangi bir üyesi yok. İşaretçinin işaret ettiği nesne üyeleri vardır ve bunlara erişim tam olarak nedir? -> İşaretçinin referanslarını, işaretçinin kendisinde olduğu gibi sağlar. - Max Truxa
Neden . ve -> vi ve emacs ile ilgisi var :) - artm
@artM - bir şakaydı ve muhtemelen yerli olmayan ingilizce konuşmacılar için anlamlı değil. Özür dilerim. VI'nın emaclardan daha iyi olup olmadığını açıklamak tamamen özneldir. Bazı insanlar vi'nin çok üstün olduğunu düşünüyor ve diğerleri tam tersini düşünüyor. Aynı şekilde kullanarak düşünüyorum . kullanmaktan daha iyidir ->ama tıpkı emacs gibi, tamamen özneldir ve hiçbir şeyi kanıtlayamazsınız. - Orion Edwards


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

Bir derleyici, değişkenlere "referanslar" ekleyerek bir adın bir bellek adresiyle ilişkilendirilmesini sağlar; Bu, derleme sırasında herhangi bir değişken adını bir bellek adresine çevirmek için yaptığı iştir.

Bir başvuru oluşturduğunuzda, yalnızca derleyiciye işaretçi değişkenine başka bir ad verdiğinizi söylersiniz; Bu yüzden referanslar “null'a işaret etmez”, çünkü bir değişken olamaz ve olamaz.

İşaretçiler değişkenlerdir; başka bir değişkenin adresini içerirler veya boş olabilirler. Önemli olan, bir işaretçinin bir değere sahip olması, bir referansın ise sadece referans aldığı bir değişkene sahip olmasıdır.

Ş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; Değerinizi tutan bellek içeriğine yeni bir ad ekliyorsunuz a. Bu anı artık iki isim var. a ve bve adıyla da ele alınabilir.

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. Fonksiyon imzası, yaratılması gereken alanları tanımlar ve bu alanlar için kullanılması gereken ismi verir. Bir parametrenin referans olarak bildirilmesi, yalnızca derleyici için yöntem çağrısı sırasında yeni bir bellek alanı ayırmak yerine girdi değişkeni bellek alanını kullanmasını söyler. İşlevinizin çağrı kapsamı içinde bildirilen bir değişkeni doğrudan manipüle edeceğini söylemek garip gelebilir, ancak derlenmiş kodu yürütürken daha fazla kapsam olmadığını unutmayın; sadece düz düz bellek var ve fonksiyon kodunuz herhangi bir değişkene zarar verebilir.

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


57
2017-09-01 03:44



Bir referans, mutlaka bir değişkene değil, l-değerine bir referanstır. Bu nedenle, bir işaretçiye gerçek bir takma addan (derleme zamanı yapısı) çok daha yakındır. Başvurulan ifadelerin örnekleri * p veya hatta * p ++ - Arkadiy
Doğru, bir referansın her zaman yeni bir değişkeni her zaman yeni bir işaretçinin olduğu şekilde yığına itemeyeceği gerçeğini işaret ediyordum. - Vincent Robert
@VincentRobert: Bir işaretçi ile aynı şekilde hareket eder ... işlev satır içi ise, hem referans hem de işaretçi en iyi duruma getirilir. Bir işlev çağrısı varsa, nesnenin adresinin işleve geçirilmesi gerekir. - Ben Voigt
int * p = NULL; int & r = * p; NULL'a işaret eden başvuru; eğer (r) {} -> boOm;) - sree
Derleme aşamasına odaklanmanız, referansların çalışma zamanında aktarılabildiğini ve bu noktada statik takma adın pencereden dışarı çıktığını hatırlayana kadar iyi görünüyor. (Ve sonra, referanslar genellikle işaretçi olarak uygulandı, ancak standart bu yöntemi gerektirmiyor.) - underscore_d


Referanslar işaretçilere çok benzer, ancak derleyicileri optimize etmek için özel olarak hazırlanmışlardır.

  • Referanslar, derleyicinin hangi değişkenleri hangi referans takma adlarını izleyeceğini önemli ölçüde kolaylaştıracak şekilde tasarlanmıştır. İki ana özellik çok önemlidir: "referans aritmetiği" yok ve referansların yeniden atanması yok. Bu, derleyicinin, derleme zamanında hangi değişkenleri takma adlarını kullanabileceğini belirlemesine izin verir.
  • Referansların, derleyicinin kayıt altına almayı seçtiği gibi bellek adresleri olmayan değişkenlere başvurmasına 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];
    }
}

Optimizasyon yapan bir derleyici, bir [0] ve bir [1] erişebildiğimizin farkında olabilir. Algoritmayı şu şekilde optimize etmeyi çok isterim:

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, çağrı sırasında hiçbir şeyin dizi [1] 'i değiştiremediğini kanıtlaması gerekir. Bu yapmak oldukça kolay. i asla 2'den az değildir, yani dizi [i] diziye [1] asla atıfta bulunamaz. belkiModify (), bir referans olarak (atama dizisi [0]) a0 olarak verilir. "Referans" aritmetiği olmadığından, derleyici, belkiModify'ın x'in adresini asla almadığını kanıtlamak zorundadır ve hiçbir şeyin dizi değiştirmediğini kanıtlamıştır [1].

Aynı zamanda gelecekteki bir aramanın bir [0] 'ı okuyabileceği / yazacağı hiçbir yolun olmadığını kanıtlamalıyız. Bu genellikle kanıtlamak için önemsizdir, çünkü birçok durumda referansın bir 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; sadece şimdi, belkiModify'ın diziyi değiştirmediğini ispatlamak çok daha zordur, çünkü biz zaten bir işaretçi verdik; kedi çantadan çıktı. Şimdi çok daha zor bir kanıtı yapmak zorunda: BelkiModify'ın hiçbir zaman & x + 1'e yazamadığını ispatlamak için statik bir analizi. Aynı zamanda, diziye [0] atıfta bulunabilecek bir işaretçiyi asla kurtaramayacağını kanıtlamak zorundadır. zor olarak.

Modern derleyiciler statik analizde daha iyi ve daha iyi oluyorlar, ancak onlara yardım etmek ve referansları kullanmak her zaman güzeldir.

Elbette, böylesine akıllı optimizasyonları, derleyicileri kısıtlamak, gerektiğinde referansları işaretçiler haline getirecektir.


50
2017-09-11 20:12



Doğru, vücudun görünür olması gerekiyor. Ancak, bunu belirleme maybeModify ilgili herhangi bir şeyin adresini almaz x Bir dizi işaret aritmetiğinin meydana gelmediğini kanıtlamaktan önemli ölçüde daha kolaydır. - Cort Ammon
Optimizatörün zaten bir "işaretçi aritemiği grubu oluşmadığı" gibi bir sürü başka neden olup olmadığını kontrol ettiğini düşünüyorum. - Ben Voigt
"Referanslar işaretçilere çok benzer" - semantik olarak, uygun bağlamlarda - ancak üretilen kod açısından, yalnızca bazı uygulamalarda ve herhangi bir tanım / gereksinim ile değil. Bunu açıkladığınızı biliyorum ve pratik yönlerinizle herhangi bir gönderiye katılmıyorum, ama zaten çok fazla problemimiz var: 'referanslar gibidir / genellikle işaretçiler gibi uygulanır' . - underscore_d
Birinin yanlış bir şekilde yanlış yorumlandığını hissettim. void maybeModify(int& x) { 1[&x]++; }Yukarıdaki diğer yorumlar tartışıyor - Ben Voigt


Bir referans asla olamaz NULL.


32
2018-05-20 19:26



Bir karşı örnek için Mark Ransom'un cevabına bakınız. Bu, referanslar hakkında en çok savunduğu efsanedir, ama bu bir efsanedir. Standart tarafından sahip olduğunuz tek garanti, NULL referansınız olduğunda hemen UB'ye sahip olmanızdır. Ama bu, "Bu araba güvende, yoldan asla çıkamayacağımıza benziyor. (Zaten yoldan çekilirsen ne olabileceğine dair herhangi bir sorumluluk almayız. Sadece patlayabilir.") - cmaster
@cmaster: Geçerli bir programda, bir başvuru boş olamaz. Ama bir işaretçi yapabilir. Bu bir efsane değil, bu bir gerçek. - Mehrdad
@Mehrdad Evet, geçerli programlar yolda kalıyor. Ancak, programınızın gerçekte uygulandığını uygulamak için herhangi bir trafik engeli yoktur. Yolun büyük kısımları aslında eksik işaretlerdir. Yani gece yoldan çıkmak son derece kolaydır. Ve bu tür hataları ayıklamak için çok önemlidir bilmek Bu olabilir: null başvuru, programınızı çökerten önce yayılabilir, tıpkı bir boş gösterici gibi. Ve ne zaman ki gibi kodunuz var void Foo::bar() { virtual_baz(); } bu segfaults. Referansların boş olabileceğinin farkında değilseniz, boş değeri kökeni tekrar izleyemezsiniz. - cmaster
int * p = NULL; int & r = * p; NULL'a işaret eden başvuru; eğer (r) {} -> boOm;) - - sree
@sree int &r=*p; tanımlanmamış davranıştır. Bu noktada, "NULL'a işaret eden bir referansınız" yok, artık sebep olamaz hiç. - cdhowie


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, bir referansın her zaman bir nesneyi ifade etmesidir: Bir referansı başlatmadan tanımlamak bir hatadır. Ödevin davranışı ikinci önemli farktır: Referansa atanmak, referansın bağlı olduğu nesneyi değiştirir; Başka bir nesneye yapılan referansı tekrarlamaz. Bir kez başlatıldığında, bir referans 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

Görevden 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, referansın kendisi değil, ri tarafından referans verilen değer olan ival'i değiştirir. Ödevden sonra, iki referans hala orijinal nesnelerine atıfta bulunur ve bu nesnelerin değeri şimdi de aynıdır.


30
2017-10-29 17:17



"bir referans her zaman bir nesneyi ifade eder" sadece tamamen yanlıştır - Ben Voigt