İşaretçi ve Referans


256

Bir işleve çalışmak için orijinal değişken verirken daha iyi uygulama ne olabilir:

unsigned long x = 4;

void func1(unsigned long& val) {
     val = 5;            
}
func1(x);

veya:

void func2(unsigned long* val) {
     *val = 5;
}
func2(&x);

IOW: Birini diğerinin üzerine seçmek için bir sebep var mı?


1
Referanslar elbette değerlidir, ancak işaretçilerin her yerde olduğu C'den geliyorum. Referansların değerini anlamak için önce işaretçilerle yetkin olmak gerekir.
Jay D

Bu, işlevsel programlamanın referans şeffaflığı gibi bir hedefe nasıl uyuyor? İşlevlerin her zaman yeni nesneler döndürmesini ve durumu asla dahili olarak değiştirmemesini istiyorsanız, özellikle işleve iletilen değişkenlerin değil. Bu kavram hala C ++ gibi bir dilde işaretçiler ve referanslar ile kullanılan bir yolu var mı. (Not, birisinin zaten referans şeffaflığı hedefi olduğunu varsayıyorum. Sahip olmak için iyi bir hedef olup olmadığı hakkında konuşmak istemiyorum.)
ely

Referansları tercih edin. Seçeneğiniz olmadığında kullanıcı işaretçileri.
Ferruccio

Yanıtlar:


285

Temel kuralım:

İşaretçilerle onlarla aritmetik yapmak istiyorsanız (örneğin, bir dizide adım atmak için işaretçi adresini artırma) veya hiç NULL-işaretçisi geçmeniz gerektiğinde işaretçileri kullanın.

Aksi takdirde referans kullanın.


10
İşaretçinin NULL olmasıyla ilgili mükemmel nokta. Bir pointer parametreniz varsa, açıkça NULL olmadığını kontrol etmeli veya asla NULL olmadığından emin olmak için işlevin tüm kullanımlarını aramalısınız. Bu çaba referanslar için gerekli değildir.
Richard Corden

26
Aritmetik ile ne demek istediğinizi açıklayın. Yeni bir kullanıcı, işaretçinin işaret ettiği şeyi ayarlamak istediğinizi anlamayabilir.
Martin York

7
Martin, aritmetik olarak bir yapıya bir işaretçi geçirdiğinizi ama bunun basit bir yapı değil, bir dizi olduğunu bildiğinizi kastediyorum. Bu durumda [] kullanarak dizine ekleyebilir veya işaretçide ++ / - kullanarak aritmetik yapabilirsiniz. Kısacası fark bu.
Nils Pipenbrinck

2
Martin, Bunu sadece doğrudan işaretçilerle yapabilirsin. Referanslarla değil. Tabii referans için bir işaretçi almak ve aynı şeyi pratikte yapabilirsiniz, ancak bunu yaparsanız çok kirli kod ile biter ..
Nils Pipenbrinck

1
Polimorfizm (örneğin Base* b = new Derived()) ne olacak ? Bu, işaretçiler olmadan ele alınamayan bir durum gibi görünüyor.
Chris Redford

72

Kodlama yönergelerini çağıran aşağıdaki işlevi oluşturmaktan gerçekten fayda sağlayacağınızı düşünüyorum:

  1. Tüm diğer yerlerde olduğu gibi, her zaman const-doğru olun.

    • Not: Bu, diğer şeylerin yanı sıra, yalnızca çıkış değerlerinin (madde 3'e bakın) ve değerden geçen değerlerin (madde 4'e bakın) belirleyiciden yoksun olabileceği anlamına gelir const.
  2. Yalnızca 0 / NULL değeri geçerli bağlamda geçerli bir girdi ise, işaretçiyi yalnızca işaretçi olarak iletin.

    • Gerekçe 1: Arayan olarak gördüğünüz her şeyin kullanılabilir durumda olması gerektiğini görürsünüz .

    • Gerekçe 2: As denilen İçinde gelirse biliyoruz olan kullanılabilir bir durumda. Bu nedenle, bu değer için NULL denetim veya hata işlemeye gerek yoktur.

    • Gerekçe 3: Gerekçeler 1 ve 2 derleyici tarafından uygulanacaktır . Mümkünse her zaman hataları derleme zamanında yakalayın.

  3. Bir işlev bağımsız değişkeni bir çıkış değeriyse, başvuru ile iletin.

    • Gerekçe: 2. maddeyi kırmak istemiyoruz ...
  4. Yalnızca değerin bir POD ( Düz eski Veri Yapısı ) veya yeterince küçük (bellek açısından) veya başka şekillerde kopyalamak için yeterince ucuz (zaman açısından) olması durumunda "sabit referansla geçiş" üzerinden "değere göre geç" seçeneğini belirleyin.

    • Gerekçe: Gereksiz kopyalardan kaçının.
    • Not: yeterince küçük ve yeterince ucuz mutlak ölçülebilir değildir.

