Cosa significa "int & foo ()" in C ++?


114

Durante la lettura di questa spiegazione su lvalues ​​e rvalues, queste righe di codice mi sono rimaste impresse:

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

L'ho provato in g ++, ma il compilatore dice "undefined reference to foo ()". Se aggiungo

int foo()
{
  return 2;
}

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

Si compila bene, ma eseguirlo dà un errore di segmentazione . Solo la linea

int& foo();

da solo si compila e funziona senza problemi.

Cosa significa questo codice? Come puoi assegnare un valore a una chiamata di funzione e perché non è un valore?


25
Non stai assegnando un valore a una chiamata di funzione , stai assegnando un valore al riferimento che restituisce .
Frédéric Hamidi

3
@ FrédéricHamidi che assegna un valore all'oggetto a cui si riferisce il riferimento restituito
MM

3
Sì, è un alias per l'oggetto; il riferimento non è l'oggetto
MM

2
Le dichiarazioni di funzioni locali di @RoyFalk sono un vaiolo, personalmente sarei felice di vederle rimosse dalla lingua. (Escluso lambda ovviamente)
MM

4
C'è già un bug GCC per questo .
jotik

Risposte:


168

La spiegazione presuppone che esista un'implementazione ragionevole per la fooquale restituisce un riferimento lvalue a un valido int.

Tale implementazione potrebbe essere:

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

int& foo() {
    return a;
} 

Ora, poiché foorestituisce un riferimento lvalue, possiamo assegnare qualcosa al valore restituito, in questo modo:

foo() = 42;

Questo aggiornerà il globale acon il valore 42, che possiamo controllare accedendo direttamente alla variabile o chiamando foonuovamente:

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

Ragionevole come nell'operatore []? O qualche altro metodo di accesso dei membri?
Jan Dorniak

8
Data la confusione sulle refrenze, è probabilmente meglio rimuovere le variabili locali statiche dai campioni. L'inizializzazione aggiunge complessità non necessaria, che è un ostacolo all'apprendimento. Rendilo un globale.
Mooing Duck

72

Tutte le altre risposte dichiarano una statica all'interno della funzione. Penso che potrebbe confonderti, quindi dai un'occhiata a questo:

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

Poiché highest()restituisce un riferimento, puoi assegnargli un valore. Quando viene eseguito, bverrà modificato in 11. Se si modifica l'inizializzazione in modo che afosse, ad esempio, 8, averrà modificato in 11. Questo è un codice che potrebbe effettivamente servire a uno scopo, a differenza degli altri esempi.


5
C'è un motivo per scrivere int a {3} invece di int a = 3? Questa sintassi non mi sembra appropriata.
Aleksei Petrenko

6
@AlexPetrenko è l'inizializzazione universale. Mi è capitato di usarlo nell'esempio che avevo a portata di mano. Niente di inappropriato al riguardo
Kate Gregory

3
So cos'è questo. a = 3 sembra molto più pulito. Preferenze personali)
Aleksei Petrenko

Proprio come dichiarare statico all'interno di una funzione potrebbe confondere alcune persone, credo anche che l'uso dell'inizializzazione universale sia altrettanto probabile.
ericcurtin

@AlekseiPetrenko In caso di int a = 1.3 converte implicitamente in a = 1. Mentre int a {1.3} restituisce un errore: restringe la conversione.
Sathvik,

32
int& foo();

Dichiara una funzione denominata foo che restituisce un riferimento a un file int. Ciò che gli esempi non riescono a fare è darti una definizione di quella funzione che potresti compilare. Se usiamo

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

Ora abbiamo una funzione che restituisce un riferimento a bar. poiché la barra è static, vivrà dopo la chiamata alla funzione, quindi restituire un riferimento ad essa è sicuro. Ora se lo facciamo

foo() = 42;

Quello che succede è che assegniamo 42 a barpoiché assegniamo al riferimento e il riferimento è solo un alias per bar. Se chiamiamo di nuovo la funzione come

std::cout << foo();

Stamperebbe 42 dato che abbiamo impostato barquello sopra.


15

int &foo();dichiara una funzione chiamata foo()con tipo di ritornoint& . Se chiamate questa funzione senza fornire un corpo, è probabile che venga visualizzato un errore di riferimento non definito.

Nel tuo secondo tentativo hai fornito una funzione int foo(). Questo ha un diverso tipo di ritorno rispetto alla funzione dichiarata da int& foo();. Quindi hai due dichiarazioni dello stesso fooche non corrispondono, il che viola la regola di una definizione causando un comportamento indefinito (nessuna diagnostica richiesta).

Per qualcosa che funziona, elimina la dichiarazione di funzione locale. Possono portare a comportamenti silenziosi e indefiniti come hai visto. Utilizzare invece solo dichiarazioni di funzione al di fuori di qualsiasi funzione. Il tuo programma potrebbe essere simile a:

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

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

10

int& foo();è una funzione che restituisce un riferimento a int. La funzione fornita viene restituita intsenza riferimento.

Puoi farlo

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

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

7

int & foo();significa che foo()restituisce un riferimento a una variabile.

Considera questo codice:

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

Questo codice stampa:

$ ./a.out k = 5

Perché foo()restituisce un riferimento alla variabile globale k.

Nel codice rivisto, stai trasmettendo il valore restituito a un riferimento, che quindi non è valido.


5

In quel contesto il & indica un riferimento, quindi foo restituisce un riferimento a un int, piuttosto che a un int.

Non sono sicuro che avresti ancora lavorato con i puntatori, ma è un'idea simile, in realtà non stai restituendo il valore dalla funzione, ma stai invece passando le informazioni necessarie per trovare la posizione nella memoria in cui int è.

Quindi, per riassumere, non stai assegnando un valore a una chiamata di funzione: stai usando una funzione per ottenere un riferimento e quindi assegni il valore a cui fa riferimento un nuovo valore. È facile pensare che tutto accada in una volta, ma in realtà il computer fa tutto in un ordine preciso.

Se ti stai chiedendo, il motivo per cui stai ottenendo un segfault è perché stai restituendo un letterale numerico '2', quindi è l'errore esatto che avresti se dovessi definire un const int e quindi provare a modificarlo valore.

Se non hai ancora imparato a conoscere i puntatori e la memoria dinamica, ti consiglio di farlo prima perché ci sono alcuni concetti che penso siano difficili da capire a meno che tu non li impari tutti in una volta.


4

Il codice di esempio nella pagina collegata è solo una dichiarazione di funzione fittizia. Non si compila, ma se avessi una funzione definita, funzionerebbe in generale. L'esempio significava "Se avessi una funzione con questa firma, potresti usarla in questo modo".

Nel tuo esempio, foorestituisce chiaramente un lvalue basato sulla firma, ma restituisci un rvalue convertito in un lvalue. Questo è chiaramente determinato a fallire. Potresti fare:

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

e avrebbe successo cambiando il valore di x, quando si dice:

foo() = 10;

3

La funzione che hai, foo (), è una funzione che restituisce un riferimento a un numero intero.

Quindi diciamo che originariamente foo ha restituito 5, e più tardi, nella tua funzione principale, dici foo() = 10;, quindi stampa foo, stamperà 10 invece di 5.

Spero che abbia un senso :)

Anch'io sono nuovo nella programmazione. È interessante vedere domande come questa che ti fanno riflettere! :)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.