Co oznacza „int & foo ()” w C ++?

114

Podczas czytania tego wyjaśnienia na temat lvalues ​​i rvalues, wyskoczyły mi następujące wiersze kodu:

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

Wypróbowałem to w g ++, ale kompilator mówi „niezdefiniowane odwołanie do foo ()”. Jeśli dodam

int foo()
{
  return 2;
}

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

Kompiluje się dobrze, ale uruchomienie go powoduje błąd segmentacji . Tylko linia

int& foo();

sama się kompiluje i działa bez żadnych problemów.

Co oznacza ten kod? Jak przypisać wartość do wywołania funkcji i dlaczego nie jest to wartość r?

Sossisos
źródło
25
Nie przypisujesz wartości do wywołania funkcji , przypisujesz wartość do zwracanego odwołania .
Frédéric Hamidi
3
@ FrédéricHamidi przypisywanie wartości do obiektu, do którego odwołuje się zwrócone odwołanie
MM
3
Tak, to alias dla obiektu; odniesienie nie jest obiektem
MM,
2
Deklaracje funkcji lokalnych @RoyFalk to ospa, osobiście byłbym szczęśliwy, gdyby zostały usunięte z języka. (Z wyłączeniem oczywiście lambd)
MM
4
Jest już dla tego błąd GCC .
jotik

Odpowiedzi:

168

Wyjaśnienie zakłada, że ​​istnieje jakaś rozsądna implementacja, dla fooktórej zwraca referencję l-wartości do poprawnej int.

Taka implementacja może być:

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

int& foo() {
    return a;
} 

Teraz, ponieważ foozwraca odniesienie do lwartości, możemy przypisać coś do zwracanej wartości, na przykład:

foo() = 42;

To zaktualizuje globalną awartością 42, którą możemy sprawdzić, uzyskując dostęp do zmiennej bezpośrednio lub wywołując fooponownie:

int main() {
    foo() = 42;
    std::cout << a;     //prints 42
    std::cout << foo(); //also prints 42
}
TartanLlama
źródło
Rozsądne, jak w przypadku operatora []? Lub inne metody dostępu członków?
Jan Dorniak
8
Biorąc pod uwagę zamieszanie związane z odniesieniami, prawdopodobnie najlepiej jest usunąć statyczne zmienne lokalne z próbek. Inicjalizacja zwiększa niepotrzebną złożoność, co jest przeszkodą w nauce. Po prostu zrób to globalnie.
Mooing Duck
72

Wszystkie inne odpowiedzi deklarują wartość statyczną wewnątrz funkcji. Myślę, że to może cię zmylić, więc spójrz na to:

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

Ponieważ highest()zwraca odwołanie, możesz przypisać mu wartość. Po uruchomieniu bzostanie zmieniony na 11. Jeśli zmienisz inicjalizację na a, powiedzmy, 8, to azostanie zmieniony na 11. To jest kod, który może faktycznie służyć celowi, w przeciwieństwie do innych przykładów.

Kate Gregory
źródło
5
Czy istnieje powód, aby pisać int a {3} zamiast int a = 3? Ta składnia nie wydaje mi się odpowiednia.
Aleksei Petrenko
6
@AlexPetrenko to uniwersalna inicjalizacja. Tak się złożyło, że używałem go w przykładzie, który miałem pod ręką. Nie ma w tym nic niewłaściwego
Kate Gregory
3
Wiem co to jest. a = 3 sprawia wrażenie dużo czystszego. Osobiste preferencje)
Aleksei Petrenko
Tak jak deklarowanie statyczności wewnątrz funkcji może dezorientować niektórych ludzi, uważam również, że użycie uniwersalnej inicjalizacji jest równie prawdopodobne.
ericcurtin
@AlekseiPetrenko W przypadku int a = 1.3 niejawnie konwertuje to na a = 1. Natomiast int a {1.3} skutkuje błędem: zawężeniem konwersji.
Sathvik
32
int& foo();

Deklaruje funkcję o nazwie foo, która zwraca odwołanie do pliku int. To, czego te przykłady nie dają, to definicja tej funkcji, którą można skompilować. Jeśli używamy

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

Teraz mamy funkcję, która zwraca odniesienie do bar. ponieważ bar staticbędzie żył po wywołaniu funkcji, więc zwrócenie do niej odwołania jest bezpieczne. Teraz, jeśli to zrobimy

foo() = 42;