Aşağıdaki durumlarda kılavuzdan yoksundur: ... "const kullanıldığında &" ... [2] değerleri için "kılavuz 2" yazılmalıdır, yalnızca NULL geçerliyse işaretçi ile iletilmelidir. Aksi takdirde const referansını (veya " küçük "nesneler, kopya) veya referans [out] değeri ise referans. Potansiyel olarak +1 eklemek için bu
gönderiyi izliyorum

Öğe 1, tarif ettiğiniz durumu kapsar.
Johann Gerell

Varsayılan olarak oluşturulamazsa, bir out parametresini referans olarak iletmek biraz zordur. Bu benim kodumda oldukça yaygındır - bir fonksiyonun o nesneyi yaratmasının bütün sebebi önemsiz değildir.
MSalters

@MSalters: Belleği işlevin içinde tahsis edecekseniz (ki bunun ne demek istediğinizi düşünüyorum), o zaman neden sadece tahsis edilen belleğe bir işaretçi döndürmüyorsunuz?
Kleist

@Kleist: @MSalters adına, bunun birçok olası nedeni vardır. Birincisi, önceden boyutlandırılmış gibi doldurmak için zaten bellek ayırmış olabilirsiniz std::vector<>.
Johann Gerell

24

Bu nihayetinde özneldir. Şimdiye kadar tartışma faydalıdır, ancak bunun doğru veya kesin bir cevabı olduğunu düşünmüyorum. Çok şey stil yönergelerine ve o sırada ihtiyaçlarınıza bağlı olacaktır.

Bir işaretçi ile bazı farklı yetenekler (bir şeyin NULL olup olmadığı) olsa da, bir çıkış parametresi için en büyük pratik fark tamamen sözdizimidir. Örneğin, Google'ın C ++ Stil Kılavuzu ( https://google.github.io/styleguide/cppguide.html#Reference_Arguments ) yalnızca çıktı parametreleri için işaretçiler zorunlu kılar ve yalnızca const referanslarına izin verir. Akıl yürütme okunabilirliktir: değer sözdizimine sahip bir şeyin işaretçi anlamsal anlamı olmamalıdır. Bunun mutlaka doğru ya da yanlış olduğunu öne sürmüyorum, ama bence buradaki mesele, doğruluk değil, bir stil meselesi.


Referansların değer sözdizimine sahip, ancak işaretçi anlamsal anlamı ne demektir?
Eric Andrew Lewis

Görünüşe göre "referansla geç" kısmı sadece funciton tanımından (değer sözdizimi) anlaşılır, ancak geçtiğiniz değeri kopyalamıyorsunuz, esas olarak kaputun altına bir işaretçi geçiriyorsunuz. değerini değiştirme işlevi.
phant0m

Unutmamak gerekir ki, Google C ++ stil rehberi çok mahrumdur.
Tekilleştirici

7

Değişkenin değerini değiştirecekseniz bir işaretçi geçmelisiniz. Teknik olarak bir referans veya işaretçi geçirme aynı olsa da, kullanım durumunuzda bir işaretçiyi iletmek, değerin işlev tarafından değiştirileceği gerçeğini "tanıdığı" için daha okunabilir.


2
Johann Gerell yönergelerini izlerseniz, sabit olmayan bir başvuru da değiştirilebilir bir değişkeni tanıtır, bu nedenle burada işaretçinin bu avantajı yoktur.
Alexander Kondratskiy

4
@AlexanderKondratskiy: Eğer ... anında göremiyorum noktayı kaçırıyorsun çağrı yerinde denilen işlevi gibi bir Paramtre kabul edip etmediğini constya da olmayan constreferans, ancak parametre en ala geçtiyseniz görebilirsiniz &xVS. xve kullanımı parametrenin değiştirilmeye yatkın olup olmadığını kodlamak için bu konvrasyon. (Yani, bir constişaretçi geçmek isteyeceğiniz zamanlar vardır , bu yüzden konveksiyon sadece bir ipucu. Bir şeyin olmayacağından şüphelenilebileceğinden şüphelenmek, değişmeyeceğinden şüphelenmekten daha az tehlikelidir. ....)
Tony Delroy

5

Bir değerin yokluğunu belirtmeniz gerekebilecek bir parametreniz varsa, parametreyi bir işaretçi değeri yapmak ve NULL olarak iletmek yaygın bir uygulamadır.

Çoğu durumda (güvenlik açısından) daha iyi bir çözüm, boost :: isteğe bağlı kullanmaktır . Bu, isteğe bağlı değerleri referans olarak ve bir dönüş değeri olarak iletmenizi sağlar.

