C ++ dilinde "int & foo ()" ne anlama geliyor?


114

Değerler ve değerler hakkındaki bu açıklamayı okurken , bu kod satırları bana yapışmıştı:

int& foo();
foo() = 42; // OK, foo() is an lvalue

Bunu g ++ 'da denedim, ancak derleyici "foo ()' ya tanımsız başvuru" diyor. Eklersem

int foo()
{
  return 2;
}

int main()
{
  int& foo();
  foo() = 42;
}

İyi derler, ancak çalıştırıldığında segmentasyon hatası verir . Sadece çizgi

int& foo();

kendi başına hem derler hem de sorunsuz çalışır.

Bu kod ne anlama geliyor? Bir fonksiyon çağrısına nasıl değer atayabilirsiniz ve bu neden bir rvalue değildir?


25
Sen bir değer atama değiliz bir işlev çağrısı da değer atıyorsanız, Döndürdüğü referans .
Frédéric Hamidi

3
@ FrédéricHamidi, döndürülen referansın atıfta bulunduğu nesneye bir değer
MM

3
Evet, nesne için bir takma addır; referans nesne değil
MM

2
@ RoyFalk yerel işlev bildirimleri bir çiçek hastalığıdır, şahsen bunların dilden kaldırıldığını görmekten mutlu olurum. (Elbette lambdalar hariç)
MM

4
Bunun için zaten bir GCC hatası var .
jotik

Yanıtlar:


168

Açıklama, foogeçerli bir değer için bir ldeğer referansı döndüren bazı makul uygulamalar olduğunu varsaymaktadır.int .

Böyle bir uygulama şunlar olabilir:

int a = 2; //global variable, lives until program termination

int& foo() {
    return a;
} 

Şimdi, foobir lvalue referansı döndürdüğünden, dönüş değerine şöyle bir şey atayabiliriz:

foo() = 42;

Bu, globali, değişkene doğrudan erişerek veya tekrar arayarak kontrol edebileceğimiz adeğerle güncelleyecektir :42foo

int main() {
    foo() = 42;
    std::cout << a;     //prints 42
    std::cout << foo(); //also prints 42
}

[] Operatörü gibi makul mü? Veya başka bir üye erişim yöntemleri?
Jan Dorniak

8
Referanslarla ilgili kafa karışıklığı göz önüne alındığında, statik yerel değişkenleri örneklerden çıkarmak muhtemelen en iyisidir. Başlatma, öğrenmeye engel olan gereksiz karmaşıklığı ekler. Sadece global yapın.
Mooing Duck

72

Diğer tüm yanıtlar, işlevin içinde bir duruk bildirir. Bunun kafanızı karıştırabileceğini düşünüyorum, o yüzden şuna bir bakın:

int& highest(int  & i, int  & j)
{
    if (i > j)
    {
        return i;
    }
    return j;
}

int main()
{
    int a{ 3};
    int b{ 4 };
    highest(a, b) = 11;
    return 0;
}

Çünkü highest()döner referans, ona bir değer atayabilirsiniz. Bu çalıştığında, b11 olarak değiştirilecektir. Eğer başlatmayı a, diyelim ki 8 aolarak değiştirdiyseniz , o zaman 11 olarak değiştirilecektir. Bu, diğer örneklerin aksine, aslında bir amaca hizmet edebilecek bir koddur.


5
İnt a = 3 yerine int a {3} yazmanın bir nedeni var mı? Bu sözdizimi bana uygun görünmüyor.
Aleksei Petrenko

6
@AlexPetrenko bu evrensel başlatma. Elimdeki örnekte kullanıyordum. Uygunsuz bir şey yok
Kate Gregory

3
Bunun ne olduğunu biliyorum. a = 3 çok daha temiz hissettiriyor. Kişisel tercih)
Aleksei Petrenko

Tıpkı bir işlevin içindeki durağanlığı bildirmek bazı kişilerin kafasını karıştırabileceği gibi, evrensel başlatmanın kullanılmasının da aynı derecede olası olduğuna inanıyorum.
ericcurtin

@AlekseiPetrenko int a = 1.3 olması durumunda örtük olarak a = 1'e dönüşür. İnt bir {1.3} hata ile sonuçlanır: dönüşüm daraltılır.
Sathvik

32
int& foo();

Foo adlı bir işlev bildirir ve bir int. Bu örneklerin yapamadığı şey, size derleyebileceğiniz işlevin bir tanımını vermektir. Eğer kullanırsak

int & foo()
{
    static int bar = 0;
    return bar;
}

Şimdi bir referans döndüren bir fonksiyonumuz var bar. bar, staticişlev çağrıldıktan sonra yaşayacağı için , ona bir referans döndürmek güvenlidir. Şimdi yaparsak