To, co się dzieje, to przypisanie 42 do, barponieważ przypisujemy odwołanie, a odniesienie jest tylko aliasem dla bar. Jeśli ponownie wywołamy tę funkcję jak

std::cout << foo();

Wypisze 42, ponieważ ustawiliśmy barto powyżej.

NathanOliver
źródło
15

int &foo();deklaruje funkcję wywoływaną foo()z typem zwracanym int&. Jeśli wywołasz tę funkcję bez podania treści, prawdopodobnie wystąpi niezdefiniowany błąd odwołania.

W drugiej próbie zapewniłeś funkcję int foo(). To ma inny typ powrotu niż funkcja zadeklarowana przez int& foo();. Masz więc dwie takie same deklaracje foo, które nie są zgodne, co narusza regułę jednej definicji, powodując niezdefiniowane zachowanie (nie jest wymagana diagnostyka).

Aby coś działało, usuń deklarację funkcji lokalnej. Jak widzieliście, mogą prowadzić do cichego, niezdefiniowanego zachowania. Zamiast tego używaj tylko deklaracji funkcji poza jakąkolwiek funkcją. Twój program mógłby wyglądać następująco:

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

int main()
{
    ++foo();  
    std::cout << foo() << '\n';
}
MM
źródło
10

int& foo();jest funkcją zwracającą odwołanie do int. Podana funkcja zwraca się intbez odniesienia.

Możesz to zrobić

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

int main()
{
    int& foo();
    foo() = 42;
}
Jarod42
źródło
7

int & foo();oznacza, że foo()zwraca odwołanie do zmiennej.

Rozważ ten kod:

#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;
}

Ten kod drukuje:

$ ./a.out k = 5

Ponieważ foo()zwraca odniesienie do zmiennej globalnej k.

W poprawionym kodzie rzutujesz zwróconą wartość na odwołanie, które jest wtedy nieprawidłowe.

vy32
źródło
5

W tym kontekście & oznacza odniesienie - więc foo zwraca odniesienie do int, a nie do int.

Nie jestem pewien, czy pracowałeś jeszcze ze wskaźnikami, ale to podobny pomysł, tak naprawdę nie zwracasz wartości z funkcji - zamiast tego przekazujesz informacje potrzebne do znalezienia lokalizacji w pamięci, w której to int jest.

Podsumowując, nie przypisujesz wartości do wywołania funkcji - używasz funkcji do uzyskania odwołania, a następnie przypisujesz wartość, do której się odwołujemy, do nowej wartości. Łatwo jest pomyśleć, że wszystko dzieje się na raz, ale w rzeczywistości komputer robi wszystko w określonej kolejności.

Jeśli zastanawiasz się - powodem, dla którego otrzymujesz segfault, jest to, że zwracasz literał numeryczny `` 2 '' - więc jest to dokładny błąd, który otrzymasz, gdybyś zdefiniował stałą int, a następnie spróbował zmodyfikować jej wartość.

Jeśli jeszcze nie nauczyłeś się wskaźników i pamięci dynamicznej, to najpierw polecam, ponieważ jest kilka pojęć, które moim zdaniem są trudne do zrozumienia, chyba że nauczysz się ich wszystkich naraz.

Dar Brett
źródło
4

Przykładowy kod na połączonej stronie jest tylko fikcyjną deklaracją funkcji. Nie kompiluje się, ale gdybyś miał zdefiniowaną jakąś funkcję, działałaby ogólnie. Przykład oznaczał „Gdybyś miał funkcję z tym podpisem, mógłbyś jej użyć w ten sposób”.

W twoim przykładzie foowyraźnie zwraca lwartość na podstawie podpisu, ale zwracasz wartość r, która jest konwertowana na lwartość. To wyraźnie jest zdeterminowane, aby zawieść. Mógłbyś:

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

i odniesie sukces, zmieniając wartość x, mówiąc:

foo() = 10;
Lód Ogień
źródło
3

Funkcja, którą masz, foo (), jest funkcją, która zwraca referencję do liczby całkowitej.

Powiedzmy, że pierwotnie foo zwróciło 5, a później, w swojej funkcji głównej, mówisz foo() = 10;, że następnie wypisuje foo, wypisze 10 zamiast 5.

Mam nadzieję, że to ma sens :)

Jestem też nowy w programowaniu. Ciekawie jest widzieć takie pytania, które sprawiają, że myślisz! :)

psj01
źródło