// Sample method using optional as input parameter
void PrintOptional(const boost::optional<std::string>& optional_str)
{
    if (optional_str)
    {
       cout << *optional_str << std::endl;
    }
    else
    {
       cout << "(no string)" << std::endl;
    }
}

// Sample method using optional as return value
boost::optional<int> ReturnOptional(bool return_nothing)
{
    if (return_nothing)
    {
       return boost::optional<int>();
    }

    return boost::optional<int>(42);
}


4

İşaretçiler

  • İşaretçi, bellek adresini tutan bir değişkendir.
  • İşaretçi bildirimi temel tür, * ve değişken adından oluşur.
  • Bir işaretçi ömür boyu herhangi bir sayıda değişkeni gösterebilir
  • Şu anda geçerli bir bellek konumuna işaret etmeyen bir işaretçiye null değeri verilir (Sıfır olan)

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • &, İşleneninin bellek adresini döndüren tekli bir operatördür.

  • Dereferencing operatörü (*), işaretçinin işaret ettiği değişkende depolanan değere erişmek için kullanılır.

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;

Referans

  • Bir başvuru (&), mevcut bir değişkenin takma adı gibidir.

  • Bir başvuru (&), otomatik olarak kaydı silinen sabit bir işaretçi gibidir.

  • Genellikle işlev bağımsız değişken listeleri ve işlev döndürme değerleri için kullanılır.

  • Bir referans oluşturulduğunda başlatılmalıdır.

  • Bir nesneye referans başlatıldıktan sonra, başka bir nesneye atıfta bulunmak için değiştirilemez.

  • NULL referansınız olamaz.

  • Bir const referansı, bir const int. Sabit değere sahip geçici bir değişkenle yapılır

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization

resim açıklamasını buraya girin

resim açıklamasını buraya girin


3

Referans, örtük bir işaretçi. Temel olarak referansın işaret ettiği değeri değiştirebilirsiniz, ancak referansı başka bir şeye işaret edecek şekilde değiştiremezsiniz. Bu yüzden 2 sentim, sadece bir parametrenin değerini değiştirmek istiyorsanız, referans olarak geçmesi, ancak parametreyi farklı bir nesneye işaret edecek şekilde değiştirmeniz gerekiyorsa, bir işaretçi kullanarak geçirin.


3

C # 's out anahtar sözcüğünü düşünün. Derleyici, zaten olup olmadıklarını bilmesine rağmen, out anahtar sözcüğünü herhangi bir bağımsız değişkene uygulamak için bir yöntemin aranmasını gerektirir. Bu, okunabilirliği artırmayı amaçlamaktadır. Her ne kadar modern IDE'lerde bunun sözdizimi (veya anlamsal) vurgulama için bir iş olduğunu düşünmeye meyilliyim.


yazım hatası: semantik değil, anlamsal; +1 (C #) veya & (C olması halinde referans yok) yazmak yerine vurgulama olasılığı konusunda hemfikirim
peenut

2

İçeriği değiştirmek / saklamak istemeniz için bir neden olmadığı sürece const referansı ile iletin.

Bu çoğu durumda en etkili yöntem olacaktır.

Değiştirmek istemediğiniz her parametrede const kullandığınızdan emin olun, çünkü bu sadece işlevde aptalca bir şey yapmaktan korumakla kalmaz, aynı zamanda diğer kullanıcılara işlevin iletilen değerlere ne yaptığını iyi bir şekilde gösterir. Bu sadece işaret edilen şeyi değiştirmek istediğinizde bir işaretçi const yapmayı içerir ...


2

İşaretçiler:

  • Atanabilir nullptr(veyaNULL ).
  • Çağrı sitesinde şunu kullanmalısınız: & türünüz bir işaretçinin kendisi değilse, nesneyi açıkça değiştirdiğinizde kullanmanız gerekir.
  • İşaretçiler geri tepebilir.

Referanslar:

  • Boş olamaz.
  • Bağlandıktan sonra değişemez.
  • Arayanların açıkça kullanmasına gerek yoktur &. Parametrenizin değiştirilip değiştirilmediğini görmek için işlevin uygulanmasına gitmeniz gerektiğinden bu bazen kötü kabul edilir.

Bilmeyenler için küçük bir nokta: nullptr veya NULL sadece 0'dır. Stackoverflow.com/questions/462165/…
Serguei Fedorov

2
nullptr, 0 ile aynı değildir. Try int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Johan Lundberg

0

Referans, işaretçiye benzer, ancak referans tarafından belirtilen değere erişmek için ∗ önekini kullanmanız gerekmez. Ayrıca, başlatıldıktan sonra farklı bir nesneye gönderme yapmak için başvuru yapılamaz.

Referanslar özellikle işlev bağımsız değişkenlerini belirtmek için kullanışlıdır.

daha fazla bilgi için bkz. "Bjarne Stroustrup" dan "C ++ Turu" (2014) Sayfa 11-12

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.