foo() = 42;

barReferansa atadığımız ve referansın sadece bir takma ad olduğu için 42'ye atadığımız şeydir bar. İşlevi tekrar şöyle çağırırsak

std::cout << foo();

barYukarıya ayarladığımızdan beri 42 basacaktır .


15

int &foo();foo()dönüş türü ile çağrılan bir işlevi bildirirint& . Bu işlevi bir gövde belirtmeden çağırırsanız, muhtemelen tanımlanmamış bir referans hatası alırsınız.

İkinci denemenizde bir işlev sağladınız int foo(). Bu, tarafından bildirilen işlevden farklı bir dönüş türüne sahiptir int& foo();. Dolayısıyla foo, eşleşmeyen ve tanımsız davranışa neden olan Tek Tanım Kuralı'nı ihlal eden iki bildiriminiz var (tanı gerektirmez).

Çalışan bir şey için yerel işlev bildirimini çıkarın. Gördüğünüz gibi sessiz tanımlanmamış davranışlara yol açabilirler. Bunun yerine, yalnızca herhangi bir işlevin dışındaki işlev bildirimlerini kullanın. Programınız şöyle görünebilir:

int &foo()
{
    static int i = 2;
    return i;
}  

int main()
{
    ++foo();  
    std::cout << foo() << '\n';
}

10

int& foo();bir başvuru döndüren bir işlevdir int. Sağladığınız işlev intreferans olmadan geri döner .

Yapabilirsin

int& foo()
{
    static int i = 42;
    return i;
}

int main()
{
    int& foo();
    foo() = 42;
}

7

int & foo();foo()bir değişkene referans döndüren anlamına gelir .

Bu kodu düşünün:

#include <iostream>
int k = 0;

int &foo()
{
    return k;
}

int main(int argc,char **argv)
{
    k = 4;
    foo() = 5;
    std::cout << "k=" << k << "\n";
    return 0;
}

Bu kod şunları yazdırır:

$ ./a.out k = 5

Çünkü foo()global değişkene bir referans verir k.

Düzeltilmiş kodunuzda, döndürülen değeri bir referansa çeviriyorsunuz, bu daha sonra geçersizdir.


5

Bu bağlamda &, bir başvuru anlamına gelir - dolayısıyla foo, bir int yerine bir int'e bir başvuru döndürür.

Henüz işaretçilerle çalışıp çalışmadığından emin değilim, ama bu benzer bir fikir, aslında değeri fonksiyonun dışına döndürmüyorsunuz - bunun yerine, hafızadaki konumu bulmak için gereken bilgiyi iletiyorsunuz. int is.

Özetlemek gerekirse, bir fonksiyon çağrısına bir değer atamıyorsunuz - bir referans almak için bir fonksiyon kullanıyorsunuz ve ardından referans alınan değeri yeni bir değere atıyorsunuz. Her şeyin aynı anda olduğunu düşünmek kolaydır, ancak gerçekte bilgisayar her şeyi kesin bir sırayla yapar.

Merak ediyorsanız - bir segfault almanın nedeni sayısal bir değişmez '2' döndürüyor olmanızdır - yani bu, bir const int tanımlayıp sonra onu değiştirmeye çalıştığınızda alacağınız hatadır. değer.

Henüz işaretçileri ve dinamik hafızayı öğrenmediyseniz, ilk önce hepsini bir kerede öğrenmediğiniz sürece anlamanın zor olduğunu düşündüğüm birkaç kavram olduğu için tavsiye ederim.


4

Bağlantılı sayfadaki örnek kod, yalnızca bir kukla işlev bildirimidir. Derlemez, ancak tanımlanmış bir işleviniz varsa, genellikle çalışır. Örnek, "Bu imzayla bir işleve sahip olsaydın, onu böyle kullanabilirdin" anlamına geliyordu.

Senin örnekte, fooaçıkça imza dayalı bir lvalue dönen, ancak bir lvalue dönüştürülür bir rvalue dönülür. Bu açıkça başarısız olmaya kararlı. Yapabilirsin:

int& foo()
{
    static int x;
    return x;
}

ve şunu söylerken x'in değerini değiştirerek başarılı olur:

foo() = 10;

3

Sahip olduğunuz işlev, foo (), bir tam sayıya başvuru döndüren bir işlevdir.

Diyelim ki başlangıçta foo 5 döndürdü ve daha sonra ana işlevinizde diyorsunuz foo() = 10;, sonra foo yazdırıyor, 5 yerine 10 yazdıracak.

Umarım bu mantıklıdır :)

Ben de programlamada yeniyim. Sizi düşündüren böyle sorular görmek ilginç! :)

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.